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 24 2026

Ephemeral Environment’larda ‘Token Budget’ Yönetimi: Otomasyon Giderlerini Kontrol Altına Alın

Modern devops pratiklerinde preview ya da ephemeral environment altyapılarını PR bazlı ayağa kaldırmak artık bir endüstri standardı. Ancak denkleme kod analiz eden, sentetik test verisi üreten veya migrasyon doğrulayan otonom AI ajanları dahil olduğunda, kontrolsüz automation süreçleri cloud costs grafiklerini saatler içinde darmadağın edebiliyor. Sonsuz döngüye giren bir agent’ın ephemeral pipeline içinde OpenAI veya Anthropic faturalarını tüketmesini izlemek istemiyorsanız, CI/CD hattınıza deterministik bir token budget mekanizması kurmak zorundasınız.

Bu makalede, geçici ortamlarda koşan AI iş yükleri için in-line bir “Token Gate” mimarisinin nasıl kurgulanacağını, PR yaşam döngüsüyle senkronize bütçe tahsisini ve acil durum devre kesicilerini (circuit breaker) ele alıyoruz.

FinOps Uyarıları Neden CI/CD Hızına Yetişemiyor?

Geleneksel FinOps araçları ve cloud provider fatura alarmları post-mortem çalışır. AWS Cost Anomaly Detection veya OpenAI Usage Webhook’ları genelde saatlik ya da günlük batch gecikmelerle tetiklenir. Oysa bir GitHub Actions runner’ı üzerinde koşan ve hatalı bir prompt yüzünden recursive döngüye giren bir test ajanı, 25 dakikalık bir test koşumunda yüz binlerce token tüketip 400 doları sessizce harcayabilir.

Sorun sadece faturanın kabarması değil; ephemeral environment’ın doğası gereği geçici olması. PR kapandığında pod’lar silinir, namespace yok edilir, geriye sadece LLM provider panelinde nedeni belirsiz devasa bir kullanım grafiği kalır. Bu yüzden bütçe denetimini harici bir fatura kontrolü olarak değil, deployment pipeline’ının doğrudan içine gömülü bir gatekeeper olarak konumlandırmalıyız.

Mimari: Proxy Tabanlı Token Gate

Agent’ların doğrudan public LLM API’lerine gitmesi en büyük governance hatasıdır. Ajan ile LLM arasına hafif siklet, statelesse yakın bir proxy katmanı yerleştiriyoruz. Bu mimaride açık kaynaklı LiteLLM Proxy veya Envoy tabanlı bir custom egress filter kullanabiliriz. Biz prodüksiyonda kendini kanıtlamış olan Redis destekli LiteLLM Proxy modelini baz alacağız.

Model şu şekilde işler:

  1. CI pipeline başladığında ephemeral environment ID’si (örn. pr-842) için proxy üzerinde tek kullanımlık bir Virtual Key üretilir.
  2. Bu key’e strictly izole edilmiş bir harcama limiti (max spend) ve TTL atanır.
  3. Ephemeral environment içerisindeki tüm agent’lar ve otomasyon araçları LLM endpoint’i olarak bu proxy’yi görür.
  4. Bütçe tükendiği anda proxy doğrudan 429 BudgetExceeded döner; pipeline runner’ı kilitlemeden, kontrollü bir exit code ile fail eder.
  5. PR merge veya close olduğunda key ve ilişkili Redis state’i tamamen süpürülür.

Adım 1: LiteLLM Proxy ve Redis Konfigürasyonu

Öncelikle proxy katmanını Kubernetes üzerinde veya CI cluster’ınızın ortak tooling namespace’inde ayağa kaldırıyoruz. Aşağıdaki konfigürasyon, harcama verilerini Redis üzerinde tutarak stateless proxy instance’ları arasında tam senkronizasyon sağlar.

# config.yaml
model_list:
  - model_name: claude-3-5-sonnet
    litellm_params:
      model: anthropic/claude-3-5-sonnet-20241022
      api_key: os.environ/ANTHROPIC_API_KEY
  - model_name: gpt-4o-mini
    litellm_params:
      model: openai/gpt-4o-mini
      api_key: os.environ/OPENAI_API_KEY

general_settings:
  master_key: sk-master-infra-gatekeeper-key
  database_url: "redis://redis-sentinel.infra.svc.cluster.local:6379"

router_settings:
  routing_strategy: "least-busy"
  timeout: 30

Proxy’yi Docker üzerinde ayağa kaldırmak için:

docker run -d \
  -p 4000:4000 \
  -v $(pwd)/config.yaml:/app/config.yaml \
  -e ANTHROPIC_API_KEY="sk-ant-live-xxxx" \
  -e OPENAI_API_KEY="sk-proj-live-xxxx" \
  ghcr.io/berriai/litellm:main-v1.49.0 \
  --config /app/config.yaml --port 4000

Adım 2: CI Pipeline İle Dinamik Token Gate Entegrasyonu

Şimdi GitHub Actions veya GitLab CI pipeline’ımızda her preview environment için dinamik bir anahtar üreteceğiz. Her PR için varsayılan olarak 2.50 USD tavan bütçe tanımlıyoruz. Testler bu bütçeyi aşarsa, faturaya sürpriz yansımadan pipeline derhal kesilecek.

# .github/workflows/ephemeral-env.yml (Özet Adım)
- name: Provision Ephemeral LLM Token Gate Key
  id: token_gate
  env:
    PROXY_MASTER_KEY: ${{ secrets.LLM_PROXY_MASTER_KEY }}
    PROXY_HOST: "https://llm-gateway.internal.kertenkerem.net"
  run: |
    PR_ID="pr-${{ github.event.pull_request.number }}"
    
    # 2.50$ bütçe ve 2 saatlik TTL ile dinamik key oluştur
    RESPONSE=$(curl -s -X POST "$PROXY_HOST/key/generate" \
      -H "Authorization: Bearer $PROXY_MASTER_KEY" \
      -H "Content-Type: application/json" \
      -d '{
        "duration": "2h",
        "max_budget": 2.50,
        "metadata": {
          "environment": "'"$PR_ID"'",
          "actor": "${{ github.actor }}",
          "repo": "${{ github.repository }}"
        }
      }')

    EPHEMERAL_KEY=$(echo $RESPONSE | jq -r '.key')
    
    if [ "$EPHEMERAL_KEY" == "null" ] || [ -z "$EPHEMERAL_KEY" ]; then
      echo "::error::Token Gate anahtarı oluşturulamadı!"
      exit 1
    fi

    # Maskele ve env'ye aktar
    echo "::add-mask::$EPHEMERAL_KEY"
    echo "EPHEMERAL_LLM_KEY=$EPHEMERAL_KEY" >> $GITHUB_ENV
    echo "LLM_BASE_URL=$PROXY_HOST" >> $GITHUB_ENV

Bu adımdan sonra ayağa kalkan ephemeral deployment pod’larına ve otomasyon agent’larına sadece bu iki parametre enjekte edilir:

  • OPENAI_BASE_URL: https://llm-gateway.internal.kertenkerem.net
  • OPENAI_API_KEY: ${{ env.EPHEMERAL_LLM_KEY }}

Adım 3: Degraded Mode ve Circuit Breaker Mantığı

Token bütçesi tamamen tükendiğinde pipeline’ın anında kırılması her zaman arzu edilen bir durum değildir. Özellikle kritik entegrasyon testlerinde, ana agent maliyetli bir modelden (örn. Claude 3.5 Sonnet) daha ucuz bir modele (örn. GPT-4o-mini) “fallback” yapabilmelidir.

Proxy seviyesinde bütçe tükenmeden hemen önce devreye giren bir model alias stratejisi kullanabilirsiniz. Ancak agent kodunuz bu hatayı ele alacak esneklikte değilse, test framework’ünüzün curl/HTTP wrapper’ına basit bir circuit breaker mantığı eklemek en güvenli yoldur:

#!/usr/bin/env bash
# run-agent-eval.sh
set -eo pipefail

echo "Starting evaluation agent in ephemeral namespace..."

# Agent'ı çalıştır ve HTTP status kodlarını kontrol et
if ! python3 -m agents.evaluator --target-env http://frontend.pr-${PR_ID}.svc.local; then
  EXIT_CODE=$?
  
  # Proxy'nin budget aşım loglarını sorgula
  CURRENT_SPEND=$(curl -s -X GET "$LLM_BASE_URL/key/info?key=$EPHEMERAL_LLM_KEY" \
    -H "Authorization: Bearer $PROXY_MASTER_KEY" | jq -r '.info.spend')
    
  MAX_BUDGET=$(curl -s -X GET "$LLM_BASE_URL/key/info?key=$EPHEMERAL_LLM_KEY" \
    -H "Authorization: Bearer $PROXY_MASTER_KEY" | jq -r '.info.max_budget')

  echo "Execution failed with code $EXIT_CODE."
  echo "Current Token Spend: \$$CURRENT_SPEND / Max: \$$MAX_BUDGET"

  if (( $(echo "$CURRENT_SPEND >= $MAX_BUDGET" | bc -l) )); then
    echo "CRITICAL: Agent bütçe limitine çarptı. Olası recursive prompt döngüsü tespit edildi."
    echo "PR yazarına bildirim gönderiliyor..."
    exit 42 # FinOps gate violation exit code
  fi

  exit $EXIT_CODE
fi

Adım 4: Ephemeral Environment Teardown ve Muhasebe

Preview ortamları yok edilirken açılan virtual key’lerin revoke edilmesi ve tüketim verisinin kalıcı metrik sunucusuna yazılması gerekir. Çoğu mühendis bu kısmı atlar; oysa harcamanın hangi PR’da, hangi geliştirici tarafından ve hangi prompt şablonuyla yapıldığını bilmek FinOps optimizasyonunun temelidir.

# PR Close workflow adımında çalıştırılır:
- name: Teardown Token Budget & Export Metrics
  if: always()
  run: |
    # Key kullanım detaylarını topla
    USAGE_DATA=$(curl -s -X GET "$LLM_BASE_URL/key/info?key=$EPHEMERAL_LLM_KEY" \
      -H "Authorization: Bearer $PROXY_MASTER_KEY")
      
    TOTAL_TOKENS=$(echo $USAGE_DATA | jq -r '.info.total_tokens // 0')
    TOTAL_SPEND=$(echo $USAGE_DATA | jq -r '.info.spend // 0')
    
    echo "PR-$PR_ID LLM Kullanım Özeti: $TOTAL_TOKENS token (\$$TOTAL_SPEND)"

    # Prometheus Pushgateway'e metrikleri ilet
    cat <<EOF | curl --data-binary @- http://pushgateway.infra.svc:9091/metrics/job/ephemeral_ci/instance/pr-${{ github.event.pull_request.number }}
    ephemeral_llm_tokens_total{repo="${{ github.repository }}",actor="${{ github.actor }}"} $TOTAL_TOKENS
    ephemeral_llm_cost_usd{repo="${{ github.repository }}",actor="${{ github.actor }}"} $TOTAL_SPEND
    EOF

    # Virtual key'i kalıcı olarak iptal et
    curl -s -X POST "$LLM_BASE_URL/key/delete" \
      -H "Authorization: Bearer $PROXY_MASTER_KEY" \
      -H "Content-Type: application/json" \
      -d '{"keys": ["'"$EPHEMERAL_LLM_KEY"'"]}'

Neden Bu Mimari?

Bu yaklaşım size üç temel avantaj sağlar:

  1. Blast Radius Kısıtlaması: Geliştiricinin yazdığı deneysel bir agent kodu infinite loop’a girse bile, maksimum zararı 2.50 USD ile sınırlıdır. Production key’lerini riske atmazsınız.
  2. Zero-Trust Credential Isolation: Ephemeral pod’lara hiçbir zaman master API anahtarları enjekte edilmez. Pipeline bittiğinde anahtar anında çöpe gider.
  3. Granüler Görünürlük: Fatura ay sonunda geldiğinde, hangi feature branch’in ne kadar LLM maliyeti yarattığını Prometheus üzerinden nokta atışı görebilirsiniz.

AI destekli CI/CD akışları bir lüks değil, hız kazandıran bir zorunluluk haline geliyor. Ancak kontrolsüz otomasyon hız kazandırmaz; sadece AWS ve OpenAI faturalarınızı katlar. Ephemeral mimarilerinize token budget kapılarını hemen bugün ekleyin.

Category: Genel | LEAVE A COMMENT
Ekim 18 2026

AI Ajanlarında ‘Judgment-First’ Yaklaşımı: Otomasyonu Denetleyen İnsan Karar Mekanizması

Son bir yıldır teknoloji dünyasında herkes aynı hayali satıyor: Bir ai agents ordusu kuracaksınız, siz kahvenizi yudumlarken onlar kod yazacak, testleri geçecek ve prodüksiyona deploy edecek. Kulağa harika geliyor değil mi? Ancak işin mutfağına girdiğinizde gerçekler biraz can yakıyor. Test sunucusunu yanlışlıkla silen veya sonsuz döngüye girip kredi kartı limitinizi eriten bir yapay zekayla karşılaştığınızda, otonomluğun aslında ne kadar tekinsiz bir lüks olduğunu anlıyorsunuz. İşte tam bu kırılma noktasında karşımıza yeni bir paradigma çıkıyor: Judgment-First yaklaşımı.

Bu makalede, kod yazımı ve devops süreçlerinde otonom ajanları başıboş bırakmak yerine, insan muhakemesini (yani judgment mekanizmasını) merkeze alan dengeli bir ai workflow nasıl kurulur, birlikte inceliyoruz.

“Bırakın Yapsın” Yanılgısı ve Prompt Engineering Sınırları

Bir süredir sadece iyi bir prompt engineering yaparak her sorunu çözebileceğimiz söylendi. “Sen kıdemli bir yazılımcısın, hata yapma” yazdığınızda modelin gerçekten kusursuz çalışacağına inandık. Ancak Large Language Models (LLM) olasılıksal makinelerdir; deterministik değillerdir. Yani aynı prompt, dokuz kez doğru çalışan bir kodu onuncu denemede çökertebilir.

Yapay zekanın “karar alma” (judgment) yetisi yoktur; sadece bir sonraki en olası kelimeyi tahmin etme yeteneği vardır. Kodun çalışması ile o kodun şirket kültürüne, güvenlik standartlarına ve iş mantığına uyması bambaşka şeylerdir. Bu yüzden kontrolsüz otomasyon yerine, ajanın sadece “öneri getirdiği”, insanın ise “onayladığı” bir mimari kurmak zorundayız.

[Görsel: Bir terminal ekranında sol tarafta otonom ajanın önerdiği Git diff çıktısı, sağ tarafta ise geliştiricinin onayını bekleyen interaktif CLI onay kutusu]

Judgment-First Mimarisi Nasıl Çalışır?

Geleneksel bir ai workflow döngüsünde ajan görevi alır, planlar, adımları yürütür ve bitirir. Judgment-First modelinde ise bu süreç kritik kontrol noktalarıyla (checkpoint) bölünür:

  • Keşif ve Taslak: Ajan bağlamı toplar, problemi analiz eder ve çözüm için bir plan sunar. Henüz hiçbir kod veya ayar değişmez.
  • İnsan Değerlendirmesi: Geliştirici ajanın planını inceler. Yanlış varsayımlar burada elenir.
  • Sınırlı İcra: Ajan sadece onaylanan adımı, izole bir ortamda (sandbox) çalıştırır.
  • Doğrulama ve Kabul: Nihai diff veya sistem değişikliği insan tarafından tekrar kontrol edilip ana dala birleştirilir.

Özellikle devops süreçlerinde bu yaklaşım hayat kurtarır. Bir ajana Terraform dosyalarını doğrudan uygulama yetkisi vermek yerine, plan çıktısını alıp Slack üzerinden onayınıza sunmasını sağlamak tipik bir “judgment” katmanıdır.

# Örnek: Ajanın doğrudan çalıştırması yerine plan çıktısı üretmesi
terraform plan -out=tfplan.binary
# Ajan bu noktada durur ve insan operatöre diff özetini iletir:
# "3 kaynak eklenecek, 1 veritabanı SİLİNECEK. Onaylıyor musunuz? (e/h)"

Gerçek Dünya Testi: LangGraph ve CrewAI ile Doğrulama

Geçtiğimiz haftalarda kendi projelerimden birinde LangGraph kullanarak basit bir hata ayıklama ajanı kurdum. Sisteme iki farklı yaklaşım uyguladım:

İlk senaryoda ajan tam yetkiliydi. Kod tabanındaki hatayı buldu, bir bağımlılığı güncelledi ve testleri çalıştırmadan doğrudan repoya commit attı. Sonuç? Paket uyumsuzluğu yüzünden build patladı. İkinci senaryoda ise araya bir insan onay fonksiyonu (human-in-the-loop) ekledim. Ajan güncelleme önerdiğinde terminal bana sordu. Bağımlılığın ana sürüm atladığını fark edip işlemi reddettim ve ajana “minor sürümü sabitle” direktifini verdim. Sorunsuz çözüldü.

[Görsel: LangGraph akış şemasında ‘Agent Execution’ ve ‘Tool Call’ arasına yerleştirilmiş kırmızı renkli ‘Human Approval Gate’ diyagramı]

Tam Otonom vs. Judgment-First Yaklaşımı

İki yaklaşım arasındaki farkı netleştirmek için artı ve eksilere bir göz atalım:

Kriter Tam Otonom Ajanlar Judgment-First Ajanlar
Hız Çok yüksek (aralıksız çalışır) Orta (insan onayına bağlıdır)
Hata Maliyeti Yüksek (prodüksiyon riskleri taşır) Düşük (hatalar kapıda yakalanır)
Maliyet / Token Tüketimi Sonsuz döngü riski yüksektir Denetimli ve öngörülebilirdir
Geliştirici Güveni Düşük (paranoya yaratır) Yüksek (kontrol geliştiricidedir)

Araçlar, Fiyatlar ve Ücretsiz Alternatifler

Bu doğrulama katmanını sıfırdan yazmak zorunda değilsiniz. Ekosistemde hem açık kaynaklı hem de ücretli birçok seçenek mevcut:

  • LangGraph (LangChain): Python ve JS kütüphanesi. Tamamen açık kaynak ve ücretsizdir (self-hosted). Human-in-the-loop (kesme noktaları) yeteneği şu an sektör standardı düzeyinde.
  • CrewAI: Kolay kurulur, ajanlar arası görev dağılımı yapar. Temel kütüphane ücretsiz, kurumsal Enterprise sürümü kullanıcı başına aylık 25 dolardan başlıyor.
  • n8n: Eğer kod yazmadan bir ai workflow kurmak istiyorsanız ideal. Kendi sunucunuzda çalıştırmak (Community Edition) ücretsiz; bulut versiyonu ise aylık 20 euro civarından başlıyor. Araya onay mailleri veya webhook eklemek çocuk oyuncağı.
  • GitHub Actions Environments: Kod süreçlerinizde ekstra maliyet ödemeden “Required Reviewers” özelliğini kullanarak yapay zekanın PR’larını insan onayına bağlayabilirsiniz (Genel repolarda ücretsiz).

Sonuç: Direksiyonu Tamamen Bırakmayın

Ajanların yetenekleri her ay katlanarak artıyor, bunda şüphe yok. Ancak en sofistike LLM bile sisteminizin iş hedeflerini, teknik borçlarını ve kullanıcı reflekslerini sizin kadar anlayamaz.

Yapay zekayı harika bir stajyer gibi düşünün: Hızlı araştırır, ilk taslağı yıldırım gibi çıkarır, rutin angaryayı sırtlar. Ancak kontratı imzalamadan veya prodüksiyona çıkmadan önce evrakın üzerinden bir çift tecrübeli gözün geçmesi şarttır. Judgment-First yaklaşımı, sizi hem yapay zeka trenini kaçırmaktan hem de o trenin altında kalmaktan kurtaracak en rasyonel formüldür.

Category: Genel | LEAVE A COMMENT
Ekim 17 2026

Kubernetes’te ‘Ghost’ Resource’ları Bulmak: Zombie Pod Avcılığı

Modern bir devops ekibinin yönettiği multi-tenant production cluster’ında her şeyin dashboard üzerinde yemyeşil görünmesi, her şeyin yolunda olduğu anlamına gelmez. Kümeler büyüdükçe, microservice mimarileri dallanıp budaklandıkça ve continuous delivery hatları günde onlarca kez release çıktıkça arka planda sessiz bir felaket birikir: “Ghost” (hayalet) resource’lar. Sağlıklı bir resource-management stratejisi ve keskin observability pratikleri kurgulamadığınızda, kubernetes worker node’larınız aslında hiçbir iş yapmayan, hiçbir trafiğe yanıt vermeyen ama faturayı kabartan “zombie pod” sürüleriyle dolar taşar.

Bu yazıda 5+ yıl deneyimli bir mühendisin bildiği o temel kavramları bir kenara bırakıyoruz. Pod nedir, replica nedir anlatmayacağım. Doğrudan sahaya inip kümedeki hayaletleri avlayacağız; neden oluştuklarını analiz edecek, gerçek komutlar, PromQL sorguları ve otomasyon script’leriyle bu yükü kümeden kazıyacağız.

Zombie Pod Nedir ve Neden Türer?

Kubernetes ekosisteminde bir kaynağa “zombie” veya “ghost” dememiz için ille de CrashLoopBackOff hatası alması gerekmez. Hatta en tehlikelisi, cluster’a göre kusursuz çalışanlardır. Bir pod şu durumlarda zombie statüsüne düşer:

  • Lich Podlar (Stuck in Terminating): Kapatılma sinyali (SIGTERM) almış fakat unmount edilemeyen volume veya takılı kalan bir finalizer yüzünden günlerdir Terminating durumunda bekleyenler.
  • Avare (Orphaned/Headless) Podlar: Bir Service selector’ı ile eşleşmeyen, ingress rotası olmayan veya Helm release’i purge edildiği halde etcd tutarsızlığı nedeniyle silinmeyenler.
  • Sıfır Yüklü “Over-provisioned” Podlar: Geliştiricinin debug amacıyla ayağa kaldırdığı, arkasında 4 vCPU / 16 GiB request unuttuğu ve haftalardır 0.001 Core tüketen test artıkları.
  • StatefulSet Kalıntıları (Unbound/Dangling PVCs): StatefulSet scale down edildiğinde Kubernetes mimari gereği PVC’leri silmez. Pod gitmiştir ama arkasında pahalı SSD block storage’lar yatmaya devam eder.

1. Adım: Terminating State’te Çakılı Kalan Podları Avlamak

En sık karşılaşılan senaryo, storage unmount işlemlerinin node üzerinde kilitlenmesi veya custom controller’ların yazdığı finalizer’ların reconcile edilememesidir. Bu podlar CPU/RAM harcamasa bile IP pool’unu (özellikle AWS-VPC CNI veya Calico kullanıyorsanız) rezerve tutar ve node drain işlemlerini bloklar.

Kümedeki tüm namespace’ler genelinde 1 saatten uzun süredir Terminating durumunda olan podları bulalım:

kubectl get pods -A --field-selector=status.phase!=Running \
  -o json | jq -r '
  .items[] | 
  select(.metadata.deletionTimestamp != null) | 
  select((now - (.metadata.deletionTimestamp | fromdateiso8601)) > 3600) | 
  "Namespace: " + .metadata.namespace + " | Pod: " + .metadata.name + " | Waiting: " + ((now - (.metadata.deletionTimestamp | fromdateiso8601))/3600 | tostring) + " hours"'

Birçok mühendis burada hemen kubectl delete pod <pod-name> --force --grace-period=0 komutuna sarılır. Yapmayın. Neden? Çünkü arkasında asılı bir CSI volume kilidi bırakırsanız, ileride o node’a schedule edilecek yeni pod Multi-Attach error for volume hatasıyla patlayacaktır.

Bunun yerine pod’un neden takıldığını görmek için metadata finalizer alanına bakın:

kubectl get pod <zombie-pod-name> -n <namespace> -o jsonpath='{.metadata.finalizers}'

Eğer storage driver çoktan node üzerinden çekildiyse ve controller yanıt vermiyorsa, finalizer’ı JSON merge patch ile söküp atın:

kubectl patch pod <zombie-pod-name> -n <namespace> \
  -p '{"metadata":{"finalizers":null}}' --type=merge

2. Adım: Arkasında Servis Olmayan “Sağır” Podları Yakalamak

CI/CD pipeline’ları çöktüğünde bazen Deployment silinir ancak pod bağımsız bir şekilde çalışmaya devam eder. Ya da deployment label’ları güncellenir fakat Service manifest’indeki selector güncellenmeyi unutulur. Sonuç: Pod CPU ve Memory tüketir ama gelen tek bir HTTP isteği dahi almaz.

Herhangi bir Service’in Endpoints veya modern Kubernetes kümelerinde EndpointSlice objesi tarafından hedeflenmeyen podları tespit etmek için bu pratik bash one-liner’ını çalıştırın:

#!/usr/bin/env bash
# Namespace bazlı hedefsiz pod kontrolü
NAMESPACE="production"

# Tüm geçerli endpoint IP'lerini al
VALID_IPS=$(kubectl get endpointslices -n "$NAMESPACE" -o jsonpath='{.items[*].endpoints[*].addresses[*]}' | tr ' ' '\n' | sort -u)

# Çalışan podların IP'lerini karşılaştır
for POD in $(kubectl get pods -n "$NAMESPACE" -o jsonpath='{range .items[?(@.status.phase=="Running")]}{.metadata.name}{":"}{.status.podIP}{"\n"}{end}'); do
    POD_NAME=$(echo "$POD" | cut -d: -f1)
    POD_IP=$(echo "$POD" | cut -d: -f2)

    if ! echo "$VALID_IPS" | grep -q "$POD_IP"; then
        # DaemonSet ve standalone jobları hariç tutmak isteyebilirsiniz
        OWNER=$(kubectl get pod "$POD_NAME" -n "$NAMESPACE" -o jsonpath='{.metadata.ownerReferences[0].kind}')
        if [[ "$OWNER" != "DaemonSet" && "$OWNER" != "Job" ]]; then
            echo "[ZOMBIE DETECTED] Pod: $POD_NAME (IP: $POD_IP) - Hiçbir Service arkasında değil!"
        fi
    fi
done

Bu senaryo özellikle preview environment’larda çok yaşanır. Pull Request merge edilir, ingress kalkar, service kalkar ama pod orada sonsuza dek sessizce yaşar.

3. Adım: Observability ile Kaynak İsrafını Ortaya Çıkarma

FinOps dünyasında en tehlikeli ghost pod, “iş yapıyor gibi durup hiçbir şey yapmayan” poddur. Geliştirici deployment manifest’inde cpu: "2" ve memory: "4Gi" talep etmiştir. Kubernetes scheduler bu talebi garanti altına almak için node üzerinde ilgili kaynağı rezerve eder (allocatable space düşer). Ancak pod arka planda sadece idle beklemektedir.

Bu israfı yakalamak için Prometheus ve Thanos üzerinde çalıştıracağımız PromQL sorguları devreye girer. Son 7 gün boyunca talep ettiği CPU kaynağının %1’inden azını kullanan podları listeleyelim:

# Prometheus / Thanos PromQL
(
  sum(rate(container_cpu_usage_seconds_total{container!=""}[1h])) by (namespace, pod)
  /
  sum(kube_pod_container_resource_requests{resource="cpu"}) by (namespace, pod)
) * 100 < 1

Bu sorgunun çıktısı size direkt olarak kümenizdeki “zombi rezervasyonları” verir. Pod fiziki olarak ölü değildir, ama maliyet açısından tam bir hayalettir. Node Autoscaler (Karpenter veya Cluster Autoscaler) bu pod yüzünden yeni ec2/compute instance ayağa kaldırmak zorunda kalır.

4. Adım: Sahipsiz StatefulSet Kalıntıları (Ghost PVC’ler)

Storage israfı, compute israfından daha sinsi seyreder çünkü disk saatlik değil, provizyon edildiği toplam boyut üzerinden (GB/ay) faturalandırılır. Bir StatefulSet’in replica sayısını 5’ten 2’ye indirdiğinizde Kubernetes, veri kaybını önlemek adına 3 adet PVC’yi cluster içinde Bound statüsünde tutmaya devam eder.

Peki bu diskleri kullanan aktif bir pod var mı? Görelim:

kubectl get pvc -A -o json | jq -r '
  .items[] | 
  select(.status.phase == "Bound") | 
  select(.metadata.annotations["volume.kubernetes.io/selected-node"] == null and .metadata.ownerReferences == null) | 
  "Namespace: " + .metadata.namespace + " | Unmounted PVC: " + .metadata.name + " | Size: " + .spec.resources.requests.storage'

Burada listelenen PVC’ler, Kubernetes API server düzeyinde kilitli kalmış, pod’u çoktan yok edilmiş disklere işaret eder. Eğer Retain politikasına sahip bir StorageClass kullanıyorsanız, deployment tamamen silinse bile AWS EBS veya GCP Persistent Disk konsolunda faturayı şişirmeye devam eder.

Otomasyon: Zombie Avını Sürekli Kılmak

Her hafta terminal başına geçip bu script’leri çalıştırmak sürdürülebilir bir SRE yaklaşımı değildir. Bunu engellemenin en temiz yolu, kuralları policy engine seviyesinde cluster’a enjekte etmektir.

Completed (başarıyla bitmiş) job podlarının cluster’da günlerce yer kaplamasını engellemek için manifest’lerinize mutlaka ttlSecondsAfterFinished ekleyin veya bir Kyverno policy’si ile bunu zorunlu kılın:

apiVersion: batch/v1
kind: Job
metadata:
  name: nightly-backup-sync
spec:
  # Pod tamamlandıktan 300 saniye (5 dk) sonra kendini imha eder
  ttlSecondsAfterFinished: 300
  template:
    spec:
      containers:
      - name: sync-worker
        image: custom-sync:v1.2
      restartPolicy: OnFailure

Aynı şekilde Kyverno veya OPA Gatekeeper kullanarak, her Deployment için memory/cpu limit ve request oranının belirli bir çarpanı (örneğin request limitin %50’sinden az olamaz kuralı) aşmasını admission webhook aşamasında engelleyebilirsiniz.

Sonuç: FinOps Salt Fatura İncelemek Değildir

DevOps dünyasında maliyet optimizasyonu (FinOps), ay sonunda AWS veya GCP faturasındaki grafikleri inceleyip “Compute Engine çok harcamış” demekten ibaret değildir. Asıl optimizasyon, runtime seviyesinde etcd ve scheduler üzerindeki gereksiz yükleri ayıklamaktır.

Terminating state’te kalan podlar IP tüketir, selector’ı kopmuş podlar gereksiz compute harcar, dangling PVC’ler storage faturasını patlatır. Kümelerinizde periyodik olarak bu kontrolleri pipeline’larınıza veya cron job’larınıza entegre edin. Ghost resource’ları temizlemek sadece faturayı düşürmekle kalmaz, scheduler gecikmelerini (scheduling latency) ve control plane üzerindeki memory overhead’ini doğrudan azaltır.

Category: Genel | LEAVE A COMMENT
Ekim 9 2026

Quantum-Safe DevOps: Mevcut CI/CD Pipeline’larınızı Kriptografik Borçtan Koruyun

Modern devops pratiklerinde güvenlik genellikle pipeline’a bir SAST/DAST aracı sıkıştırmak, image’ları Trivy ile taramak ve artifact’leri RSA-2048 ile imzalamaktan ibaret sanılıyor. Oysa kapıda sessizce büyüyen devasa bir problem var: Kriptografik borç. Kuantum bilgisayarlar pratik ölçeğe ulaştığında, bugün asimetrik cryptography temelleri üzerine inşa ettiğimiz tüm pipeline security mimarisi domino taşı gibi devrilecek. Daha da kötüsü, tehdit “o gün” başlamayacak; Harvest Now, Decrypt Later (HNDL) saldırıları sebebiyle bugünden depolanan imzalar ve şifreli trafik yarın aleyhimize delil olarak kullanılacak. Bu yazıda teorik fizik tartışmalarına girmeden, doğrudan production hatlarında uygulayabileceğiniz quantum-safe dönüşüm stratejilerini ve pratik konfigürasyonları masaya yatırıyoruz.

Neden Şimdi? Moska Teoremi ve CI/CD Gerçekliği

Kuantum tehdidini konuşurken çoğu mühendisin düştüğü hata, “Q-Day gelene kadar en az 8-10 yılımız var” rehavetine kapılmaktır. Kriptograf Michele Mosca’nın denklemi durumu çok net özetler: X + Y > Z ise sisteminiz zaten risk altındadır. Burada X verinizin gizli kalması gereken süre, Y sistemlerinizi kuantum dirençli hale getirme süreniz, Z ise kuantum bilgisayarların RSA/ECC’yi kıracağı tahmini tarihtir.

Bir bankanın veya fintech girişiminin CI/CD pipeline’ında build edilen artifact’lerin, firmware imajlarının veya Git commit’lerinin imza geçerlilik ömrü bazen 10-15 yılı bulur. Eğer bugün artifact imzalarken ML-DSA (CRYSTALS-Dilithium) veya stateful hash-based imzalara geçiş yapmazsanız, 2030’da geçmişe dönük supply chain doğrulamalarınızın hiçbir matematiksel güvencesi kalmayacaktır.

1. Git İletişimi: OpenSSH Hibrit Anahtar Değişimine Geçiş

Git runner’larınız repository’lere SSH üzerinden bağlanıyorsa, aradaki trafik bugün kaydedilip gelecekte çözülebilir. OpenSSH 9.0 ve üzeri sürümler, varsayılan olarak Streamlined NTRU Prime ve X25519 hibrit algoritmasını (sntrup761x25519-sha512@openssh.com) destekliyor. Bu hibrit yapı şu anlama gelir: Klasik X25519 kırılsa bile kuantum-dirençli katman sizi korur; yeni algoritmada bilinmeyen bir açık çıksa bile klasik katman güvence sağlar.

CI/CD runner’larınızın ve Git sunucularınızın (GitLab, self-hosted Gitea vs.) SSH konfigürasyonunu zorunlu olarak hibrit moda geçirmek için /etc/ssh/ssh_config (veya sunucu tarafında sshd_config) dosyasında KexAlgorithms direktifini güncelleyin:

# /etc/ssh/sshd_config veya CI Runner ~/.ssh/config
KexAlgorithms sntrup761x25519-sha512@openssh.com,curve25519-sha256,curve25519-sha256@libssh.org

# Host key olarak klasik Ed25519 kullanmaya devam etseniz bile,
# oturum anahtarı değişimi (KEX) kuantum dirençli hale gelir.
HostKeyAlgorithms ssh-ed25519,rsa-sha2-512

Runner üzerinde bağlantının hibrit algoritma kullanıp kullanmadığını doğrulamak için şu komutla debug çıktısını inceleyin:

ssh -vvv git@gitlab.internal.corp 2>&1 | grep "kex: algorithm:"
# Çıktıda şunu görmelisiniz:
# debug1: kex: algorithm: sntrup761x25519-sha512@openssh.com

2. Artifact İmzalama: Cosign ve Post-Quantum Algoritmalar

Yıllardır Cosign ile container imzalıyoruz. Peki o imzalar neye güveniyor? ECDSA P-256 veya RSA-4096. Shor algoritması çalışan bir kuantum bilgisayar karşısında P-256’nın ömrü birkaç saniyedir. NIST, Ağustos 2024’te resmi FIPS standartlarını yayımladı: FIPS 203 (ML-KEM), FIPS 204 (ML-DSA) ve FIPS 205 (SLH-DSA).

Şu an üretim ortamlarında saf PQC (Post-Quantum Cryptography) kullanmak imza boyutları nedeniyle (RSA’in 256 baytına karşılık Dilithium-3’ün ~3.3 KB boyutu) overhead yaratabilir. Bu yüzden ara çözüm Hibrit İmzalar veya liboqs entegrasyonudur. Sigstore ekosistemi PQC desteğini deneysel olarak sürdürürken, build pipeline’ınızda OpenSSL 3.x ve oqsprovider kullanarak kendi post-quantum PKI altyapınızı ayağa kaldırabilirsiniz.

Runner container’ı içerisinde ML-DSA-65 (Dilithium3) tabanlı bir keypair üretip artifact imzalamak için örnek bir yaklaşım:

# liboqs ve oqsprovider kurulu bir base image üzerinde:
# 1. Post-Quantum Private Key üret (FIPS 204 / ML-DSA-65)
openssl req -x509 -new -newkey mldsa65 \
  -keyout pqc_private.key \
  -out pqc_cert.crt \
  -nodes -subj "/CN=CI-PQC-Signer/O=KertenKerem" \
  -provider oqsprovider -provider default

# 2. Release artifact'ının (örneğin binary veya tarball) SHA-256 hash'ini al
sha256sum release-v1.4.0.tar.gz | awk '{print $1}' > artifact.digest

# 3. Hash'i ML-DSA anahtarıyla imzala
openssl dgst -sign pqc_private.key \
  -provider oqsprovider -provider default \
  -out artifact.sig artifact.digest

# 4. Doğrulama testi (Deployment aşamasında çalışacak kod)
openssl dgst -verify <(openssl x509 -in pqc_cert.crt -pubkey -noout -provider oqsprovider) \
  -signature artifact.sig \
  -provider oqsprovider -provider default \
  artifact.digest

Bu akışın klasik Cosign akışına göre tek farkı, kullanılan matematiksel yapının lattice-based (örgü tabanlı) olmasıdır. Bir imzayı production cluster’ında Admission Controller seviyesinde doğrulamak istiyorsanız, gatekeeper veya Kyverno pod’larınızın da liboqs kütüphanesini tanıyan OpenSSL/BoringSSL runtime’larına sahip olması gerektiğini unutmayın.

3. Kriptografik Envanter: CBOM (Cryptographic Bill of Materials)

SBOM (Software Bill of Materials) çıkarmayı öğrendik; peki uygulamanızın hangi şifreleme kütüphanelerine, hangi anahtar uzunluklarına ve hangi TLS versiyonlarına bağımlı olduğunu biliyor musunuz? Kodunuz doğrudan crypto/tls çağırıyor olabilir ama üçüncü parti bir Go paketi derleme anında RSA-1024’e hardcode edilmiş eski bir algoritma kullanıyor olabilir.

CycloneDX standardı, CBOM konseptiyle kriptografik varlıkların taranmasını ve listelenmesini destekler. CI aşamasına ekleyeceğiniz bir CBOM adımı, kuantum göçü (migration) başladığında envanter çıkarmak için harcayacağınız ayları kurtarır.

# Syft ile projenin kriptografik bağımlılıklarını içeren SBOM JSON çıktısı üretme
syft dir:. -o cyclonedx-json=bom.json

# bom.json içerisindeki zayıf kriptografik bağımlılıkları CI aşamasında filtreleme
jq '.components[] | select(.name | test("crypto|tls|ssl|ssh")) | {name: .name, version: .version}' bom.json

Eğer pipeline’ınızda kurumsal bir tarama yapıyorsanız, IBM CBOM Toolkit veya benzeri statik analiz araçlarını derleme öncesi kancalara (pre-commit / CI gate) entegre ederek kod tabanında crypto.GenerateKey(rsa.Reader, 2048) gibi çağrıları CI seviyesinde hata verdirerek engelleyebilirsiniz:

# semgrep ile CI pipeline'ında kuantum-zayıf anahtar kullanımını fail etme örneği
semgrep --config=p/crypto \
  --lang=go \
  --pattern='rsa.GenerateKey($R, 2048)' \
  --error \
  ./src

4. HashiCorp Vault ve Transit Secret Motoru

Pipeline içerisinde hassas environment variable’ları şifrelemek (envelope encryption) için yaygın olarak HashiCorp Vault Transit Engine kullanılır. Mevcut transit engine konfigürasyonunuz büyük olasılıkla aes256-gcm96 ile data encryption, anahtar değişimi için ise ECDSA/RSA kullanıyordur. AES-256, kuantum bilgisayarlar karşısında güvenlidir (Grover algoritması anahtar arama uzayını 2^128’e düşürür ki bu hâlâ pratikte kırılamaz bir seviyedir). Buradaki asıl problem, Vault client ile server arasındaki mTLS oturumu ve transit key’lerin wrap edilme yöntemidir.

Vault mTLS iletişimini güvenceye almak için, reverse proxy ve ingress seviyesinde TLS 1.3 zorunlu tutulmalı ve desteklenen cipher suite’ler sınırlandırılmalıdır:

# NGINX veya Cloud Ingress TLS 1.3 konfigürasyonu
ssl_protocols TLSv1.3;
# Kuantum sonrası anahtar değişimini destekleyen hibrit gruplar (X25519MLKEM768)
# Envoy / Nginx son sürümlerinde BoringSSL desteği ile aktifleştirilebilir
ssl_ecdh_curve X25519MLKEM768:x25519:secp384r1;

Cloudflare ve Google Chrome’un halihazırda üretimde kullandığı X25519MLKEM768 hibrit anahtar değişim mekanizması, pipeline runner’larınız ile internal API’larınız arasındaki veri trafiğini HNDL saldırılarına karşı bugünden korur.

DevOps Mühendisleri İçin Aksiyon Planı

Kriptografik dönüşüm, son gün yapabileceğiniz bir “hotfix” değildir. Bugün almanız gereken aksiyonlar şunlardır:

1. SSH Altyapısını Denetleyin: Tüm CI runner ve bastion host’larda OpenSSH 9.0+ sürümüne geçin ve hibrit KEX algoritmalarını zorunlu kılın.
2. CBOM Üretin: Build süreçlerinize SBOM yanında kriptografik varlık taramalarını ekleyin; kütüphanelerinizdeki sert kodlanmış asimetrik anahtar boyutlarını tespit edin.
3. AES-128’i Terk Edin: Simetrik şifrelemede taban çizgisi olarak her yerde AES-256 ve ChaCha20-Poly1305 kullanın.
4. Kriptografik Esneklik (Crypto-Agility) Kazanın: Kod içinde algoritma isimlerini string parametre olarak geçmeyin; anahtar yönetimini soyutlayan provider katmanları kullanın ki NIST FIPS 204/205 kütüphaneleri genel paket yöneticilerine tam girdiğinde sadece config değiştirerek göç edebilin.

Kuantum çağı geldiğinde pipeline’ları baştan yazmak zorunda kalmamak için mimarideki kriptografik borcu bugünden amorti etmeye başlayın.

Category: Genel | LEAVE A COMMENT
Ekim 2 2026

Ölçeklenen Altyapılarda Observability Maliyetini Yönetme: FinOps ve Sampling Stratejileri

Ay başında Datadog, New Relic veya AWS CloudWatch faturası geldiğinde dashboard’lardaki p99 gecikme grafiklerinden daha hızlı yükselen tek bir metrik vardır: Finans direktörünün nabzı. Modern bir DevOps mimarisinde mikroservislerin sayısı ve trafik hacmi arttıkça, observability altyapısının maliyeti çoğu zaman production ortamının compute maliyetini geride bırakır. Sistemin kör noktalarını kapatmak isterken bütçeyi tüketmek sürdürülebilir değil. Doğru bir FinOps yaklaşımı ve akıllı log yönetimi stratejileriyle, sistem görünürlüğünden ödün vermeden veri hacmini %60 ila %80 oranında düşürmek mümkün.

Fatura Neden Şişer? Görünmez Maliyet Kalemleri

Kıdemli mühendislerin sıkça düştüğü tuzak şudur: “Her şeyi loglayalım, OpenSearch veya Loki’ye basalım; lazım olursa grep’leriz.” Trafiğiniz saniyede 100 istek seviyesindeyken bu kabul edilebilir bir tembelliktir. Ancak saniyede 50.000 request aldığınız anda bu alışkanlık bir bütçe felaketine dönüşür.

Maliyeti patlatan üç ana faktör vardır:

  • Sağlık Kontrolleri (Health Checks): Kubernetes liveness/readiness probları ve ALB ping’leri log havuzunuzun %30’unu oluşturabilir. HTTP 200 GET /healthz satırının size hata anında hiçbir faydası yoktur.
  • Yüksek Kardinalite (High Cardinality): Metrik etiketlerine (label) user_id, order_id gibi sınırsız varyasyonu olan değerler eklemek, time-series veritabanınızın (Prometheus/Mimir) bellek tüketimini ve ingest maliyetini katlar.
  • Head-Based Sampling Hataları: Tracing verisinde rastgele %5 sampling aldığınızda, sistemdeki asıl yakalamak istediğiniz 500 hatalarının ve p99 outlier’larının da %95’ini çöpe atarsınız. Geriye sadece gürültü kalır.

Log Yönetimi: Vector ile Edge Düzeyinde Filtreleme ve Dönüştürme

Logları merkezi sisteme göndermeden önce daemonset katmanında filtrelemek en ucuz ve en etkili çözümdür. Logstash veya Fluentd gibi JVM/Ruby tabanlı hantal çözümler yerine Rust ile yazılmış düşük ayak izli Vector bu iş için biçilmiş kaftandır.

Aşağıdaki Vector konfigürasyonunda üç kritik optimizasyon yapıyoruz: Başarılı health check’leri drop ediyoruz, hassas verileri tokenize ediyoruz ve kalan debug loglarını örnekliyoruz (sampling).

cat << 'EOF' > /etc/vector/vector.yaml
sources:
  kubernetes_logs:
    type: kubernetes_logs

transforms:
  filter_health_checks:
    type: filter
    inputs:
      - kubernetes_logs
    # HTTP 200 dönen healthz loglarını doğrudan çöpe at
    condition: '!includes(["/healthz", "/readyz", "/livez"], .message)'

  drop_noisy_debugs:
    type: filter
    inputs:
      - filter_health_checks
    # Log seviyesi DEBUG olanları %90 oranında sample et
    condition: |
      if .level == "debug" {
        random() < 0.1
      } else {
        true
      }

  sanitize_and_prune:
    type: remap
    inputs:
      - drop_noisy_debugs
    source: |
      # Gereksiz Kubernetes metadata alanlarını temizle
      del(.kubernetes.pod_labels)
      del(.kubernetes.pod_annotations)
      
      # Kredi kartı gibi hassas verileri maskele
      .message = replace(.message, r'[0-9]{4}-[0-9]{4}-[0-9]{4}-[0-9]{4}', "[REDACTED_CC]")

sinks:
  elasticsearch_out:
    type: elasticsearch
    inputs:
      - sanitize_and_prune
    endpoints:
      - "https://elasticsearch.internal:9200"
    mode: "bulk"
EOF

Neden böyle yaptık? Kubernetes metadata’sındaki pod annotation’ları production’da devasa JSON payload’ları üretir. Bunları edge katmanında del() fonksiyonu ile silmek, taşınan ve indekslenen veri boyutunu anında %25 azaltır.

Tracing Verisinde Tail-Based Sampling Stratejisi

Geleneksel distributed tracing araçları trace’in başlangıcında karar verir (Head-based sampling). Bir istek geldiğinde yazı tura atılır: %1 seçilirse tüm akış kaydedilir. Fakat istek sırasında bir mikroservis patlarsa ve siz o isteği sample etmediyseniz, elinizde hiçbir şey kalmaz.

Tail-based sampling, isteğin tamamlanmasını bekler. İstek hata aldıysa veya belirlenen latency eşiğini aştıysa trace’i %100 saklar; sorunsuz HTTP 200 yanıtlarını ise agresif bir şekilde eler.

Bunu OpenTelemetry (OTel) Collector katmanında şu şekilde konfigüre ediyoruz:

cat << 'EOF' > /etc/otelcol/config.yaml
receivers:
  otlp:
    protocols:
      grpc:
      http:

processors:
  tail_sampling:
    decision_wait: 10s
    num_traces: 10000
    expected_new_traces_per_sec: 2000
    policies:
      # 1. Kural: Status code ERROR olan tüm trace'leri eksiksiz tut
      - name: drop_errors_policy
        type: status_code
        status_code: { status_codes: [ ERROR ] }

      # 2. Kural: Süresi 1.5 saniyeyi aşan tüm yavaş istekleri yakala
      - name: latency_policy
        type: latency
        latency: { threshold_ms: 1500 }

      # 3. Kural: Kalan normal akıştan sadece %1 örnek al
      - name: probabilistic_policy
        type: probabilistic
        probabilistic: { sampling_percentage: 1.0 }

exporters:
  otlp/backend:
    endpoint: "tempo.monitoring.svc.cluster.local:4317"
    tls:
      insecure: true

service:
  pipelines:
    traces:
      receivers: [otlp]
      processors: [tail_sampling]
      exporters: [otlp/backend]
EOF

Bu konfigürasyonda OTel Collector, trace parçalarını decision_wait: 10s süresince bellekte tutar. Span ağacı tamamlandığında kuralları yukarıdan aşağıya değerlendirir. İstisna veya lag varsa diskte saklanır; yoksa veri hacmi anında %99 bastırılır.

Metriklerin Sessiz Katili: Kardinaliteyi Düşürme (Metric Relabeling)

Datadog veya Prometheus faturalarında metrik maliyetlerinin patlamasının temel nedeni “Custom Metrics” ve time-series patlamalarıdır. Bir metriğin kaç time-series ürettiğini şu formülle hesaplarız:

Total Time Series = (Değer_Sayısı_Label_A) * (Değer_Sayısı_Label_B) * (Değer_Sayısı_Label_C)

Eğer bir yazılımcı HTTP istek metriğine user_id veya rastgele UUID’ler içeren path eklediyse, o metrik saniyeler içinde 500.000 time-series üreterek bellek limitlerini vurur. Prometheus scrape konfigürasyonunda metric_relabel_configs kullanarak bu etiketleri ingest anında silmelisiniz:

scrape_configs:
  - job_name: 'api-gateway'
    kubernetes_sd_configs:
      - role: pod
    metric_relabel_configs:
      # Belirli yüksek kardinaliteli dinamik labelları drop et
      - action: labeldrop
        regex: '(user_id|client_ip|session_token)'
      
      # REST URL path'lerindeki UUID'leri regex ile normalize et
      - source_labels: [__name__, path]
        regex: 'http_requests_total;/api/v1/orders/[a-f0-9\-]+'
        target_label: path
        replacement: '/api/v1/orders/:id'

Yukarıdaki işlem yapılmadığında /api/v1/orders/a1b2-c3d4 ve /api/v1/orders/e5f6-g7h8 iki ayrı metrik olarak depolanır. Regex normalizasyonu sayesinde her iki istek de /api/v1/orders/:id altında birleşir; metrik tekilleşir ve time-series sayısı tek hanelere iner.

Veri Yaşam Döngüsü: Tiering ve Soğuk Depolama

SRE ekiplerinin yaptığı analizler, log sorgularının %92’sinin son 48 saat içinde yazılan verilere yapıldığını gösteriyor. 30 gün önceki bir INFO logunu pahalı NVMe disklerde tutmak safi kaynak israfıdır.

Loki veya Elasticsearch kullanırken iki katmanlı (hot/cold) saklama stratejisi kurgulayın:

  • Hot Tier (0-7 Gün): Hızlı sorgulama için NVMe diskler veya SSD blok depolama. Tam indeksleme açık.
  • Warm/Cold Tier (7-30 Gün): İndeksleri küçültülmüş, read-only modda çalışan uygun maliyetli HDD veya S3/GCS object storage katmanı.
  • Deep Archive (30+ Gün): Yalnızca regülasyon (KVKK/GDPR/PCI-DSS) gereği tutulması gereken veriler için S3 Glacier / GCS Archive lifecycle kuralları. İndeks yok, yalnızca ham gzip blokları.

Özet: FinOps Mühendisliği Bir Süreçtir

Observability sistemlerini optimize etmek, kör uçuş yapmak anlamına gelmez. Tam aksine, doğru sinyali yakalayıp gürültüyü (noise) elimine etmektir. Edge katmanında Vector ile gürültüyü kesin, APM tarafında tail-based sampling ile hatalara odaklanın ve scraping aşamasında kardinaliteyi regex ile dizginleyin. Bu üç adımı attığınızda, faturanız düşerken platformunuzun asıl sorunları tespit etme kabiliyeti katlanarak artacaktır.

Category: Genel | LEAVE A COMMENT
Eylül 25 2026

Platform Mühendisliğinde Internal Developer Portal (IDP) Kurulumu: Backstage mi, Port mu?

Modern Platform Engineering pratiklerinin merkezinde tek bir nihai hedef var: Kognitif yükü azaltıp Developer Experience (DevX) çıtasını yukarı çekmek. Ancak bir sabah uyanıp “Hadi şirkete bir IDP kuralım” dediğinizde, DevOps dünyasının en büyük iki kutbu önünüze dikiliyor: Açık kaynak kodlu, Spotify kökenli Backstage ve SaaS/API-first yaklaşımıyla son dönemin parlayan yıldızı Port. Bu iki aracın broşür vaatlerini bir kenara bırakıp üretim ortamındaki gerçek maliyetlerine, mimari esnekliklerine ve Day-2 operasyonlarına bakalım.

SlackOps’tan Self-Service’e: Neden Bir IDP’ye İhtiyacımız Var?

Kabul edelim, şirket içinde yazdığınız bash script’leri, Terraform modülleri ve “production-hazır” Helm chart’ları harika çalışıyor olabilir. Fakat yazılımcı yeni bir microservice açmak için hâlâ Slack kanalından “#devops-help bana yeni bir Redis açar mısınız?” yazıyorsa, ortada bir platform değil, sadece insan gücüyle çalışan bir bilet kuyruğu vardır. IDP; Golden Path (Altın Yol) felsefesini somutlaştıran, altyapı yetkilendirmesini ve servis kataloğunu tek bir panele toplayan arayüzdür.

1. Spotify Backstage: Kodun Gücü ve Bakımın Laneti

Backstage bir ürün değildir; bir framework’tür. TypeScript ve React ile yazılmış bir monorepo iskeletidir. Kurulumu yaptığınız an elinizde bitmiş bir portal değil, sizin geliştirmeniz gereken bir yazılım projesi olur.

Backstage Mimarisi ve Plugin Ekosistemi

Backstage’in en büyük artısı, limitsiz özelleştirilebilmesidir. Şirket içi legacy bir LDAP sisteminiz veya kurum içi custom bir deployment engine’iniz varsa, Backstage içine plugin yazarak entegre edemeyeceğiniz hiçbir şey yoktur. Servislerinizi catalog-info.yaml dosyalarıyla declarative olarak tanımlarsınız:

apiVersion: backstage.io/v1alpha1
kind: Component
metadata:
  name: payment-gateway-service
  description: Core payment processing engine
  annotations:
    github.com/project-slug: acme-corp/payment-gateway
    backstage.io/techdocs-ref: dir:.
    argocd/app-name: payment-gateway-prod
    prometheus.io/rule: alert_payment_latency
spec:
  type: service
  lifecycle: production
  owner: payments-team
  system: core-banking
  providesApis:
    - payment-api
  dependsOn:
    - resource:default/payment-postgres-db

Yazılım Şablonları (Software Templates) ile Self-Service

Backstage Scaffolder, geliştiricinin parametreleri girdiği ve arka planda bir Git repo’su oluşturup ilk commit’i attığı şablon mekanizmasıdır:

apiVersion: scaffolder.backstage.io/v1beta3
kind: Template
metadata:
  name: go-service-template
  title: Go Microservice (Golden Path)
spec:
  owner: platform-core
  type: service
  parameters:
    - title: Servis Konfigürasyonu
      required: [name, owner]
      properties:
        name:
          type: string
          description: Servis adı (kebab-case)
        owner:
          type: string
          ui:field: OwnerPicker
  steps:
    - id: template
      name: Dosyaları Hazırla
      action: fetch:template
      input:
        url: ./skeleton
        values:
          name: ${{ parameters.name }}
          owner: ${{ parameters.owner }}
    - id: publish
      name: GitHub Reposu Aç
      action: publish:github
      input:
        allowedHosts: ['github.com']
        description: Service created via Backstage
        repoUrl: github.com?owner=acme-corp&repo=${{ parameters.name }}
        defaultBranch: main

Neden Can Sıkar?

  • Node.js Dependency Cehennemi: Her yarn upgrade çalıştırdığınızda core plugin’lerin kırılma ihtimali çok yüksektir.
  • Frontend Eforu: Platform ekibinde React ve modern frontend toolchain’lerine hakim mühendis yoksa, arayüz geliştirmeleri kabusa dönüşür.
  • State ve DB Yönetimi: Catalog senkronizasyonu için PostgreSQL tutmanız, cache stratejilerini (Redis) yönetmeniz ve auth mekanizmalarını tek tek implemente etmeniz gerekir.

2. Port (getport.io): Data-Model First ve Low-Code IDP

Port, Backstage’in tam zıddı bir felsefeyle gelir: “Siz platform mühendisisiniz, frontend geliştiricisi değilsiniz.” Port bir SaaS veya Control-Plane-Only çözümüdür. UI tarafında tek satır kod yazmazsınız; her şey Blueprints (Veri Modelleri) ve Actions (Eylemler) üzerinden kurgulanır.

Entity ve Blueprint Mantığı

Port’ta altyapınızı bir Graph Database gibi modelllersiniz. Bir Kubernetes Cluster’ı, bir Microservice’e; o Microservice, bir RDS instance’ına ve bir PagerDuty On-Call rotasyonuna bağlanabilir.

Bu modeli Terraform ile Infrastructure as Code (IaC) prensibine uygun şekilde yönetebilirsiniz:

# Port Terraform Provider ile Blueprint tanımı
resource "port_blueprint" "microservice" {
  title      = "Microservice"
  icon       = "Microservice"
  identifier = "microservice"
  properties = {
    string_props = {
      "language" = {
        title = "Language"
        enum  = ["Go", "NodeJS", "Python"]
      }
      "slack_channel" = {
        title = "Slack Channel"
      }
    }
  }
}

resource "port_action" "scaffold_service" {
  title       = "Scaffold New Microservice"
  icon        = "Github"
  identifier  = "create_microservice"
  blueprint   = port_blueprint.microservice.identifier
  trigger_type = "self-service"

  user_inputs = {
    string_props = {
      "service_name" = {
        title = "Service Name"
      }
    }
  }

  backend_invocation = {
    type = "github"
    org  = "acme-corp"
    repo = "platform-scaffolder-workflows"
    workflow = "scaffold.yaml"
  }
}

