AWS Fargate 104: Deployment mit CDK, Terraform und SAM

Wie du Fargate effektiv mit verschiedenen IaC-Tools deployst. Praktische Patterns, häufige Fallstricke und was für jeden Ansatz am besten funktioniert.

Nach drei Posts über Fargate (101, 102, 103), denkst du vielleicht "cool, aber wie deploye ich das Zeug tatsächlich, ohne durch die AWS Console zu klicken wie 2015?"

Gute Frage. Lass mich dir von der Zeit erzählen, als wir versuchten, 50+ Fargate-Services über die Console zu verwalten. Spoiler: Es endete nicht gut.

Die Evolution unserer Fargate IaC-Reise#

Jahr 1: CloudFormation (Das dunkle Zeitalter)

YAML
# 500 Zeilen YAML, die Entwickler zum Weinen brachten
Resources:
  TaskDefinition:
    Type: AWS::ECS::TaskDefinition
    Properties:
      Family: my-app
      NetworkMode: awsvpc
      RequiresCompatibilities:
        - FARGATE
      Cpu: '256'
      Memory: '512'
      # ... 50 weitere Zeilen Boilerplate

Jahr 2: Terraform (Die Renaissance)

hcl
# Wenigstens war es lesbar
resource "aws_ecs_task_definition" "app" {
  family                   = "my-app"
  network_mode            = "awsvpc"
  requires_compatibilities = ["FARGATE"]
  cpu                     = "256"
  memory                  = "512"
  # Immer noch verbose, aber manageable
}

Jahr 3: CDK (Die Aufklärung)

TypeScript
// Endlich, echte Programmierung
const taskDefinition = new ecs.FargateTaskDefinition(this, 'TaskDef', {
  memoryLimitMiB: 512,
  cpu: 256,
});

Lass mich teilen, was wir auf die harte Tour gelernt haben.

CDK: Wenn du deine Infrastruktur coden willst#

AWS CDK (Cloud Development Kit) lässt dich Infrastruktur in TypeScript, Python, Java oder C# schreiben. Nach zwei Jahren intensiver Nutzung, hier meine ehrliche Meinung.

Das Gute: Echte Programmierung#

TypeScript
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);

    // Dieses einzelne Construct erstellt:
    // - VPC, Subnets, NAT Gateways
    // - ECS Cluster
    // - Fargate Service
    // - Application Load Balancer
    // - Task Definition
    // - Security Groups
    // - 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 hinzufügen
    const scaling = fargateService.service.autoScaleTaskCount({
      maxCapacity: 10,
      minCapacity: 2,
    });

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

    // CloudWatch-Alarme hinzufügen
    new cloudwatch.Alarm(this, 'HighMemory', {
      metric: fargateService.service.metricMemoryUtilization(),
      threshold: 80,
      evaluationPeriods: 2,
    });
  }
}

Was 20 Zeilen CDK tatsächlich erstellen:

  • ~300 Zeilen CloudFormation
  • 15+ AWS-Ressourcen
  • Alle IAM-Rollen und Policies
  • Ordentliche Security Group-Regeln
  • CloudWatch Log Groups

Das Schlechte: Abstraktions-Lecks#

Erinnerst du dich, als ich sagte, Abstraktionen sind undicht? CDK ist ein Feuerwehrschlauch voller Lecks.

TypeScript
// Das sieht harmlos genug aus
const cluster = new ecs.Cluster(this, 'Cluster', {
  vpc: myVpc,
});

// Aber dann brauchst du einen Escape Hatch für etwas, das CDK nicht exposed
const cfnCluster = cluster.node.defaultChild as ecs.CfnCluster;
cfnCluster.capacityProviders = ['FARGATE_SPOT'];

// Und plötzlich schreibst du CloudFormation in TypeScript

Das Hässliche: Versions-Hölle#

JSON
{
  "dependencies": {
    "aws-cdk-lib": "2.100.0",  // Update das...
    "@aws-cdk/aws-ecs-patterns": "2.100.0",  // ...bricht das
    "constructs": "^10.0.0"  // ...was damit konfliktiert
  }
}

Wir haben einmal 3 Tage damit verbracht zu debuggen, warum unser CDK-Deploy plötzlich nicht mehr funktionierte. Stellt sich heraus, ein Minor-Version-Update änderte, wie VPC-Subnets ausgewählt wurden. Spaßige Zeiten.

