kertenkerem_
· DevOps · 5 dk okuma

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ındaki pipeline sorunsuz tamamlanıyor. Sonra deployment paketini gerçek bir AWS ortamına gönderdiğiniz an CloudWatch logları kırmızıya boyanıyor; staging de olsa prod da. Cloud migration ve yerel geliştirme döngülerinde simülatör kullanmak maliyet açısından büyük bir avantaj, ama simülasyonla 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ş konularını 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üanslara, IAM’in sessiz tuzaklarına ve prodüksiyonda baş ağrıtan asenkron davranış farklarına bakıyoruz.

1. IAM illüzyonu: “Her şeye izin 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
          }
        }
      }
    ]
  })
}

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. Bu yüzden 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 ölçekte 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ı izolasyonu: Docker bridge vs. AWS VPC

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

Gerçek hayatta Lambda’yı bir VPC içine aldığınızda 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 indiren hibrit pipeline mimarisi

LocalStack’i tamamen çöpe atmak gerekmez. Hızlı geri bildirim (feedback loop) ve birim entegrasyon testleri için rakipsizdir. Yine de deployment stratejinizi yalnızca 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. Yalnızca “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 iyi bir geliştirme aracıdır ama nihai 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 öder.