AWS Fargate 102: Kimsenin Bahsetmediği Pattern'ler

Production workload'ları çalıştırırken öğrenilen gelişmiş Fargate pattern'leri. Maliyet optimizasyonundan stateful container'lara, dokümantasyonun size söylemeyecekleri.

Fargate 101'de temelleri ele aldık. Bu hafta, sadece bir süredir production workload'ları çalıştırdıktan sonra ortaya çıkan şeylerden bahsedelim. Bilirsiniz, bir olay sırasında sabah 2'de "aa, demek böyle çalışıyormuş" dedirten şeyler.

Maliyet Optimizasyonu: İflas Etmeme Sanatı#

Fargate'in EC2'den daha pahalı olduğunu söylemiştim ya? İşte acıyı azaltmanın yolları var. AWS faturamız bir ay beş haneli rakamlara ulaştıktan sonra (sormayın), optimizasyon konusunda ciddileştik.

Fargate Spot: Kimsenin Kullanmadığı %70 İndirim#

Fargate Spot normal Fargate gibi, ama AWS 2 dakikalık uyarıyla container'ınızı çekebiliyor. Kulağa korkutucu mu geliyor? Aslında çoğu workload için sorun değil.

İşte akıllıca nasıl kullanılır:

hcl
resource "aws_ecs_service" "batch_processor" {
  name            = "batch-processor"
  cluster         = aws_ecs_cluster.main.id
  task_definition = aws_ecs_task_definition.batch.arn
  
  capacity_provider_strategy {
    capacity_provider = "FARGATE_SPOT"
    weight            = 4
    base              = 0
  }
  
  capacity_provider_strategy {
    capacity_provider = "FARGATE"
    weight            = 1
    base              = 2  # Her zaman 2'sini normal Fargate'te tut
  }
  
  desired_count = 10
}

Bu konfigürasyon task'larınızın %80'ini Spot'ta çalıştırır (~%70 tasarruf) ve stabilite için bir baseline'ı normal Fargate'te tutar. Bunu şunlar için kullanıyoruz:

  • Batch işleme job'ları
  • Development ortamları
  • Kritik olmayan async worker'lar
  • CI/CD runner'ları

Pro ipucu: Spot kesintileri için CloudWatch alarm'ları kurun. Bir spike gördüğümüzde, trafiği geçici olarak normal Fargate'e kaydırıyoruz:

TypeScript
// Spot kesintilerini ele alan Lambda fonksiyonu
export const handleSpotInterruption = async (event: any) => {
  const ecs = new AWS.ECS();
  
  // Geçici olarak normal Fargate ağırlığını artır
  await ecs.putClusterCapacityProviders({
    cluster: 'production',
    capacityProviders: ['FARGATE', 'FARGATE_SPOT'],
    defaultCapacityProviderStrategy: [
      { capacityProvider: 'FARGATE', weight: 10, base: 5 },
      { capacityProvider: 'FARGATE_SPOT', weight: 1, base: 0 }
    ]
  }).promise();
  
  // 30 dakika sonra geri almak için timer ayarla
  await scheduleReversion();
};

Doğru Boyutlandırma: Goldilocks Problemi#

Çoğu kişi OOM kill'lerden korktuğu için Fargate task'larını fazla provision'lar. İşte doğru boyutu gerçekten nasıl buluyoruz:

  1. Büyük başla, ölç, sonra küçült

    Bash
    # Cömert kaynaklarla deploy et
    CPU: 1024
    Memory: 2048
    
    # Bir hafta sonra, gerçek kullanımı kontrol et
    aws cloudwatch get-metric-statistics \
      --namespace ECS/ContainerInsights \
      --metric-name MemoryUtilized \
      --dimensions Name=ServiceName,Value=my-service \
      --statistics Average,Maximum \
      --start-time 2024-01-01T00:00:00Z \
      --end-time 2024-01-08T00:00:00Z \
      --period 3600
    
  2. %80 kuralını kullan: %100 değil, peak kullanımın %80'i için boyutlandır. O %20 buffer spike'ları halleder.

  3. Farklı ortamlar için farklı boyutlar:

    hcl
    locals {
      task_sizes = {
        production = {
          cpu    = "512"
          memory = "1024"
        }
        staging = {
          cpu    = "256"
          memory = "512"
        }
        development = {
          cpu    = "256"
          memory = "512"
        }
      }
    }
    
    resource "aws_ecs_task_definition" "app" {
      cpu    = local.task_sizes[var.environment].cpu
      memory = local.task_sizes[var.environment].memory
      # ...
    }
    

ARM + Savings Plans: Çifte İndirim#

AWS Graviton (ARM) işlemciler %20 daha ucuz ve genelde daha hızlı. Savings Plans ile birleştirin, %20 daha indirim:

Dockerfile
# Multi-arch Dockerfile
FROM --platform=$BUILDPLATFORM node:18-alpine AS builder
ARG TARGETPLATFORM
ARG BUILDPLATFORM
RUN echo "Building on $BUILDPLATFORM for $TARGETPLATFORM"

# Build adımlarınız burada...

FROM node:18-alpine
COPY --from=builder /app /app
CMD ["node", "index.js"]

Her iki mimari için build edin:

Bash
docker buildx build \
  --platform linux/amd64,linux/arm64 \
  --tag myapp:latest \
  --push .

Sonra task definition'ınızda:

JSON
{
  "runtimePlatform": {
    "cpuArchitecture": "ARM64",
    "operatingSystemFamily": "LINUX"
  }
}

Node.js servislerimizi Graviton'a taşıyarak %35 tasarruf ettik. Çalışmayan tek şey? x86'ya özel JNI kütüphaneleri olan eski bir Java uygulaması. Geri kalan her şey sorunsuzdı.

Stateful Workload'lar: Evet, Yapabilirsiniz (Ama Yapmalı mısınız?)#

Herkes container'ların stateless olması gerektiğini söyler. Herkes çoğunlukla haklı. Ama bazen state'e ihtiyacınız olur ve EFS burada hem dostunuz hem düşmanınız.

EFS: İyi, Kötü ve Çirkin#

Loading diagram...

Fargate ile EFS kurulumu:

hcl
resource "aws_efs_file_system" "shared" {
  creation_token = "shared-storage"
  
  performance_mode = "generalPurpose"  # veya daha fazla operasyon için "maxIO"
  throughput_mode  = "bursting"        # veya tutarlı performans için "provisioned"
  
  lifecycle_policy {
    transition_to_ia = "AFTER_30_DAYS"  # Soğuk veride tasarruf
  }
}

resource "aws_ecs_task_definition" "app" {
  # ... diğer konfigürasyon ...
  
  volume {
    name = "shared-storage"
    
    efs_volume_configuration {
      file_system_id          = aws_efs_file_system.shared.id
      root_directory          = "/"
      transit_encryption      = "ENABLED"
      transit_encryption_port = 2999
      
      authorization_config {
        access_point_id = aws_efs_access_point.app.id
        iam             = "ENABLED"
      }
    }
  }
  
  container_definitions = jsonencode([{
    name = "app"
    # ...
    mountPoints = [{
      sourceVolume  = "shared-storage"
      containerPath = "/data"
    }]
  }])
}

Gerçeklik Kontrolü:

  • EFS gecikmesi: Operasyon başına 5-10ms (yerel SSD için 0.1ms)
  • Throughput: 100MB/s'ye burst, 10MB/s'de sürdürür (daha fazla ödemezseniz)
  • Maliyet: $0.30/GB/ay (EBS için $0.10)

EFS Ne Zaman Mantıklı:

  • Paylaşılan konfigürasyon dosyaları
  • Birden fazla container'ın ihtiyaç duyduğu kullanıcı yüklemeleri
  • Build cache'leri (dikkatli kilitlemeyle)
  • Kesinlikle paylaşımlı dosya sistemi gereken eski uygulamalar

Ne Zaman Değil:

  • Database depolama (sadece RDS kullanın)
  • Yüksek frekanslı yazma işlemleri
  • Geçici dosyalar (container'ın ephemeral storage'ını kullanın)
  • Cache katmanları (ElastiCache kullanın)

