LAB 14 – CI/CD: from commit to AKS with Helm¶
Story: Every merge to
mainmust build a new image, deploy it to AKS and prove it works – with no stored passwords. You build the pipeline in Azure DevOps (or GitHub Actions, if your team lives there).
| Duration | ~45 min |
| Requires | an Azure DevOps project or a GitHub fork |
| Files | charts/cloudcart/, pipelines/azure-pipelines.yml, pipelines/github-actions-deploy.yml |
flowchart LR
C[commit to main] --> B[Build stage<br/>az acr build → cloudcart:BuildId<br/>helm lint]
B --> A{environment<br/>approval}
A --> D[Deploy stage<br/>kubelogin + helm upgrade --install --wait]
D --> T[helm test<br/>smoke test pod]
Step 1 – Look at the chart¶
source env.sh
helm lint charts/cloudcart
helm template demo charts/cloudcart --set image.repository=$ACR.azurecr.io/cloudcart | less
templates/tests/smoke-test.yaml is a Helm test hook: helm test starts a pod that calls the Service and checks the returned version.
Create the target namespace as cluster admin (the pipeline identity only gets namespace rights):
Track A – Azure DevOps¶
A1 – Repository and service connection¶
- Import this repository into Azure Repos (or connect your GitHub fork).
- Project settings → Service connections → New → Azure Resource Manager → Workload identity federation (automatic), scope: resource group
$RG, namesc-aks-lab. No secret is created – Azure DevOps gets federated tokens. - Find the identity behind it: Manage service connection roles opens it in Entra ID – copy its Object ID.
A2 – Permissions (least privilege)¶
SC_ID=<object id of the service connection identity>
ACR_ID=$(az acr show -n $ACR --query id -o tsv)
AKS_ID=$(az aks show -g $RG -n $CLUSTER --query id -o tsv)
az role assignment create --assignee-object-id $SC_ID --assignee-principal-type ServicePrincipal --role Contributor --scope $ACR_ID # run ACR Tasks
az role assignment create --assignee-object-id $SC_ID --assignee-principal-type ServicePrincipal --role "Azure Kubernetes Service Cluster User Role" --scope $AKS_ID
az role assignment create --assignee-object-id $SC_ID --assignee-principal-type ServicePrincipal --role "Azure Kubernetes Service RBAC Writer" --scope $AKS_ID/namespaces/cloudcart-cicd
A3 – Pipeline¶
Edit pipelines/azure-pipelines.yml: set acrName, resourceGroup, clusterName. Commit.
Pipelines → New pipeline → (your repo) → Existing Azure Pipelines YAML file → /pipelines/azure-pipelines.yml → Run.
✅ Checkpoint: both stages green; the log of helm test shows Phase: Succeeded.
A4 – Gate production¶
Pipelines → Environments → cloudcart-lab → Approvals and checks → Approvals – add yourself. Change title in charts/cloudcart/values.yaml, push: the Deploy stage waits for your approval.
Track B – GitHub Actions¶
- Copy
pipelines/github-actions-deploy.ymlto.github/workflows/deploy.ymlin your fork. - Create an identity with a federated credential for your repository (environment
cloudcart-lab):
az identity create -g $RG -n id-github-deploy
GH_PRINCIPAL=$(az identity show -g $RG -n id-github-deploy --query principalId -o tsv)
az identity federated-credential create -g $RG --identity-name id-github-deploy --name github-cloudcart \
--issuer https://token.actions.githubusercontent.com \
--subject "repo:<your-user>/<your-fork>:environment:cloudcart-lab" \
--audiences api://AzureADTokenExchange
- Grant the same three roles as in A2 to
$GH_PRINCIPAL. - Repository secrets
AZURE_CLIENT_ID(az identity show … --query clientId),AZURE_TENANT_ID,AZURE_SUBSCRIPTION_ID; variablesACR_NAME,RESOURCE_GROUP,CLUSTER_NAME. - Actions → Build and deploy CloudCart → Run workflow.
Step 2 – Roll back¶
Discussion¶
- Push-based CI/CD (this lab) vs. GitOps (LAB13): combine them – CI builds the image and updates Git, Flux deploys.
- Image tags: build ID vs. Git SHA vs. semantic versions; never
latest. - Add quality gates before deploy: unit tests, image scan (Defender / Trivy), policy checks.
Troubleshooting¶
| Symptom | Likely cause |
|---|---|
AuthorizationFailed … scheduleRun |
the identity lacks rights on the ACR |
Forbidden … secrets is forbidden during helm |
RBAC Writer missing on the namespace, or propagation delay |
| pipeline hangs at device login | kubelogin convert-kubeconfig -l azurecli missing (Entra-enabled cluster) |
helm test fails |
the new pods aren't ready, or appVersion mismatch – check kubectl -n cloudcart-cicd logs cloudcart-smoke-test |
Files for this lab: Think-Cube/aks-labs/labs/LAB14-cicd-pipeline