AWS ECS ile Konteyner Yönetimi: Kapsamlı Bir Rehber

Konteyner teknolojisi hayatımıza girdiğinden beri “bunu production’da nasıl yöneteceğiz?” sorusu herkesin kafasını meşgul etti. Docker’ı local’de çalıştırmak kolay, ama onlarca servisi, yüzlerce konteyneri canlıda ayakta tutmak bambaşka bir iş. AWS ECS (Elastic Container Service) tam bu noktada devreye giriyor ve düşündüğünden daha güçlü bir platform sunuyor. Kubernetes kadar karmaşık değil, ama ciddi production workload’larını rahatlıkla kaldırıyor. Bu yazıda ECS’i sıfırdan anlayıp, gerçek dünya senaryolarıyla nasıl kullanacağınızı ele alacağız.

ECS Nedir ve Neden Kullanmalısınız?

ECS, AWS’nin tamamen yönetilen konteyner orkestrasyon servisidir. Kendi içinde iki farklı launch type sunuyor: EC2 ve Fargate. EC2 modunda altındaki sunucuları kendiniz yönetiyorsunuz, Fargate’te ise AWS her şeyi hallediyor, siz sadece konteyneri tanımlıyorsunuz.

Ekibinizde Kubernetes uzmanı yoksa, ECS çok daha hızlı sonuç almanızı sağlar. AWS ekosistemiyle (ALB, CloudWatch, IAM, Secrets Manager) doğal entegrasyonu var ve bu entegrasyonları kurmak için saatler harcamanıza gerek kalmıyor.

Temel Kavramlar

ECS’i anlamak için şu kavramları kafanıza oturtmanız gerekiyor:

  • Cluster: Konteynerlerinizin çalıştığı mantıksal grup. EC2 instance’larından ya da Fargate kapasitesinden oluşur.
  • Task Definition: Konteynerin nasıl çalışacağını tanımlayan blueprint. CPU, RAM, environment variable, port mapping gibi her şey burada.
  • Task: Task Definition’dan türetilen ve gerçekten çalışan konteyner örneği.
  • Service: Belirli sayıda task’ın her zaman ayakta durmasını garanti eden yapı. Birini öldürürsünüz, hemen yenisini başlatır.

İlk Kurulum: CLI ile Hızlı Başlangıç

AWS CLI’yi kurduktan ve credentials’ı ayarladıktan sonra işe başlayalım. Önce bir cluster oluşturalım:

# Fargate cluster oluşturma
aws ecs create-cluster 
  --cluster-name production-cluster 
  --capacity-providers FARGATE FARGATE_SPOT 
  --default-capacity-provider-strategy 
    capacityProvider=FARGATE,weight=1,base=1 
    capacityProvider=FARGATE_SPOT,weight=4 
  --region eu-west-1

# Cluster durumunu kontrol et
aws ecs describe-clusters 
  --clusters production-cluster 
  --region eu-west-1

Burada dikkat edin: FARGATE_SPOT kullanımı maliyetlerinizi ciddi oranda düşürür. Spot kapasitesi kesildiğinde Fargate’e fallback yapıyor, kritik olmayan workload’lar için harika bir seçenek.

Task Definition Oluşturma

Task Definition, her şeyin kalbi. Bunu JSON formatında tanımlıyorsunuz. Örnek bir Node.js API için:

# task-definition.json dosyası oluşturun
cat > task-definition.json << 'EOF'
{
  "family": "nodejs-api",
  "networkMode": "awsvpc",
  "requiresCompatibilities": ["FARGATE"],
  "cpu": "512",
  "memory": "1024",
  "executionRoleArn": "arn:aws:iam::ACCOUNT_ID:role/ecsTaskExecutionRole",
  "taskRoleArn": "arn:aws:iam::ACCOUNT_ID:role/ecsTaskRole",
  "containerDefinitions": [
    {
      "name": "nodejs-api",
      "image": "ACCOUNT_ID.dkr.ecr.eu-west-1.amazonaws.com/nodejs-api:latest",
      "portMappings": [
        {
          "containerPort": 3000,
          "protocol": "tcp"
        }
      ],
      "environment": [
        {
          "name": "NODE_ENV",
          "value": "production"
        }
      ],
      "secrets": [
        {
          "name": "DB_PASSWORD",
          "valueFrom": "arn:aws:secretsmanager:eu-west-1:ACCOUNT_ID:secret:prod/db-password"
        }
      ],
      "logConfiguration": {
        "logDriver": "awslogs",
        "options": {
          "awslogs-group": "/ecs/nodejs-api",
          "awslogs-region": "eu-west-1",
          "awslogs-stream-prefix": "ecs"
        }
      },
      "healthCheck": {
        "command": ["CMD-SHELL", "curl -f http://localhost:3000/health || exit 1"],
        "interval": 30,
        "timeout": 5,
        "retries": 3,
        "startPeriod": 60
      }
    }
  ]
}
EOF

# Task definition'ı kaydet
aws ecs register-task-definition 
  --cli-input-json file://task-definition.json 
  --region eu-west-1

Şifreyi environment variable olarak düz metin yazmak yerine Secrets Manager ile entegre etmek kritik önemde. Yukarıdaki örnekte secrets bloğu tam olarak bunu yapıyor. Bu şekilde container içinde environment variable olarak görünüyor ama hiçbir zaman düz metin olarak loglara ya da config dosyalarına düşmüyor.

Service Oluşturma ve Load Balancer Entegrasyonu

Cluster ve task definition hazır olduğunda service oluşturuyoruz. Ama önce Application Load Balancer’a ihtiyacımız var:

# Önce target group oluşturun (VPC ID'nizi girin)
aws elbv2 create-target-group 
  --name nodejs-api-tg 
  --protocol HTTP 
  --port 3000 
  --vpc-id vpc-XXXXXXXX 
  --target-type ip 
  --health-check-path /health 
  --health-check-interval-seconds 30 
  --healthy-threshold-count 2 
  --unhealthy-threshold-count 3 
  --region eu-west-1

# ECS Service oluşturma
aws ecs create-service 
  --cluster production-cluster 
  --service-name nodejs-api-service 
  --task-definition nodejs-api:1 
  --desired-count 2 
  --launch-type FARGATE 
  --network-configuration "awsvpcConfiguration={subnets=[subnet-XXXX,subnet-YYYY],securityGroups=[sg-XXXXXXXX],assignPublicIp=DISABLED}" 
  --load-balancers "targetGroupArn=arn:aws:elasticloadbalancing:eu-west-1:ACCOUNT_ID:targetgroup/nodejs-api-tg/XXXX,containerName=nodejs-api,containerPort=3000" 
  --deployment-configuration "minimumHealthyPercent=100,maximumPercent=200" 
  --region eu-west-1

Burada minimumHealthyPercent=100 önemli: deployment sırasında mevcut task’ları kapatmadan önce yenilerini ayağa kaldırıyor. Yani sıfır kesinti ile deployment yapıyorsunuz. maximumPercent=200 ise normalde 2 task çalışıyorsa deployment sırasında en fazla 4’e kadar çıkabileceğini söylüyor.

Auto Scaling Kurulumu

Production ortamında sabit sayıda task çalıştırmak israf. Trafik arttığında scale out, azaldığında scale in yapması gerekiyor:

# Auto scaling target kaydet
aws application-autoscaling register-scalable-target 
  --service-namespace ecs 
  --scalable-dimension ecs:service:DesiredCount 
  --resource-id service/production-cluster/nodejs-api-service 
  --min-capacity 2 
  --max-capacity 10 
  --region eu-west-1

# CPU bazlı scaling policy
aws application-autoscaling put-scaling-policy 
  --policy-name nodejs-api-cpu-scaling 
  --service-namespace ecs 
  --scalable-dimension ecs:service:DesiredCount 
  --resource-id service/production-cluster/nodejs-api-service 
  --policy-type TargetTrackingScaling 
  --target-tracking-scaling-policy-configuration '{
    "TargetValue": 70.0,
    "PredefinedMetricSpecification": {
      "PredefinedMetricType": "ECSServiceAverageCPUUtilization"
    },
    "ScaleInCooldown": 300,
    "ScaleOutCooldown": 60
  }' 
  --region eu-west-1

CPU kullanımı yüzde 70’i aştığında yeni task’lar ekleniyor. Scale out için 60 saniye bekleme süresi koydum çünkü trafik spike’larına hızlı tepki vermesi lazım. Scale in için 300 saniye koydum, gereksiz yere task’ları öldürüp tekrar başlatmak istemiyoruz.

ECR ile Container Image Yönetimi

ECS ile çalışırken image’larınızı genellikle ECR (Elastic Container Registry)‘de tutuyorsunuz. Hem güvenli hem de ECS ile entegrasyonu sorunsuz:

# ECR repository oluştur
aws ecr create-repository 
  --repository-name nodejs-api 
  --image-scanning-configuration scanOnPush=true 
  --encryption-configuration encryptionType=AES256 
  --region eu-west-1

# ECR'a login ol
aws ecr get-login-password --region eu-west-1 | 
  docker login --username AWS 
  --password-stdin ACCOUNT_ID.dkr.ecr.eu-west-1.amazonaws.com

# Image'ı build et ve push et
docker build -t nodejs-api .
docker tag nodejs-api:latest ACCOUNT_ID.dkr.ecr.eu-west-1.amazonaws.com/nodejs-api:latest
docker push ACCOUNT_ID.dkr.ecr.eu-west-1.amazonaws.com/nodejs-api:latest

# Image tarama sonuçlarını kontrol et
aws ecr describe-image-scan-findings 
  --repository-name nodejs-api 
  --image-id imageTag=latest 
  --region eu-west-1

scanOnPush=true ile her push’ta güvenlik açığı taraması yapılıyor. Production’a gönderdiğiniz image’larda kritik CVE’ler varsa önceden haberdar oluyorsunuz. Bu özelliği mutlaka aktif edin.

CI/CD Pipeline ile Otomatik Deployment

Gerçek dünyada her değişiklikten sonra elle deployment yapmıyorsunuz. Basit bir bash script ile GitHub Actions veya başka bir CI/CD sistemine entegre edebilirsiniz:

#!/bin/bash
# deploy.sh - ECS Service güncelleme scripti

set -e

CLUSTER_NAME="production-cluster"
SERVICE_NAME="nodejs-api-service"
TASK_FAMILY="nodejs-api"
ECR_REPO="ACCOUNT_ID.dkr.ecr.eu-west-1.amazonaws.com/nodejs-api"
REGION="eu-west-1"
IMAGE_TAG="${1:-latest}"

echo "==> ECR'a login olunuyor..."
aws ecr get-login-password --region $REGION | 
  docker login --username AWS --password-stdin $ECR_REPO

echo "==> Image build ediliyor: $IMAGE_TAG"
docker build -t $ECR_REPO:$IMAGE_TAG .
docker push $ECR_REPO:$IMAGE_TAG

echo "==> Mevcut task definition alınıyor..."
TASK_DEF=$(aws ecs describe-task-definition 
  --task-definition $TASK_FAMILY 
  --region $REGION 
  --query 'taskDefinition' 
  --output json)

echo "==> Yeni image ile task definition güncelleniyor..."
NEW_TASK_DEF=$(echo $TASK_DEF | jq 
  --arg IMAGE "$ECR_REPO:$IMAGE_TAG" 
  '.containerDefinitions[0].image = $IMAGE | 
   del(.taskDefinitionArn, .revision, .status, 
       .requiresAttributes, .compatibilities,
       .registeredAt, .registeredBy)')

NEW_TASK_ARN=$(aws ecs register-task-definition 
  --cli-input-json "$NEW_TASK_DEF" 
  --region $REGION 
  --query 'taskDefinition.taskDefinitionArn' 
  --output text)

echo "==> Service güncelleniyor: $NEW_TASK_ARN"
aws ecs update-service 
  --cluster $CLUSTER_NAME 
  --service $SERVICE_NAME 
  --task-definition $NEW_TASK_ARN 
  --region $REGION

echo "==> Deployment tamamlanması bekleniyor..."
aws ecs wait services-stable 
  --cluster $CLUSTER_NAME 
  --services $SERVICE_NAME 
  --region $REGION

echo "==> Deployment basarili!"

Bu script’i ./deploy.sh v1.2.3 şeklinde çalıştırıyorsunuz. aws ecs wait services-stable komutu deployment tamamlanana kadar bekliyor, böylece CI/CD pipeline’ınız deployment’ın başarılı olup olmadığını kesin olarak biliyor.

Monitoring ve Log Yönetimi

CloudWatch entegrasyonu ECS’in güçlü yanlarından biri. Log gruplarını düzgün kurmak ve metrikleri izlemek şart:

# Log grubu oluştur ve retention ayarla
aws logs create-log-group 
  --log-group-name /ecs/nodejs-api 
  --region eu-west-1

aws logs put-retention-policy 
  --log-group-name /ecs/nodejs-api 
  --retention-in-days 30 
  --region eu-west-1

# Çalışan task'ları listele
aws ecs list-tasks 
  --cluster production-cluster 
  --service-name nodejs-api-service 
  --region eu-west-1

# Task detaylarını görüntüle
aws ecs describe-tasks 
  --cluster production-cluster 
  --tasks TASK_ARN 
  --region eu-west-1

# Container loglarını çek (son 100 satır)
aws logs get-log-events 
  --log-group-name /ecs/nodejs-api 
  --log-stream-name ecs/nodejs-api/TASK_ID 
  --limit 100 
  --region eu-west-1

# CloudWatch alarm kur - task sayısı düşerse uyar
aws cloudwatch put-metric-alarm 
  --alarm-name "ecs-nodejs-api-low-task-count" 
  --alarm-description "ECS task sayisi minimumun altina dustu" 
  --metric-name RunningTaskCount 
  --namespace AWS/ECS 
  --statistic Average 
  --period 60 
  --threshold 1 
  --comparison-operator LessThanThreshold 
  --dimensions Name=ServiceName,Value=nodejs-api-service Name=ClusterName,Value=production-cluster 
  --evaluation-periods 2 
  --alarm-actions arn:aws:sns:eu-west-1:ACCOUNT_ID:production-alerts 
  --region eu-west-1

Çalışan task sayısı 1’in altına düştüğünde SNS üzerinden alarm alıyorsunuz. Production’da bu tip alarmlar hayat kurtarır.

Gerçek Dünya Senaryosu: Sıfır Kesintili Database Migration

Bir microservice’te database migration çalıştırmanız gerekiyor ama aynı anda API’nin çalışmaya devam etmesi lazım. ECS’te bunu one-off task ile yapıyorsunuz:

# Migration task'ı çalıştır (service olmadan, tek seferlik)
TASK_ARN=$(aws ecs run-task 
  --cluster production-cluster 
  --task-definition nodejs-api:latest 
  --launch-type FARGATE 
  --network-configuration "awsvpcConfiguration={subnets=[subnet-XXXX],securityGroups=[sg-XXXX],assignPublicIp=DISABLED}" 
  --overrides '{
    "containerOverrides": [
      {
        "name": "nodejs-api",
        "command": ["node", "scripts/migrate.js"],
        "environment": [
          {
            "name": "RUN_MIGRATIONS",
            "value": "true"
          }
        ]
      }
    ]
  }' 
  --count 1 
  --region eu-west-1 
  --query 'tasks[0].taskArn' 
  --output text)

echo "Migration task baslatildi: $TASK_ARN"

# Task tamamlanana kadar bekle
aws ecs wait tasks-stopped 
  --cluster production-cluster 
  --tasks $TASK_ARN 
  --region eu-west-1

# Exit kodunu kontrol et
EXIT_CODE=$(aws ecs describe-tasks 
  --cluster production-cluster 
  --tasks $TASK_ARN 
  --region eu-west-1 
  --query 'tasks[0].containers[0].exitCode' 
  --output text)

if [ "$EXIT_CODE" = "0" ]; then
  echo "Migration basarili!"
else
  echo "Migration basarisiz oldu! Exit code: $EXIT_CODE"
  exit 1
fi

Bu yaklaşım şunu sağlıyor: Migration task’ı ayrı bir konteyner olarak çalışıyor, API kesintisiz devam ediyor. Migration başarılı olursa yeni API versiyonunu deploy ediyorsunuz. Başarısız olursa pipeline duruyor ve rollback yapabiliyorsunuz.

IAM Rol Yapısı ve Güvenlik

ECS’te iki farklı IAM rolü var ve çok sık karıştırılıyor:

  • Task Execution Role: ECS agent’ının ECR’dan image çekmesi, CloudWatch’a log yazması, Secrets Manager’dan secret okuması için gerekli. ECS altyapısı kullanıyor.
  • Task Role: Konteynerin kendi içinde AWS servislerine erişmesi için. S3’e yazmak, SQS’e mesaj göndermek gibi işlemler için konteyner kodunuz kullanıyor.
