Ekim 26 2026

Ghost Infrastructure: Cloud Maliyetlerinizi Gizlice Artıran Kullanılmayan Resource’ları Tespit Etme

Modern bir devops mühendisinin kabusları genellikle gece yarısı gelen PagerDuty alarmlarıyla sınırlı değildir. Asıl soğuk terler, ay sonunda FinOps ekibinin Slack’ten attığı “Bu ayki cloud faturası neden %40 arttı?” mesajıyla başlar. Şirketler büyüdükçe, microservice mimarileri genişledikçe ve CI/CD pipeline’ları günde yüzlerce kez ephemeral ortamlar ayağa kaldırıp indirdikçe, arkada sessizce biriken “ghost infrastructure” (hayalet altyapı) kaçınılmaz hale gelir. Cost optimization sadece instance type küçültmekten ibaret değildir; asıl mesele, varlığından bile haberdar olmadığınız ama her saniye faturalandırılan zombi kaynakları yönetebilmektir. Bu rehberde, proaktif resource management ve finops pratikleriyle bu sızıntıları nasıl tespit edip kökünden temizleyeceğimizi inceliyoruz.

Zombi Altyapının Anatomisi: Bu Kaynaklar Nereden Çıkıyor?

Kıdemli bir mühendis olarak “Biz her şeyi Terraform ile yönetiyoruz, drift olmaz” yanılgısına düşmüş olabilirsiniz. Ne yazık ki gerçek dünya production ortamları ideal koşullarda çalışmaz. Zombi kaynakların arkasında genellikle üç ana faktör yatar:

1. Pipeline Race Condition ve Timeout’lar: CI runner’da koşan bir terraform destroy komutunun ağ kesintisi veya SIGKILL ile sonlanması, state dosyasının güncellenememesine ama cloud provider tarafında bazı resource’ların (örneğin provision edilmiş bir ALB veya NAT Gateway) yetim (orphaned) kalmasına yol açar.

2. Kubernetes CSI Driver Sızıntıları: K8s cluster’ında bir PersistentVolumeClaim silindiğinde, underlying cloud diskini (AWS EBS, GCP Persistent Disk) temizlemesi beklenen CSI driver zaman zaman API rate limit’e takılır veya reclaim policy’si yanlış yapılandırıldığı için diski available durumunda serbest bırakır. Pod ölür, PVC silinir ama disk sonsuza kadar faturaya yazmaya devam eder.

3. Manuel Acil Müdahaleler: Production incident sırasında debug amaçlı açılan read-replica’lar, detach edilen root volume’lar veya snapshot’lar post-mortem sonrasında ticket’lar kapandığında unutulur.

En Büyük Sessiz Katiller ve CLI ile Avlanma Taktikleri

Şimdi lafı uzatmadan, ay sonunda faturayı kabartan majör zombi kaynakları tespit etmek için terminale geçelim. AWS ekosistemini baz alacağız ancak mantık GCP ve Azure için de birebir aynıdır.

1. Unattached EBS Volume’lar

Bir EC2 instance termine edildiğinde, varsayılan olarak root volume silinir; fakat secondary volume’lar (özellikle DeleteOnTermination=false flag’i ile attach edilmiş olanlar) veya dinamik PVC diskleri arkada kalır. Üstelik bunlar io2 veya gp3 gibi yüksek IOPS provision edilmiş pahalı disklerse maliyet çarpanı devasa olur.

Aşağıdaki AWS CLI + JMESPath query’si ile sadece boşa çıkmış (available) diskleri, tipleri ve boyutlarıyla filtreleyebilirsiniz:

aws ec2 describe-volumes \
    --filters Name=status,Values=available \
    --query "Volumes[*].{ID:VolumeId,Size:Size,Type:VolumeType,AZ:AvailabilityZone,Created:CreateTime}" \
    --output table

Eğer 30 günden eski ve unattached olan diskleri doğrudan bulup silinebilir listesine atmak isterseniz, küçük bir bash one-liner işinizi görür:

aws ec2 describe-volumes \
    --filters Name=status,Values=available \
    --query "Volumes[?CreateTime<='$(date -v-30d -u +%Y-%m-%d)'].VolumeId" \
    --output text | tr '\t' '\n'

2. Unassociated Elastic IP (EIP) Sızıntıları

Cloud provider’ların en sevdiği para kazanma yöntemi: Bir IP’yi kullanırsanız ücretsizdir (veya çok ucuzdur), ama tahsis edip hiçbir instance veya Network Interface’e bağlamazsanız saatlik ceza faturası kesilir. IPv4 kıtlığı nedeniyle AWS, 1 Şubat 2024’ten itibaren tüm public IPv4 adreslerini (kullanılsın veya kullanılmasın) saatlik $0.005 ile ücretlendirmeye başladı. 100 adet boşta EIP, ayda yaklaşık $360 doğrudan çöp demektir.

aws ec2 describe-addresses \
    --query "Addresses[?AssociationId==null].{AllocationId:AllocationId,PublicIp:PublicIp}" \
    --output table

Çıkan listedeki adresleri serbest bırakmak için:

for alloc_id in $(aws ec2 describe-addresses --query "Addresses[?AssociationId==null].AllocationId" --output text); do
    echo "Releasing: $alloc_id"
    aws ec2 release-address --allocation-id "$alloc_id"
done

3. Hayalet Load Balancer’lar ve Trafiksiz NAT Gateway’ler

Bir ortamı decommission ederken compute katmanını (Auto Scaling Group, ECS Task, EKS Node) kapatıp, önündeki ALB’yi veya VPC içindeki NAT Gateway’i silmeyi unutmak çok yaygındır. NAT Gateway, üzerinden 1 byte bile geçmese bile sırf ayakta kaldığı için her ay yaklaşık $32 taban ücret çeker (bölgeye göre değişir). Çoklu AZ kurulumunda 3 NAT Gateway ayda ~$100 eder.

Son 7 gün içinde üzerinden sıfır request geçmiş ALB’leri CloudWatch metriği sorgulamadan doğrudan tespit edemezsiniz. AWS CLI ile CloudWatch metriklerini tarayarak RequestCount toplamı 0 olan load balancer’ları yakalamak en garanti yoldur:

START_TIME=$(date -u -v-7d +%Y-%m-%dT%H:%M:%SZ)
END_TIME=$(date -u +%Y-%m-%dT%H:%M:%SZ)

for alb_arn in $(aws elbv2 describe-load-balancers --query "LoadBalancers[*].LoadBalancerArn" --output text); do
    alb_name=$(echo "$alb_arn" | awk -F'/' '{print $2"/"$3"/"$4}')
    sum=$(aws cloudwatch get-metric-data --metric-data-queries '[{
        "Id": "m1",
        "MetricStat": {
            "Metric": {
                "Namespace": "AWS/ApplicationELB",
                "MetricName": "RequestCount",
                "Dimensions": [{"Name": "LoadBalancer", "Value": "'"$alb_name"'"}]
            },
            "Period": 604800,
            "Stat": "Sum"
        }
    }]' --start-time "$START_TIME" --end-time "$END_TIME" --query "MetricDataResults[0].Values[0]" --output text)

    if [ "$sum" == "None" ] || [ -z "$sum" ] || [ "$sum" == "0.0" ]; then
        echo "Zombie ALB Tespit Edildi: $alb_arn (Son 7 günde 0 request)"
    fi
done

Modern Tespit Yöntemi: Steampipe ile SQL Tabanlı Cloud Auditing

Bütün AWS API’lerini bash scriptleriyle gezip JSON parse etmek bir noktadan sonra kırılgan hale gelir. Infrastructure as Code kültürüne SQL sorgulama yeteneğini entegre eden Steampipe, cloud auditing için endüstri standardı araçlardan biri haline geldi. Cloud kaynaklarını bir Postgres veritabanı gibi sorgulamanızı sağlar.

Örneğin, bağlı bir instance’ı olmayan tüm network interface’leri (ENI) bulmak için:

steampipe query "
select
  network_interface_id,
  status,
  interface_type,
  description
from
  aws_ec2_network_interface
where
  status = 'available';
"

Ya da tüm region’lardaki sahipsiz EBS snapshot’larını tek seferde listelemek için:

steampipe query "
select
  snapshot_id,
  volume_size,
  start_time,
  description
from
  aws_ebs_snapshot
where
  volume_id not in (select volume_id from aws_ebs_volume);
"

Zombileri Kalıcı Olarak Engellemek: Preventative FinOps

Manuel olarak veya haftalık cron job’lar ile CLI çalıştırmak geçici bir pansumandır. Asıl amaç, ghost altyapının oluşmasını mühendislik seviyesinde engellemektir.

1. TTL (Time-To-Live) Tabanlı Resource Tagging

Test ve staging ortamlarında ayağa kaldırılan her şey zorunlu bir TTL veya ExpiresAt tag’ine sahip olmalıdır. Bir Open Policy Agent (OPA) kuralı veya AWS SCP (Service Control Policy) ile tag’i olmayan kaynakların provision edilmesini CI seviyesinde engelleyin.

# Örnek: Cloud Custodian policy snippet (custodian.yml)
policies:
  - name: terminate-expired-ephemeral-ec2
    resource: ec2
    filters:
      - "tag:Environment": "development"
      - type: marked-for-op
        op: terminate
    actions:
      - terminate

2. Terraform lifecycle Bloklarını Doğru Kullanmak

Instance oluştururken persistent diskler bağlıyorsanız, instance silindiğinde diskin yetim kalmaması için Terraform konfigürasyonunuzda block device mapping ayarlarını açıkça belirtin:

resource "aws_instance" "app_server" {
  ami           = "ami-0c55b159cbfafe1f0"
  instance_type = "c6i.xlarge"

  ebs_block_device {
    device_name           = "/dev/xvdf"
    volume_size           = 100
    volume_type           = "gp3"
    delete_on_termination = true # Asla unattached kalmaz
  }
}

Özet ve Aksiyon Planı

Bulut sağlayıcıları altyapınızı kurmanızı inanılmaz kolaylaştırır; ancak temizlemeyi zorlaştırmak onların iş modelinin doğal bir sonucudur. Boşta duran diskler, rotasız IP’ler ve trafiği kesilmiş load balancer’lar şirketinizin sermayesini günden güne eritir.

Yarın sabah yapacağınız ilk iş, ekibinizle 1 saatlik bir “Zombie Hunting” sprint’i düzenlemek olsun. Yukarıdaki scriptleri read-only modda çalıştırın, çıkan faturayı görün ve silme scriptlerini pipeline’larınıza gömün. FinOps sadece muhasebecilerin işi değildir; production’a saygı duyan her DevOps mühendisinin asli görevidir.

Category: Genel | LEAVE A COMMENT
Ekim 25 2024

Terraform State Yönetimi: Remote Backend ve State Locking

Modern devops dünyasında, Altyapıyı Kod Olarak Yönetmek (iac) denince akla gelen ilk araç tartışmasız terraform. Ancak projeler büyüdükçe ve ekip arkadaşlarınızla aynı cloud kaynakları üzerinde çalışmaya başladığınızda, o meşhur local terraform.tfstate dosyası tam bir kabusa dönüşebilir. Bu yazımızda, state yönetimini local’den kurtarıp AWS üzerinde S3 ve DynamoDB kullanarak nasıl kurumsal seviyede güvenli, kilitlenebilir (state locking) ve ölçeklenebilir bir yapıya kavuşturacağımızı adım adım inceleyeceğiz.

Eğer production ortamınızda hala local state kullanıyorsanız, muhtemelen adrenaline aşırı bağımlı bir hayat yaşıyorsunuzdur. Gelin, o state dosyasını ait olduğu yere, buluta taşıyalım ve işleri profesyonelleştirelim.

Local State’in Vedası: Neden Remote Backend?

Terraform ile çalışırken oluşturduğunuz her kaynak, default olarak projenizin kök dizinindeki terraform.tfstate dosyasına kaydedilir. Tek başınıza çalışırken bu durum idare edilebilir gibi görünse de, ekibe ikinci bir kişi dahil olduğu anda şu sorunlar baş gösterir:

  • Race Condition (Yarış Durumu): Aynı anda iki mühendisin terraform apply çalıştırması, kaynakların çakışmasına ve state dosyasının bozulmasına neden olur.
  • Güvenlik ve Hassas Veriler: State dosyası, oluşturulan kaynakların şifrelerini, private key’lerini ve tüm meta datasını plain-text (açık metin) olarak tutar. Bu dosyayı Git deposuna göndermek, güvenlik ekiplerinin kapınıza dayanması için en kestirme yoldur.
  • Tek Nokta Hatası (Single Point of Failure): Bilgisayarınızın diski bozulduğunda veya kahve döküldüğünde tüm altyapı geçmişiniz yok olur.

Bu sorunların çözümü, state dosyasını merkezi ve güvenli bir yerde tutmaktır. AWS ekosisteminde bunun endüstri standardı karşılığı S3 (Storage) + DynamoDB (Locking) ikilisidir.

Adım Adım S3 ve DynamoDB Backend Kurulumu

