İçeriğe atla

AWS Fargate Deploy Etmek: CDK vs Terraform vs SAM

Fargate'i farklı IaC araçlarıyla nasıl etkin şekilde deploy edersiniz. Pratik pattern'ler, yaygın tuzaklar ve her yaklaşım için en iyi çalışan yöntemler.

Ayhan Sipahi Ayhan Sipahi

Fargate servislerini CDK, Terraform veya SAM ile deploy etmek çalışan bir altyapı üretir; ancak yanlış araç seçimi her yeni servisle katlanarak büyüyen bir bakım yükü yaratır. Yalnızca AWS üzerinde yaşayacak yeni bir servis için varsayılan CDK’dır: üst düzey construct’ları düzinelerce CloudFormation kaynağını birkaç satıra indirir. Terraform’a geçmek, aynı değişim yüzeyini birden fazla ekip paylaştığında ya da ikinci bir bulut ihtimali gerçekçiyse anlam kazanır. SAM ise ancak Fargate, Lambda’nın yanında küçük bir ayrıntı kaldığında uygundur.

Aradaki fark ilk deployment’ta pek görünmez. Bir yıl sonra görünür: bir değişiklik nasıl inceleniyor, drift nasıl fark ediliyor ve her yeni servis kaç satır koda mal oluyor.

Fargate için IaC Araç Karşılaştırması#

CloudFormation - Temel

# Verbose ama kapsamlı
Resources:
  TaskDefinition:
    Type: AWS::ECS::TaskDefinition
    Properties:
      Family: my-app
      NetworkMode: awsvpc
      RequiresCompatibilities:
        - FARGATE
      Cpu: '256'
      Memory: '512'
      # Detaylı konfigürasyon gerektirir

Terraform - Endüstri Standardı

# Deklaratif ve açık
resource "aws_ecs_task_definition" "app" {
  family  = "my-app"
  network_mode  = "awsvpc"
  requires_compatibilities = ["FARGATE"]
  cpu  = "256"
  memory  = "512"
  # Okunabilirlik ve kontrol arasında iyi denge
}

CDK - Programlama Yaklaşımı

// Programlama yapılarıyla high-level soyutlamalar
const taskDefinition = new ecs.FargateTaskDefinition(this, 'TaskDef', {
  memoryLimitMiB: 512,
  cpu: 256,
});

Üç snippet de aynı task definition’ı farklı soyutlama seviyelerinde tanımlıyor.

CDK ile Fargate Deploy Etmek#

AWS CDK, programatik kontrol ve üst düzey soyutlama istediğinizde Fargate deployment’larında öne çıkar.

Fargate için CDK Avantajları#

import * as cdk from 'aws-cdk-lib';
import * as ecs from 'aws-cdk-lib/aws-ecs';
import * as ecsPatterns from 'aws-cdk-lib/aws-ecs-patterns';

export class FargateStack extends cdk.Stack {
  constructor(scope: Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    // Bu tek construct şunları yaratıyor:
    // - VPC, Subnet'ler, NAT Gateway'ler
    // - ECS Cluster
    // - Fargate Service
    // - Application Load Balancer
    // - Task Definition
    // - Security Group'lar
    // - CloudWatch Logs
    const fargateService = new ecsPatterns.ApplicationLoadBalancedFargateService(this, 'Service', {
      taskImageOptions: {
        image: ecs.ContainerImage.fromRegistry('nginx'),
        containerPort: 80,
        environment: {
          NODE_ENV: 'production',
          API_URL: 'https://api.example.com'
        }
      },
      desiredCount: 3,
      domainName: 'app.example.com',
      domainZone: hostedZone,
      certificate: certificate,
    });

    // Auto-scaling ekle
    const scaling = fargateService.service.autoScaleTaskCount({
      maxCapacity: 10,
      minCapacity: 2,
    });

    scaling.scaleOnCpuUtilization('CpuScaling', {
      targetUtilizationPercent: 50,
    });

    // CloudWatch alarm'ları ekle
    new cloudwatch.Alarm(this, 'HighMemory', {
      metric: fargateService.service.metricMemoryUtilization(),
      threshold: 80,
      evaluationPeriods: 2,
    });
  }
}

