Ekim 19 2026

Hibrit Bulut Stratejileri: LocalStack’ten Üretim Ortamına Geçişin Riskleri

Hepimiz o sahneyi yaşadık: Docker daemon üzerinde koşan LocalStack konteyneri kusursuz çalışıyor, entegrasyon testleri saniyeler içinde yeşile dönüyor, CI/CD hattından geçen pipeline sorunsuz tamamlanıyor. Ancak deployment paketini gerçek bir AWS ortamına fırlattığınız an—ister staging olsun ister prod—CloudWatch logları kırmızıya boyanıyor. Cloud migration ve yerel geliştirme döngülerinde simülatör kullanmak harika bir maliyet avantajı sağlasa da, simülasyon ile gerçek dünya arasındaki uçurum bazen bir cuma akşamınızı mahvetmeye yeter.

Bu yazıda “LocalStack nedir, nasıl kurulur?” gibi giriş seviyesi konuları es geçiyoruz. 5 yıldan uzun süredir terminal başında sabahlayan bir mühendisin gözünden; LocalStack’in taklit edemediği alt seviye mimari nüansları, IAM’in sessiz tuzaklarını ve prodüksiyon seviyesinde baş ağrıtan asenkron davranış farklarını masaya yatırıyoruz.

1. IAM İllüzyonu: “Her Şeye İzin Ver” Rahatlığından Prod Çakılmasına

LocalStack Community sürümünün en büyük handikabı, varsayılan olarak IAM izinlerini denetlememesidir. S3’e dosya yazan bir Lambda fonksiyonu yazarsınız; fonksiyona hiçbir s3:PutObject izni tanımlamazsınız ama yerel ortamda tıkır tıkır çalışır. Pro sürümde ENFORCE_IAM=1 bayrağını açsanız bile, AWS IAM Evaluation Logic motorunun tüm karmaşıklığını Docker içinde replike etmek imkansıza yakındır.

Özellikle Resource-based Policies ile Identity-based Policies kesiştiğinde ve araya KMS şifrelemesi girdiğinde LocalStack havlu atar. Aşağıdaki tipik Terraform bloğunu ele alalım:

# LocalStack üzerinde çalışırken KMS yetkisi eksik olsa bile hata vermeyen 
# ancak AWS üzerinde anında 403 Access Denied fırlatan senaryo

resource "aws_s3_bucket_policy" "strict_policy" {
  bucket = aws_s3_bucket.data_lake.id

  policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Sid       = "EnforceKMSAuth"
        Effect    = "Deny"
        Principal = "*"
        Action    = "s3:PutObject"
        Resource  = "${aws_s3_bucket.data_lake.arn}/*"
        Condition = {
          StringNotEquals = {
            "s3:x-amz-server-side-encryption-aws-kms-key-id" = aws_kms_key.custom_key.arn
          }
        }
      }
    ]
  })
}

Neden böyle? AWS IAM motoru, bir isteği işlerken explicit deny, SCP (Service Control Policies), Permissions Boundary ve Session Policies gibi çok katmanlı bir evaluation tree üzerinden geçer. LocalStack ise Python tabanlı basitleştirilmiş bir kural kontrolü yapar. Sonuç: CI ortamında yeşil geçen test, canlı deployment sonrası API Gateway arkasındaki servisinizin KMS.AccessDeniedException hatasıyla çökmesine yol açar.

2. Dağıtık Sistemlerdeki Zamanlama ve Eventual Consistency Farkları

LocalStack bellekte (in-memory) veya hafif SQLite/DynamoDB-Local süreçleriyle çalışır. Bu mimari, AWS’in küresel bazda dağıtık veri merkezlerindeki gecikme ve tutarlılık modellerini maskeler.

DynamoDB: Tek Node Yanılgısı

LocalStack üzerinde koşan DynamoDB Local, tek bir Java prosesidir. Prodüksiyon ortamındaki partition splitting, hot partition, read/write capacity throttling (RCU/WCU) veya Burst Capacity tüketimini yerelde gözlemleyemezsiniz. Yerelde 5000 RPS ile geçen yük testiniz, AWS üzerinde rastgele ProvisionedThroughputExceededException fırlatabilir; çünkü veriniz gerçek partition’lara düzgün dağılmamıştır.

SQS ve SNS: Sıralama ve Görünürlük

LocalStack üzerinde FIFO kuyrukları simüle ederken MessageGroupId mantığı kusursuz gibi görünür. Ancak gerçek AWS SQS FIFO’da maksimum 300 msg/s (veya batching ile 3000) sınırı, message deduplication interval penceresi (5 dakika) ve visibility timeout yarış koşulları (race conditions) çok daha agresiftir.

# Yerelde anında mesaj çeken bu kod, AWS SQS üzerinde ReceiveMessageWaitTimeSeconds 
# ve VisibilityTimeout dinamikleri yüzünden mesaj kaybı/çift işleme yaratabilir.

aws --endpoint-url=http://localhost:4566 sqs receive-message \
    --queue-url http://localhost:4566/000000000000/orders.fifo \
    --max-number-of-messages 10 \
    --wait-time-seconds 0

LocalStack’te mesaj hemen oradadır. AWS’te ise kuyruk arka planda onlarca storage node’a dağılmıştır; kısa yoklama (short polling) yaptığınızda kuyrukta mesaj olmasına rağmen boş yanıt dönebilir. Testinizi WaitTimeSeconds=20 ile yazmadıysanız prod ortamında hayalet bug’lar peşinizi bırakmaz.

3. Ağ ve Altyapı İzolasyonu: Docker Bridge vs. AWS VPC

LocalStack konteynerleri varsayılan olarak Docker network’ü üzerinden konuşur. Bu durum, AWS’in karmaşık VPC mimarisini, Security Group kurallarını ve en önemlisi VPC Endpoint politikalarını tamamen görmezden gelmenize yol açar.

Gerçek hayatta Lambda’yı bir VPC içine aldığınızda ne olur? Hyperplane ENI (Elastic Network Interface) atanır. İlk başlatmada soğuk başlatma (cold start) süreleri uzar ve outbound internet trafiğiniz için bir NAT Gateway veya VPC Endpoint (PrivateLink) tanımlamanız gerekir. LocalStack içinde çalışan bir Lambda ise Docker host’un internetini doğrudan kullanır.

# Docker Compose ile LocalStack ve bağımlı servisler çalıştırılırken 
# ağ gecikmesi ve DNS kısıtlamaları simüle edilemez

services:
  localstack:
    image: localstack/localstack:latest
    ports:
      - "4566:4566"
    environment:
      - SERVICES=s3,sqs,sns,lambda,dynamodb
      - DOCKER_HOST=unix:///var/run/docker.sock
    volumes:
      - "/var/run/docker.sock:/var/run/docker.sock"

Eğer Lambda fonksiyonunuz bir S3 bucket’ına VPC Endpoint olmadan erişmeye çalışıyorsa ve subnet’inizde NAT Gateway rotası yoksa, prod ortamında timeout alırsınız. LocalStack testlerinizde bu senaryonun patlama ihtimali sıfırdır; çünkü Docker konteyneri her yere doğrudan routing yapar.

4. Terraform Konfigürasyonunda “Provider Kirliliği” (Provider Drift)

CI/CD pipeline’larında LocalStack kullanırken altyapı kodunu (IaC) simülatöre yönlendirmek için genellikle Terraform provider override teknikleri kullanılır:

# main_override.tf örneği
provider "aws" {
  access_key                  = "mock_key"
  secret_key                  = "mock_secret"
  region                      = "eu-central-1"
  s3_use_path_style           = true
  skip_credentials_validation = true
  skip_metadata_api_check     = true
  skip_requesting_account_id  = true

  endpoints {
    s3       = "http://localhost:4566"
    dynamodb = "http://localhost:4566"
    sqs      = "http://localhost:4566"
  }
}

Bu yaklaşımın iki büyük riski vardır:

  • s3_use_path_style: AWS, S3 için path-style (s3.amazonaws.com/bucket) erişimini yıllar önce deprecated ilan etti ve virtual-hosted-style (bucket.s3.amazonaws.com) modeline zorluyor. LocalStack’te path-style kullanıp production konfigürasyonunda bunu unutursanız, DNS resolution ve SSL wildcard sertifika hatalarıyla karşılaşırsınız.
  • Feature Parity Eksikliği: Terraform AWS Provider’ın her yeni sürümünde eklenen yeni argümanlar (örneğin gelişmiş S3 Lifecycle kuralları veya EventBridge Pipe filtreleri), LocalStack tarafından hemen desteklenmeyebilir. Kod yerelde InvalidParameter verirken AWS’te geçerli olabilir veya tam tersi yaşanabilir.

5. LocalStack Risklerini Sıfıra İndiren Hibrit Pipeline Mimarisi

LocalStack’i tamamen çöpe mi atalım? Kesinlikle hayır. Hızlı geri bildirim (feedback loop) ve birim entegrasyon testleri için rakipsizdir. Ancak deployment stratejinizi sadece LocalStack onayına bağlayamazsınız.

Önerilen Doğrulama Piramidi

  1. Pre-commit / Local Dev: Mock kütüphaneleri (moto vb.) veya LocalStack Community. Sadece “happy-path” iş mantığı doğrulaması.
  2. CI Pipeline (PR Aşaması): Testcontainers ile izole LocalStack üzerinde API sözleşme testleri (Contract Testing).
  3. CD Pipeline (Merge Sonrası – Staging/Ephemeral): Gerçek bir AWS Sandbox hesabında, kısa ömürlü (TTL: 2 saat) dinamik ortamlar. Terraform ile gerçek kaynakların ayağa kaldırılması ve end-to-end (E2E) testlerin koşulması.
#!/usr/bin/env bash
# CI/CD aşamasında Ephemeral Environment yaşam döngüsü

ENVIRONMENT_ID="ci-pr-${PR_NUMBER}-${GITHUB_SHA:0:7}"

echo "Deploying ephemeral stack to real AWS: $ENVIRONMENT_ID"
terraform init
terraform workspace new "$ENVIRONMENT_ID"
terraform apply -auto-approve -var="env=$ENVIRONMENT_ID"

# Gerçek AWS API üzerinde testleri koştur
pytest tests/e2e --aws-env="$ENVIRONMENT_ID"
TEST_EXIT_CODE=$?

# Temizlik adımı (Cost Optimization)
echo "Tearing down ephemeral stack..."
terraform destroy -auto-approve -var="env=$ENVIRONMENT_ID"
terraform workspace select default
terraform workspace delete "$ENVIRONMENT_ID"

exit $TEST_EXIT_CODE

LocalStack harika bir geliştirme aracıdır; ancak hiçbir zaman nihai bir doğrulama katmanı değildir. Gerçek bir cloud migration sürecinde ve kesintisiz CI/CD hatlarında parite farklarını hesaba katmayan ekipler, simülasyonun konforunda boğulup faturayı canlı ortam kesintileriyle öderler.

Category: Genel | LEAVE A COMMENT
Ağustos 22 2026

FinOps 2.0: AWS/Cloud Faturasını Otomatik Kısıtlayan Event-Driven Mimariler

Her ayın sonunda gelen o meşhur kabarık faturayı görüp Slack kanallarında “Bu p3.8xlarge instance’ı kim açık unuttu?” cadı avı başlatmaktan yorulduysanız, yalnız değilsiniz. Geleneksel bütçe uyarıları reaktiftir; fatura eşiği aşıldığında iş işten çoktan geçmiş olur. Modern FinOps yaklaşımı, cloud maliyet yönetimini sadece raporlama tablosu olmaktan çıkarıp, AWS altyapısında gerçek zamanlı çalışan bir otomasyon zincirine dönüştürmeyi gerektiriyor.

Bu yazıda; gecikmeli Cost and Usage Report (CUR) analizlerini bir kenara bırakıp, EventBridge, CloudWatch Metric Streams ve Lambda tabanlı event-driven bir maliyet kısıtlama (circuit breaker) mimarisini nasıl kuracağımızı adım adım ele alıyoruz.

Geleneksel Bütçe Uyarıları Neden İşe Yaramaz?

AWS Budgets veya CloudWatch Billing Alarms kurduğunuzda, arka planda çalışan telemetri verisi gerçek zamanlı değildir. CloudWatch billing metrikleri genellikle 8 ila 14 saatlik bir gecikmeyle (delay) güncellenir. Bir developer cuma akşamı yanlışlıkla 10 nodeluk bir GPU kümesini ayağa kaldırıp unuttuysa, pazartesi sabahı gelen e-posta sadece felaketin faturasını tebliğ eder.

Bize gereken şey: Anomaly Detection + Immediate Event Routing + Automated Remediation. Yani faturanın toplamını değil, kaynak oluşturma hızını (velocity), beklenmeyen metrik sıçramalarını ve yetim (orphaned) kaynakları anlık yakalayan bir mimari.

Event-Driven FinOps Mimarisi Nasıl Çalışır?

Kurgulayacağımız mimari üç ana ayaktan oluşuyor:

  1. Sinyal Katmanı: AWS Cost Anomaly Detection, AWS Health Events, CloudTrail Management Events ve Config Rule ihlalleri.
  2. Yönlendirme Katmanı (Routing): Amazon EventBridge default bus veya custom bus. Gelen payload’u filtreleyip uygun remediation hedefine yönlendirir.
  3. Aksiyon Katmanı (Execution): AWS Lambda veya Step Functions. Kaynağı durdurur, scale-in yapar, volume snapshot’ı alıp diski siler ya da doğrudan security group seviyesinde trafiği keser.

Adım 1: Cost Anomaly Detection için EventBridge Kuralı

AWS Cost Anomaly Detection, makine öğrenmesi modelleriyle harcama paternlerinizi analiz eder. Bir anomali tespit edildiğinde doğrudan EventBridge’e event fırlatabilir. Aşağıdaki CloudFormation / EventBridge Pattern tanımı, belirlenen eşik üzerindeki anomalileri dinler:

{
  "source": ["aws.cost-anomaly-detection"],
  "detail-type": ["Cost Anomaly Detected"],
  "detail": {
    "anomalyScore": [{ "numeric": [">=", 80] }],
    "impact": {
      "totalImpactPercentage": [{ "numeric": [">=", 50] }]
    }
  }
}

Bu pattern; anomali skoru 80’in üzerinde olan ve harcama etkisinin beklenen baseline’a göre en az %50 saptığı senaryoları yakalar. Anlamsız mikro dalgalanmalar için Lambda tetiklenmez.

Adım 2: Dev/Staging Ortamlarında Kaçak Kaynakları Kesen Lambda

Birçok şirkette faturayı patlatan şey prod ortamı değil, dev/stage hesaplarında “test edip sileceğim” denilerek unutulan yüksek profilli kaynaklardır. Aşağıdaki Python tabanlı Lambda fonksiyonu, tag eksikliği olan veya anomali üreten instance’ları hedef alarak devreye girer. Dry-run kontrolü ve güvenli tag filtresi içerir:

import boto3
import os
import json
import logging

logger = logging.getLogger()
logger.setLevel(logging.INFO)

ec2 = boto3.client('ec2')
TAG_IMMUNITY_KEY = "FinOpsImmunity"
ALLOWED_ENVIRONMENTS = ["development", "staging", "sandbox"]

def lambda_handler(event, context):
    logger.info(f"Received event: {json.dumps(event)}")
    
    # Hedef filtreleri: Sadece dev/stage hesaplar ve belirli instance tipleri
    filters = [
        {'Name': 'instance-state-name', 'Values': ['running']},
        {'Name': 'tag:Environment', 'Values': ALLOWED_ENVIRONMENTS}
    ]
    
    response = ec2.describe_instances(Filters=filters)
    instances_to_stop = []

    for reservation in response['Reservations']:
        for instance in reservation['Instances']:
            instance_id = instance['InstanceId']
            instance_type = instance['InstanceType']
            tags = {t['Key']: t['Value'] for t in instance.get('Tags', [])}
            
            # Bağışıklık tag'i var mı?
            if tags.get(TAG_IMMUNITY_KEY, "false").lower() == "true":
                logger.info(f"Instance {instance_id} has immunity tag. Skipping.")
                continue
                
            # Büyük/Pahalı aileleri hedef al (GPU, High Memory vb.)
            if instance_type.startswith(('p3', 'p4', 'g4', 'g5', 'r5b', 'x2gd')):
                logger.warning(f"High cost instance detected without immunity: {instance_id} ({instance_type})")
                instances_to_stop.append(instance_id)

    if instances_to_stop:
        logger.info(f"Stopping instances: {instances_to_stop}")
        ec2.stop_instances(InstanceIds=instances_to_stop)
        return {"status": "SUCCESS", "stopped_instances": instances_to_stop}

    return {"status": "NO_ACTION", "stopped_instances": []}

Burada kritik olan felsefe şudur: Fail-safe by default. Prod hesabına dokunmamak için ortam etiketlerini katı şekilde izole ediyoruz ve geliştiricilere acil durumlarda kullanmaları için FinOpsImmunity=true gibi bir bypass tag mekanizması tanıyoruz.

Adım 3: Yetim (Orphaned) EBS ve Unassociated EIP Temizliği

Terraform ile altyapıyı yıkan ama lifecycle { prevent_destroy = true } veya unattached volume’leri unutan mühendislerin faturaya hediyesidir yetim kaynaklar. CloudWatch Event yerine her gece EventBridge Schedule (Cron) ile tetiklenen hafif bir garbage collector kuralı tanımlayalım:

# AWS CLI ile Unattached (Available) EBS Volume'leri sorgulama
aws ec2 describe-volumes \
    --filters Name=status,Values=available \
    --query "Volumes[*].{ID:VolumeId,Size:Size,Created:CreateTime}" \
    --output table

Bunu otomatize ederken dikkat: Silmeden önce mutlaka snapshot alın. Event-driven mimarilerde en büyük risk, veri kaybına yol açabilecek agresif otomasyonlardır. Lambda’nın adımları şöyle olmalıdır:

  1. Volume state available mı?
  2. Tag’lerinde SnapshotBeforeDelete=true oluştur.
  3. Snapshot tetikle, snapshot tamamlandığında EventBridge’den gelen EC2 Snapshot Successful eventiyle eski volume’ü drop et.

Kubernetes / Karpenter için Runaway Cost Breaker

Eğer EKS üzerinde Karpenter veya Cluster Autoscaler kullanıyorsanız, container’ların crashloop’a girip sürekli yeni node istemesi sonucu dakikalar içinde onlarca nodeluk bir cluster anomalisi yaşayabilirsiniz.

Karpenter’ın NodePool limitleri bu işin ilk savunma hattıdır ama tek başına yetmez. Event-driven bir Lambda ile Karpenter NodePool CRD’sinin limitlerini dinamik olarak sıkılaştırabilirsiniz:

apiVersion: karpenter.sh/v1beta1
kind: NodePool
metadata:
  name: default
spec:
  limits:
    cpu: "100"
    memory: 400Gi
  template:
    spec:
      requirements:
        - key: "karpenter.sh/capacity-type"
          operator: In
          values: ["spot"]
        - key: "karpenter.k8s.aws/instance-category"
          operator: In
          values: ["c", "m", "r"]

Limit aşıldığında cluster’a yeni node eklenmesi engellenir. Bu aşamada Prometheus Alertmanager’dan EventBridge Webhook’una düşen bir alarm ile nöbetçi SRE’ye Slack üzerinden interactive button ile onay düşürmek en sağlıklı yaklaşımdır.

Least Privilege IAM İzni

FinOps otomasyon Lambda’nızın AdministratorAccess almasına gerek yoktur. Sadece durdurma ve tanımlama yetkilerini içeren sıkı bir IAM policy tanımlayın:

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "FinOpsEC2Operations",
            "Effect": "Allow",
            "Action": [
                "ec2:DescribeInstances",
                "ec2:DescribeVolumes",
                "ec2:StopInstances",
                "ec2:CreateSnapshot",
                "ec2:DeleteVolume"
            ],
            "Resource": "*"
        }
    ]
}

Özet: FinOps Kültür Değil, Kod Tabanıdır

Mühendisleri spreadsheet tablolarına bakarak eğitmek bir yere kadar çalışır. İnsan hata yapar, acil durumlarda makineler açık unutulur. FinOps 2.0; kuralları şirket wikisine yazmak yerine EventBridge kurallarına, Lambda fonksiyonlarına ve Kubernetes admission controller’larına yazmaktır.

Reaktif fatura şoklarından proaktif event-driven kısıtlamalara geçtiğinizde, hem CFO’nuz daha rahat uyur hem de nöbetçi SRE hafta sonu faturayı değil asıl işi olan sistem güvenilirliğini düşünür.

Category: Genel | LEAVE A COMMENT