Terraform Destroy: Güvenli Altyapı Kaldırma Rehberi

Altyapıyı ayağa kaldırmak kadar, onu güvenli bir şekilde kaldırmak da ciddi bir beceri ister. terraform destroy komutu kulağa basit gelse de, yanlış ellerde veya eksik hazırlıkla çalıştırıldığında production veritabanlarını, kritik ağ yapılarını ve geri dönülemez kaynakları silebilir. Bu yazıda, terraform destroy komutunu gerçekten güvenli kullanmanın ne anlama geldiğini, hangi önlemleri almanız gerektiğini ve CI/CD pipeline’larında bu işlemi nasıl kontrol altında tutabileceğinizi ele alacağız.

Terraform Destroy Neden Bu Kadar Kritik?

Terraform’un temel gücü, altyapıyı kod olarak tanımlaması ve state dosyası üzerinden kaynakları takip etmesidir. terraform destroy ise bu state dosyasındaki tüm kaynakları tersine çevirerek siler. Yani Terraform’un ne oluşturduğunu biliyorsa, onu nasıl sileceğini de biliyor demektir.

Ancak şunu düşünün: Bir geliştirici test ortamını kaldırmak için terraform destroy çalıştırıyor, ama çalışma dizini yanlışlıkla production workspace’ini gösteriyor. Bu senaryo gerçek dünyada defalarca yaşandı ve her seferinde sonuç felaket oldu. AWS RDS instance’ları, Kubernetes cluster’ları, VPC’ler… hepsi dakikalar içinde silinebilir.

Bu yüzden “destroy güvenliği” dediğimizde şunları kastediyoruz:

  • Yanlışlıkla yanlış ortamı silmemek
  • Kritik kaynakları kazara destroy’dan korumak
  • Destroy öncesi ne silineceğini kesin olarak bilmek
  • CI/CD süreçlerinde onay mekanizmaları kurmak
  • Silme işlemini kayıt altına almak ve izlenebilir kılmak

Temel Destroy Komutu ve Parametreler

İlk olarak komutun sözdizimini netleştirelim:

# Basit destroy - her şeyi siler, onay ister
terraform destroy

# Onay istemeden direkt sil (tehlikeli!)
terraform destroy -auto-approve

# Sadece belirli bir kaynağı sil
terraform destroy -target=aws_instance.web_server

# Belirli değişkenlerle destroy çalıştır
terraform destroy -var="environment=staging" -var-file="staging.tfvars"

# Paralel işlem sayısını sınırla
terraform destroy -parallelism=5

# Plan dosyası üzerinden destroy
terraform plan -destroy -out=destroy.plan
terraform apply destroy.plan

-auto-approve: Onay adımını atlar. Production’da asla kullanmayın.

-target: Sadece belirtilen kaynağı ve bağımlılıklarını siler. Seçici temizlik için kullanılır ama dikkatli olunmalıdır.

-parallelism: Eş zamanlı silme işlemi sayısını belirler. Varsayılan 10’dur. Düşürmek bazı API rate limit sorunlarını çözer.

-refresh=false: State’i güncel durumla senkronize etmeden destroy yapar. Genellikle önerilmez.

Destroy Planı Oluşturma ve İnceleme

En iyi pratik, destroy işlemini asla doğrudan çalıştırmamak ve önce bir plan oluşturmaktır. Bu plan size tam olarak neyin silineceğini gösterir:

# Destroy planını dosyaya yaz
terraform plan -destroy -out=destroy-$(date +%Y%m%d-%H%M%S).plan

# Planı insan tarafından okunabilir formatta görüntüle
terraform show destroy-20241215-143022.plan

# JSON formatında detaylı çıktı al
terraform show -json destroy-20241215-143022.plan | jq '.resource_changes[] | select(.change.actions[] == "delete") | .address'

Bu yaklaşımın kritik avantajı şu: Plan dosyasını oluşturduğunuz anda state’in bir snapshot’ını alıyorsunuz. Planı inceleyip onayladıktan sonra uygularsınız ve aradaki sürede başka biri state’i değiştirse bile, siz tam olarak incelediğiniz şeyi siliyorsunuz.

Pratikte bir pipeline’da bu şöyle görünür:

