İçeriğe atla

AWS Fargate Production Pattern'leri: Maliyet, İzleme, Blue-Green

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

Ayhan Sipahi Ayhan Sipahi

Fargate 101’de temelleri ele aldık. Aşağıdaki pattern’ler, o temelden sonra bozulan şeylerden geliyor: fatura, Fargate’in sizden sakladığı altyapı katmanı ve geri dönüş yolu olması gereken deployment’lar. Sırasıyla maliyet optimizasyonu, stateful container’lar, monitoring ve blue-green deployment.

Maliyet Optimizasyonu#

Fargate’in aynı compute için EC2’den daha pahalı olduğunu söylemiştik. Bu farkın büyük kısmını dört kaldıraç kapatır: Spot kapasite, right-sizing, ARM ve Savings Plans.

Kesintiye Dayanıklı İşler için Fargate Spot#

Fargate Spot %70’e varan tasarruf sağlar; karşılığında AWS container’larınızı 2 dakikalık uyarıyla sonlandırabilir. Kulağa riskli gelse de, doğru kurgulandığında birçok workload için sorunsuz çalışır.

Karma capacity provider stratejisi bu riski sınırlar:

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
}

Baseline’ın üzerindeki her beş task’tan dördü Spot’a düşer. desired_count = 10 ve base = 2 ile bu, kabaca 6 task Spot’ta ve 4 task normal Fargate’te demek; yani tüm Spot kapasitesi geri alınsa bile servis ayakta kalır. Bu pattern özellikle şunlar için uygundur:

  • Batch işleme job’ları
  • Development ve staging ortamları
  • Restart’ı kaldırabilen async worker’lar
  • CI/CD runner’ları

Kritik bir ders: Her zaman Spot kesintileri için CloudWatch alarm’ları kurun. Kesinti oranları arttığında, trafiği geçici olarak normal Fargate’e kaydırın:

// Spot kesintilerini ele alan Lambda fonksiyonu
import { ECSClient, PutClusterCapacityProvidersCommand } from "@aws-sdk/client-ecs";

export const handleSpotInterruption = async (event: any) => {
  const ecs = new ECSClient({ region: process.env.AWS_REGION });
  
  // Geçici olarak normal Fargate ağırlığını artır
  const command = new PutClusterCapacityProvidersCommand({
    cluster: 'production',
    capacityProviders: ['FARGATE', 'FARGATE_SPOT'],
    defaultCapacityProviderStrategy: [
      { capacityProvider: 'FARGATE', weight: 10, base: 5 },
      { capacityProvider: 'FARGATE_SPOT', weight: 1, base: 0 }
    ]
  });
  await ecs.send(command);
  
  // 30 dakika sonra geri almak için timer ayarla
  await scheduleReversion();
};

Doğru Boyutlandırma: Dengeyi Bulmak#

Birçok ekip OOM kill’lerden kaçınmak için Fargate task’larını fazla provision’lar. İşte doğru kaynak tahsisi için sistematik bir yaklaşım:

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

    # Önce 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:

    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 task başına %20 daha ucuz ve genelde daha hızlı. Savings Plans ile birleştirin, %20 daha indirim:

# 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:

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

Sonra task definition’ınızda:

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

Not: Fargate ARM task’larını Graviton2 üzerinde çalıştırır; bu da çoğu workload’ın fiyat/performans ihtiyacını karşılar.

Node.js servisleri için geçiş genelde bir rebuild ve task definition değişikliğinden ibaret; tasarruf da bu %20’lik fiyat farkını ve workload’ın kazandığı throughput’u takip eder. Uyumluluk sorunları x86’ya özel JNI kütüphaneleri kullanan eski Java uygulamalarında yoğunlaşır; modern imajların çoğu değişiklik gerektirmeden geçer.

Stateful Container’larla Çalışmak#

Genel kanı container’ların stateless olması gerektiği yönünde ve bu çoğunlukla doğru bir tavsiye. Yine de kalıcı state’e ihtiyaç duyduğunuz durumlar var; EFS bu noktada hem işinizi görür hem başınızı ağrıtır.

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

Paylaşımlı Depolama

Fargate Task'ları

Yavaş I/O

Yavaş I/O

Yavaş I/O

Task 1

Task 2

Task 3

EFS Mount Point

Fargate ile EFS kurulumu:

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"
    }]
  }])
}

Tipik performans karakteristikleri:

  • EFS gecikmesi: Operasyona göre 0.25-10ms (okuma yazmadan hızlıdır; yerel SSD ~0.1ms)
  • Throughput: 100MB/s’ye burst, general purpose modda sürdürülen ~10MB/s
  • Maliyet: Yaklaşık $0.30/GB/ay (EBS’te $0.10)
  • Not: Ocak 2024 itibarıyla Fargate, daha hızlı kalıcı depolama gerektiğinde EBS volume desteği de sunuyor

EFS nerede işe yarar:

  • Birden fazla container’ın erişmesi gereken paylaşılan konfigürasyon dosyaları
  • Container’lar arası erişilebilir olması gereken kullanıcı yüklemeleri
  • Build cache’leri (kilitleme kısmı zor olabilir)
  • Dosya sistemine sıkı bağımlılığı olan eski uygulamalar

Nerede alternatife yönelmeli:

  • Veritabanı depolama (RDS veya yönetilen veritabanları daha iyi çalışır)
  • 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:

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"
  }
}

Ancak sticky sessions ve auto-scaling iyi anlaşmaz. Task’lar beklenmedik şekilde sonlandığında, o session’lar kaybolur. Daha iyi bir yaklaşım:

  1. Session verisini ElastiCache Redis’te sakla
  2. Sunucu session’ları yerine JWT token’ları kullan
  3. Deployment sırasında session kaybını zarif karşılayacak şekilde tasarla

Fargate Workload’larını İzleme#

Fargate’in altyapıya görünürlüğü azaltması debugging’i zorlaştırır. İşte etkili bir monitoring yaklaşımı:

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

  1. CloudWatch Container Insights (Temeller)

    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)

    // Node.js uygulamanıza ekleyin
    const AWSXRay = require('aws-xray-sdk-core');
    const { S3Client, GetObjectCommand } = require('@aws-sdk/client-s3');
    
    // Client'ı bir kez sarın; gönderdiği her komut trace edilir
    const s3 = AWSXRay.captureAWSv3Client(new S3Client({}));
    await s3.send(new GetObjectCommand({ Bucket: 'my-bucket', Key: 'file.txt' }));
  3. StatsD ile Custom Metrikler (Detaylar)

    // 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" }
          ]
        }
      ]
    };

ECS Exec ile Debug#

SSH yapabileceğiniz bir host yok, dolayısıyla içeri girmenin tek yolu ECS Exec. İçeri girdiğinizde ihtiyaç duyacağınız araçları imaja önceden koymak işinizi kolaylaştırır:

# Dockerfile'ınıza bunu ekleyin (netstat net-tools'ta, ps procps'te gelir)
RUN apk add --no-cache \
    curl \
    net-tools \
    procps \
    htop \
    strace \
    tcpdump

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

# Çalışan bir task'ta shell aç
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 ve CodeDeploy birlikte, otomatik geri dönüş yolu olan zero-downtime deployment sağlar. İşi yapan deployment group şöyle:

resource "aws_codedeploy_deployment_group" "app" {
  app_name  = aws_codedeploy_app.app.name
  deployment_group_name  = "production"
  deployment_config_name = "CodeDeployDefault.ECSLinear10PercentEvery1Minutes"
  
  deployment_style {
    deployment_option = "WITH_TRAFFIC_CONTROL"
    deployment_type   = "BLUE_GREEN"
  }
  
  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"
    }
  }
  
  ecs_service {
    cluster_name = aws_ecs_cluster.main.name
    service_name = aws_ecs_service.app.name
  }
  
  load_balancer_info {
    target_group_pair_info {
      prod_traffic_route {
        listener_arns = [aws_lb_listener.prod.arn]
      }
      
      target_group { name = aws_lb_target_group.blue.name }
      target_group { name = aws_lb_target_group.green.name }
    }
  }
}

Kopyalanan örneklerin çoğunda green_fleet_provisioning_option geçer, ama o blok yalnızca EC2 ve on-premises deployment’lar için geçerli. ECS tarafında green task set’i CodeDeploy kendisi oluşturur; sizden beklediği şey target group çiftidir.

Asıl işe yarayan kısım, CloudWatch alarm’ında otomatik rollback:

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#

İkinci bir region’da Fargate ayağa kaldırmak kolay; asıl zor olan ikisini senkron tutmak. Emek de veri tutarlılığı, failover testi ve maliyete (her region ayrı faturalanır) gidiyor. Sağlam duran yapı şöyle:

Primary Region

eu-west-1

us-east-1

Route 53

Replikasyon

Replikasyon

Health-based Routing

ALB

Fargate Task'ları

RDS Read Replica

ALB

Fargate Task'ları

RDS Read Replica

RDS Primary

Dikkat edilmesi gereken tuzaklar:

  1. Region başına environment variable’lar: Endpoint’leri hardcode etmeyin; her region kendi RDS, S3 ve servis endpoint’lerine sahip olmalı.
  2. S3 bucket isimleri global olarak benzersiz olmalı: Region veya account suffix ekleyin.
  3. Cross-region gecikme: Region çiftine göre 80-150ms; transatlantik hatlar bu aralığın alt ucunda kalır.
  4. Failover anında değil: Route 53 health check’lerinin devreye girmesi 30-60 saniye alır. Bu süreyi disaster recovery planınıza yazın.

Production Fargate için Temel Pattern’ler#

Sidecar Pattern’i#

{
  "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:

# 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#

Harici servis çağrılarını circuit breaker ile sarın. Timeout ve retry ile birlikte, hata veren tek bir bağımlılığın task’ın geri kalanını da aşağı çekmesini engeller.

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();
  }
}

Bu pattern’ler, sürekli çalışan ve faturada görünen servislerde karmaşıklığını hak eder. Kısa ömürlü bir job’da ya da hiçbir zaman birkaç task’ı geçmeyen bir serviste, normal kapasitede cömert boyutlandırılmış düz Fargate’i işletmek yukarıdaki mekanizmaların hepsinden ucuza gelir. Kesintiye dayanıklı işlerde Spot ve her yerde right-sizing ile başlayın; EFS, blue-green veya ikinci bir region’ı ancak somut bir gereksinim zorladığında ekleyin.

Kaynaklar#

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.

İlerleme 2/4 yazı tamamlandı

İlgili yazılar