Warmly lit home office with dual screens for coding and programming. Perfect modern workspace for tech enthusiasts.

Samsun’da 320 çalışanlı bir gıda üreticisi, 24 ay önce “tıklayarak Azure portal’dan kurma” yönteminden Infrastructure as Code’a geçiş başlattı. Bugün 220+ resource Terraform + Bicep ile yönetiliyor, deployment süresi 4 saat → 12 dakika, environment parity (dev/test/prod) sağlandı. Bu yazı projenin sahadaki teknik notları.

Terraform mu Bicep mi?

Kriter Terraform Bicep
Multi-cloud Evet (AWS, GCP, Azure) Sadece Azure
Azure özellik desteği (yeni) 1-2 hafta gecikme Day 1
State management Backend (Azure Storage) Stateless (Azure Resource Manager)
Syntax HCL DSL (JSON benzeri)
Modül ekosistemi Çok geniş (Terraform Registry) Daha sınırlı (Bicep registry yeni)
Öğrenme eğrisi Orta Düşük (Azure native)

Karar: hibrit yaklaşım. Multi-cloud + 3rd party servisler Terraform; Azure-spesifik karmaşık (App Service, Functions, AKS configürasyonu) Bicep.

Repository Yapısı

infra/
├── terraform/
│   ├── modules/
│   │   ├── azure-rg/
│   │   ├── azure-vnet/
│   │   ├── azure-aks/
│   │   ├── azure-sql/
│   │   └── github-actions-runner/
│   ├── environments/
│   │   ├── dev/
│   │   │   ├── main.tf
│   │   │   ├── terraform.tfvars
│   │   │   └── backend.tf
│   │   ├── test/
│   │   └── prod/
│   └── README.md
├── bicep/
│   ├── modules/
│   │   ├── app-service.bicep
│   │   ├── function-app.bicep
│   │   └── private-endpoint.bicep
│   ├── main.dev.bicepparam
│   ├── main.test.bicepparam
│   ├── main.prod.bicepparam
│   └── main.bicep
└── .github/workflows/
    ├── tf-plan.yml
    ├── tf-apply.yml
    ├── bicep-validate.yml
    └── bicep-deploy.yml

Terraform Backend (Remote State)

terraform {
  backend "azurerm" {
    resource_group_name  = "rg-tfstate"
    storage_account_name = "tfstateprod"
    container_name       = "tfstate"
    key                  = "prod.tfstate"
    use_oidc             = true   # GitHub Actions OIDC
  }
  
  required_providers {
    azurerm = { source = "hashicorp/azurerm", version = "~> 3.110" }
  }
  
  required_version = ">= 1.7"
}

provider "azurerm" {
  features {}
  use_oidc = true
}

State Azure Storage’da, locking otomatik (blob lease). 3 environment için 3 ayrı state.

Modül Örneği: AKS

module "aks" {
  source = "../../modules/azure-aks"
  
  name                = "aks-prod"
  resource_group_name = module.rg.name
  location            = "northeurope"
  
  system_node_count   = 3
  system_node_size    = "Standard_D4s_v5"
  
  user_node_pools = {
    apps = {
      vm_size    = "Standard_D8s_v5"
      min_count  = 3
      max_count  = 12
      mode       = "User"
    }
    spot = {
      vm_size    = "Standard_D8s_v5"
      min_count  = 0
      max_count  = 8
      mode       = "User"
      spot       = true
    }
  }
  
  network_plugin    = "azure"
  network_policy    = "calico"
  private_cluster   = true
  
  identity = {
    type = "SystemAssigned"
  }
  
  tags = local.common_tags
}

Bicep: App Service Detayları

param appName string
param location string
param skuName string = 'P1v3'
param vnetSubnetId string

resource appServicePlan 'Microsoft.Web/serverfarms@2023-12-01' = {
  name: 'asp-${appName}'
  location: location
  sku: { name: skuName, tier: 'PremiumV3' }
  properties: { reserved: true }
}

resource webApp 'Microsoft.Web/sites@2023-12-01' = {
  name: appName
  location: location
  identity: { type: 'SystemAssigned' }
  properties: {
    serverFarmId: appServicePlan.id
    virtualNetworkSubnetId: vnetSubnetId
    httpsOnly: true
    siteConfig: {
      linuxFxVersion: 'DOTNETCORE|8.0'
      minTlsVersion: '1.2'
      ftpsState: 'Disabled'
      vnetRouteAllEnabled: true
      ipSecurityRestrictionsDefaultAction: 'Deny'
    }
  }
}

output appServiceUrl string = 'https://${webApp.properties.defaultHostName}'

CI/CD: Plan/Apply Discipline

name: tf-plan
on:
  pull_request:
    paths: ['infra/terraform/**']

jobs:
  plan:
    runs-on: ubuntu-latest
    permissions:
      id-token: write
      contents: read
      pull-requests: write
    steps:
    - uses: actions/checkout@v4
    - uses: hashicorp/setup-terraform@v3
    - uses: azure/login@v2
      with:
        client-id: ${{ secrets.AZURE_CLIENT_ID }}
        tenant-id: ${{ secrets.AZURE_TENANT_ID }}
        subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
    
    - name: terraform init
      run: terraform init
      working-directory: infra/terraform/environments/prod
    
    - name: terraform plan
      run: terraform plan -out=tfplan -no-color
      working-directory: infra/terraform/environments/prod
    
    - uses: actions/upload-artifact@v4
      with:
        name: tfplan
        path: infra/terraform/environments/prod/tfplan

PR açıldığında plan otomatik, output PR yorumuna düşüyor. Reviewer plan’ı görüp onaylıyor. Merge sonrası apply workflow tetikleniyor.

Drift Detection

Manuel portal’dan değişiklik (acil müdahale, hızlı düzeltme) drift yaratır. Haftalık terraform plan cron job, drift varsa Slack notify.

on:
  schedule:
    - cron: '0 6 * * MON'

jobs:
  drift-check:
    runs-on: ubuntu-latest
    steps:
    - run: |
        terraform plan -detailed-exitcode -no-color > plan.out
        if [ $? -eq 2 ]; then
          gh issue create --title "IaC Drift Detected" --body "$(cat plan.out)"
        fi

Sonuçlar (8 Ay)

Metrik Önce Sonra
Yeni environment kurulum süresi ~~4 saat (manuel) ~~12 dk (one-click)
Dev/test/prod parity %~~60 (drift’li) %~~98
Deployment hata oranı ~~%18 ~~%2
Audit trail (kim ne değiştirdi) Yok Tam (Git history)
Rollback süresi 2-4 saat ~~8 dk

Sahada Düşülen Üç Tuzak

  1. State’i Git’e commit etmek: Secret leak, lock yok. Remote backend (Azure Storage) şart.
  2. Manuel portal değişikliklerini engellemek yerine ignore etmek: Drift birikir, IaC anlamsızlaşır. Drift detection + sıkı governance şart.
  3. Module versioning yapmamak: Module değişince tüm environment etkileniyor. Semantic versioning (v1.2.0) ve pinned reference şart.

CloudSpark olarak Terraform + Bicep IaC programı kurulumu, modül kütüphanesi tasarımı, CI/CD entegrasyon ve drift detection projeleri için danışmanlık veriyoruz.

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