#!/bin/bash
# destroy-pipeline.sh

set -euo pipefail

PLAN_FILE="destroy-$(date +%Y%m%d-%H%M%S).plan"
LOG_FILE="destroy-$(date +%Y%m%d-%H%M%S).log"

echo "Destroy planı oluşturuluyor..."
terraform plan -destroy -out="${PLAN_FILE}" 2>&1 | tee "${LOG_FILE}"

echo ""
echo "Silinecek kaynaklar:"
terraform show -json "${PLAN_FILE}" | 
  jq -r '.resource_changes[] | select(.change.actions[] == "delete") | "  - (.address) [(.type)]"'

echo ""
read -p "Bu kaynakları silmek istediginizden emin misiniz? (yes/no): " confirm

if [ "${confirm}" != "yes" ]; then
  echo "Destroy iptal edildi."
  rm -f "${PLAN_FILE}"
  exit 0
fi

echo "Destroy uygulanıyor..."
terraform apply "${PLAN_FILE}" 2>&1 | tee -a "${LOG_FILE}"

echo "Destroy tamamlandı. Log: ${LOG_FILE}"

Prevent Destroy ile Kritik Kaynakları Koruma

Terraform’un en değerli güvenlik özelliklerinden biri lifecycle bloğundaki prevent_destroy argümanıdır. Bu argümanı true olarak ayarladığınızda, ilgili kaynak terraform destroy veya herhangi bir plan sırasında silinmeye çalışılırsa Terraform hata verir ve işlemi durdurur.

# main.tf - Production veritabanı koruma örneği
resource "aws_db_instance" "production_db" {
  identifier        = "prod-mysql-primary"
  engine            = "mysql"
  engine_version    = "8.0"
  instance_class    = "db.r5.xlarge"
  allocated_storage = 500

  backup_retention_period = 30
  deletion_protection     = true

  lifecycle {
    prevent_destroy = true
  }

  tags = {
    Environment = "production"
    Critical    = "true"
  }
}

# S3 bucket - production verileri
resource "aws_s3_bucket" "production_data" {
  bucket = "company-production-data-2024"

  lifecycle {
    prevent_destroy = true
  }
}

# Route53 hosted zone - DNS kaybı felakettir
resource "aws_route53_zone" "primary" {
  name = "example.com"

  lifecycle {
    prevent_destroy = true
  }
}

prevent_destroy yalnızca Terraform konfigürasyon dosyasında bu satırı kaldırdığınızda bypass edilebilir. Bu da kasıtlı bir eylem gerektirir ve code review sürecinden geçmesini sağlar. Birisi production veritabanını silmek istiyorsa, önce PR açmak zorunda kalır.

Workspace Bazlı Güvenlik Stratejisi

Farklı ortamlar için farklı güvenlik seviyeleri uygulamak iyi bir pratiktir. Bunu workspace’ler ve koşullu prevent_destroy kombinasyonuyla yapabilirsiniz:

# variables.tf
variable "environment" {
  type        = string
  description = "Ortam adı: dev, staging, production"
}

locals {
  is_production = var.environment == "production"
}

# database.tf
resource "aws_db_instance" "main" {
  identifier        = "${var.environment}-database"
  engine            = "postgres"
  instance_class    = local.is_production ? "db.r5.2xlarge" : "db.t3.medium"
  allocated_storage = local.is_production ? 1000 : 20

  deletion_protection = local.is_production

  lifecycle {
    prevent_destroy = true
  }
}

Ancak dikkat: prevent_destroy bir değişken veya local değerine bağlanamaz. Bu bir Terraform limitasyonudur. Lifecycle argümanları literal değerler olmalıdır. Bu yüzden genellikle iki ayrı kaynak tanımı veya module kullanılır:

# modules/database/prod/main.tf - Production modülü
resource "aws_db_instance" "main" {
  # ... konfigürasyon

  lifecycle {
    prevent_destroy = true
  }
}

# modules/database/non-prod/main.tf - Non-production modülü
resource "aws_db_instance" "main" {
  # ... konfigürasyon

  lifecycle {
    prevent_destroy = false
  }
}

CI/CD Pipeline’larında Güvenli Destroy

GitLab CI veya GitHub Actions üzerinde destroy işlemlerini nasıl güvenli hale getirirsiniz? Şu temel kurallar işe yarar:

  • Manuel onay adımı zorunlu olsun
  • Sadece belirli branch’lerden tetiklensin
  • Sadece belirli kişiler onaylayabilsin
  • Her destroy işlemi loglanarak artifact olarak saklansin
# .gitlab-ci.yml - Güvenli destroy pipeline örneği
stages:
  - validate
  - plan-destroy
  - approve
  - execute-destroy

variables:
  TF_VAR_environment: ${CI_ENVIRONMENT_NAME}

plan-destroy:
  stage: plan-destroy
  image: hashicorp/terraform:1.6
  script:
    - terraform init -backend-config="key=${CI_ENVIRONMENT_NAME}/terraform.tfstate"
    - terraform plan -destroy -out=destroy.plan
    - terraform show -json destroy.plan > destroy-plan.json
    - |
      echo "=== SİLİNECEK KAYNAKLAR ==="
      cat destroy-plan.json | jq -r '.resource_changes[] | 
        select(.change.actions[] == "delete") | 
        "SILINECEK: (.address)"'
  artifacts:
    paths:
      - destroy.plan
      - destroy-plan.json
    expire_in: 1 hour
  only:
    - /^destroy/.*/
  environment:
    name: ${DEPLOY_ENV}

execute-destroy:
  stage: execute-destroy
  image: hashicorp/terraform:1.6
  script:
    - terraform init -backend-config="key=${CI_ENVIRONMENT_NAME}/terraform.tfstate"
    - terraform apply destroy.plan
  dependencies:
    - plan-destroy
  when: manual
  allow_failure: false
  only:
    - /^destroy/.*/
  environment:
    name: ${DEPLOY_ENV}
  rules:
    - if: $CI_COMMIT_REF_PROTECTED == "true"
      when: never

GitHub Actions için benzer bir yaklaşım:

# .github/workflows/terraform-destroy.yml
name: Terraform Destroy

on:
  workflow_dispatch:
    inputs:
      environment:
        description: 'Hangi ortam silinsin?'
        required: true
        type: choice
        options:
          - staging
          - dev
      confirm:
        description: 'DESTROY yazarak onaylayın'
        required: true

jobs:
  validate-input:
    runs-on: ubuntu-latest
    steps:
      - name: Onay kontrolü
        run: |
          if [ "${{ github.event.inputs.confirm }}" != "DESTROY" ]; then
            echo "Onay metni hatalı. İşlem iptal edildi."
            exit 1
          fi
          if [ "${{ github.event.inputs.environment }}" == "production" ]; then
            echo "Production ortamı bu pipeline üzerinden silinemez!"
            exit 1
          fi

  plan-and-destroy:
    needs: validate-input
    runs-on: ubuntu-latest
    environment: ${{ github.event.inputs.environment }}
    steps:
      - uses: actions/checkout@v4

      - uses: hashicorp/setup-terraform@v3
        with:
          terraform_version: 1.6.0

      - name: Terraform Init
        run: terraform init
        working-directory: environments/${{ github.event.inputs.environment }}

      - name: Destroy Plan
        run: terraform plan -destroy -out=destroy.plan
        working-directory: environments/${{ github.event.inputs.environment }}

      - name: Plan Özeti
        run: |
          terraform show -json destroy.plan | 
          jq -r '.resource_changes[] | select(.change.actions[] == "delete") | .address'
        working-directory: environments/${{ github.event.inputs.environment }}

      - name: Execute Destroy
        run: terraform apply -auto-approve destroy.plan
        working-directory: environments/${{ github.event.inputs.environment }}

State Lock ve Concurrent Destroy Sorunları

Birden fazla kişi veya pipeline aynı anda destroy çalıştırmaya çalışırsa ne olur? Terraform’un state locking mekanizması burada devreye girer. S3 + DynamoDB backend kullanıyorsanız, lock otomatik yönetilir:

# backend.tf
terraform {
  backend "s3" {
    bucket         = "company-terraform-state"
    key            = "environments/staging/terraform.tfstate"
    region         = "eu-west-1"
    encrypt        = true
    dynamodb_table = "terraform-state-lock"
  }
}