Kubernetes ve CI/CD Entegrasyonu: Port Exporter

Backstage katalog için repo taraması (polling) yaparken, Port event-driven çalışır. Cluster’ınıza kurduğunuz bir exporter agent, kaynakları dinamik olarak Port paneline push eder:

helm repo add port-labs https://port-labs.github.io/helm-charts
helm repo update

helm install port-k8s-exporter port-labs/port-k8s-exporter \
  --set port.clientId="YOUR_CLIENT_ID" \
  --set port.clientSecret="YOUR_CLIENT_SECRET" \
  --values values.yaml

Burada values.yaml içinde basit JQ mapping’leri tanımlayarak Kubernetes CRD’lerinizi doğrudan UI component’lerine dönüştürebilirsiniz. Sıfır React, sıfır TypeScript.

Derin Kıyaslama: Hangi Senaryoda Hangisi?

1. Bakım Maliyeti (Total Cost of Ownership)

Backstage açık kaynak ve “bedava” gibi görünse de en az 1-2 platform mühendisinin tam zamanlı mesaisini tüketir. Güvenlik yamaları, TypeScript bağımlılıkları ve plugin güncellemeleri ciddi bir yüktür. Port ise SaaS lisanslama maliyetine sahiptir ancak kurulumu günler içinde tamamlanıp minimum eforla ayakta tutulur.

2. Esneklik ve Kurumsal Uyum

Kurum içi compliance politikalarınız multi-tenant SaaS kullanımına izin vermiyorsa, air-gapped ortamlarda çalışıyorsanız veya tamamen şirketinizin UI design system’ına (örneğin kurumsal Material UI teması) gömülmüş bir portal istiyorsanız Backstage tek seçenektir. Port hibrit çalışabilir (self-hosted exporter ve runner modelleriyle verinizi içeride tutabilir) ancak UI kontrolü her zaman onlardadır.

3. Self-Service Workflow Tetikleme

Backstage, kendi Scaffolder motoruna güvenir. Node.js backend’i üzerinde repo klonlar, commit atar ve PR açar. Port ise orkestrasyonu var olan araçlarınıza devreder: GitHub Actions, GitLab CI, Argo Workflows veya doğrudan bir Webhook. DevOps mühendisi için Port’un bu yaklaşımı çok daha doğaldır çünkü CI pipeline’ı yazmak, Scaffolder Action yazmaktan katbekat daha hızlıdır.

Karar Ağacı

Mimarinize karar verirken şu kontrol listesini uygulayabilirsiniz:

  • Ekip Büyüklüğü: 150+ yazılımcınız ve sadece portal geliştirmeye adanmış 3+ kişilik bir Platform Engineering ekibiniz varsa: Backstage.
  • Hızlı Değer Üretimi (Time-to-Value): 20-100 kişilik yazılımcı grubuna hizmet veren yalın bir DevOps ekibiyseniz ve iki hafta içinde bir katalog + self-service portal istiyorsanız: Port.
  • Arayüz İhtiyacı: “Bize standart bir dashboard yetmez, production tracing ve billing verilerini custom canvas grafikleriyle basacağız” diyorsanız: Backstage.
  • IaC ve GitOps Entegrasyonu: Portalı da Terraform ile yönetip altyapı state’i gibi kontrol etmek istiyorsanız: Port.

Özet

Platform Mühendisliğinin amacı yeni bir monorepo yazılım bakım yükü yaratmak değil, yazılım ekiplerinin önündeki altyapı bariyerlerini kaldırmaktır. Eğer bir yazılım eviyseniz ve temel yetkinliğiniz portal geliştirmek değilse, Port gibi modern platformlar operasyonel yükü minimize etmek için daha rasyoneldir. Ancak regülasyonların boğduğu, UI üzerinde tam egemenlik isteyen devasa bir kurumsal yapıdaysanız, Backstage’in getirdiği bakım yükünü göze alıp kendi iç ürününüzü inşa etmek doğru yoldur.

Category: Genel | LEAVE A COMMENT
Şubat 5 2026

Bash’te Hata Yönetimi: set -euo pipefail, Trap ve Bats ile Defansif Scripting

Gece saat 03:00. PagerDuty çalıyor. Sorun: Kubernetes cluster’ına yeni bir deployment çıkılırken pipeline yarıda kalmış ama deploy başarılıymış gibi davranıp eski stabil replicaları da temizlemiş. Loglara bakıyorsunuz; kritik bir environment variable tanımlanmadığı için script sessizce patlamış, fakat exit code 0 döndüğü için CI/CD pipeline’ı her şeyin yolunda olduğunu varsayarak devam etmiş. Hepimiz bu filmi en az bir kere izledik. Günümüz modern altyapılarında bash scripting ve linux tabanlı otomasyon süreçleri, devops mühendisliğinin görünmez omurgasını oluşturuyor. Ancak bu omurga genellikle oldukça kırılgan temeller üzerine kurulu.

Bu makalede, Bash betiklerinizi kurumsal seviyede, hata toleransı yüksek ve test edilebilir yapılara dönüştürmenin yollarını inceleyeceğiz. Temel kavramları bir kenara bırakıp, doğrudan production ortamlarında hayat kurtaran pratik yaklaşımlara odaklanacağız.

Sessiz Katilleri Durdurun: “set -euo pipefail” Anatomisi

Bash, varsayılan olarak aşırı iyimser bir kabuktur. Bir komut hata verse de, bir değişken tanımlanmamış olsa da “yola devam et” felsefesini benimser. Bu felsefe interaktif terminal kullanımı için harika olsa da, otomasyon scriptleri için tam bir felakettir. Bu felaketi önlemenin ilk adımı, script’lerin başına o meşhur üçlüyü eklemektir:

set -euo pipefail

Peki bu parametreler arka planda tam olarak ne yapar ve neden hayati önem taşırlar? Tek tek inceleyelim.

1. set -e (errexit)

Bu parametre, script içindeki herhangi bir komut non-zero (sıfır dışı) bir exit code ile sonuçlandığında script’in anında sonlandırılmasını sağlar. Varsayılan davranışta Bash, komut hata alsa bile bir sonraki satıra geçmeye çalışır.

Neden kritik? Aşağıdaki senaryoyu düşünün:

cd /tmp/non_existent_directory
rm -rf *

Eğer ilk satırdaki cd komutu dizin bulunamadığı için başarısız olursa ve set -e aktif değilse, script bir sonraki satıra geçer ve o an hangi dizinde bulunuyorsa (muhtemelen script’in çalıştığı root dizini) oradaki tüm dosyaları siler. set -e bu felaketi engeller.

2. set -u (nounset)

Eğer tanımlanmamış bir değişkeni okumaya çalışırsanız, Bash bunu boş bir string olarak kabul eder ve hata vermez. set -u, tanımlanmamış her değişkeni ölümcül bir hata (fatal error) olarak kabul eder ve script’i durdurur.

Neden kritik?

# set -u aktif değilse:
TARGET_DIR="" # ya da yanlışlıkla typo yapılmış bir değişken
rm -rf "${TARGET_DIR}/bin" # Sistem "rm -rf /bin" komutunu çalıştırır!

set -u aktif olduğunda, Bash bu satıra geldiğinde TARGET_DIR: unbound variable hatası fırlatacak ve execution’ı hemen durduracaktır.

3. set -o pipefail

Bash’te pipeline (boru) işlemlerinde varsayılan olarak sadece son komutun exit code’u dikkate alınır. Örneğin komut1 | komut2 | komut3 zincirinde komut1 patlasa bile, komut3 başarılı olursa tüm pipeline başarılı (exit 0) sayılır.

Neden kritik?

# pipefail aktif değilse:
curl -s https://invalid-url-destination.tar.gz | tar -xzf -
echo $? # Çıktı: 0

Yukarıdaki örnekte curl başarısız olacak ve hiçbir veri indiremeyecektir. Ancak tar komutu boş girdi aldığında (veya pipe kapandığında) hata üretse de, exit code bazen yanıltıcı olabilir. Daha da kötüsü, aradaki kritik log analiz araçlarında hatalar yutulur. set -o pipefail aktif edildiğinde, pipeline içindeki en sağdaki non-zero exit code tüm pipeline’ın dönüş kodu kabul edilir.

—

İstisnaları Yönetmek: set -e Altında Güvenli Hata Toleransı

set -e kullanmaya başladığınızda karşılaşacağınız ilk sorun, bazı komutların hata vermesinin aslında beklenen bir durum olmasıdır. Örneğin bir dizinin varlığını kontrol etmek veya grep ile bir log dosyasında spesifik bir kelimeyi aramak gibi durumlarda, exit code’un 1 gelmesi script’i patlatmamalıdır.

Hata Kodunu Güvenli Bir Şekilde Absorbe Etmek

Eğer bir komutun başarısız olabileceğini biliyor ve bunun script’i durdurmasını istemiyorsanız, OR (||) operatörünü kullanarak hata durumunu bypass edebilirsiniz:

# grep satır bulamazsa normalde exit 1 verir ve script set -e yüzünden durur.
# || true kullanarak bunu engelliyoruz.
result=$(grep "ERROR" /var/log/app.log || true)

# Veya alternatif olarak lokal bir hata değişkenine atayabilirsiniz:
grep "CRITICAL" /var/log/app.log || local_exit=$?
if [ ${local_exit:-0} -ne 0 ]; then
    echo "Kritik log bulunamadı, ama yola devam ediyoruz."
fi

Bir diğer profesyonel yaklaşım ise conditional block’lar kullanmaktır. if, elif, while veya until ifadelerinin içinde çalıştırılan komutlar, hata verseler dahi set -e engeline takılmazlar:

# Bu blok set -e aktif olsa bile güvenle çalışır
if ! ping -c 1 8.8.8.8 > /dev/null 2>&1; then
    echo "İnternet bağlantısı yok, ancak script devam ediyor."
fi

—

Trap Mekanizması: Kaynakları Temizlemek ve Graceful Shutdown

Script’iniz ister başarıyla tamamlansın, ister yarıda hata alıp dursun; arkasında çöp bırakmamalıdır. Geçici dosyalar (temp files), oluşturulan socket’ler, kilit dosyaları (lock files) veya aktif edilen SSH tünelleri her durumda temizlenmelidir. İşte bu noktada Linux kernel sinyallerini yakalayan trap devreye girer.

Aşağıdaki boilerplate şablonu, production seviyesindeki script’lerinizde güvenle kullanabilirsiniz:

#!/usr/bin/env bash

set -euo pipefail

# Geçici dizin oluşturuluyor
TMP_DIR=$(mktemp -d -t my-app-XXXXXX)
echo "Geçici çalışma alanı: ${TMP_DIR}"

# Temizlik fonksiyonu
cleanup() {
    local exit_code=$?
    echo "Temizlik işlemi başlatılıyor... Son exit code: ${exit_code}"
    rm -rf "${TMP_DIR}"
    
    # Eğer script bir hata yüzünden durduysa ek bildirimler buraya yazılabilir (Slack webhook vb.)
    if [ ${exit_code} -ne 0 ]; then
        echo "Script beklenmedik bir şekilde sonlandı!" >&2
    fi
}

# EXIT sinyalini yakala. Script nasıl biterse bitsin cleanup çalışacak.
trap cleanup EXIT

Neden EXIT Sinyali?

Birçok junior geliştirici trap için sadece ERR veya INT sinyallerini tanımlar. Ancak EXIT pseudosignal’i, script normal yollarla bittiğinde de, set -e yüzünden yarıda kesildiğinde de veya bir syntax hatası alındığında da tetiklenir. Bu yüzden tek bir merkezden temizlik yönetimi için en güvenli limandır.

—

Exit Code Standartları: Kurumsal Entegrasyon

Yazdığınız script’lerin diğer otomasyon araçları (Ansible, Jenkins, ArgoCD vb.) tarafından doğru yorumlanabilmesi için POSIX standartlarına uygun exit code’lar dönmesi gerekir. Sadece exit 1 yazıp geçmek, hata analizi yaparken size zaman kaybettirir.

Exit Code Anlamı Kullanım Senaryosu
0 Success Script görevini başarıyla tamamladı.
1 Catchall for general errors Genel hatalar (izin yetersizliği, parse hataları vb.)
2 Misuse of shell builtins Eksik veya hatalı argüman kullanımı.
126 Command invoked cannot execute Çalıştırılmak istenen dosya execute yetkisine sahip değil.
127 Command not found Script içindeki bir binary sistemde yüklü değil.

Scriptlerinizde custom hata kodları tanımlarken 64-113 (BSD standardı) veya 128’den büyük olmayan serbest kodları (örneğin 10, 20, 30 gibi) kullanmanız, sistem hata kodlarıyla çakışmayı önler:

readonly ERR_INVALID_ARG=2
readonly ERR_DB_CONNECTION=10
readonly ERR_DISK_FULL=11

if [ $# -lt 1 ]; then
    echo "Hata: En az bir argüman girmelisiniz." >&2
    exit ${ERR_INVALID_ARG}
fi

—

Bats (Bash Automated Testing System) ile Unit Test Yazımı

“Bash script’inin testi mi olur?” demeyin. Eğer yazdığınız script kritik bir deploy sürecini yönetiyorsa, her değişiklikten sonra manuel test yapmak hem riskli hem de zaman alıcıdır. Bats, Bash script’lerinizi gerçek birer yazılım projesi gibi test etmenizi sağlar.

Öncelikle test edeceğimiz basit ama fonksiyonel bir script yazalım (isim: deploy.sh):

#!/usr/bin/env bash
set -euo pipefail

deploy_artifact() {
    local env=$1
    if [ -z "${env}" ]; then
        echo "Hata: Environment belirtilmedi." >&2
        return 2
    fi
    
    if [ "${env}" == "prod" ]; then
        echo "Production deployment başarılı."
        return 0
    else
        echo "Bilinmeyen environment: ${env}" >&2
        return 10
    fi
}

# Script doğrudan çalıştırıldıysa (test import etmediyse) main'i çalıştır
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
    deploy_artifact "${1:-}"
fi

Şimdi bu script için yazacağımız unit test dosyasına bakalım (isim: deploy.bats):

#!/usr/bin/env bats

# Test öncesi script'i load et
setup() {
    source ./deploy.sh
}

@test "Parametre verilmediğinde script exit code 2 ile patlamalı" {
    run deploy_artifact ""
    [ "$status" -eq 2 ]
    [ "$output" = "Hata: Environment belirtilmedi." ]
}

@test "Prod environment sağlandığında başarılı dönmeli" {
    run deploy_artifact "prod"
    [ "$status" -eq 0 ]
    [ "$output" = "Production deployment başarılı." ]
}

@test "Bilinmeyen env verildiğinde exit code 10 dönmeli" {
    run deploy_artifact "staging"
    [ "$status" -eq 10 ]
}

Bu testleri lokalinizde veya CI/CD pipeline’ınızda çalıştırmak için tek yapmanız gereken bats binary’sini çağırmaktır:

bats deploy.bats

Sonuçlar yeşil yandığında, refactoring yaparken hiçbir şeyi bozmadığınızdan emin olarak production’a güvenle push edebilirsiniz.

—

Sonuç: Defansif Scripting’i Alışkanlık Haline Getirin

Bash scripting, küçümsenen ama sistem mühendisliğinin en kritik katmanlarından biridir. Script yazarken kodun sadece “mutlu senaryoda” (happy path) nasıl çalışacağını değil, nerede ve nasıl patlayabileceğini tasarlamak gerekir. set -euo pipefail ile sessiz hataların önüne geçmek, trap ile sistem kaynaklarını her durumda temiz bırakmak ve bats ile bu süreçleri test edilebilir kılmak, sizi sıradan bir script yazıcısından profesyonel bir sistem mimarına dönüştürür.

Category: Genel | LEAVE A COMMENT
Kasım 7 2025

Elasticsearch ILM ile Disk Tasarrufu: Hot-Warm-Cold-Delete Yapılandırması

DevOps dünyasının en acı verici maliyet kalemlerinden biri, her gün çığ gibi büyüyen log verileridir. Elasticsearch üzerinde koşan log kümelerinin kontrolsüz büyümesi, disk doluluk alarmları ve şişen bulut faturalarıyla sonuçlanır. İşte tam bu noktada, akıllı bir elasticsearch ilm (Index Lifecycle Management) stratejisi kurmak, disk ve donanım maliyetlerinizi kalıcı olarak optimize etmenin en efektif yoludur. Sadece eski verileri silmek bir çözüm değildir; verinin yaşlandıkça daha ucuz kaynaklara taşınması, sıkıştırılması ve segmentlerinin birleştirilmesi gerekir. Bu rehberde, production ortamında doğrudan uygulayabileceğiniz, shrink ve force-merge adımlarıyla desteklenmiş agresif bir hot-warm-cold-delete mimarisini nasıl kuracağınızı inceleyeceğiz.

1. Altyapının Hazırlanması: Node Rollerinin Dağıtımı

ILM mekanizmasının veriyi doğru katmanlar arasında taşıyabilmesi için öncelikle Elasticsearch cluster üyelerinin rollerini net bir şekilde tanımlamalıyız. Eski node.attr.box_type yaklaşımı yerine, modern Elasticsearch mimarisinde (7.x/8.x) yerleşik veri rollerini kullanıyoruz. Node’larınızın elasticsearch.yml dosyalarında şu tanımlamaların yapıldığından emin olun:

Hot Node Konfigürasyonu (Yüksek IOPS NVMe Diskler)

node.roles: [ master, data_hot, ingest ]

Warm Node Konfigürasyonu (Orta Segment SSD / Standart Diskler)

node.roles: [ data_warm ]

Cold Node Konfigürasyonu (Ucuz, Yüksek Kapasiteli HDD / Object Storage)

node.roles: [ data_cold ]

Neden böyle? Yazma (ingest) yükü her zaman CPU ve I/O canavarıdır. Hot node’ları pahalı ve hızlı disklerde tutarken, artık arama sıklığı düşmüş eski verileri barındıran warm ve cold node’larda daha ucuz depolama birimleri kullanarak maliyetleri ciddi oranda dengeliyoruz.

2. Agresif Bir ILM Policy Tanımlama

Şimdi disk tasarrufunu asıl optimize edecek olan ILM policy yapısını kuralım. Senaryomuzda:

  • Hot Fazı: Veri yazılır. Primary shard boyutu 50 GB’a ulaştığında veya 7 gün geçtiğinde rollover tetiklenir.
  • Warm Fazı: Rollover olan veri warm node’lara taşınır. Shard sayısı 1’e düşürülür (shrink) ve segment birleştirmesi (force-merge) yapılarak diskte maksimum sıkıştırma sağlanır.
  • Cold Fazı: Veri cold node’lara taşınır, replica sayısı 0 veya 1’e çekilerek alan kazanılır.
  • Delete Fazı: 90 günün ardından veri cluster’dan tamamen silinir.

Bu akışı tetikleyecek API çağrısını aşağıdaki gibi cluster’a uygulayalım:

curl -X PUT "localhost:9200/_ilm/policy/log_maliyeti_opt_policy" -H 'Content-Type: application/json' -d'
{
  "policy": {
    "phases": {
      "hot": {
        "min_age": "0ms",
        "actions": {
          "rollover": {
            "max_primary_shard_size": "50gb",
            "max_age": "7d"
          },
          "set_priority": {
            "priority": 100
          }
        }
      },
      "warm": {
        "min_age": "0ms",
        "actions": {
          "shrink": {
            "number_of_shards": 1
          },
          "forcemerge": {
            "max_num_segments": 1
          },
          "allocate": {
            "number_of_replicas": 1
          },
          "set_priority": {
            "priority": 50
          }
        }
      },
      "cold": {
        "min_age": "30d",
        "actions": {
          "allocate": {
            "number_of_replicas": 0
          },
          "set_priority": {
            "priority": 0
          }
        }
      },
      "delete": {
        "min_age": "90d",
        "actions": {
          "delete": {}
        }
      }
    }
  }
}'

3. Neden Shrink ve Force Merge?

Burada “Neden böyle yaptık?” sorusunu yanıtlayalım. Birçok DevOps mühendisi sadece rollover ve delete adımlarını kullanır. Ancak asıl disk tasarrufu warm fazındaki iki kritik adımda gizlidir:

  1. Shrink: Hot fazda yazma performansını optimize etmek için muhtemelen 5 primary shard kullandınız. Veri salt okunur (read-only) olduğunda, bu kadar çok shard’a ihtiyacınız kalmaz. Shard sayısını 1’e düşürerek hem Elasticsearch JVM Heap bellek tüketimini azaltırız hem de metadata overhead’ini sıfırlarız.
  2. Force Merge (max_num_segments: 1): Elasticsearch arkada verileri sürekli segmentler halinde yazar. Silinen veya güncellenen dökümanlar fiziksel olarak hemen silinmez, sadece silindi olarak işaretlenir. Force merge operasyonu ile tüm segmentleri tek bir segmente indirgeriz. Bu işlem, silinmiş tüm verileri diskten fiziksel olarak kazır ve sıkıştırma algoritmasının (LZ4/DEFLATE) maksimum verimle çalışmasını sağlayarak ortalama %20 ila %40 arasında anlık disk tasarrufu sağlar.

4. Index Template ve Bootstrap Oluşturma

ILM politikasının otomatik olarak yeni index’lere uygulanabilmesi için bir index template tanımlamamız gerekiyor. Burada önemli olan nokta, verilerin yazılacağı ilk “bootstrap” index’ini elle oluşturup alias’ı bağlamaktır.

Index Template Tanımlanması

curl -X PUT "localhost:9200/_index_template/log_template" -H 'Content-Type: application/json' -d'
{
  "index_patterns": ["app-logs-*"],
  "template": {
    "settings": {
      "index.lifecycle.name": "log_maliyeti_opt_policy",
      "index.lifecycle.rollover_alias": "app-logs",
      "index.number_of_shards": 3,
      "index.number_of_replicas": 1,
      "index.routing.allocation.include._tier_preference": "data_hot"
    }
  }
}'

Burada index.routing.allocation.include._tier_preference: "data_hot" satırı çok kritiktir. Yeni oluşturulan tüm index’lerin öncelikli olarak hot node’lara yazılmasını garanti altına alır.

İlk Bootstrap Index’inin Tetiklenmesi

Rollover mekanizmasının çalışabilmesi için alias’ın işaret ettiği fiziksel bir index’in bulunması şarttır. İlk index’i write_index olarak tanımlayarak döngüyü başlatıyoruz:

curl -X PUT "localhost:9200/%3Capp-logs-%7Bnow%2Fd%7D-000001%3E" -H 'Content-Type: application/json' -d'
{
  "aliases": {
    "app-logs": {
      "is_write_index": true
    }
  }
}'

Artık uygulamanız logları doğrudan app-logs alias’ına yazabilir. Elasticsearch, arka planda ILM kurallarını işleterek index boyutlarını izleyecek ve eşikler aşıldığında rollover yaparak süreci warm katmanına devredecektir.

5. SRE Gözünden Production Sorunları ve Çözümler

Production ortamlarında bu yapıyı koştururken karşınıza çıkabilecek bazı tipik tıkanma noktaları ve bunları aşma yöntemleri şunlardır:

Shrink Operasyonunun Askıda Kalması

Bir index’in shrink edilebilmesi için, o index’e ait tüm primary shard kopyalarının cluster içinde tek bir node üzerine toplanması gerekir. Eğer cluster’ınızda disk doluluğu nedeniyle allocation engelleri varsa, shrink işlemi AWAITING_REALLOCATION durumunda takılır.

Çözüm: Warm node’larınızda tek bir node’un index’in tamamını (örneğin 150 GB) tek başına barındırabilecek kadar boş disk alanına sahip olduğundan emin olun. Gerekirse geçici olarak disk limit sınırlarını (watermark) esnetin:

curl -X PUT "localhost:9200/_cluster/settings" -H 'Content-Type: application/json' -d'
{
  "transient": {
    "cluster.routing.allocation.disk.watermark.low": "90%",
    "cluster.routing.allocation.disk.watermark.high": "95%"
  }
}'

Force Merge Sırasında IOPS Tavan Yapması

Force merge işlemi, diski yoğun şekilde okuyup yazan (I/O intensive) ağır bir operasyondur. Aynı anda onlarca index warm faza geçip force merge olmaya başlarsa cluster yanıt veremez hale gelebilir.

Çözüm: ILM policy’nizdeki rollover tetikleyicilerini zamansal olarak yaymaya çalışın (örneğin tüm servislerin rollover’ını aynı gece saatinde tetiklemeyin, boyut bazlı rollover tercih edin) ve force merge limitlerini sınırlandırın.

Özet

Bu makalede kurguladığımız hot-warm-cold-delete yapısı sayesinde, aktif yazılan taze log verilerinizi yüksek performanslı disklerde tutarken, geriye dönük analizler için sakladığınız eski verileri minimum kaynak tüketecek şekilde optimize ettik. Shrink ile heap bellek ayak izini düşürdük, force-merge ile diskteki boşlukları temizledik ve soğuk katmanda replikaları azaltarak donanım maliyetlerimizi kalıcı olarak aşağı çektik. Bu kurgu, doğru uygulandığında Elasticsearch log altyapınızın sürdürülebilirliğini katbekat artıracaktır.

Category: Genel | LEAVE A COMMENT
Mart 7 2025

Mikroservis Labirentinde Kaybolmamak: OpenTelemetry ve Jaeger ile Dağıtık İzleme

Modern bir devops ekibinin kabusu, gecenin üçünde gelen bir PagerDuty alarmıyla başlar. Sisteminizdeki 10’larca mikroservis birbirine zincirlenmişken, ödeme adımındaki bir gecikmenin ya da hatanın kaynağını bulmak samanlıkta iğne aramaya benzer. Geleneksel log analiz araçları (ELK, Loki) bu kaotik ortamda yetersiz kalır; çünkü size korelasyonu değil, sadece bağımsız olayları sunarlar. Tam da bu yüzden, modern sistem mimarilerinde uçtan uca observability sağlamak ve hata tespitini dakikalara indirmek için opentelemetry ve jaeger ikilisi fiili endüstri standardı haline geldi. Bu rehberde, lafı hiç uzatmadan, production ortamında çalışan servisleriniz için dağıtık izleme (distributed tracing) altyapısını nasıl kuracağımızı ve karmaşık darboğazları nasıl analiz edeceğimizi göreceğiz.

Neden APM Ajanları Değil de OpenTelemetry?

Geçmişte New Relic, Datadog veya Dynatrace gibi kapalı kaynak APM (Application Performance Monitoring) ajanlarını uygulamaya gömmek kolay bir kaçış yoluydu. Ancak bu yaklaşım beraberinde ciddi bir “vendor lock-in” (tedarikçiye bağımlılık) ve kontrol edilemez maliyet artışları getiriyor. OpenTelemetry (OTel), CNCF çatısı altında geliştirilen satıcıdan bağımsız (vendor-agnostic) açık bir standarttır. Yarın öbür gün Jaeger yerine başka bir backend’e (örneğin Grafana Tempo) geçmek isterseniz, uygulama kodunuzda tek bir satır bile değiştirmeden sadece konfigürasyon seviyesinde bu değişikliği yapabilirsiniz.

Peki, arka planda bu iş nasıl dönüyor? İşin sırrı W3C Trace Context standardında saklıdır. Servisler arası HTTP veya gRPC çağrıları yapılırken, istek başlıklarına (headers) benzersiz bir traceparent eklenir:

traceparent: 00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01
# Format: sürüm-traceId-spanId-traceFlags

Bu başlık sayesinde, istek hangi servise giderse gitsin, o servisin ürettiği loglar ve span’ler aynı üst kimlik (Trace ID) altında birleşir.

Mimariyi Doğru Tasarlamak: Collector Neden Şart?

Uygulamalarınızdan trace verilerini doğrudan Jaeger’a göndermek ilk bakışta cazip görünebilir. Ancak bu yaklaşım production ortamında intihardır. Jaeger geçici olarak ulaşılamaz olduğunda uygulamanızın bellek (heap) tüketimi tavan yapabilir veya ağ trafiğiniz optimize edilmemiş paketlerle dolabilir.

Doğru yaklaşım, her Kubernetes node’unda bir DaemonSet olarak veya merkezi bir Gateway olarak OpenTelemetry Collector konumlandırmaktır. Uygulamalar trace verilerini localhost üzerindeki Collector’a gönderir (çok düşük gecikme ile), Collector ise veriyi tamponlar (buffer), sıkıştırır (batch) ve asenkron olarak Jaeger’a iletir.

Adım Adım Kurulum: Production-Ready OTel Collector Konfigürasyonu

Aşağıda, yük altında ezilmeyecek, memory limiter ve batching mekanizmaları aktif edilmiş örnek bir otel-collector-config.yaml dosyası yer alıyor. Bu konfigürasyonu Kubernetes ortamında ConfigMap olarak tanımlayabilirsiniz:

receivers:
  otlp:
    protocols:
      grpc:
        endpoint: 0.0.0.0:4317
      http:
        endpoint: 0.0.0.0:4318

processors:
  memory_limiter:
    check_interval: 1s
    limit_percentage: 75
    spike_limit_percentage: 15
  batch:
    send_batch_size: 8192
    timeout: 5s
    send_batch_max_size: 10240

exporters:
  otlp/jaeger:
    endpoint: "jaeger-collector.observability.svc.cluster.local:4317"
    tls:
      insecure: true

service:
  pipelines:
    traces:
      receivers: [otlp]
      processors: [memory_limiter, batch]
      exporters: [otlp/jaeger]

Neden bu işlemcileri (processors) kullandık?

  • memory_limiter: Collector’ın bellek tüketimi belirlenen limitin (%75) üzerine çıktığında, çökmeyi (OOMKilled) önlemek için yeni gelen verileri dropping moduna alır. Güvenli limanda kalmanızı sağlar.
  • batch: Her trace span’ini tek tek ağ üzerinden göndermek yerine, bunları gruplayarak gönderir. CPU ve network overhead’ini dramatik ölçüde düşürür.

Uygulama Seviyesinde Context Propagation (Go Örneği)

OTel SDK’sını uygulamanıza entegre ederken en kritik nokta, bağlamın (context) kaybolmamasını sağlamaktır. Eğer bir HTTP çağrısı yapıyorsanız, HTTP istemcinizi OTel transport katmanı ile sarmalamanız gerekir. Aşağıdaki Go kod bloğu, gelen isteğin trace bağlamını alıp bir sonraki servise nasıl güvenli bir şekilde aktaracağınızı gösterir:

package main

import (
	"context"
	"net/http"

	"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
	"go.opentelemetry.io/otel"
	"go.opentelemetry.io/otel/trace"
)

func handleCheckout(w http.ResponseWriter, req *http.Request) {
	// Gelen istekten trace context'i çıkar ve yeni bir span başlat
	ctx := req.Context()
	tracer := otel.Tracer("checkout-service")
	ctx, span := tracer.Start(ctx, "ProcessPayment", trace.WithSpanKind(trace.SpanKindServer))
	defer span.End()

	// Ödeme servisine yapılacak HTTP çağrısını sarmala
	client := http.Client{Transport: otelhttp.NewTransport(http.DefaultTransport)}
	
	nextReq, _ := http.NewRequestWithContext(ctx, "POST", "http://payment-service/charge", nil)
	resp, err := client.Do(nextReq)
	if err != nil {
		span.RecordError(err)
		span.SetStatus(500, "Payment initiation failed")
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	defer resp.Body.Close()

	w.Write([]byte("Checkout successful"))
}

Bu kodda otelhttp.NewTransport, arka planda W3C standartlarına uygun traceparent header’ını giden HTTP isteğine otomatik olarak enjekte eder. Manuel müdahaleye gerek kalmaz.

Production SRE Pratikleri: Sampling Rate (Örnekleme) Ayarı

Saniyede 10.000 istek alan bir sistemde her bir isteğin trace verisini saklamak hem Jaeger depolama alanınızı (Elasticsearch/Cassandra) saniyeler içinde doldurur hem de ciddi bir maliyet kalemi oluşturur. Çözüm: Head-based veya Tail-based sampling uygulamaktır.

Uygulama seviyesinde (Head-based) sadece başarılı olan isteklerin %1’ini, hatalı (error status) olanların ise %100’ünü saklamak mantıklıdır. Ancak uygulamanın bir isteğin hata vereceğini en baştan bilmesi imkansızdır. Bu yüzden Collector seviyesinde Tail-based sampling yapılandırmak en profesyonel çözümdür. Collector, trace tamamlanana kadar veriyi belleğinde tutar; eğer trace içinde bir hata kodu veya yüksek gecikme saptanırsa trace’in tamamını saklar, aksi takdirde belirlediğiniz oranda eler.

Bunun için Collector konfigürasyonunuza şu işlemciyi ekleyebilirsiniz:

processors:
  tail_sampling:
    decision_wait: 10s
    num_traces: 10000
    expected_new_traces_per_sec: 2000
    policies:
      - name: filter_errors
        type: status_code
        status_code: { status_codes: [ ERROR ] }
      - name: filter_latency
        type: latency
        latency: { threshold_ms: 500 } # 500ms üzerindeki tüm trace'leri sakla
      - name: probabilistic_sample
        type: probabilistic
        probabilistic: { sampling_percentage: 5.0 } # Normal trafikten %5 örnek al

Jaeger Arayüzünde Darboğaz Analizi: Nereden Başlamalı?

Her şey kuruldu ve Jaeger UI’a girdiniz. Önünüzde yüzlerce span içeren karmaşık bir trace ağacı duruyor. SRE bakış açısıyla analiz yaparken şu üç altın kuralı unutmayın:

  1. Gap Analizi (Boşluklar): İki ardışık span arasında büyük bir zaman boşluğu varsa, bu durum ağ gecikmesine, kuyrukta bekleyen (message queue) mesajlara veya uygulama içindeki CPU-bound kilitlemelere (mutex contention) işaret eder.
  2. Database Span’leri (N+1 Sorgu Problemi): Eğer tek bir HTTP isteğinin altında yüzlerce ardışık SQL sorgu span’i görüyorsanız, yazılımcılarınız ORM kütüphanesini yanlış kullanmış ve N+1 query tuzağına düşmüş demektir.
  3. Baggage vs Span Attributes Ayrımı: Trace akışı boyunca tüm downstream servislere taşınmasını istediğiniz kritik meta verileri (örneğin tenant_id veya user_tier) “Baggage” olarak ekleyin. Sadece o servise ait verileri (örneğin SQL query string) ise “Span Attribute” olarak tutun.

Sonuç: Kör Noktaları Yok Edin

OpenTelemetry ve Jaeger yatırımı, ilk kurulumda kod değişikliği ve konfigürasyon yükü getirse de, production’da yaşanacak ilk büyük krizde kendini amorti eder. Servislerinizin birbiriyle nasıl konuştuğunu tahmin etmek yerine, onları canlı olarak izleyin. Unutmayın; ölçemediğiniz sistemi yönetemezsiniz.

Category: Genel | LEAVE A COMMENT