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
- Build script’i pipeline YAML’ına gömmek: 28 service’te bakım imkansız. Templates + extends pattern + composite actions kullanılmalı.
- Approval’ı pipeline kodunda yapmaya çalışmak: Approval environment scope’ta ayarlanır, kod değil. Auditability artar.
- 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.