Terraform: Der Industriestandard#

Terraform ist wie die Schweiz der IaC-Tools - neutral, zuverlässig, und jeder akzeptiert es.

Das Gute: Vorhersagbarkeit#

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

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

Das Modul-Pattern, das unseren Verstand rettete#

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

# ... 200 Zeilen wiederverwendbares Terraform ...

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

# In deiner Hauptkonfiguration
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
}

Die State-File-Horrorgeschichte#

Lass mich dir von der Zeit erzählen, als jemand terraform apply von seinem Laptop mit einer 3 Wochen alten State-File lief...

Bash
# Der Morgen begann unschuldig
$ 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 Ressourcen werden zerstört
  }

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

# Jemand las den Plan-Output nicht
$ terraform apply -auto-approve

# 5 Minuten später: Production ist down

Lektion gelernt: Verwende immer Remote State. Immer.

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

SAM: Der Lambda-First-Ansatz#

AWS SAM (Serverless Application Model) ist großartig für Lambda, aber für Fargate? Es ist wie einen Schraubenzieher zum Hämmern von Nägeln zu verwenden.

YAML
# 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'
      # Zurück zur CloudFormation-Verbosität
  
  # SAM glänzt, wenn du Lambda mit Fargate mischst
  ProcessorFunction:
    Type: AWS::Serverless::Function
    Properties:
      Handler: index.handler
      Runtime: python3.9
      Events:
        ECSTask:
          Type: CloudWatchEvent
          Properties:
            Pattern:
              source:
                - aws.ecs
              detail-type:
                - ECS Task State Change

Wann SAM für Fargate Sinn macht:

  • Du bist primär Lambda-basiert mit etwas Fargate
  • Du brauchst Step Functions Orchestrierung
  • Du bist bereits in SAM für andere Services investiert

Wann nicht:

  • Fargate ist dein primäres Compute
  • Du brauchst komplexes Networking
  • Du willst Programmiersprachen-Features

Die Migrations-Geschichten#

Geschichte 1: CloudFormation zu Terraform (6 Monate Schmerz)#

Wir hatten 200+ CloudFormation Stacks. Der Migrationsplan schien einfach:

  1. Bestehende Ressourcen exportieren
  2. Äquivalentes Terraform schreiben
  3. Ressourcen importieren
  4. CloudFormation Stacks löschen

Realität:

Bash
# Monat 1: Zuversicht
$ terraform import aws_ecs_service.app production-app-service
Import successful!

# Monat 2: Realität schlägt zu
$ terraform plan
~ 147 Ressourcen zu modifizieren
! 23 Ressourcen werden neu erstellt

# Monat 3: Der Drift
Error: Resource already exists

# Monat 4: Die Workaround-Skripte
$ python cloudformation_to_terraform.py --bete

# Monat 5: Akzeptanz
$ terraform apply -target=aws_ecs_service.app

# Monat 6: Sieg (irgendwie)
$ aws cloudformation delete-stack --stack-name old-stack-47-of-200

Geschichte 2: Terraform zu CDK (Das gelobte Land?)#

TypeScript
// Wir dachten CDK wäre besser
class LegacyInfraStack extends cdk.Stack {
  constructor(scope: Construct, id: string) {
    super(scope, id);
    
    // Bestehende Ressourcen importieren
    const cluster = ecs.Cluster.fromClusterArn(
      this,
      'ImportedCluster',
      'arn:aws:ecs:us-east-1:123456789:cluster/production'
    );
    
    // Das funktionierte für etwa 5 Ressourcen
    // Dann trafen wir auf das:
    
    // CDK unterstützt keinen Import von Task Definitions
    // CDK unterstützt keinen Import von Services mit mehreren Target Groups
    // CDK unterstützt keinen Import von Services mit Service Registries
    // CDK unterstützt nicht...
  }
}

Wir liefen ein Jahr lang sowohl Terraform als auch CDK. Nicht empfehlenswert.

Die Entscheidungsmatrix#

Nach all diesen Kämpfen, hier meine ehrliche Empfehlung:

Wähle CDK wenn:#

  • ✅ Dein Team kennt TypeScript/Python gut
  • ✅ Du startest frisch (kein Legacy)
  • ✅ Du willst High-Level-Abstraktionen
  • ✅ Du bist all-in auf AWS
  • ✅ Du lebst gerne am Edge

Wähle Terraform wenn:#

  • ✅ Du brauchst Multi-Cloud-Potenzial
  • ✅ Dein Team bevorzugt deklarative Syntax
  • ✅ Du hast bestehende Terraform-Module
  • ✅ Stabilität > Neueste Features
  • ✅ Du schätzt große Community-Unterstützung

Wähle SAM wenn:#

  • ✅ Du hast Lambda-first Architektur
  • ✅ Du brauchst Step Functions
  • ✅ Du willst minimales Tooling
  • ✅ Deine Fargate-Nutzung ist minimal

Verwende noch CloudFormation wenn:#

  • ✅ Du genießt Schmerzen (Scherz!)
  • ✅ Du brauchst AWS Support zum Debuggen
  • ✅ Du verwendest AWS Service Catalog
  • ✅ Unternehmensmandat (mein Beileid)

Die Patterns, die überall funktionieren#

Unabhängig vom Tool, diese Patterns haben uns gerettet:

1. Die Umgebungs-Abstraktion#

TypeScript
// 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 }
};
hcl
# 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. Das Service-Template-Pattern#

Anstatt Code zu kopieren, erstelle Templates:

TypeScript
// 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 Zeilen Boilerplate
    this.service = new ecs.FargateService(this, 'Service', {
      // Gemeinsame Konfiguration
    });
    
    // Standard-Alarme
    this.setupAlarms();
    
    // Standard-Dashboard
    this.setupDashboard();
  }
}

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

3. Die GitOps-Pipeline#

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

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

jobs:
  plan:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      
      - name: Terraform Plan
        run: |
          cd infrastructure
          terraform init
          terraform plan -out=tfplan
          
      - name: Post Plan to PR
        uses: actions/github-script@v6
        with:
          script: |
            // Plan-Output als PR-Kommentar posten
            
  apply:
    needs: plan
    if: github.ref == 'refs/heads/main'
    steps:
      - name: Terraform Apply
        run: |
          terraform apply tfplan

Die Kosten jedes Ansatzes#

Lass uns über Geld reden, denn Cloud-Rechnungen lügen nicht:

ToolLernkurveWartungskostenFlexibilitätAWS Feature Lag
CDK2 WochenMittelHoch0-2 Wochen
Terraform1 WocheNiedrigHoch2-4 Wochen
SAM3 TageNiedrigNiedrig0 Wochen
CloudFormation1 WocheHochMittel0 Wochen

Aber die echten Kosten? Developer-Glück.

Unsere Team-Geschwindigkeit:

  • Mit CloudFormation: 5 Story Points/Sprint
  • Mit Terraform: 8 Story Points/Sprint
  • Mit CDK: 12 Story Points/Sprint

Das Urteil#

Nach drei Jahren und vier verschiedenen IaC-Tools, hier ist was ich tatsächlich verwende:

  • Neue Projekte: CDK mit TypeScript
  • Bestehende Projekte: Was auch immer schon da ist (migriere nicht, es sei denn du musst)
  • Multi-Cloud-Potenzial: Terraform
  • Schnelle Prototypen: SAM
  • Nie wieder: Raw CloudFormation

Das schmutzige Geheimnis? Sie alle generieren sowieso CloudFormation. Wähle die Abstraktionsebene, die dein Team produktiv macht.

Denk dran: Das beste IaC-Tool ist das, das dein Team tatsächlich verwenden wird. Lass nicht zu, dass perfekt der Feind von deployed wird.

AWS Fargate Deep Dive Serie

Vollständiger Leitfaden zu AWS Fargate von den Grundlagen bis zur Produktion. Lerne serverlose Container, Kostenoptimierung, Debugging-Techniken und Infrastructure-as-Code-Deployment-Patterns durch reale Erfahrungen.

Fortschritt4/4 Beiträge abgeschlossen
Loading...

Kommentare (0)

An der Unterhaltung teilnehmen

Melde dich an, um deine Gedanken zu teilen und mit der Community zu interagieren

Noch keine Kommentare

Sei der erste, der deine Gedanken zu diesem Beitrag teilt!

Related Posts