Realtime DevOps Project using Azure DevOps and GitOps (Beginner to Advanced)
This chapter provides a comprehensive, end-to-end implementation of a multi-microservice Continuous Integration (CI) and Continuous Delivery (CD) pipeline using Azure DevOps, Azure Container Registry (ACR), Azure Kubernetes Service (AKS), and Argo CD (GitOps).
1. Why Do We Need This? (Problem Statement & Status Quo)
Modern software development often involves dozens or hundreds of developers contributing to a single application repository (e.g., Docker's multi-microservice voting application with over 225 commits and 33 contributors).
- The Manual Verification Bottleneck: Manually running unit tests, building Docker images, testing endpoints, and deploying artifacts for multiple independent services is error-prone, slow, and impossible to scale.
- The Scale of Modern Microservices: In a multi-language architecture where services are written in Python, Node.js, and .NET, each component has different build dependencies, runtimes, and deployment targets.
- The Solution: Automate testing, image packaging, and registry publishing using CI pipelines (Azure Pipelines), and automate immutable, drift-resistant deployments using Gitops (Argo CD on AKS).
2. Core Mental Model & Application Architecture
The project analyzed in this lecture is the Docker Samples Example Voting App, which consists of three custom application microservices and two data stores:
Component Breakdown
Note: Although this application is intentionally over-engineered for educational purposes by the Docker team, it provides an ideal sandbox for practicing multi-language CI/CD patterns.
3. Naive vs. Better Approach
- Naive Approach: Developers manually SSH into production servers, run
git pull, rebuild containers locally, rundocker-compose up, and manage secrets by hand. This leads to configuration drift, untracked modifications, and downtime during failures. - Better Approach (CI/CD + GitOps):
- Developers commit code to a Version Control System (Azure Repos).
- Azure Pipelines automatically executes unit tests, builds container images, and pushes versioned artifacts to ACR.
- An Update Script modifies the Kubernetes YAML manifests in the Git repository with the new image tag.
- Argo CD detects the Git change via Continuous Reconciliation and safely updates the AKS cluster.
4. Writing Docker Files for Multi-Language Microservices
Because each microservice uses a different runtime, custom Dockerfile configurations are required at the root of each service folder.
A. Node.js Result Microservice (Dockerfile)
FROM node:latest
RUN apt-get update && apt-get install -y tiny curl
WORKDIR /usr/src/app
COPY package*.json ./
RUN npm install
COPY . .
EXPOSE 80
CMD ["npm", "start"]
B. Python Voting Microservice (Dockerfile)
FROM python:3.11-slim
WORKDIR /app
COPY requirements.txt ./
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
EXPOSE 80
CMD ["python", "app.py"]
C. .NET Worker Microservice (Dockerfile)
FROM mcr.microsoft.com/dotnet/sdk:7.0 AS build
WORKDIR /source
COPY *.csproj .
RUN dotnet restore
COPY . .
RUN dotnet publish -c Release -o /app
FROM mcr.microsoft.com/dotnet/runtime:7.0
WORKDIR /app
COPY --from=build /app .
ENTRYPOINT ["dotnet", "Worker.dll"]
5. Setting Up Azure DevOps Repos & Self-Hosted Agents
To build Docker images on a free trial account without encountering Azure-hosted agent compute limitations, a self-hosted Linux virtual machine agent must be configured.
Step-by-Step Agent Registration Flow
- Create an Azure Linux VM: Provision an Ubuntu virtual machine inside an Azure Resource Group (e.g.,
azure-cicd). - Configure Agent Pool in Azure DevOps:
- Navigate to
Organization Settings->Agent pools->Add pool(Name:Azure agent).
- Navigate to
- Register the Agent on the VM via SSH:
BASH
# Connect to VM via SSH ssh -i AzureAgent_key.pem azureuser@<VM_PUBLIC_IP> # Install Docker and grant permissions sudo apt-get update sudo apt-get install -y docker.io sudo usermod -aG docker azureuser sudo systemctl restart docker # Download and configure the Azure DevOps agent binary mkdir myagent && cd myagent wget https://vstsagentpackage.azureedge.net/agent/3.225.0/vsts-agent-linux-x64-3.225.0.tar.gz tar zxvf vsts-agent-linux-x64-3.225.0.tar.gz # Run configuration script (provide Personal Access Token & pool name) ./config.sh # Start the agent listener ./run.sh
6. Implementing Continuous Integration (CI) with Path-Based Triggers
To prevent unnecessary builds when changes occur in unrelated folders, path-based triggers ensure that updating the result service only triggers the result CI pipeline.
Azure Pipeline YAML (azure-pipelines-result.yaml)
trigger:
branches:
include:
- main
paths:
include:
- result/*
variables:
dockerRegistryServiceConnection: 'acr-service-connection'
imageRepository: 'result-app'
containerRegistry: 'abhishekazurecicd.azurecr.io'
dockerfilePath: '$(Build.SourcesDirectory)/result/Dockerfile'
tag: '$(Build.BuildId)'
pool:
name: 'Azure agent'
stages:
- stage: Build
displayName: 'Build Image Stage'
jobs:
- job: BuildJob
steps:
- task: Docker@2
displayName: 'Build Docker Image'
inputs:
command: 'build'
repository: '$(imageRepository)'
dockerfile: '$(dockerfilePath)'
containerRegistry: '$(dockerRegistryServiceConnection)'
tags: '$(tag)'
- stage: Push
displayName: 'Push Image Stage'
dependsOn: Build
jobs:
- job: PushJob
steps:
- task: Docker@2
displayName: 'Push Docker Image to ACR'
inputs:
command: 'push'
repository: '$(imageRepository)'
containerRegistry: '$(dockerRegistryServiceConnection)'
tags: '$(tag)'
7. Automated Manifest Updates via Shell Scripting
Because GitOps tools like Argo CD require Git to act as the single source of truth, an automated shell script is injected as a CI stage to update the image tag inside the Kubernetes deployment manifests.
Update Script (scripts/update-k8s-manifests.sh)
#!/bin/bash
set -x
# Arguments:
# $1 = Deployment name prefix (e.g., voting, result)
# $2 = Repository name in ACR
# $3 = Build tag ID
REPO_URL="https://<PAT>@dev.azure.com/helpdevops/voting-app/_git/voting-app"
TEMP_DIR=$(mktemp -d)
git clone "$REPO_URL" "$TEMP_DIR"
cd "$TEMP_DIR"
git config user.email "pipeline@azure.devops"
git config user.name "Azure Pipeline Agent"
# Update image tag inside Kubernetes manifests using sed
MANIFEST_FILE="k8s-specifications/$1-deployment.yaml"
sed -i "s|image: .*/$2:.*\vert image: abhishekazurecicd.azurecr.io/$2:$3\vert g" "$MANIFEST_FILE"
git add "$MANIFEST_FILE"
git commit -m "CI Pipeline: Update $2 image tag to $3"
git push origin main
8. Continuous Delivery (CD) with Argo CD and GitOps
Instead of traditional imperative deployment scripts, Argo CD continuously reconciles the desired state defined in the Git repository with the running state in Azure Kubernetes Service (AKS).
Argo CD Setup Flow
- Provision AKS Cluster:
BASH
az aks create --resource-group azure-cicd --name azure-devops-cluster --node-count 1 --enable-cluster-autoscaler --min-count 1 --max-count 2 az aks get-credentials --resource-group azure-cicd --name azure-devops-cluster --overwrite-existing - Install Argo CD:
BASH
kubectl create namespace argocd kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml - Expose Argo CD UI:
Modify the
argocd-serverservice type fromClusterIPtoNodePort:BASHkubectl patch svc argocd-server -n argocd -p '{"spec": {"type": "NodePort"}}' - Retrieve Admin Password:
BASH
kubectl get secret argocd-initial-admin-secret -n argocd -o jsonpath="{.data.password}" | base64 --decode
9. Handling Private Registry Authentication (imagePullSecrets)
When AKS pulls container images from a private Azure Container Registry (ACR), Kubernetes requires an imagePullSecrets configuration.
Creating and Applying ACR Secrets in Kubernetes
# 1. Create Docker Registry Secret in default namespace
kubectl create secret docker-registry acr-secret \
--namespace=default \
--docker-server=abhishekazurecicd.azurecr.io \
--docker-username=<ACR_ADMIN_USERNAME> \
--docker-password=<ACR_ADMIN_PASSWORD>
# 2. Attach secret to Kubernetes Deployment Manifest (e.g., voting-deployment.yaml)
apiVersion: apps/v1
kind: Deployment
metadata:
name: voting-app
spec:
template:
spec:
imagePullSecrets:
- name: acr-secret
containers:
- name: voting-app
image: abhishekazurecicd.azurecr.io/voting-app:65
10. Summary and Key Takeaways
- Multi-Microservice CI: Azure Pipelines successfully execute decoupled builds for Python, Node.js, and .NET components using specialized base container images.
- Path-Based Trigger Optimization: Restricting pipeline triggers to specific repository paths ensures that code modifications only build and deploy affected microservices.
- GitOps Paradigm: Argo CD eliminates configuration drift by continuously reconciling the live AKS state against desired Kubernetes manifests stored in Git.
- Automated Manifest Updates: Integrating shell scripts within CI pipelines to programmatically update image tags bridges the gap between image generation in ACR and declarative GitOps deployments.
- Private Registry Security: Securing AKS workloads against private container registries requires explicit
imagePullSecretsreferencing valid ACR credentials. - Declarative Infrastructure: Combining Azure DevOps, ACR, AKS, and Argo CD provides an enterprise-grade, highly resilient DevOps workflow ideal for modern production environments and professional resumes.