# Task Role için S3 erişim policy ekle
aws iam put-role-policy 
  --role-name ecsTaskRole 
  --policy-name nodejs-api-s3-access 
  --policy-document '{
    "Version": "2012-10-17",
    "Statement": [
      {
        "Effect": "Allow",
        "Action": [
          "s3:GetObject",
          "s3:PutObject"
        ],
        "Resource": "arn:aws:s3:::my-production-bucket/*"
      },
      {
        "Effect": "Allow",
        "Action": [
          "sqs:SendMessage",
          "sqs:ReceiveMessage",
          "sqs:DeleteMessage"
        ],
        "Resource": "arn:aws:sqs:eu-west-1:ACCOUNT_ID:production-queue"
      }
    ]
  }'

Least privilege prensibi burada kritik. Her servis için ayrı task role oluşturun ve sadece ihtiyaç duyduğu izinleri verin. Hepsine aynı rolü vermek kolay ama güvenlik açığı oluşturuyor.

Maliyet Optimizasyonu İpuçları

ECS kullanırken maliyetleri kontrol altında tutmak için dikkat etmeniz gerekenler:

  • Fargate Spot Kullanın: Batch işlemler, background worker’lar gibi kesinti tolere edebilen workload’larda Fargate Spot normal fiyatın yüzde 70’e kadar ucuz.
  • CPU ve Memory’yi Doğru Boyutlandırın: Task definition’da fazla kaynak ayırtmak para yakar. CloudWatch Container Insights ile gerçek kullanımı izleyin.
  • ECR Lifecycle Policy Kurun: Eski image’lar depolama maliyeti yaratır. 30 günden eski image’ları otomatik sil.
  • Kullanılmayan Task Definition’ları Deregister Edin: Aktif olmayan eski revizyon’lar ECR’da yer kaplamaya devam eder.
  • Scale-to-Zero Düşünün: Geliştirme ortamları için gece otomatik olarak 0’a scale eden bir Lambda yazabilirsiniz.
# ECR lifecycle policy - 30 günden eski untagged image'ları sil
aws ecr put-lifecycle-policy 
  --repository-name nodejs-api 
  --lifecycle-policy-text '{
    "rules": [
      {
        "rulePriority": 1,
        "description": "30 gunluk untagged imagelari sil",
        "selection": {
          "tagStatus": "untagged",
          "countType": "sinceImagePushed",
          "countUnit": "days",
          "countNumber": 30
        },
        "action": {
          "type": "expire"
        }
      },
      {
        "rulePriority": 2,
        "description": "En fazla 10 tagged image tut",
        "selection": {
          "tagStatus": "tagged",
          "tagPrefixList": ["v"],
          "countType": "imageCountMoreThan",
          "countNumber": 10
        },
        "action": {
          "type": "expire"
        }
      }
    ]
  }' 
  --region eu-west-1

Troubleshooting: Sık Karşılaşılan Sorunlar

Birkaç yaygın sorun ve çözümleri:

  • Task stopped with exit code 1: Önce CloudWatch loglarına bakın. aws logs tail /ecs/nodejs-api --follow ile canlı log izleyebilirsiniz.
  • Task pending durumda kalıyor: Subnet’te yeterli IP adresi olmayabilir, ya da security group ECS endpoint’lerine erişim izni vermiyor olabilir.
  • Image pull hatası: Task Execution Role’un ECR okuma iznine sahip olduğunu kontrol edin. Private subnet kullanıyorsanız ECR için VPC endpoint gerekli.
  • Health check sürekli başarısız: startPeriod değerini artırın. Uygulama başlamak için zaman gerektirebilir.
  • Out of memory: Task definition’da memory limitini artırın ya da uygulamanızdaki memory sızıntısını araştırın.

Sonuç

ECS, doğru kurulduğunda gerçekten sağlam bir konteyner platformu. Kubernetes’e geçmeden önce ECS’in sizin için yeterli olup olmadığını değerlendirin; birçok senaryoda fazlasıyla yeterli oluyor ve operasyonel yükü çok daha düşük. Fargate ile sunucu yönetiminden tamamen kurtuluyorsunuz, Secrets Manager entegrasyonu güvenliği sağlıyor, CloudWatch ile izleme kutu dışında geliyor.

Başlangıç noktası olarak şunu öneririm: Önce tek bir servisi Fargate üzerinde ayağa kaldırın, auto scaling ekleyin, CI/CD pipeline’ınıza deployment script’ini entegre edin. Bu temeli oturdurduktan sonra diğer servisleri sistematik olarak taşımak çok daha kolay hale geliyor. Her şeyi bir anda taşımaya çalışmak yerine kademeli geçiş, hem riskleri azaltır hem de ekibin öğrenmesine zaman tanır.

Bir yanıt yazın

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir