← Semua pembelajaran / Astro Nol → Portal Berita
Fase 10 · Deploy di AWS

ECS Fargate — task definition & service

Fargate menghapus urusan server, tapi menggantinya dengan satu berkas JSON yang setiap fieldnya punya konsekuensi. Ini yang penting dari sekitar lima puluh field yang tersedia.

Intisari

  • Kombinasi CPU/memori Fargate terbatas pada daftar yang sah — tidak bebas.
  • initProcessEnabled atau tini: salah satunya wajib, atau SIGTERM diabaikan.
  • stopTimeout harus lebih besar dari total waktu graceful shutdown-mu.
  • secrets untuk rahasia, environment untuk yang bukan — nilainya terlihat berbeda.
  • Dua IAM role yang berbeda: executionRoleArn untuk menarik image, taskRoleArn untuk kode aplikasimu.

Task definition

{
  "family": "portal-web",
  "requiresCompatibilities": ["FARGATE"],
  "networkMode": "awsvpc",
  "cpu": "1024",
  "memory": "2048",
  "runtimePlatform": {
    "cpuArchitecture": "ARM64",
    "operatingSystemFamily": "LINUX"
  },
  "executionRoleArn": "arn:aws:iam::123456789012:role/portal-ecs-execution",
  "taskRoleArn": "arn:aws:iam::123456789012:role/portal-ecs-task",

  "containerDefinitions": [
    {
      "name": "web",
      "image": "123456789012.dkr.ecr.ap-southeast-3.amazonaws.com/portal:a1b2c3d",
      "essential": true,
      "initProcessEnabled": true,

      "portMappings": [
        { "containerPort": 4321, "protocol": "tcp", "name": "http" }
      ],

      "environment": [
        { "name": "NODE_ENV",          "value": "production" },
        { "name": "HOST",              "value": "0.0.0.0" },
        { "name": "PORT",              "value": "4321" },
        { "name": "DB_HOST_RW",        "value": "portal.abc.ap-southeast-3.rds.amazonaws.com" },
        { "name": "DB_HOST_RO",        "value": "portal-ro.abc.ap-southeast-3.rds.amazonaws.com" },
        { "name": "DB_NAME",           "value": "portal" },
        { "name": "DB_USER",           "value": "portal_app" },
        { "name": "MIDTRANS_PRODUKSI", "value": "true" },
        { "name": "PUBLIC_SITE_URL",   "value": "https://portal.contoh.id" }
      ],

      "secrets": [
        { "name": "DB_PASSWORD",         "valueFrom": "arn:aws:secretsmanager:ap-southeast-3:123456789012:secret:portal/db-AbC123:sandi::" },
        { "name": "MIDTRANS_SERVER_KEY", "valueFrom": "arn:aws:secretsmanager:ap-southeast-3:123456789012:secret:portal/midtrans-XyZ:server_key::" },
        { "name": "ASTRO_KEY",           "valueFrom": "arn:aws:secretsmanager:ap-southeast-3:123456789012:secret:portal/astro-Def:key::" },
        { "name": "CF_API_TOKEN",        "valueFrom": "arn:aws:secretsmanager:ap-southeast-3:123456789012:secret:portal/cf-Ghi:token::" },
        { "name": "ORIGIN_TOKEN",        "valueFrom": "arn:aws:secretsmanager:ap-southeast-3:123456789012:secret:portal/origin-Jkl:token::" }
      ],

      "healthCheck": {
        "command": ["CMD-SHELL", "wget -qO- http://127.0.0.1:4321/sehat || exit 1"],
        "interval": 15,
        "timeout": 5,
        "retries": 3,
        "startPeriod": 30
      },

      "stopTimeout": 45,

      "logConfiguration": {
        "logDriver": "awslogs",
        "options": {
          "awslogs-group": "/ecs/portal-web",
          "awslogs-region": "ap-southeast-3",
          "awslogs-stream-prefix": "web",
          "mode": "non-blocking",
          "max-buffer-size": "4m"
        }
      },

      "ulimits": [{ "name": "nofile", "softLimit": 65536, "hardLimit": 65536 }]
    }
  ]
}

Enam pengaturan yang menentukan

1. stopTimeout

Graceful shutdown (materi sebelumnya):
   5 detik  menunggu ALB menyadari health check gagal
+ 20 detik  menunggu request yang berjalan
+  2 detik  menutup pool database
= 27 detik

stopTimeout: 45     ← harus lebih besar. Maksimum 120 di Fargate.

Kalau stopTimeout lebih kecil dari waktu shutdown-mu, ECS mengirim SIGKILL di tengah jalan — dan seluruh penanganan yang kamu tulis jadi sia-sia. Default ECS adalah 30 detik, yang sering tidak cukup. Hitung waktu shutdown-mu yang sebenarnya, lalu beri margin.

2. initProcessEnabled

Menjalankan proses init sebagai PID 1 yang meneruskan sinyal dengan benar. Alternatif dari tini di Dockerfile — pakai salah satu, tidak perlu keduanya. Kalau tidak ada satu pun, SIGTERM tidak sampai ke Node.

3. mode: "non-blocking" pada log

Ini pengaturan yang menyelamatkanmu saat CloudWatch melambat. Dengan mode blocking (default), penulisan log yang lambat akan memblokir aplikasimu — proses Node berhenti menunggu log terkirim. Di trafikmu, gangguan CloudWatch berubah jadi gangguan portal. Mode non-blocking membuang log kalau buffer penuh, yang jauh lebih baik daripada membuang request pembaca.

4. startPeriod pada health check

Tiga puluh detik memberi container waktu memulai sebelum kegagalan health check dihitung. Tanpa itu, task bisa dibunuh saat masih menginisialisasi pool database — dan ECS akan mengulanginya terus dalam lingkaran.

5. Kombinasi CPU/memori yang sah

CPUMemori yang sahUntuk portalmu
512 (0,5 vCPU)1–4 GBTerlalu kecil
1024 (1 vCPU)2–8 GBTitik awal yang baik
2048 (2 vCPU)4–16 GBKalau render berat
4096 (4 vCPU)8–30 GBJarang perlu

Node satu proses hanya memakai satu inti untuk JavaScript. Menaikkan vCPU melebihi 2 jarang membantu — lebih baik menambah task daripada memperbesar task. Perkecualiannya: kalau kamu memproses gambar dengan Sharp di container, yang memang memakai banyak inti.

6. Dua IAM role yang berbeda

executionRoleArntaskRoleArn
Dipakai olehAgen ECSKode aplikasimu
UntukMenarik image, membaca secret, menulis logMemanggil API AWS dari dalam kode
Izin khasECR pull, Secrets Manager read, CloudWatch writeS3 untuk unggahan, SES untuk email

Mencampur keduanya adalah kesalahan izin yang paling umum di ECS. Kalau aplikasimu tidak memanggil API AWS sama sekali, taskRoleArn boleh punya nol izin — dan itu bagus. Jangan memberi taskRoleArn izin membaca Secrets Manager "supaya lebih mudah": secret sudah disuntikkan sebagai variabel lingkungan oleh execution role, dan memberi kodemu akses langsung berarti satu kerentanan aplikasi bisa membaca seluruh secret di akunmu.

Definisi service

aws ecs create-service \
  --cluster portal \
  --service-name portal-web \
  --task-definition portal-web \
  --desired-count 4 \
  --launch-type FARGATE \
  --platform-version LATEST \
  --network-configuration 'awsvpcConfiguration={
      subnets=[subnet-priv-a,subnet-priv-b,subnet-priv-c],
      securityGroups=[sg-portal-web],
      assignPublicIp=DISABLED}' \
  --load-balancers 'targetGroupArn=arn:...:targetgroup/portal-tg/abc,containerName=web,containerPort=4321' \
  --health-check-grace-period-seconds 60 \
  --deployment-configuration 'maximumPercent=200,minimumHealthyPercent=100,
      deploymentCircuitBreaker={enable=true,rollback=true}' \
  --enable-execute-command
PengaturanKenapa
assignPublicIp=DISABLEDTask di subnet privat; tidak bisa dihubungi dari internet
minimumHealthyPercent=100Kapasitas penuh selama deploy — tidak ada penurunan layanan
maximumPercent=200Task baru dinaikkan dulu sebelum yang lama diturunkan
deploymentCircuitBreakerRollback otomatis kalau deploy gagal
healthCheckGracePeriodJangan bunuh task yang masih memanaskan diri
enableExecuteCommandBisa exec ke container untuk menelusuri masalah

deploymentCircuitBreaker dengan rollback: true adalah pagar paling berharga di seluruh konfigurasi ini. Kalau task baru gagal health check berulang kali, ECS membatalkan deploy dan mengembalikan revisi sebelumnya — otomatis, tanpa ada yang perlu bangun tengah malam. Ia tidak menangkap semua kegagalan (bug logika tetap lolos), tapi ia menangkap seluruh kelas "container tidak bisa start".

Menelusuri masalah

# Kenapa task berhenti?
aws ecs describe-tasks --cluster portal --tasks <id> \
  --query 'tasks[0].{stop:stoppedReason,containers:containers[].{name:name,reason:reason,exit:exitCode}}'

# Masuk ke container yang sedang jalan
aws ecs execute-command --cluster portal --task <id> \
  --container web --interactive --command "/bin/sh"

# Peristiwa service
aws ecs describe-services --cluster portal --services portal-web \
  --query 'services[0].events[:10]'
GejalaPenyebab biasanya
CannotPullContainerErrorExecution role tidak punya izin ECR, atau tidak ada rute ke ECR dari subnet privat
ResourceInitializationError untuk secretExecution role tidak punya secretsmanager:GetSecretValue
Task start lalu mati, exit 0Proses selesai — biasanya CMD yang salah
Task mati, exit 137OOM — naikkan memori atau turunkan max-old-space-size
Health check selalu gagalHOST bukan 0.0.0.0
Task berputar terusstartPeriod terlalu pendek

Latihan: buat task definition di atas dan jalankan service dengan dua task. Uji rollback otomatis: deploy revisi dengan CMD yang sengaja salah, dan amati circuit breaker mengembalikannya. Lalu uji graceful shutdown di lingkungan nyata: kirim trafik terus-menerus dengan hey atau k6 sambil melakukan deploy, dan hitung berapa 5xx yang muncul — targetnya nol.

Rangkuman ini sengaja dipangkas ke bagian yang dipakai di roadmap. Buka sumber aslinya saat kamu butuh detail lengkap atau referensi parameter.