Bu CDK construct’ının yarattıkları:

  • ~300 satır CloudFormation
  • 15+ AWS kaynağı
  • Tüm IAM role ve policy’ler
  • Düzgün security group kuralları
  • CloudWatch log group’ları

Fargate-Spesifik CDK Pattern’leri#

1. Ortam Varyasyonlarıyla Servis Template’leri

interface FargateServiceProps {
  serviceName: string;
  image: string;
  environment: 'dev' | 'staging' | 'prod';
  port?: number;
}

class FargateService extends Construct {
  constructor(scope: Construct, id: string, props: FargateServiceProps) {
    super(scope, id);

    // Ortam-spesifik boyutlandırma
    const configs = {
      dev: { cpu: 256, memory: 512, desiredCount: 1 },
      staging: { cpu: 512, memory: 1024, desiredCount: 2 },
      prod: { cpu: 1024, memory: 2048, desiredCount: 5 }
    };

    const config = configs[props.environment];

    const service = new ecsPatterns.ApplicationLoadBalancedFargateService(this, 'Service', {
      taskImageOptions: {
        image: ecs.ContainerImage.fromRegistry(props.image),
        containerPort: props.port || 80,
      },
      cpu: config.cpu,
      memoryLimitMiB: config.memory,
      desiredCount: config.desiredCount,
      // ALB, VPC, subnet'ler, security group'ları otomatik yapılandır
    });

    // Fargate-spesifik monitoring ekle
    this.addFargateMonitoring(service);
  }

  private addFargateMonitoring(service: ecsPatterns.ApplicationLoadBalancedFargateService) {
    // Memory utilization alarmı
    new cloudwatch.Alarm(this, 'MemoryAlarm', {
      metric: service.service.metricMemoryUtilization(),
      threshold: 80,
      evaluationPeriods: 2,
    });

    // Çalışan task sayısı alarmı (cluster'da Container Insights gerekir)
    new cloudwatch.Alarm(this, 'TaskCountAlarm', {
      metric: new cloudwatch.Metric({
        namespace: 'ECS/ContainerInsights',
        metricName: 'RunningTaskCount',
        dimensionsMap: {
          ClusterName: service.cluster.clusterName,
          ServiceName: service.service.serviceName,
        },
      }),
      threshold: 1,
      evaluationPeriods: 2,
      comparisonOperator: cloudwatch.ComparisonOperator.LESS_THAN_THRESHOLD,
    });
  }
}

2. CDK ile Fargate Spot İşleme

// Önce cluster üzerinde capacity provider'ları etkinleştirin:
// cluster.enableFargateCapacityProviders();
const service = new ecs.FargateService(this, 'Service', {
  cluster,
  taskDefinition,
  capacityProviderStrategies: [
    {
      capacityProvider: 'FARGATE_SPOT',
      weight: 4,
      base: 0,
    },
    {
      capacityProvider: 'FARGATE',
      weight: 1,
      base: 2, // Her zaman 2'sini normal Fargate'te tut
    }
  ],
});

Fargate için CDK Tuzakları#

Sorun: ENI Limitleri

// awsvpc modundaki her task bir network interface tutar ve
// bölge başına kota 5000'den başlar. AWS bunun için metrik
// yayınlamaz; zamanlanmış bir job ile kendi metriğinizi basın.
const eniUsageMetric = new cloudwatch.Metric({
  namespace: 'Custom/VPC',
  metricName: 'ENIsInUse',
});

new cloudwatch.Alarm(this, 'ENIUsage', {
  metric: eniUsageMetric,
  threshold: 4500, // Default 5000 limitinin %90'ı
});

Terraform ile Fargate Deploy Etmek#

Terraform mükemmel state yönetimiyle açık, öngörülebilir Fargate deploymentları sağlar. Fargate altyapınızı etkili şekilde nasıl yapılandıracağınız:

Terraform Fargate Temelleri#

resource "aws_ecs_cluster" "main" {
  name = "production"
  
  setting {
    name  = "containerInsights"
    value = "enabled"
  }
}

resource "aws_ecs_task_definition" "app" {
  family  = "my-app"
  network_mode  = "awsvpc"
  requires_compatibilities = ["FARGATE"]
  cpu  = "512"
  memory  = "1024"
  execution_role_arn  = aws_iam_role.ecs_task_execution_role.arn
  task_role_arn  = aws_iam_role.ecs_task_role.arn

  container_definitions = jsonencode([{
    name  = "app"
    image = "nginx:latest"
    
    portMappings = [{
      containerPort = 80
      protocol  = "tcp"
    }]
    
    logConfiguration = {
      logDriver = "awslogs"
      options = {
        awslogs-group  = aws_cloudwatch_log_group.app.name
        awslogs-region  = var.aws_region
        awslogs-stream-prefix = "ecs"
      }
    }
    
    environment = [
      {
        name  = "NODE_ENV"
        value = "production"
      }
    ]
  }])
}

resource "aws_ecs_service" "app" {
  name  = "my-app-service"
  cluster  = aws_ecs_cluster.main.id
  task_definition = aws_ecs_task_definition.app.arn
  desired_count  = var.app_count
  launch_type  = "FARGATE"
  enable_execute_command = true

  network_configuration {
    security_groups  = [aws_security_group.ecs_tasks.id]
    subnets  = aws_subnet.private[*].id
    assign_public_ip = false
  }

  load_balancer {
    target_group_arn = aws_alb_target_group.app.arn
    container_name  = "app"
    container_port  = 80
  }

  depends_on = [aws_alb_listener.front_end]
}

Yeniden Kullanılabilir Module Pattern’i#

# modules/fargate-service/main.tf
variable "service_name" {}
variable "image" {}
variable "cpu" { default = "256" }
variable "memory" { default = "512" }
variable "desired_count" { default = 2 }

# ... 200 satır tekrar kullanılabilir Terraform ...

output "service_url" {
  value = aws_alb.main.dns_name
}

# Ana konfigürasyonunuzda
module "api_service" {
  source  = "./modules/fargate-service"
  service_name  = "api"
  image  = "myapp/api:latest"
  cpu  = "512"
  memory  = "1024"
  desired_count = 3
}

module "worker_service" {
  source  = "./modules/fargate-service"
  service_name  = "worker"
  image  = "myapp/worker:latest"
  cpu  = "256"
  memory  = "512"
  desired_count = 5
}

Temel State Yönetimi#

Düzgün state yönetimi Terraform deploymentları için kritiktir. Eski state dosyaları istenmeyen kaynak yok etmeye yol açabilir.

# Plan output'unu her zaman dikkatli inceleyin
$ terraform plan
Terraform will perform the following actions:
  # aws_ecs_service.app will be destroyed
  - resource "aws_ecs_service" "app" {
      - name = "production-api" -> null
      # ... 50 kaynak yok edilecek
  }

Plan: 0 to add, 0 to change, 52 to destroy.

# Production'da asla auto-approve kullanmayın
$ terraform apply  # Manuel olarak inceleyin ve onaylayın

Gerekli: Ekip ortamları için her zaman remote state kullanın.

terraform {
  backend "s3" {
    bucket  = "terraform-state-prod"
    key  = "fargate/terraform.tfstate"
    region  = "us-east-1"
    dynamodb_table = "terraform-locks"
    encrypt  = true
  }
}

SAM: Lambda-First Yaklaşım#

AWS SAM (Serverless Application Model) Lambda için harika, ama Fargate için? Çivi çakmak için tornavida kullanmak gibi.

# template.yaml
Transform: AWS::Serverless-2016-10-31

Resources:
  FargateCluster:
    Type: AWS::ECS::Cluster
  
  TaskDefinition:
    Type: AWS::ECS::TaskDefinition
    Properties:
      RequiresCompatibilities:
        - FARGATE
      NetworkMode: awsvpc
      Cpu: '256'
      Memory: '512'
      # CloudFormation verbosity'sine geri dönüş
  
  # SAM Lambda'yı Fargate ile karıştırdığınızda parlıyor
  ProcessorFunction:
    Type: AWS::Serverless::Function
    Properties:
      Handler: index.handler
      Runtime: python3.12
      Events:
        ECSTask:
          Type: CloudWatchEvent
          Properties:
            Pattern:
              source:
                - aws.ecs
              detail-type:
                - ECS Task State Change

SAM Fargate için ne zaman mantıklı:

  • Ağırlıklı olarak Lambda tabanlısınız, yanında az miktarda Fargate var
  • Step Functions orchestration’a ihtiyacınız var
  • Diğer servisler için zaten SAM’e yatırım yapmışsınız

Ne zaman uygun değil:

  • Fargate birincil compute’unuz
  • Karmaşık networking’e ihtiyacınız var
  • Programlama dili özelliklerini istiyorsunuz

Migration Stratejileri#

CloudFormation’dan Terraform Migration’ı#

Mevcut altyapıyı migrate etmek dikkatli planlama gerektirir. Bu zorlukları göz önünde bulundurun:

Migration Süreci:

  1. Mevcut kaynakları export et
  2. Eşdeğer Terraform yaz
  3. Kaynakları dikkatli şekilde import et
  4. CloudFormation’ı kaldırmadan önce doğrula

Yaygın Sorunlar:

# Import ID'si servis adı değil, cluster-adı/servis-adı biçimindedir
$ terraform import aws_ecs_service.app production/production-app-service
Import successful!

# Import edilen state, config'inizde yazmayan tüm AWS varsayılanlarını taşır
$ terraform plan
Plan: 0 to add, 12 to change, 3 to destroy.

# State'in zaten izlediği bir adresi tekrar import etmek
$ terraform import aws_ecs_cluster.main production
Error: Resource already managed by Terraform

En İyi Uygulamalar:

  • Kritik olmayan kaynaklarla başla
  • Hedeflenen apply’lar kullan: terraform apply -target=resource
  • Geçiş sırasında paralel stack’ler çalıştır
  • Kaynak keşfi ve import için script’ler

Terraform’dan CDK Migration’ı#

CDK migration’ları import sınırlarıyla karşılaşır:

class MigrationStack extends cdk.Stack {
  constructor(scope: Construct, id: string) {
    super(scope, id);

    // Sınırlı import desteği
    const cluster = ecs.Cluster.fromClusterArn(
      this,
      'ImportedCluster',
      'arn:aws:ecs:us-east-1:123456789:cluster/production'
    );

    // CDK import sınırları:
    // - Task definition'lar yeniden yaratım gerektirir
    // - Karmaşık servis konfigürasyonları
    // - Service discovery entegrasyonu
  }
}

Migration Stratejisi: Karmaşık geçişler için her iki aracı da geçici olarak çalıştırmayı düşünün.

Karar Matrisi#

CDK Tercih Koşulları#

  • Ekibiniz TypeScript veya Python’u iyi biliyor
  • Sıfırdan başlıyorsunuz; import edilecek legacy stack yok
  • High-level abstraction’lar istiyorsunuz
  • AWS’ye all-in’siniz
  • Yeni AWS özelliklerini üçüncü parti provider’lar yetişmeden kullanmak istiyorsunuz

Terraform Tercih Koşulları#

  • Multi-cloud potansiyeline ihtiyacınız var
  • Ekibiniz declarative syntax tercih ediyor
  • Mevcut Terraform module’leriniz var
  • Stabilite en yeni özelliklerden daha önemli
  • Geniş provider ve module ekosistemine değer veriyorsunuz

SAM Tercih Koşulları#

  • Mimariniz Lambda-first
  • Step Functions’a ihtiyacınız var
  • Minimal tooling istiyorsunuz
  • Fargate kullanımınız marjinal

CloudFormation Tercih Koşulları#

  • Deployment yolunu AWS Support’un debug etmesine ihtiyacınız var
  • AWS Service Catalog üzerinden yayınlıyorsunuz
  • Kurumsal bir zorunluluk alternatifleri kapatıyor

Her Yerde İşe Yarayan Pattern’ler#

Üç pattern araçtan bağımsız olarak işe yarar:

1. Environment Abstraction’ı#

// CDK
interface EnvironmentConfig {
  cpu: number;
  memory: number;
  desiredCount: number;
  environment: Record<string, string>;
}

const configs: Record<string, EnvironmentConfig> = {
  dev: { cpu: 256, memory: 512, desiredCount: 1 },
  staging: { cpu: 512, memory: 1024, desiredCount: 2 },
  prod: { cpu: 1024, memory: 2048, desiredCount: 5 }
};
# Terraform
locals {
  env_config = {
    dev  = { cpu = 256, memory = 512, count = 1 }
    staging = { cpu = 512, memory = 1024, count = 2 }
    prod  = { cpu = 1024, memory = 2048, count = 5 }
  }
  
  config = local.env_config[var.environment]
}

2. Service Template Pattern’i#

Kod kopyalamak yerine, template’ler yaratın:

// CDK: Base service construct
export class BaseEcsService extends Construct {
  public readonly service: ecs.FargateService;
  
  constructor(scope: Construct, id: string, props: BaseEcsServiceProps) {
    super(scope, id);
    
    // 100 satır boilerplate
    this.service = new ecs.FargateService(this, 'Service', {
      // Ortak konfigürasyon
    });
    
    // Standart alarm'lar
    this.setupAlarms();
    
    // Standart dashboard
    this.setupDashboard();
  }
}

// Kullanım
new BaseEcsService(this, 'ApiService', {
  image: 'api:latest',
  port: 3000,
  cpu: 512
});

3. GitOps Pipeline#

# .github/workflows/deploy.yml
name: Deploy Infrastructure

on:
  push:
    branches: [main]
    paths:
      - 'infrastructure/**'

jobs:
  plan:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      
      - name: Terraform Plan
        run: |
          cd infrastructure
          terraform init
          terraform plan -out=tfplan
          
      - name: Post Plan to PR
        uses: actions/github-script@v7
        with:
          script: |
            // Plan output'u PR comment olarak post et

  apply:
    needs: plan
    runs-on: ubuntu-latest
    if: github.ref == 'refs/heads/main'
    steps:
      - name: Terraform Apply
        run: |
          terraform apply tfplan

Her Yaklaşımın Maliyeti#

AWS faturası araç seçimiyle değişmez; dördü de sonunda aynı ECS kaynaklarına dönüşür. Maliyet ekip zamanına biner.

ToolÖğrenme eğrisiBakım maliyetiEsneklikAWS özellik gecikmesi
CDKEn dikOrtaYüksekL1 construct’larda yok; L2’ler geriden gelir
TerraformOrtaDüşükYüksekProvider sürümlerine bağlı
SAMEn düşükDüşükDüşükYok
CloudFormationOrtaYüksekOrtaYok

Asıl maliyet günlük işteki sürtünmede:

  • CloudFormation: daha yavaş iterasyon, daha çok debug
  • Terraform: öngörülebilir ama uzun soluklu akışlar
  • CDK: ekip construct kütüphanesine alıştıktan sonra daha hızlı

Sonuç#

  • Yeni projeler: TypeScript ile CDK
  • Mevcut projeler: hâlihazırda ne varsa o; ancak mevcut araç somut bir şeyi engelliyorsa migrate edin
  • Multi-cloud potansiyeli: Terraform
  • Hızlı prototip: SAM
  • Raw CloudFormation: yalnızca kurumsal bir zorunluluk başka seçenek bırakmadığında

Servis AWS içinde kaldığı ve tanımların sahibi tek bir ekip olduğu sürece varsayılan CDK’dır. İki durumda varsayılan olmaktan çıkar: altyapı değişikliklerini o ekibin dışındaki kişiler okuyup onaylamak zorunda kaldığında ve yeni bir servisin bir öğleden sonrada katılabileceği bir module kütüphanesi zaten mevcut olduğunda. Her iki durumda da inceleme yüzeyi satır sayısından daha önemlidir.

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 4/4 yazı tamamlandı

İlgili yazılar