AdvancedLesson 10 of 16

Deploy to AKS with Helm

Implement a real Azure DevOps delivery path: build container image, push to Azure Container Registry, then deploy to Azure Kubernetes Service with Helm using controlled multi-environment promotion.

🧒 Simple Explanation (ELI5)

Think of your app as a product going through a shipping company. First the factory builds it. Then it gets stored in a warehouse. Then a delivery truck brings it to the customer using clear delivery instructions.

The Azure DevOps pipeline coordinates the full journey automatically and records exactly what was shipped where.

💡
Cross-Course Alignment

This lesson assumes you already know Kubernetes, Helm, and AKS basics. The focus here is the Azure DevOps delivery orchestration and operational patterns that connect to those platforms.

🔧 Technical Explanation

Target Delivery Flow

Real-World Pipeline
Lint & Test
Docker Build
Push to ACR
Helm Deploy to AKS

Core Dependencies

DependencyPurposeExample
Service connectionAuthenticate pipeline to AzureAzure Resource Manager connection
ACRStore built imagecontosoregistry.azurecr.io
AKSRun workloadsaks-prod-eastus
Helm chartPackage Kubernetes manifestscharts/webapp
Variable groupsStore environment-specific confignamespace, ingress host, chart path

Build and Push Stage

yaml
stages:
- stage: Build
  jobs:
  - job: BuildImage
    pool:
      vmImage: ubuntu-latest
    steps:
    - task: Docker@2
      displayName: 'Build and push image'
      inputs:
        command: buildAndPush
        repository: 'webapp'
        dockerfile: 'Dockerfile'
        containerRegistry: 'sc-acr-prod'
        tags: |
          $(Build.BuildId)
          latest

In production, prefer immutable tags like $(Build.SourceVersion) or $(Build.BuildId). Keep latest only as a convenience tag, never as the source of truth for release selection.

Deploy Stage with Helm

yaml
- stage: Deploy_Prod
  dependsOn: Build
  jobs:
  - deployment: HelmDeployProd
    environment: production
    strategy:
      runOnce:
        deploy:
          steps:
          - checkout: self
          - task: HelmInstaller@1
            inputs:
              helmVersionToInstall: 'latest'
          - task: AzureCLI@2
            displayName: 'AKS get-credentials'
            inputs:
              azureSubscription: 'sc-aks-prod'
              scriptType: bash
              scriptLocation: inlineScript
              inlineScript: |
                az aks get-credentials \
                  --resource-group $(resourceGroup) \
                  --name $(clusterName) \
                  --overwrite-existing
          - script: |
              helm upgrade --install webapp ./charts/webapp \
                --namespace $(namespace) \
                --create-namespace \
                --set image.repository=$(acrLoginServer)/webapp \
                --set image.tag=$(Build.BuildId) \
                --set ingress.host=$(ingressHost) \
                --wait --timeout 10m
            displayName: 'Helm upgrade'

Deployment Strategy Choices

StrategyHow It WorksBest For
Rolling updateGradually replaces podsDefault low-complexity deployments
Blue-greenDeploys a parallel environment, then switches trafficLow-risk cutover with fast rollback
CanarySends limited traffic to new version firstHigh-confidence progressive delivery

The Azure DevOps part is mostly about orchestrating validation and environment promotion. Helm and ingress configuration handle the platform-specific rollout details.

Production-Grade Real-World Pipeline

The minimal example above proves the concept. A real team usually wants stronger guardrails: PR validation, immutable image tags, staging smoke tests, explicit artifact flow, and production approval through environments.

