Azure DevOps Pipelines can deploy directly to Azure Kubernetes Service (AKS).
Let’s see it in action. Imagine you have a simple Node.js app, app.js, and a Dockerfile:
FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
EXPOSE 80
CMD ["node", "app.js"]
Your app.js could be as basic as:
const http = require('http');
const port = 80;
const server = http.createServer((req, res) => {
res.statusCode = 200;
res.setHeader('Content-Type', 'text/plain');
res.end('Hello from AKS!\n');
});
server.listen(port, () => {
console.log(`Server running at http://localhost:${port}/`);
});
Now, for the pipeline. In your Azure DevOps project, create a new pipeline. Choose "Azure Repos Git" (or your source control) and select your repository. For the template, start with a "Starter pipeline".
Here’s a typical azure-pipelines.yml:
trigger:
- main
variables:
azureSubscription: '<Your Azure Service Connection Name>'
aksClusterName: '<Your AKS Cluster Name>'
resourceGroupName: '<Your AKS Resource Group Name>'
imageRepository: 'mywebapp'
dockerfilePath: '$(Build.SourcesDirectory)/Dockerfile'
tag: '$(Build.BuildId)'
stages:
- stage: Build
displayName: Build and push Docker image
jobs:
- job: Build
displayName: Build and push job
pool:
vmImage: 'ubuntu-latest'
steps:
- task: Docker@2
displayName: Build and push an image to container registry
inputs:
command: buildAndPush
repository: $(imageRepository)
dockerfile: $(dockerfilePath)
containerRegistry: '<Your Azure Container Registry Service Connection Name>'
tags: |
$(tag)
latest
- stage: Deploy
displayName: Deploy to AKS
jobs:
- deployment: Deploy
displayName: Deploy to AKS job
environment: 'production' # Or any environment name
pool:
vmImage: 'ubuntu-latest'
strategy:
runOnce:
deploy:
steps:
- task: KubernetesManifest@0
displayName: Deploy to Kubernetes cluster
inputs:
action: deploy
kubernetesServiceConnection: '<Your Kubernetes Service Connection Name>'
manifests: |
$(Build.SourcesDirectory)/kubernetes/deployment.yaml
$(Build.SourcesDirectory)/kubernetes/service.yaml
containers: |
$(imageRepository):$(tag)
This pipeline has two stages: Build and Deploy.
The Build stage uses the Docker@2 task to build your Docker image and push it to an Azure Container Registry (ACR). It tags the image with the unique BuildId and also latest. You’ll need to set up a "Docker Registry service connection" in Azure DevOps to your ACR.
The Deploy stage uses the KubernetesManifest@0 task. This is where the magic for AKS happens. It takes your Kubernetes manifest files (which you’d typically store in a kubernetes/ folder in your repo) and applies them to your AKS cluster.
For this to work, you need a kubernetes/deployment.yaml and kubernetes/service.yaml.
deployment.yaml:
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app-deployment
spec:
replicas: 2
selector:
matchLabels:
app: my-app
template:
metadata:
labels:
app: my-app
spec:
containers:
- name: my-app-container
image: <your-acr-name>.azurecr.io/mywebapp:$(tag) # The image pushed by the build stage
ports:
- containerPort: 80
service.yaml:
apiVersion: v1
kind: Service
metadata:
name: my-app-service
spec:
selector:
app: my-app
ports:
- protocol: TCP
port: 80
targetPort: 80
type: LoadBalancer # This will provision an Azure Load Balancer with a public IP
Crucially, you need to set up a "Kubernetes service connection" in Azure DevOps. This connection links your Azure DevOps project to your AKS cluster. When creating this connection, you’ll select your AKS cluster and provide credentials. Azure DevOps will then use these credentials to interact with the AKS API server. The KubernetesManifest@0 task uses this service connection to apply your YAML manifests.
The containers input in the KubernetesManifest@0 task is key for dynamic image updates. It tells the task to find containers matching mywebapp in your deployment YAML and replace their image tag with the one generated by the build pipeline ($(tag)). This ensures you’re deploying the specific version of the image built in the Build stage.
The type: LoadBalancer in service.yaml is what exposes your application externally. AKS will provision an Azure Load Balancer, assign it a public IP address, and direct traffic to your application pods. You can then find this public IP in the Azure portal under your AKS cluster’s Load Balancer resource, or by running kubectl get service my-app-service after deployment.
The environment field in the deployment job is part of Azure DevOps Environments, which offer deployment history, approval gates, and traceability.
The most surprising thing about this setup is how seamlessly the KubernetesManifest@0 task can manage complex deployments by simply applying declarative YAML files, abstracting away the kubectl apply commands and ensuring idempotency.
The next step is to explore GitOps with Azure DevOps Pipelines and AKS, perhaps using tools like Flux or Argo CD to manage your Kubernetes deployments.