Session Affinity Pattern'i#

Bazen sticky session'lara ihtiyacınız olur. Fargate ile nasıl yapılır:

hcl
resource "aws_lb_target_group" "app" {
  name     = "app-tg"
  port     = 80
  protocol = "HTTP"
  vpc_id   = aws_vpc.main.id
  target_type = "ip"
  
  stickiness {
    type            = "app_cookie"
    cookie_duration = 86400
    cookie_name     = "FARGATE_SESSION"
  }
  
  health_check {
    enabled = true
    path    = "/health"
    matcher = "200"
  }
}

Ama şu var: sticky sessions + auto-scaling = üzüntü. Bir task öldüğünde, o session'lar gitti. Öğrendiklerimiz:

  1. Session verisini ElastiCache Redis'te sakla
  2. Sunucu session'ları yerine JWT token'ları kullan
  3. Deployment'lar sırasında bazı kullanıcıların çıkış yapacağını kabul et

Monitoring: Orada Aslında Ne Oluyor?#

Fargate bir kara kutu, bu da debugging'i... ilginç yapıyor. İşte gerçekten çalışan monitoring stack'imiz:

Fargate Gözlemlenebilirliğinin Üç Direği#

  1. CloudWatch Container Insights (Temeller)

    Bash
    aws ecs put-account-setting \
      --name containerInsights \
      --value enabled
    

    Bu size CPU, bellek, network ve disk metriklerini verir. Temeller için iyi ama application-level şeyleri kaçırır.

  2. Distributed Tracing için X-Ray (Bağlantılar)

    JavaScript
    // Node.js uygulamanıza ekleyin
    const AWSXRay = require('aws-xray-sdk-core');
    const AWS = AWSXRay.captureAWS(require('aws-sdk'));
    
    // Artık tüm AWS SDK çağrıları trace ediliyor
    const s3 = new AWS.S3();
    await s3.getObject({ Bucket: 'my-bucket', Key: 'file.txt' }).promise();
    
  3. StatsD ile Custom Metrikler (Detaylar)

    TypeScript
    // StatsD için sidecar container çalıştırın
    const taskDef = {
      containerDefinitions: [
        {
          name: "app",
          // uygulama config'iniz
        },
        {
          name: "datadog-agent",
          image: "datadog/agent:latest",
          environment: [
            { name: "DD_API_KEY", value: process.env.DD_API_KEY },
            { name: "ECS_FARGATE", value: "true" }
          ]
        }
      ]
    };
    

Saatleri Kurtaran Debug Pattern'i#

Fargate'e SSH yapamıyor musunuz? ECS Exec kullanın, ama faydalı hale getirin:

Bash
# Dockerfile'ınıza bunu ekleyin
RUN apk add --no-cache \
    curl \
    netstat \
    ps \
    htop \
    strace \
    tcpdump

# Task definition'da ECS Exec'i etkinleştirin
aws ecs update-service \
  --cluster production \
  --service my-service \
  --enable-execute-command

# Profesyonel gibi debug yapın
aws ecs execute-command \
  --cluster production \
  --task abc123 \
  --container app \
  --interactive \
  --command "/bin/sh"

# Container içinde:
> netstat -tulpn  # Neyin dinlediğini kontrol et
> ps aux          # Tüm process'leri gör
> strace -p 1     # System call'ları trace et
> tcpdump -i any  # Network trafiğini izle

Blue-Green Deployment'lar: Güvenli Yol#

Fargate + CodeDeploy = zero-downtime deployment'lar. Bizi birçok kötü deploy'dan kurtaran setup:

hcl
resource "aws_codedeploy_deployment_group" "app" {
  app_name               = aws_codedeploy_app.app.name
  deployment_group_name  = "production"
  deployment_config_name = "CodeDeployDefault.ECSLinear10PercentEvery1Minutes"
  
  auto_rollback_configuration {
    enabled = true
    events  = ["DEPLOYMENT_FAILURE", "DEPLOYMENT_STOP_ON_ALARM"]
  }
  
  blue_green_deployment_config {
    terminate_blue_instances_on_deployment_success {
      action                                          = "TERMINATE"
      termination_wait_time_in_minutes               = 5
    }
    
    deployment_ready_option {
      action_on_timeout = "CONTINUE_DEPLOYMENT"
    }
    
    green_fleet_provisioning_option {
      action = "COPY_AUTO_SCALING_GROUP"
    }
  }
  
  ecs_service {
    cluster_name = aws_ecs_cluster.main.name
    service_name = aws_ecs_service.app.name
  }
}

Killer özellik? CloudWatch alarm'larında otomatik rollback:

TypeScript
const errorRateAlarm = new cloudwatch.Alarm(this, 'ErrorRate', {
  metric: new cloudwatch.Metric({
    namespace: 'MyApp',
    metricName: 'Errors',
    statistic: 'Sum'
  }),
  threshold: 10,
  evaluationPeriods: 2
});

// Deployment group'a bağla
deploymentGroup.addAlarm(errorRateAlarm);

Multi-Region Fargate: Çünkü Felaketler Olur#

Fargate'i region'lar arası çalıştırmak zor değil, ama onları senkronize tutmak öyle. İşte bizim pattern'imiz:

Loading diagram...

Keşfettiğimiz tuzaklar:

  1. Region başına environment variable'lar: Endpoint'leri hardcode etmeyin
  2. S3 bucket isimleri global olarak benzersiz olmalı: Region suffix ekleyin
  3. Cross-region gecikme: US ve EU arasında ~100ms
  4. Failover anında değil: Route 53 health check'leri 30-60 saniye alır

Daha Önce Bilmeyi Dilediğimiz Pattern'ler#

Sidecar Pattern'i#

JSON
{
  "containerDefinitions": [
    {
      "name": "app",
      "dependsOn": [{
        "containerName": "envoy",
        "condition": "HEALTHY"
      }]
    },
    {
      "name": "envoy",
      "healthCheck": {
        "command": ["CMD-SHELL", "curl -f http://localhost:9901/ready || exit 1"]
      }
    }
  ]
}

Init Container Pattern'i (Bir Nevi)#

Fargate'in gerçek init container'ları yok, ama taklit edebilirsiniz:

Bash
# Entrypoint script'inizde
#!/bin/sh
echo "Initialization çalıştırılıyor..."
/app/init-db.sh
if [ $? -ne 0 ]; then
  echo "Init başarısız, çıkılıyor"
  exit 1
fi
echo "Ana uygulama başlatılıyor..."
exec node index.js

Circuit Breaker Pattern'i#

TypeScript
class FargateCircuitBreaker {
  private failures = 0;
  private lastFailTime = 0;
  private readonly threshold = 5;
  private readonly timeout = 60000; // 1 dakika
  
  async call<T>(fn: () => Promise<T>): Promise<T> {
    if (this.isOpen()) {
      throw new Error('Circuit breaker açık');
    }
    
    try {
      const result = await fn();
      this.onSuccess();
      return result;
    } catch (error) {
      this.onFailure();
      throw error;
    }
  }
  
  private isOpen(): boolean {
    return this.failures >= this.threshold && 
           Date.now() - this.lastFailTime < this.timeout;
  }
  
  private onSuccess(): void {
    this.failures = 0;
  }
  
  private onFailure(): void {
    this.failures++;
    this.lastFailTime = Date.now();
  }
}

Unutmayın: Fargate bir İsviçre çakısı gibi. İnanılmaz faydalı, ama dikkatli olmazsanız ara sıra kendinizi kesersiniz.

AWS Fargate Derinlemesine Serisi

AWS Fargate'e temellerden production'a tam rehber. Gerçek dünya deneyimleri ile serverless container'ları, maliyet optimizasyonu, debugging tekniklerini ve Infrastructure-as-Code deployment pattern'lerini öğrenin.

İlerleme2/4 yazı tamamlandı
Loading...

Yorumlar (0)

Sohbete katıl

Düşüncelerini paylaşmak ve toplulukla etkileşim kurmak için giriş yap

Henüz yorum yok

Bu yazı hakkında ilk düşüncelerini paylaşan sen ol!

Related Posts