yaml
trigger:
  branches:
    include:
      - main
  paths:
    include:
      - src/*
      - charts/*
      - Dockerfile

pr:
  branches:
    include:
      - main

variables:
- group: aks-shared
- name: imageRepository
  value: 'webapp'
- name: imageTag
  value: '$(Build.SourceVersion)'

stages:
- stage: Validate
  displayName: 'Lint and test'
  jobs:
  - job: AppChecks
    pool:
      vmImage: ubuntu-latest
    steps:
    - task: NodeTool@0
      inputs:
        versionSpec: '20.x'
    - script: npm ci
      displayName: 'Install dependencies'
    - script: npm run lint
      displayName: 'Run lint'
    - script: npm test -- --ci
      displayName: 'Run unit tests'

- stage: Build_Image
  displayName: 'Build and push image to ACR'
  dependsOn: Validate
  condition: succeeded()
  jobs:
  - job: BuildPush
    pool:
      vmImage: ubuntu-latest
    steps:
    - task: Docker@2
      displayName: 'Build and push image'
      inputs:
        command: buildAndPush
        containerRegistry: 'sc-acr-prod'
        repository: '$(imageRepository)'
        dockerfile: 'Dockerfile'
        tags: |
          $(imageTag)
          $(Build.BuildId)
    - publish: charts/webapp
      artifact: helm-chart

- stage: Deploy_Staging
  displayName: 'Deploy to staging AKS'
  dependsOn: Build_Image
  jobs:
  - deployment: StagingDeploy
    environment: staging
    strategy:
      runOnce:
        deploy:
          steps:
          - download: current
            artifact: helm-chart
          - task: HelmInstaller@1
            inputs:
              helmVersionToInstall: 'latest'
          - task: AzureCLI@2
            inputs:
              azureSubscription: 'sc-aks-staging'
              scriptType: bash
              scriptLocation: inlineScript
              inlineScript: |
                az aks get-credentials --resource-group $(resourceGroup) --name $(stagingClusterName) --overwrite-existing
                helm upgrade --install webapp $(Pipeline.Workspace)/helm-chart \
                  --namespace staging \
                  --create-namespace \
                  --set image.repository=$(acrLoginServer)/$(imageRepository) \
                  --set image.tag=$(imageTag) \
                  --set ingress.host=$(stagingIngressHost) \
                  --wait --timeout 10m

- stage: Smoke_Test
  displayName: 'Validate staging release'
  dependsOn: Deploy_Staging
  jobs:
  - job: SmokeTest
    pool:
      vmImage: ubuntu-latest
    steps:
    - bash: |
        curl --fail --retry 5 --retry-delay 10 https://$(stagingIngressHost)/health
      displayName: 'Run smoke test'

- stage: Deploy_Production
  displayName: 'Deploy to production AKS'
  dependsOn: Smoke_Test
  condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/main'))
  jobs:
  - deployment: ProductionDeploy
    environment: production
    strategy:
      runOnce:
        deploy:
          steps:
          - task: HelmInstaller@1
            inputs:
              helmVersionToInstall: 'latest'
          - task: AzureCLI@2
            inputs:
              azureSubscription: 'sc-aks-prod'
              scriptType: bash
              scriptLocation: inlineScript
              inlineScript: |
                az aks get-credentials --resource-group $(resourceGroup) --name $(prodClusterName) --overwrite-existing
                helm upgrade --install webapp ./charts/webapp \
                  --namespace production \
                  --create-namespace \
                  --set image.repository=$(acrLoginServer)/$(imageRepository) \
                  --set image.tag=$(imageTag) \
                  --set ingress.host=$(prodIngressHost) \
                  --wait --timeout 10m
Design ChoiceWhy It Is Used
$(Build.SourceVersion) tagCreates an immutable mapping from deployed image back to the exact commit.
Separate staging and production service connectionsLimits blast radius and keeps production access isolated.
Smoke test stage before productionStops promotion if the release is alive technically but unhealthy functionally.
Environment-based production deploymentAllows approvals and checks outside the editable YAML file.
Published chart artifactEnsures later stages use the exact build output instead of rebuilding.

🛠️ Hands-on

Minimal End-to-End Multi-Stage Example

yaml
trigger:
- main

variables:
- group: aks-prod-shared

stages:
- stage: Verify
  jobs:
  - job: VerifyApp
    pool:
      vmImage: ubuntu-latest
    steps:
    - script: npm ci
    - script: npm test

- stage: Build
  dependsOn: Verify
  jobs:
  - job: BuildPush
    steps:
    - task: Docker@2
      inputs:
        command: buildAndPush
        containerRegistry: 'sc-acr-prod'
        repository: 'webapp'
        dockerfile: 'Dockerfile'
        tags: '$(Build.BuildId)'

- stage: Deploy
  dependsOn: Build
  jobs:
  - deployment: Prod
    environment: production
    strategy:
      runOnce:
        deploy:
          steps:
          - task: AzureCLI@2
            inputs:
              azureSubscription: 'sc-aks-prod'
              scriptType: bash
              scriptLocation: inlineScript
              inlineScript: |
                az aks get-credentials --resource-group $(resourceGroup) --name $(clusterName) --overwrite-existing
          - script: |
              helm upgrade --install webapp ./charts/webapp \
                --namespace $(namespace) \
                --set image.repository=$(acrLoginServer)/webapp \
                --set image.tag=$(Build.BuildId) \
                --wait --timeout 10m

Rollbacks

🐛 Debugging Scenarios

Scenario 1: Pipeline Can Push to ACR but AKS Cannot Pull Image

Scenario 2: Helm Upgrade Times Out

Scenario 3: Azure CLI Task Fails with Authorization Error

⚠️
Pipeline vs Platform

Many deployment failures are Kubernetes or application failures surfaced by the pipeline. Distinguish authentication problems, image distribution problems, and cluster health problems before changing the pipeline itself.

📋 Interview Questions

Beginner

What is the role of ACR in an AKS deployment pipeline?

ACR stores the built container images so AKS can pull and run them. It is the registry layer between build and cluster deployment.

Why use Helm in Azure DevOps deployments?

Helm packages Kubernetes manifests into reusable charts, making environment-specific deployment inputs easier to manage than raw manifest duplication.

What does az aks get-credentials do?

It fetches cluster access credentials and writes kubeconfig so kubectl and Helm can communicate with the AKS cluster.

Why should image tags be immutable?

Immutable tags provide traceability, safer rollback, and certainty about exactly what version was deployed.

Can Azure DevOps deploy to AKS without Classic pipelines?

Yes. YAML multi-stage pipelines are the modern and preferred approach.

Intermediate

What is the difference between pushing to ACR and deploying to AKS?

Pushing to ACR publishes a container image into the registry. Deploying to AKS updates the cluster workload to reference and run that image.

How do you avoid repeating Kubernetes basics in an AKS delivery interview answer?

I focus on the delivery chain: artifact traceability, service connections, environment approvals, Helm parameterization, and troubleshooting handoff between the pipeline and the platform.

How would you structure environment-specific Helm values?

I keep shared defaults in the chart and inject environment-specific values via values files or explicit pipeline inputs, avoiding copy-paste chart forks.

What should happen before a production AKS deployment stage runs?

The image should be built and tested, lower-environment validation should complete, and environment checks or approvals should pass.

How do you roll back a bad Helm deployment?

Use Helm release history and rollback or redeploy a previous immutable image tag, depending on the root cause and operational preference.

Scenario-Based

Your pipeline is green but users get 500 errors after deployment. What do you do?

I verify ingress, service, pod health, probes, logs, and any config changes introduced by the release. The green pipeline only proves the orchestration completed, not that the application is healthy.

An image tag exists in ACR, but the cluster still runs the old version. Why?

The Helm values may still reference the old tag, the deployment may not have rolled out, or a failed release may have left the previous ReplicaSet active. I inspect the live deployment spec and rollout status.

How would you secure AKS deployments from Azure DevOps?

I use least-privilege service connections, protected environments, restricted variable groups, approval checks, and strong auditing around who can edit deploy logic or approve production releases.

What would make you choose blue-green over rolling update?

When the application is highly critical, rollback speed matters, and the platform can afford the extra environment or routing complexity.

How do you explain Azure DevOps to AKS flow in one sentence?

Azure DevOps validates the app, builds an immutable container, stores it in ACR, and orchestrates a controlled Helm-based rollout into AKS environments with auditability and rollback options.

🌍 Real-World Usage

This pattern is common in enterprise Azure delivery: code in Repos, CI in Azure Pipelines, package in ACR, runtime on AKS, and chart-driven deployment with Helm. The delivery system becomes the glue that connects source control, security, and operations without duplicating Kubernetes concepts already defined elsewhere.

🧾 Summary

Deploying to AKS with Helm from Azure DevOps is about connecting validated builds to controlled cluster releases. The operational quality comes from immutable tags, strong authentication, environment checks, and clear separation between pipeline orchestration and platform diagnosis.