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)
# 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)
# 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)
// 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#
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.
// 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#
{
"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#
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#
# 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...
# 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.
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.
# 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:
- Bestehende Ressourcen exportieren
- Äquivalentes Terraform schreiben
- Ressourcen importieren
- CloudFormation Stacks löschen
Realität:
# 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?)#
// 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#
// 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. Das Service-Template-Pattern#
Anstatt Code zu kopieren, erstelle Templates:
// 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#
# .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:
Tool | Lernkurve | Wartungskosten | Flexibilität | AWS Feature Lag |
---|---|---|---|---|
CDK | 2 Wochen | Mittel | Hoch | 0-2 Wochen |
Terraform | 1 Woche | Niedrig | Hoch | 2-4 Wochen |
SAM | 3 Tage | Niedrig | Niedrig | 0 Wochen |
CloudFormation | 1 Woche | Hoch | Mittel | 0 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.
Alle Beiträge in dieser Serie
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!
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!