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.

Bir yanıt yazın

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir