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
-destroyplan oluşturun ve detaylıca inceleyin - Kritik kaynakları
prevent_destroy = trueile 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.
