CD - Deploy Backend (then Frontend) #19
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: CD - Deploy Backend (then Frontend) | |
| on: | |
| workflow_dispatch: | |
| inputs: | |
| aks_cluster_name: { description: 'AKS name', required: false, default: '' } | |
| aks_resource_group: { description: 'RG name', required: false, default: '' } | |
| image_tag: { description: 'Image tag to deploy (optional)', required: false, default: '' } | |
| workflow_run: | |
| workflows: ["CI - Test, Build & Push (Backend + Frontend)"] | |
| types: [completed] | |
| branches: [main] | |
| permissions: | |
| id-token: write | |
| contents: read | |
| concurrency: | |
| group: deploy-backend-prod | |
| cancel-in-progress: false | |
| jobs: | |
| deploy_backend: | |
| # Run if manual OR CI completed successfully | |
| if: github.event_name == 'workflow_dispatch' || github.event.workflow_run.conclusion == 'success' | |
| runs-on: ubuntu-latest | |
| environment: Production | |
| outputs: | |
| PRODUCT_API_IP: ${{ steps.capture.outputs.product_ip }} | |
| ORDER_API_IP: ${{ steps.capture.outputs.order_ip }} | |
| IMAGE_TAG: ${{ steps.compute_tag.outputs.val }} | |
| AKS_NAME: ${{ steps.compute_aks.outputs.name }} | |
| AKS_RG: ${{ steps.compute_aks.outputs.rg }} | |
| steps: | |
| - uses: actions/checkout@v4 | |
| # Decide which image tag to deploy | |
| - name: Compute IMAGE_TAG | |
| id: compute_tag | |
| run: | | |
| if [ "${{ github.event_name }}" = "workflow_run" ]; then | |
| echo "val=${{ github.event.workflow_run.head_sha }}" >> $GITHUB_OUTPUT | |
| elif [ -n "${{ github.event.inputs.image_tag }}" ]; then | |
| echo "val=${{ github.event.inputs.image_tag }}" >> $GITHUB_OUTPUT | |
| else | |
| echo "val=${{ github.sha }}" >> $GITHUB_OUTPUT | |
| fi | |
| - name: Ensure image tag exists (fallback to latest) | |
| id: tag_check | |
| env: | |
| REGISTRY_NAME: ${{ secrets.AZURE_ACR_NAME }} # e.g. cyweek09acr | |
| IMAGE_TAG: ${{ steps.compute_tag.outputs.val }} | |
| run: | | |
| set -e | |
| echo "Checking if tag exists in ACR: $IMAGE_TAG" | |
| if az acr repository show-tags --name "$REGISTRY_NAME" --repository product_service --output tsv | grep -q "^${IMAGE_TAG}$"; then | |
| echo "resolved=${IMAGE_TAG}" >> $GITHUB_OUTPUT | |
| else | |
| echo "Tag not found in ACR, falling back to: latest" | |
| echo "resolved=latest" >> $GITHUB_OUTPUT | |
| fi | |
| # use tag_check.outputs.resolved everywhere instead of compute_tag.outputs.val | |
| # Compute AKS name/RG (inputs override secrets) | |
| - name: Compute AKS values | |
| id: compute_aks | |
| run: | | |
| NAME="${{ github.event.inputs.aks_cluster_name }}" | |
| RG="${{ github.event.inputs.aks_resource_group }}" | |
| if [ -z "$NAME" ]; then NAME="${{ secrets.AKS_NAME }}"; fi | |
| if [ -z "$RG" ]; then RG="${{ secrets.AKS_RG }}"; fi | |
| echo "name=$NAME" >> $GITHUB_OUTPUT | |
| echo "rg=$RG" >> $GITHUB_OUTPUT | |
| echo "AKS_NAME=$NAME" >> $GITHUB_ENV | |
| echo "AKS_RG=$RG" >> $GITHUB_ENV | |
| # Azure login | |
| - name: Azure Login | |
| uses: azure/login@v1 | |
| with: | |
| creds: ${{ secrets.AZURE_CREDENTIALS }} | |
| enable-AzPSSession: true | |
| - name: Set AKS context | |
| run: | | |
| az aks get-credentials \ | |
| --resource-group "$AKS_RG" \ | |
| --name "$AKS_NAME" \ | |
| --overwrite-existing | |
| - name: Attach ACR | |
| run: | | |
| az aks update \ | |
| --resource-group "$AKS_RG" \ | |
| --name "$AKS_NAME" \ | |
| --attach-acr "${{ secrets.AZURE_ACR_NAME }}" | |
| - name: Deploy Config & Databases | |
| working-directory: k8s | |
| run: | | |
| kubectl apply -f configmaps.yaml | |
| kubectl apply -f secrets.yaml | |
| kubectl apply -f product-db.yaml | |
| kubectl apply -f order-db.yaml | |
| - name: Deploy Services (apply manifests and pin images) | |
| env: | |
| REGISTRY_LOGIN_SERVER: ${{ secrets.AZURE_ACR_LOGIN_SERVER }} | |
| IMAGE_TAG: ${{ steps.tag_check.outputs.resolved }} | |
| run: | | |
| kubectl apply -f k8s/product-service.yaml | |
| kubectl apply -f k8s/order-service.yaml | |
| kubectl set image deploy/product-service-w08e1 product-service-container="${REGISTRY_LOGIN_SERVER}/product_service:${IMAGE_TAG}" --record=true || true | |
| kubectl set image deploy/order-service-w08e1 order-service-container="${REGISTRY_LOGIN_SERVER}/order_service:${IMAGE_TAG}" --record=true || true | |
| kubectl rollout status deploy/product-service-w08e1 --timeout=600s | |
| kubectl rollout status deploy/order-service-w08e1 --timeout=600s | |
| - name: Debug (product-service) on failure | |
| if: failure() | |
| run: | | |
| echo "=== Deployments ===" | |
| kubectl get deploy -o wide | |
| echo "=== ReplicaSets (product) ===" | |
| kubectl get rs -l app=product-service -o wide || true | |
| echo "=== Pods (product) ===" | |
| kubectl get pods -l app=product-service -o wide || true | |
| echo "=== Describe deployment ===" | |
| kubectl describe deploy/product-service-w08e1 || true | |
| echo "=== Describe pods ===" | |
| for p in $(kubectl get pods -l app=product-service -o name); do | |
| kubectl describe "$p" || true | |
| echo "--- Logs $p ---" | |
| kubectl logs "$p" --all-containers --tail=200 || true | |
| done | |
| echo "=== Recent events ===" | |
| kubectl get events --sort-by=.lastTimestamp | tail -n 200 || true | |
| - name: Capture LoadBalancer IPs | |
| id: capture | |
| run: | | |
| for i in {1..60}; do | |
| PRODUCT_IP=$(kubectl get svc product-service-w08e1 -o jsonpath='{.status.loadBalancer.ingress[0].ip}') | |
| ORDER_IP=$(kubectl get svc order-service-w08e1 -o jsonpath='{.status.loadBalancer.ingress[0].ip}') | |
| if [[ -n "$PRODUCT_IP" && -n "$ORDER_IP" ]]; then | |
| echo "product_ip=$PRODUCT_IP" >> $GITHUB_OUTPUT | |
| echo "order_ip=$ORDER_IP" >> $GITHUB_OUTPUT | |
| exit 0 | |
| fi | |
| sleep 5 | |
| done | |
| echo "Timed out waiting for IPs"; exit 1 | |
| deploy_frontend: | |
| needs: deploy_backend | |
| uses: ./.github/workflows/frontend-cd.yml | |
| with: | |
| product_api_ip: "http://${{ needs.deploy_backend.outputs.PRODUCT_API_IP }}:8000" | |
| order_api_ip: "http://${{ needs.deploy_backend.outputs.ORDER_API_IP }}:8001" | |
| aks_cluster_name: ${{ needs.deploy_backend.outputs.AKS_NAME }} | |
| aks_resource_group: ${{ needs.deploy_backend.outputs.AKS_RG }} | |
| image_tag: ${{ needs.deploy_backend.outputs.IMAGE_TAG }} | |
| secrets: inherit | |
| # update |