Buradaki en büyük ironi şudur: Remote backend’i oluşturmak için de Terraform kullanmak isteriz. Ancak backend henüz var olmadığı için, bootstrap aşamasında local state kullanıp, kaynakları oluşturduktan sonra state’i yeni oluşturduğumuz bu remote backend’e migrate ederiz (taşırız).

1. S3 Bucket ve DynamoDB Kaynaklarının Tanımlanması

Öncelikle backend kaynaklarımızı tanımlayacağımız bir main.tf dosyası oluşturalım. S3 bucket’ımızın versiyonlamaya (versioning) açık olması hayati önem taşır. Bu sayede olası bir state bozulmasında geçmişe dönebiliriz.

provider "aws" {
  region = "eu-west-1"
}

# State dosyasını saklayacağımız S3 Bucket
resource "aws_s3_bucket" "terraform_state" {
  bucket        = "kertenkerem-tf-state-bucket"
  force_destroy = false

  lifecycle {
    prevent_destroy = true
  }
}

# S3 Bucket versiyonlamasını aktif ediyoruz
resource "aws_s3_bucket_versioning" "state_versioning" {
  bucket = aws_s3_bucket.terraform_state.id
  versioning_configuration {
    status = "Enabled"
  }
}

# State dosyasının diskte şifrelenmesi (Encryption)
resource "aws_s3_bucket_server_side_encryption_configuration" "state_encryption" {
  bucket = aws_s3_bucket.terraform_state.id

  rule {
    apply_server_side_encryption_by_default {
      sse_algorithm = "AES256"
    }
  }
}

# State locking mekanizması için DynamoDB Tablosu
resource "aws_s3_bucket_public_access_block" "block_public" {
  bucket = aws_s3_bucket.terraform_state.id

  block_public_acls       = true
  block_public_policy     = true
  ignore_public_acls      = true
  restrict_public_buckets = true
}

resource "aws_dynamodb_table" "terraform_locks" {
  name         = "kertenkerem-tf-state-locks"
  billing_mode = "PAY_PER_REQUEST"
  hash_key     = "LockID"

  attribute {
    name = "LockID"
    type = "S"
  }
}

Kritik Detay: DynamoDB tablosunun partition key (hash_key) değeri tam olarak LockID (büyük-küçük harf duyarlı) olmalıdır. Terraform, kilit bilgisi yazarken bu spesifik anahtarı arar.

Bu aşamada projemizi initialize edip kaynakları AWS üzerinde oluşturalım:

terraform init
terraform apply

2. Backend Konfigürasyonunun Eklenmesi

Kaynaklarımız AWS üzerinde başarıyla oluşturulduktan sonra, projemize bu backend’i kullanmasını söyleme zamanı geldi. Proje dizininde bir backend.tf dosyası oluşturalım:

terraform {
  backend "s3" {
    bucket         = "kertenkerem-tf-state-bucket"
    key            = "global/s3/terraform.tfstate"
    region         = "eu-west-1"
    dynamodb_table = "kertenkerem-tf-state-locks"
    encrypt        = true
  }
}

Büyük Göç: State Dosyasını Local’den S3’e Taşımak (Migration)

Backend tanımını ekledikten sonra Terraform’a bu değişikliği bildirmemiz gerekiyor. Terminale dönüp şu sihirli komutu çalıştırıyoruz:

terraform init

Terraform, local’de mevcut bir state dosyası olduğunu ve konfigürasyonda yeni bir S3 backend tanımlandığını algılayacaktır. Karşınıza şu şekilde bir uyarı gelecektir:

Do you want to copy existing state to the new backend?
  Pre-existing state was found while migrating the previous "local" backend to the
  newly configured "s3" backend. No existing state was found in the "s3" backend.
  Do you want to copy this state to the new "s3" backend? Enter "yes" to copy and "no"
  to start with an empty state.

  Enter a value: yes

Bu soruya yes yanıtını verdiğinizde, Terraform yerel diskinizdeki tüm state geçmişini güvenli bir şekilde AWS S3 bucket’ına yükler. Artık projenizin kök dizinindeki local terraform.tfstate dosyasını güvenle silebilirsiniz (yine de silmeden önce yedeklemek her zaman iyi bir refleksdir).

Kriz Anı: State Lock Çakışması ve “force-unlock” Sanatı

Remote backend’imizin en güzel yanı, birisi terraform apply veya terraform plan çalıştırdığında DynamoDB’ye bir lock (kilit) kaydı yazmasıdır. Bu esnada başka biri aynı işlemi yapmaya çalışırsa, Terraform işlemi durdurur ve şu hatayı fırlatır:

Error: Error acquiring the state lock
Error info:
   ID:     e9b72a44-dfc1-4b11-9a73-519280d8f0f0
   Path:   kertenkerem-tf-state-bucket/global/s3/terraform.tfstate
   Who:    ahmet@kertenkerem-macbook
   Version: 1.5.0
   Created: 2023-10-27 12:00:00 +0000 UTC
   Info:    org: kertenkerem-devops

Peki ya bu kilitlenme, Jenkins/GitLab pipeline’ınızın yarıda kesilmesi, internetinizin kopması veya Docker container’ının crash olması nedeniyle havada asılı kaldıysa? Kimse işlem yapamadığı için deployment’lar kilitlenir.

Çözüm: force-unlock

Böyle bir durumda, hatanın size verdiği Lock ID‘yi kullanarak kilidi manuel olarak kırmak zorundasınız. Ancak dikkatli olun: Bu komutu çalıştırmadan önce, kilidin gerçekten “sahipsiz” kaldığından ve arka planda başka bir mühendisin veya pipeline’ın canlı bir apply işlemi yürütmediğinden emin olmalısınız.

terraform force-unlock e9b72a44-dfc1-4b11-9a73-519280d8f0f0

Komut başarıyla çalıştığında DynamoDB üzerindeki kilit kaydı silinecek ve pipeline’larınız tekrar çalışabilir hale gelecektir.

Workspace Stratejileri: Hangisini Seçmeliyiz?

Birden fazla ortamı (dev, stage, prod) yönetirken state dosyalarını nasıl izole edeceğiz? Burada iki temel yaklaşım var: Terraform Workspace CLI ve Directory-based (Klasör Tabanlı) İzolasyon.

Yöntem A: Terraform Workspace CLI

Terraform’un built-in workspace özelliğidir. Tek bir konfigürasyon kodunu kullanarak arka planda farklı state dosyaları oluşturur.

terraform workspace new dev
terraform workspace new prod

# Geçiş yapmak için:
terraform workspace select dev

S3 backend tarafında Terraform, state dosyalarınızı otomatik olarak env:/dev/global/s3/terraform.tfstate ve env:/prod/global/s3/terraform.tfstate yollarına kaydeder.

Neden Dikkatli Olunmalı? Workspace’ler CLI üzerinden kolayca değiştirilebildiği için, yanlışlıkla prod ortamındayken dev zannedip yıkıcı bir apply komutu çalıştırmak çok kolaydır. Ayrıca her iki ortam da aynı AWS account’unu ve aynı yetki sınırlarını paylaşıyorsa, güvenlik zafiyeti doğurabilir.

Yöntem B: Klasör Tabanlı İzolasyon (Önerilen)

Büyük ölçekli ve multi-account (çoklu AWS hesabı) yapılarında en güvenli yol, ortamları dizin bazında tamamen ayırmaktır.

├── environments
│   ├── dev
│   │   ├── backend.tf  # Dev AWS Account S3 kovasını gösterir
│   │   ├── main.tf
│   │   └── variables.tf
│   └── prod
│       ├── backend.tf  # Prod AWS Account S3 kovasını gösterir
│       ├── main.tf
│       └── variables.tf

Bu yaklaşımda, Dev ve Prod ortamlarının state dosyaları tamamen farklı AWS hesaplarındaki S3 bucket’larında durur. Prod ortamına dokunmak için Prod AWS credentials’ına ihtiyacınız vardır. Dolayısıyla, local’deki bir kaza eseri tüm prod altyapısını silme ihtimaliniz sıfıra iner.

Son Sözler ve Altın Kurallar

Terraform ile profesyonel bir altyapı yönetmek istiyorsanız, state dosyanızı gözünüz gibi korumalısınız. Özetlemek gerekirse:

  • Asla ama asla local state ile production yönetmeyin.
  • S3 bucket’ınızda Versioning ve Encryption özelliklerini her zaman açık tutun.
  • State lock için mutlaka DynamoDB entegrasyonunu yapın.
  • Büyük ve kritik projelerde CLI workspace’leri yerine, dizin tabanlı (ve tercihen Terragrunt destekli) izole yapılar kurun.
  • .gitignore dosyanıza .terraform/, *.tfstate ve *.tfstate.backup maskelerini eklemeyi unutmayın.

State’iniz kilitli, pipeline’larınız yeşil kalsın!

Category: Genel | LEAVE A COMMENT