Bazen bir önceki işlem yarıda kesilirse lock takılı kalabilir. Bu durumda:

# Mevcut lock bilgisini gör
terraform force-unlock --help

# Lock ID'yi state dosyasından veya DynamoDB'den bul
aws dynamodb scan --table-name terraform-state-lock 
  --filter-expression "attribute_exists(LockID)"

# Kilidi zorla aç (dikkatli kullanın!)
terraform force-unlock <LOCK_ID>

Lock’u zorla açmadan önce şunları doğrulayın: Başka bir işlem gerçekten devam ediyor mu? Hayır ise, lock’u açmak güvenlidir.

Kısmi Destroy: Belirli Kaynakları Silme

Bazen tüm altyapıyı değil, sadece belirli kaynakları silmek istersiniz. -target bayrağı bunun için kullanılır ancak dikkatli olunmalıdır çünkü bağımlılık grafiğini bozabilir:

# Tek bir EC2 instance'ı sil
terraform destroy -target=aws_instance.web_server_1

# Tüm bir modülü sil
terraform destroy -target=module.frontend

# Birden fazla kaynak belirt
terraform destroy 
  -target=aws_instance.web_1 
  -target=aws_instance.web_2 
  -target=aws_lb.frontend

# Önce planı gör
terraform plan -destroy -target=module.frontend

Gerçek dünya senaryosu: Staging ortamında sadece Kubernetes node group’larını silip cluster altyapısını korumak istiyorsunuz:

# Node group'ları listele
terraform state list | grep node_group

# Sadece node group'ları sil
terraform destroy 
  -target=aws_eks_node_group.workers 
  -target=aws_eks_node_group.spot_workers 
  -auto-approve

Destroy Öncesi Backup ve Snapshot Alma

Destroy yapmadan önce kritik kaynakların yedeğini almak iyi bir pratiktir. Bunu Terraform içinde null_resource ve local-exec ile otomatize edebilirsiniz:

# pre-destroy-backup.tf
resource "null_resource" "pre_destroy_snapshot" {
  # Bu kaynak her zaman destroy sırasında tetiklenir
  triggers = {
    db_instance_id = aws_db_instance.main.id
  }

  provisioner "local-exec" {
    when    = destroy
    command = <<-EOT
      echo "Destroy öncesi snapshot alınıyor: ${self.triggers.db_instance_id}"
      aws rds create-db-snapshot 
        --db-instance-identifier ${self.triggers.db_instance_id} 
        --db-snapshot-identifier "pre-destroy-$(date +%Y%m%d-%H%M%S)"
      echo "Snapshot alındı."
    EOT
  }
}

Ayrıca destroy öncesi otomatik kontrol scripti:

#!/bin/bash
# pre-destroy-checks.sh

set -euo pipefail

ENVIRONMENT=${1:-"unknown"}

echo "=== Destroy Öncesi Kontroller: ${ENVIRONMENT} ==="

# 1. Ortam kontrolü
if [ "${ENVIRONMENT}" == "production" ]; then
  echo "HATA: Production ortamı bu script ile silinemez!"
  exit 1
fi

# 2. Son deployment zamanını kontrol et
LAST_APPLY=$(terraform show -json | jq -r '.values.root_module.resources[0].values.tags.LastApplied // "unknown"')
echo "Son deployment: ${LAST_APPLY}"

# 3. Aktif bağlantı kontrolü (RDS için)
DB_CONNECTIONS=$(aws cloudwatch get-metric-statistics 
  --namespace AWS/RDS 
  --metric-name DatabaseConnections 
  --start-time $(date -u -d '5 minutes ago' +%Y-%m-%dT%H:%M:%SZ) 
  --end-time $(date -u +%Y-%m-%dT%H:%M:%SZ) 
  --period 300 
  --statistics Maximum 
  --query 'Datapoints[0].Maximum' 
  --output text 2>/dev/null || echo "0")

if [ "${DB_CONNECTIONS}" != "None" ] && [ "${DB_CONNECTIONS}" != "0" ]; then
  echo "UYARI: Veritabanına aktif bağlantılar var: ${DB_CONNECTIONS}"
  read -p "Devam etmek istiyor musunuz? (yes/no): " confirm
  [ "${confirm}" != "yes" ] && exit 1
fi

# 4. Terraform state kontrolü
RESOURCE_COUNT=$(terraform state list | wc -l)
echo "Silinecek kaynak sayısı: ${RESOURCE_COUNT}"

echo "Tüm kontroller tamamlandı. Destroy başlayabilir."

Destroy Sonrası Doğrulama

Destroy tamamlandıktan sonra gerçekten her şeyin silindiğini doğrulamak da önemlidir. Özellikle cloud provider’ların bazı kaynakları asenkron sildiği durumlarda, Terraform “tamamlandı” dese de kaynaklar hala var olabilir:

#!/bin/bash
# post-destroy-verify.sh

echo "=== Destroy Sonrası Doğrulama ==="

# State dosyasının boş olduğunu kontrol et
REMAINING=$(terraform state list 2>/dev/null | wc -l)
if [ "${REMAINING}" -gt 0 ]; then
  echo "UYARI: State'de hala ${REMAINING} kaynak var:"
  terraform state list
else
  echo "State dosyası temiz. Tüm kaynaklar silindi."
fi

# AWS üzerinde tag ile arama yap
echo "AWS üzerinde orphan kaynakları kontrol ediliyor..."
aws resourcegroupstaggingapi get-resources 
  --tag-filters "Key=Environment,Values=staging" 
  --query 'ResourceTagMappingList[].ResourceARN' 
  --output text | tr 't' 'n' | while read arn; do
    echo "Hala mevcut: ${arn}"
done

echo "Doğrulama tamamlandı."

Sık Yapılan Hatalar ve Önlemler

Yıllar içinde sysadminlerin terraform destroy konusunda yaptığı tekrarlayan hatalar şöyle özetlenebilir:

Yanlış workspace ile çalışmak: Her zaman destroy öncesi hangi workspace’te olduğunuzu kontrol edin.

# .bashrc veya .zshrc'ye ekleyin - her terminalde workspace göster
terraform_prompt() {
  if [ -d ".terraform" ]; then
    ws=$(terraform workspace show 2>/dev/null)
    echo "[tf:${ws}]"
  fi
}
# PS1'e ekleyin: $(terraform_prompt)

State dosyasının yanlış yere işaret etmesi: Backend konfigürasyonunu her zaman doğrulayın.

# Hangi state dosyasına baktığınızı her zaman doğrulayın
terraform state list | head -5
cat .terraform/terraform.tfstate | jq '.backend.config'

Auto-approve’u varsayılan yapmak: CI/CD’de bile mümkünse plan + manual approval kullanın. En azından destroy için.

Rollback planı olmadan destroy: Her destroy öncesi “bu geri alınamaz, planım nedir?” sorusunu sorun.

Sonuç

terraform destroy küçük bir komut ama arkasında büyük bir sorumluluk taşıyor. Bu yazıda ele aldığımız pratikleri özetleyecek olursak:

  • Her destroy öncesi -destroy plan oluşturun ve detaylıca inceleyin
  • Kritik kaynakları prevent_destroy = true ile koruyun
  • CI/CD pipeline’larında manuel onay adımı zorunlu kılın
  • Production ortamını pipeline’dan tamamen izole edin veya çok katmanlı koruma ekleyin
  • Workspace’inizin ve backend konfigürasyonunuzun doğruluğunu her seferinde kontrol edin
  • Destroy öncesi backup/snapshot alın, sonrasında doğrulama yapın
  • Her destroy işlemini logla ve artifact olarak sakla

Infrastructure as Code’un gücü sadece oluşturmakta değil, kontrollü bir şekilde yönetmekte yatıyor. Destroy işlemi de bu yönetimin bir parçası ve aynı titizliği hak ediyor. “Bir daha yazmayız” diyerek rahat hissetmek yerine, her silme işlemini potansiyel bir risk noktası olarak değerlendirin. Bu zihniyetle çalışan ekipler, yanlışlıkla kritik ortam silme vakalarını yaşayan ekipler değil, saat 3’te panikle kalkıp “nasıl geri döneceğiz” diye sormayan ekipler oluyor.

Bir yanıt yazın

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