Azure DevOps Pipeline Kurulumu ve Yapılandırması
Yazılım geliştirme süreçlerini otomatize etmek artık bir lüks değil, zorunluluk haline geldi. Azure DevOps Pipeline, Microsoft’un bu ihtiyaca verdiği yanıt ve doğru kurulduğunda ekibinizin hayatını ciddi anlamda kolaylaştırıyor. Bu yazıda sıfırdan bir Azure DevOps Pipeline kurarak gerçek dünya senaryolarında nasıl kullanacağınızı adım adım anlatacağım.
Azure DevOps Nedir ve Neden Kullanmalısınız
Azure DevOps, Microsoft’un bulut tabanlı DevOps platformudur. İçinde Boards (görev takibi), Repos (git depoları), Pipelines (CI/CD), Test Plans ve Artifacts bileşenleri barındırır. Biz bu yazıda ağırlıklı olarak Pipelines kısmına odaklanacağız.
Klasik senaryo şu şekilde işler: Geliştirici kodu commit eder, pipeline tetiklenir, testler çalışır, uygulama build edilir ve otomatik olarak hedefe deploy edilir. Bu döngüyü elle yapmaya çalışmak hem zaman kaybıdır hem de hata riskini artırır. Bir sysadmin olarak pipeline kurmayı öğrenmek, artık sadece developer’ların değil bizim de görevimiz haline geldi.
Ön Gereksinimler
Başlamadan önce elinizde şunlar olmalı:
- Aktif bir Azure aboneliği
- Azure DevOps Organization hesabı (dev.azure.com üzerinden ücretsiz açılabilir)
- Bir proje ve Git deposu
- Temel YAML bilgisi
- Eğer self-hosted agent kullanacaksanız bir Linux veya Windows sunucu
Azure DevOps’u Microsoft hesabıyla açabilirsiniz. dev.azure.com adresine giderek organization oluşturun, ardından ilk projenizi başlatın. Ücretsiz planda Microsoft-hosted agent’larla ayda 1800 dakika build süresi alıyorsunuz, bu da başlangıç için yeterli.
Pipeline Türleri: Classic vs YAML
Azure DevOps’ta iki farklı pipeline yöntemi var:
- Classic Editor: GUI tabanlı, sürükle bırak mantığı, yeni başlayanlar için kolay ama versiyon kontrolü zayıf
- YAML Pipeline: Kod olarak tanımlanmış, git reposunda saklanır, versiyon kontrolü mükemmel, production için önerilen yöntem
Kesinlikle YAML pipeline kullanmanızı öneririm. Hem infrastructure-as-code prensibine uyuyor hem de ekip içinde review edilebiliyor, geri alınabiliyor. Classic editor kullananları çok zor durumlarda gördüm, özellikle “bu pipeline nasıl çalışıyordu” sorusunu kim kurdu hatırlamıyordu, kimse bilmiyordu durumu.
İlk YAML Pipeline Dosyası
Repository’nizin root dizininde azure-pipelines.yml adında bir dosya oluşturun. En basit haliyle şu şekilde görünür:
trigger:
- main
pool:
vmImage: 'ubuntu-latest'
steps:
- script: echo "Merhaba Azure DevOps!"
displayName: 'İlk adım'
- script: |
echo "Build başlıyor"
ls -la
displayName: 'Dosyaları listele'
Bu dosyayı commit edip push ettiğinizde, Azure DevOps otomatik olarak pipeline’ı algılar ve main branch’e her push’ta çalıştırır. trigger kısmını değiştirerek hangi branch’lerde tetikleneceğini belirleyebilirsiniz.
Stages, Jobs ve Steps Kavramları
YAML pipeline’ın hiyerarşisini anlamak çok önemli:
- Stage: En üst seviye gruplandırma (Build, Test, Deploy gibi)
- Job: Bir agent üzerinde çalışan görev grubu, paralel çalışabilir
- Step: Job içindeki tek bir işlem birimi
Gerçek dünya senaryosunda genellikle şu yapıyı kullanırız:
trigger:
branches:
include:
- main
- develop
variables:
buildConfiguration: 'Release'
dotnetVersion: '8.x'
stages:
- stage: Build
displayName: 'Uygulama Build'
jobs:
- job: BuildJob
displayName: 'Build ve Test'
pool:
vmImage: 'ubuntu-latest'
steps:
- task: UseDotNet@2
inputs:
version: $(dotnetVersion)
- script: dotnet restore
displayName: 'Bağımlılıkları yükle'
- script: dotnet build --configuration $(buildConfiguration)
displayName: 'Build al'
- script: dotnet test --configuration $(buildConfiguration) --logger trx
displayName: 'Testleri çalıştır'
- task: PublishTestResults@2
inputs:
testResultsFormat: 'VSTest'
testResultsFiles: '**/*.trx'
displayName: 'Test sonuçlarını yayınla'
- stage: Deploy
displayName: 'Production Deploy'
dependsOn: Build
condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/main'))
jobs:
- deployment: DeployJob
environment: 'production'
strategy:
runOnce:
deploy:
steps:
- script: echo "Deploy ediliyor..."
displayName: 'Deploy adımı'
Burada condition kısmına dikkat edin. Deploy stage’i sadece build başarılı olduğunda ve sadece main branch’ten tetiklendiğinde çalışır. develop branch’e gelen push’lar sadece build ve test aşamasından geçer, deploy edilmez.
Self-Hosted Agent Kurulumu
Microsoft-hosted agent’lar çoğu durumda yeterli olsa da bazı senaryolarda kendi agent’ınızı kurmanız gerekir:
- Özel network’e erişim gerekiyorsa
- Şirket içi sunuculara deploy yapılacaksa
- Çok fazla build süresi gerekiyorsa ve maliyet önemliyse
- Özel yazılımlar kurulu bir ortam gerekiyorsa
Linux üzerinde agent kurulumu oldukça basit. Önce Azure DevOps’ta Organization Settings > Agent Pools > New Agent Pool yolunu izleyin ve bir pool oluşturun. Ardından sunucunuzda şu adımları izleyin:
# Agent için kullanıcı oluştur
sudo useradd -m -s /bin/bash azagent
sudo usermod -aG sudo azagent
# Agent dizini oluştur
sudo mkdir -p /opt/azagent
sudo chown azagent:azagent /opt/azagent
# azagent kullanıcısına geç
su - azagent
cd /opt/azagent
# Agent paketini indir (versiyonu kontrol edin, değişmiş olabilir)
wget https://vstsagentpackage.azureedge.net/agent/3.232.0/vsts-agent-linux-x64-3.232.0.tar.gz
tar zxvf vsts-agent-linux-x64-3.232.0.tar.gz
# Agent'ı yapılandır
./config.sh
config.sh çalıştırdığınızda sizden Azure DevOps URL’i ve Personal Access Token (PAT) isteyecek. PAT oluşturmak için Azure DevOps’ta User Settings > Personal Access Tokens yolunu izleyin, Agent Pools okuma ve yönetim yetkisi verin.
Agent’ı servis olarak çalıştırmak için:
# Root olarak servis kur
sudo /opt/azagent/svc.sh install azagent
sudo /opt/azagent/svc.sh start
# Servis durumunu kontrol et
sudo systemctl status vsts.agent.*.service
Docker ile Build Pipeline
Containerized uygulamalar için Docker build pipeline’ı kurmak çok yaygın bir senaryo. Şu yapıyı kullanabilirsiniz:
trigger:
- main
variables:
containerRegistry: 'myacr.azurecr.io'
imageRepository: 'myapp'
dockerfilePath: '$(Build.SourcesDirectory)/Dockerfile'
tag: '$(Build.BuildId)'
stages:
- stage: Build
jobs:
- job: DockerBuild
pool:
vmImage: 'ubuntu-latest'
steps:
- task: Docker@2
displayName: 'Azure Container Registry Login'
inputs:
command: login
containerRegistry: 'ACR-ServiceConnection'
- task: Docker@2
displayName: 'Docker Image Build'
inputs:
command: build
repository: $(imageRepository)
dockerfile: $(dockerfilePath)
containerRegistry: 'ACR-ServiceConnection'
tags: |
$(tag)
latest
- task: Docker@2
displayName: 'Image Push'
inputs:
command: push
repository: $(imageRepository)
containerRegistry: 'ACR-ServiceConnection'
tags: |
$(tag)
latest
Bu pipeline her main push’unda Docker image’ı build edip Azure Container Registry’e push ediyor. Build.BuildId unique bir numara olduğu için her build farklı tag alıyor ve hangi build’den hangi image’ın çıktığını takip edebiliyorsunuz.
Service Connection Kurulumu
Pipeline’ların Azure kaynaklarına, Docker registry’ye veya başka servislere erişebilmesi için Service Connection tanımlamanız gerekir. Bu güvenlik açısından kritik bir nokta.
Azure DevOps’ta Project Settings > Service Connections > New Service Connection yolunu izleyin. Azure Resource Manager için Managed Identity veya Service Principal kullanabilirsiniz. Service Principal oluşturmak için:
# Azure CLI ile service principal oluştur
az login
az ad sp create-for-rbac
--name "azure-devops-pipeline-sp"
--role Contributor
--scopes /subscriptions/SUBSCRIPTION_ID/resourceGroups/myResourceGroup
--output json
Bu komut size appId, password, tenant değerlerini döndürür. Bunları Azure DevOps’taki Service Connection formuna girin. Böylece pipeline’ınız sadece belirlediğiniz resource group üzerinde işlem yapabilir, daha geniş yetkiler vermemiş olursunuz. Minimum yetki prensibi burada da geçerli.
Değişkenler ve Gizli Bilgi Yönetimi
Pipeline’da şifre, connection string gibi hassas bilgileri YAML dosyasına yazmak büyük hata. Bunun için Azure DevOps’un Variable Groups ve Azure Key Vault entegrasyonunu kullanın.
Variable Group oluşturmak için Pipelines > Library > Variable Groups yolunu izleyin. Değişkenleri secret olarak işaretlerseniz pipeline loglarında görünmez. YAML’da kullanmak için:
trigger:
- main
variables:
- group: 'production-secrets'
- name: 'environment'
value: 'production'
stages:
- stage: Deploy
jobs:
- job: DeployApp
pool:
vmImage: 'ubuntu-latest'
steps:
- script: |
echo "Ortam: $(environment)"
# $(DB_PASSWORD) değeri log'da gizlenir
echo "Veritabanına bağlanılıyor..."
export DB_CONN="Server=myserver;Password=$(DB_PASSWORD)"
displayName: 'Konfigürasyon yükle'
Daha iyi bir alternatif Azure Key Vault entegrasyonu. Key Vault’taki secret’ları direkt pipeline variable’ı olarak kullanabilirsiniz:
variables:
- group: 'keyvault-secrets' # Key Vault'a bağlı Variable Group
steps:
- task: AzureKeyVault@2
inputs:
azureSubscription: 'Azure-ServiceConnection'
KeyVaultName: 'myKeyVault'
SecretsFilter: 'DB-Password,API-Key'
RunAsPreJob: true
Bu yaklaşımla secret’lar Key Vault’ta merkezi olarak yönetilir, rotation yaptığınızda pipeline’ı güncellemenize gerek kalmaz.
Approval Gate ve Environment Koruma
Production ortamına otomatik deploy yapılması bazen istenmiyor. Manuel onay mekanizması eklemek için Azure DevOps’ta Environment oluşturun ve Approvals & Checks ekleyin.
Pipelines > Environments > New Environment yolunu izleyin, “production” adında bir environment oluşturun. Ardından bu environment’a girerek Approvals and Checks > Approvals ekleyin, onay verecek kişileri belirleyin.
YAML’da bu environment’ı kullanmak yeterli:
- stage: ProductionDeploy
dependsOn: StagingDeploy
jobs:
- deployment: ProdDeployJob
environment: 'production' # Bu environment onay bekletir
strategy:
runOnce:
deploy:
steps:
- script: |
echo "Production'a deploy ediliyor"
echo "Build: $(Build.BuildId)"
displayName: 'Production Deploy'
Pipeline bu noktaya geldiğinde duraklar ve belirlediğiniz kişilere email gider. Onay verilince devam eder. Bu sayede production’a kazara bir şey gitmez.
Pipeline Template Kullanımı
Birden fazla projeniz varsa her birinde aynı pipeline kodunu yazmak yerine template kullanın. Bu gerçek hayatta çok işe yarıyor, özellikle 10-20 mikroservis yönetiyorsanız.
Merkezi bir repository’de templates/build-dotnet.yml dosyası oluşturun:
# templates/build-dotnet.yml
parameters:
- name: dotnetVersion
type: string
default: '8.x'
- name: projectPath
type: string
default: '.'
- name: runTests
type: boolean
default: true
steps:
- task: UseDotNet@2
inputs:
version: ${{ parameters.dotnetVersion }}
displayName: '.NET ${{ parameters.dotnetVersion }} kur'
- script: dotnet restore ${{ parameters.projectPath }}
displayName: 'Restore'
- script: dotnet build ${{ parameters.projectPath }} --configuration Release
displayName: 'Build'
- ${{ if parameters.runTests }}:
- script: dotnet test ${{ parameters.projectPath }} --logger trx
displayName: 'Test'
Projenizde bu template’i kullanmak için:
# Her projenin azure-pipelines.yml dosyası
trigger:
- main
resources:
repositories:
- repository: templates
type: git
name: 'InfraTeam/pipeline-templates'
ref: refs/heads/main
stages:
- stage: Build
jobs:
- job: BuildApp
pool:
vmImage: 'ubuntu-latest'
steps:
- template: templates/build-dotnet.yml@templates
parameters:
dotnetVersion: '8.x'
projectPath: './src/MyApp'
runTests: true
Böylece build template’ini güncellemek istediğinizde tek bir yerde yapıyorsunuz, tüm projeler otomatik olarak yeni versiyonu kullanıyor.
Pipeline Başarısızlıklarını Debug Etmek
Pipeline hata verdiğinde ne yapacaksınız? Birkaç pratik ipucu:
System.Debug değişkenini aktifleştirin: Pipeline’ı manuel çalıştırırken Variables sekmesinde System.Debug değişkenini true yapın. Bu çok daha detaylı log basar.
Diagnostic bilgileri ekleyin: Pipeline’ın hangi ortamda çalıştığını anlamak için:
# Bu adımı pipeline başına ekleyin, sorun gidermede çok işe yarar
- script: |
echo "Agent: $(Agent.Name)"
echo "OS: $(Agent.OS)"
echo "Build ID: $(Build.BuildId)"
echo "Branch: $(Build.SourceBranch)"
echo "Commit: $(Build.SourceVersion)"
env | grep -E "^(BUILD|AGENT|SYSTEM)" | sort
displayName: 'Ortam Bilgilerini Göster'
condition: always()
condition: always() ile bu adım önceki adım başarısız olsa bile çalışır.
Re-run failed jobs: Azure DevOps, başarısız olan job’ları tekrar başlatma imkanı veriyor. Eğer intermittent bir hata aldıysanız (network timeout gibi) tüm pipeline’ı yeniden çalıştırmak yerine sadece başarısız job’ı re-run edebilirsiniz.
Maliyet Optimizasyonu
Azure DevOps’ta pipeline maliyetlerini kontrol altında tutmak için şunlara dikkat edin:
- Paralel job sayısını optimize edin: Her paralel job için ücret ödersiniz. Job’ları gereksiz yere paralele ayırmayın
- Cache kullanın: Her build’de bağımlılıkları indirmek zaman ve kaynak harcar
steps:
- task: Cache@2
inputs:
key: 'npm | "$(Agent.OS)" | package-lock.json'
restoreKeys: |
npm | "$(Agent.OS)"
path: $(npm_config_cache)
displayName: 'NPM Cache'
- script: npm ci
displayName: 'Bağımlılıkları yükle'
- Trigger’ları akıllıca kullanın: Her commit’te her şeyin build edilmesi gerekmez. Path filter ile sadece ilgili dosyalar değiştiğinde tetikleyin
trigger:
paths:
include:
- src/**
- tests/**
exclude:
- docs/**
- '*.md'
Bu sayede sadece dokümantasyon güncellemelerinde gereksiz build tetiklenmez.
Sonuç
Azure DevOps Pipeline, doğru yapılandırıldığında ekibinizin verimliliğini dramatik biçimde artırır. Bu yazıda anlattıklarımı özetleyecek olursam:
- YAML pipeline tercih edin, Classic Editor’den uzak durun
- Stage, Job, Step hiyerarşisini doğru kurun
- Hassas bilgileri asla YAML’a yazmayın, Variable Groups ve Key Vault kullanın
- Self-hosted agent’ları sadece gerçekten gerektiğinde kullanın
- Template kullanarak tekrar eden kod yazmaktan kaçının
- Production ortamını Approval Gates ile koruyun
- Cache mekanizmaları ile build sürelerini ve maliyetleri düşürün
İlk pipeline’ınızı kurarken basit tutun, zamanla karmaşıklaştırın. Bir günde her şeyi yapmaya çalışmak yerine temel CI/CD akışını önce sağlamaya, ardından onay mekanizmaları ve güvenlik katmanlarını eklemeye odaklanın. Ekibinizin alışkanlıklarını ve mevcut süreçleri değiştirmek zaman alır, pipeline sadece teknik bir araç, asıl iş kültürel dönüşüm.
Sorularınız olursa yorumlara yazın, elimden geldiğince yanıtlamaya çalışırım.
