Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
139 changes: 107 additions & 32 deletions .github/workflows/backend-cd.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,14 @@ name: CD - Deploy Backend (then Frontend)
on:
workflow_dispatch:
inputs:
aks_cluster_name: { description: 'AKS name', required: true }
aks_resource_group: { description: 'RG name', required: true }
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]

env:
REGISTRY_LOGIN_SERVER: ${{ secrets.AZURE_ACR_LOGIN_SERVER }}
IMAGE_TAG: ${{ github.event.workflow_run?.outputs.image_tag || github.sha }}

permissions:
id-token: write
contents: read
Expand All @@ -24,36 +21,85 @@ concurrency:

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 }}
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

- name: Azure Login (OIDC)
uses: azure/login@v2
# 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:
client-id: ${{ secrets.AZURE_CLIENT_ID }}
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
creds: ${{ secrets.AZURE_CREDENTIALS }}
enable-AzPSSession: true

- name: Set AKS context
run: |
az aks get-credentials \
--resource-group "${{ github.event.inputs.aks_resource_group || secrets.AKS_RG }}" \
--name "${{ github.event.inputs.aks_cluster_name || secrets.AKS_NAME }}" \
--resource-group "$AKS_RG" \
--name "$AKS_NAME" \
--overwrite-existing

- name: Attach ACR
run: |
az aks update \
--resource-group "${{ github.event.inputs.aks_resource_group || secrets.AKS_RG }}" \
--name "${{ github.event.inputs.aks_cluster_name || secrets.AKS_NAME }}" \
--resource-group "$AKS_RG" \
--name "$AKS_NAME" \
--attach-acr "${{ secrets.AZURE_ACR_NAME }}"

- name: Deploy Config & Databases
Expand All @@ -64,25 +110,50 @@ jobs:
kubectl apply -f product-db.yaml
kubectl apply -f order-db.yaml

- name: Deploy Services with pinned images
working-directory: k8s
- 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: |
# Patch images to the exact CI-built tag
kubectl set image deploy/product-service-w08e1 product-service-container="${{ env.REGISTRY_LOGIN_SERVER }}/product_service:${{ env.IMAGE_TAG }}" --record=true || true
kubectl set image deploy/order-service-w08e1 order-service-container="${{ env.REGISTRY_LOGIN_SERVER }}/order_service:${{ env.IMAGE_TAG }}" --record=true || true
# If first time apply:
kubectl apply -f product-service.yaml
kubectl apply -f order-service.yaml

- name: Wait for LoadBalancer IPs
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}')
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
echo "order_ip=$ORDER_IP" >> $GITHUB_OUTPUT
exit 0
fi
sleep 5
Expand All @@ -93,7 +164,11 @@ jobs:
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: ${{ github.event.inputs.aks_cluster_name || secrets.AKS_NAME }}
aks_resource_group: ${{ github.event.inputs.aks_resource_group || secrets.AKS_RG }}
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
1 change: 0 additions & 1 deletion .github/workflows/frontend-cd.yml
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,6 @@ on:
permissions:
id-token: write
contents: read
packages: write

env:
REGISTRY_NAME: ${{ secrets.AZURE_ACR_NAME }}
Expand Down
2 changes: 1 addition & 1 deletion k8s/frontend.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ spec:
spec:
containers:
- name: frontend-container
image: cyweek09acr.azurecr.io/frontend:351d67963dc6fa999d76cd122dac19bb0220b0b9
image: cyweek09acr.azurecr.io/frontend:latest
imagePullPolicy: Always
ports:
- containerPort: 80
Expand Down
2 changes: 1 addition & 1 deletion k8s/order-service.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ spec:
spec:
containers:
- name: order-service-container
image: cyweek09acr.azurecr.io/order_service:351d67963dc6fa999d76cd122dac19bb0220b0b9
image: cyweek09acr.azurecr.io/order_service:latest
imagePullPolicy: Always
ports:
- containerPort: 8000
Expand Down
2 changes: 1 addition & 1 deletion k8s/product-service.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ spec:
spec:
containers:
- name: product-service-container
image: cyweek09acr.azurecr.io/product_service:351d67963dc6fa999d76cd122dac19bb0220b0b9
image: cyweek09acr.azurecr.io/product_service:latest
imagePullPolicy: Always
ports:
- containerPort: 8000
Expand Down
Loading