a golden docker logo on a black background — Azure DevOps ile CI/CD işlem hattı kurma: Adım Adım Kılavuz

Kocaeli merkezli, lojistik SaaS hizmeti veren 65 mühendis çalıştıran bir şirkette, manuel deployment ve “elden git” build süreçleri 28 microservice’lik mimaride sürdürülemez hale geldi. 4 ayda Azure DevOps tabanlı multi-stage pipeline kuruldu. Bu yazı sahadaki teknik notlar.

Mimari Genel Bakış

Bileşen Görev
Azure Repos Git repository (28 mono → multi mix)
Azure Pipelines Multi-stage YAML pipelines
Azure Container Registry (ACR) Container image registry, Premium SKU
Azure Key Vault Secret + certificate
AKS Production runtime
Argo CD GitOps deployment to AKS
SonarQube Code quality gate
Trivy Container security scan

Branch Strategy

main → production (protected, PR + 2 reviewer + build green)
release/* → staging
develop → dev environment
feature/* → ephemeral PR environment

Multi-Stage Pipeline (YAML)

trigger:
  branches:
    include: [main, release/*, develop]
  paths:
    include: [src/order-service/*]

variables:
  - group: shared-secrets    # Variable group from Key Vault
  - name: imageName
    value: order-service
  - name: dotnetVersion
    value: '8.0'

stages:

# === BUILD ===
- stage: Build
  jobs:
  - job: BuildAndTest
    pool: { vmImage: ubuntu-latest }
    steps:
    - task: UseDotNet@2
      inputs: { version: $(dotnetVersion) }
    
    - script: dotnet restore src/order-service
      displayName: Restore
    
    - script: dotnet build --no-restore -c Release src/order-service
      displayName: Build
    
    - script: dotnet test src/order-service --logger trx --collect:"XPlat Code Coverage"
      displayName: Unit Tests
    
    - task: PublishTestResults@2
      inputs:
        testResultsFormat: VSTest
        testResultsFiles: '**/*.trx'
    
    - task: SonarQubePrepare@5
      inputs: { SonarQube: 'sq-conn', scannerMode: MSBuild, projectKey: 'order-service' }
    - task: SonarQubeAnalyze@5
    - task: SonarQubePublish@5
      inputs: { pollingTimeoutSec: '300' }

# === CONTAINER BUILD + SCAN ===
- stage: ContainerBuild
  dependsOn: Build
  condition: succeeded()
  jobs:
  - job: BuildImage
    pool: { vmImage: ubuntu-latest }
    steps:
    - task: Docker@2
      inputs:
        containerRegistry: 'acr-prod'
        repository: $(imageName)
        command: build
        Dockerfile: src/order-service/Dockerfile
        tags: |
          $(Build.BuildNumber)
          latest
    
    - script: |
        trivy image --severity HIGH,CRITICAL --exit-code 1 
          $(imageName):$(Build.BuildNumber)
      displayName: Trivy security scan
    
    - task: Docker@2
      inputs:
        containerRegistry: 'acr-prod'
        repository: $(imageName)
        command: push
        tags: |
          $(Build.BuildNumber)
          latest

# === DEPLOY DEV ===
- stage: DeployDev
  dependsOn: ContainerBuild
  condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/develop'))
  jobs:
  - deployment: Deploy
    environment: 'dev'
    pool: { vmImage: ubuntu-latest }
    strategy:
      runOnce:
        deploy:
          steps:
          - script: |
              # Update GitOps repo (Argo CD watch)
              git clone https://$(GIT_TOKEN)@dev.azure.com/myorg/gitops/_git/manifests
              cd manifests/dev/order-service
              yq eval -i '.spec.template.spec.containers[0].image = "acrprod.azurecr.io/$(imageName):$(Build.BuildNumber)"' deployment.yaml
              git commit -am "Deploy order-service $(Build.BuildNumber) to dev"
              git push
            displayName: Update GitOps manifest

# === DEPLOY STAGING (manual approval) ===
- stage: DeployStaging
  dependsOn: ContainerBuild
  condition: and(succeeded(), startsWith(variables['Build.SourceBranch'], 'refs/heads/release/'))
  jobs:
  - deployment: Deploy
    environment: 'staging'   # Approval configured on environment
    strategy:
      runOnce:
        deploy:
          steps:
          - script: ... (GitOps update)

# === DEPLOY PROD (manual approval + change window) ===
- stage: DeployProd
  dependsOn: DeployStaging
  condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/main'))
  jobs:
  - deployment: Deploy
    environment: 'production'   # Approval + business hours gate
    strategy:
      canary:
        increments: [10, 25, 50, 100]
        deploy:
          steps:
          - script: ... (Argo CD progressive rollout)

Approval Gates

Environment scope’ta approval ayarlanır (pipeline kodunda değil):

  • dev: No approval, auto-deploy from develop
  • staging: 1 reviewer (release manager)
  • production: 2 reviewer (engineering lead + product) + business hours (08:00-18:00 TR)

Variable Groups + Key Vault

variables:
- group: prod-secrets    # Linked to Key Vault kv-prod-shared

# Pipeline'da $(SQL_CONNECTION_STRING) gibi kullanılır
# Key Vault'tan otomatik fetch (service connection üzerinden)

GitOps with Argo CD

# manifests/prod/order-service/application.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: order-service-prod
  namespace: argocd
spec:
  project: production
  source:
    repoURL: https://dev.azure.com/myorg/gitops/_git/manifests
    targetRevision: HEAD
    path: prod/order-service
  destination:
    server: https://kubernetes.default.svc
    namespace: order-service
  syncPolicy:
    automated:
      prune: true
      selfHeal: true
    syncOptions:
    - CreateNamespace=true

Branch Policy (Repos)

Branch Policy
main 2 reviewer, build success, no work item link required, linked work item recommended
release/* 1 reviewer, build success
develop 1 reviewer, build success

Self-hosted Agent

Microsoft-hosted agent (free) build minutes sınırlı (parallel job 1). 8 self-hosted Linux agent (8 vCPU, 32 GB RAM, AKS-yi node pool):

curl -s https://vstsagentpackage.azureedge.net/agent/3.232.0/vsts-agent-linux-x64-3.232.0.tar.gz -o agent.tgz
tar zxvf agent.tgz
./config.sh --unattended 
  --url https://dev.azure.com/myorg 
  --auth pat --token $PAT 
  --pool linux-aks-pool 
  --agent $(hostname)
sudo ./svc.sh install
sudo ./svc.sh start

Sonuçlar

Metrik Önce Sonra
Manuel deployment süresi ~~3 saat (1 service) ~~8 dk (CI/CD)
Hatalı deployment / ay ~~6 ~~1
Mean Time to Recovery (rollback) ~~45 dk ~~3 dk (Argo CD revert)
Build → prod sürüsü ~~5 gün ~~4 saat (release branch)
Container CVE high/critical 0 (Trivy gate block)

Sahada Düşülen Üç Tuzak

  1. Build script’i pipeline YAML’ına gömmek: 28 service’te bakım imkansız. Templates + extends pattern + composite actions kullanılmalı.
  2. Approval’ı pipeline kodunda yapmaya çalışmak: Approval environment scope’ta ayarlanır, kod değil. Auditability artar.
  3. Production’a doğrudan deploy etmek: Canary / progressive rollout (Argo CD Rollouts veya AKS native) şart, %100’e doğrudan asla.

CloudSpark olarak Azure DevOps CI/CD pipeline tasarımı, multi-stage YAML, GitOps (Argo CD/Flux), container security scan ve self-hosted agent pool kurulumu için danışmanlık veriyoruz.

🇹🇷 Türkçe🇬🇧 English🇩🇪 Deutsch🇫🇷 Français🇸🇦 العربية🇷🇺 Русский🇪🇸 Español