Ghost Infrastructure: Cloud Maliyetlerinizi Gizlice Artıran Kullanılmayan Resource’ları Tespit Etme
Bir devops mühendisinin kabusu çoğu zaman gece yarısı gelen PagerDuty alarmı 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 ortam ayağa kaldırıp indirdikçe, arkada sessizce biriken “ghost infrastructure” (hayalet altyapı) kaçınılmaz olur. Cost optimization, instance type küçültmekten ibaret değildir. Asıl mesele, varlığından haberdar olmadığınız ama her saniye faturalandırılan zombi kaynakları yönetebilmektir. Aşağıda resource management ve FinOps pratikleriyle bu sızıntıları nasıl tespit edip kökünden temizleyeceğimizi anlatıyorum.
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 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. Reclaim policy’si yanlış yapılandırılmışsa disk available durumunda serbest kalır. Pod ölür, PVC silinir, ama disk faturaya yazılmaya 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
Ay sonunda faturayı kabartan başlıca zombi kaynakları bulmak için terminale geçelim. AWS’yi baz alacağız, ama 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. Bunlar io2 veya gp3 gibi yüksek IOPS provision edilmiş pahalı disklerse maliyet çarpanı da büyür.
Aşağıdaki AWS CLI + JMESPath query’si ile yalnızca 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
30 günden eski ve unattached diskleri doğrudan 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öntemlerinden biri bu. Bir IP’yi kullanırsanız ücretsizdir (veya çok ucuzdur), ama tahsis edip hiçbir instance ya da 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 ya da 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ğini sorgulamadan tespit edemezsiniz. En garanti yol, AWS CLI ile metrikleri tarayıp RequestCount toplamı 0 olan load balancer’ları yakalamaktır:
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ği katan Steampipe, cloud auditing için endüstri standardı araçlardan biri oldu. 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
Elle ya da haftalık cron job’larla 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 şeyin zorunlu bir TTL veya ExpiresAt tag’i olmalıdır. Open Policy Agent (OPA) kuralı ya da 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 disk yetim kalmasın diye Terraform konfigürasyonunda 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ıyı kurmayı çok kolaylaştırır, temizlemeyi ise zorlaştırır. Bu, iş modellerinin 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 ilk iş, ekibinizle 1 saatlik bir “Zombie Hunting” sprint’i düzenleyin. 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 yalnızca muhasebecilerin işi değildir, production’a saygı duyan her DevOps mühendisinin de görevidir.