ECS Fargate — menjalankan container
Fargate menjalankan container tanpa kamu menyentuh EC2. Yang tersisa untuk diputuskan adalah ukuran task, cara rahasia disuntikkan, dan bagaimana ECS tahu container-mu sehat.
Intisari
- Task definition adalah cetakan (image, CPU, memori, env); service menjaga jumlah task tetap sesuai.
- Satu image, beberapa service:
server,worker,cron— cukup timpacommand. stopTimeoutharus lebih besar dari total waktu graceful shutdown-mu (Fase 3).- Health check ECS tidak boleh menyentuh database — kalau tidak, gangguan sebagian jadi pemadaman total.
- Rahasia lewat
secrets(Secrets Manager), bukanenvironment.
Task definition
{
"family": "toko-server",
"networkMode": "awsvpc",
"requiresCompatibilities": ["FARGATE"],
"cpu": "512",
"memory": "1024",
"runtimePlatform": {
"cpuArchitecture": "ARM64",
"operatingSystemFamily": "LINUX"
},
"executionRoleArn": "arn:aws:iam::123456789012:role/ecsTaskExecutionRole",
"taskRoleArn": "arn:aws:iam::123456789012:role/tokoTaskRole",
"containerDefinitions": [{
"name": "server",
"image": "123456789012.dkr.ecr.ap-southeast-1.amazonaws.com/toko/server:a1b2c3d",
"essential": true,
"portMappings": [{ "containerPort": 8080, "protocol": "tcp" }],
"environment": [
{ "name": "APP_ENV", "value": "production" },
{ "name": "ADDR", "value": ":8080" },
{ "name": "GOMEMLIMIT", "value": "800MiB" },
{ "name": "LOG_LEVEL", "value": "info" }
],
"secrets": [
{ "name": "DATABASE_URL",
"valueFrom": "arn:aws:secretsmanager:ap-southeast-1:123456789012:secret:prod/db-AbC123" },
{ "name": "JWT_SECRET",
"valueFrom": "arn:aws:secretsmanager:ap-southeast-1:123456789012:secret:prod/jwt-XyZ789" }
],
"logConfiguration": {
"logDriver": "awslogs",
"options": {
"awslogs-group": "/ecs/toko-server",
"awslogs-region": "ap-southeast-1",
"awslogs-stream-prefix": "ecs",
"mode": "non-blocking",
"max-buffer-size": "4m"
}
},
"healthCheck": {
"command": ["CMD", "/server", "-health"],
"interval": 15,
"timeout": 3,
"retries": 3,
"startPeriod": 30
},
"stopTimeout": 60,
"readonlyRootFilesystem": true,
"user": "65532:65532"
}]
}
| Setelan | Kenapa seperti itu |
|---|---|
GOMEMLIMIT: 800MiB | ~80% dari 1024 MB — mencegah OOM kill (Fase 9) |
stopTimeout: 60 | Harus > jeda + Shutdown + worker (Fase 3) |
mode: non-blocking | Aplikasi tidak tertahan saat CloudWatch lambat |
readonlyRootFilesystem | Binari Go tidak menulis ke disk; matikan seluruh kelas serangan |
user: 65532 | Bukan root — sesuai distroless:nonroot |
ARM64 | Graviton: sekitar 20% lebih murah untuk kinerja setara |
startPeriod: 30 | Kegagalan health check selama masa ini tidak dihitung |
Dua peran IAM yang selalu tertukar: executionRoleArn dipakai agen ECS
untuk menarik image dan membaca rahasia — ia butuh izin ECR dan Secrets Manager.
taskRoleArn dipakai aplikasimu untuk memanggil AWS (S3, SQS) — dan ini yang harus
seketat mungkin. Menaruh izin S3 di execution role adalah kesalahan yang membuat izin jadi lebih luas
dari seharusnya.
Ukuran task
| CPU | Memori yang sah | Untuk |
|---|---|---|
| 256 (.25 vCPU) | 0,5 / 1 / 2 GB | Terlalu kecil untuk API produksi |
| 512 (.5 vCPU) | 1–4 GB | Titik awal yang wajar |
| 1024 (1 vCPU) | 2–8 GB | Beban sedang |
| 2048 (2 vCPU) | 4–16 GB | Beban CPU tinggi |
Untuk aplikasi Go, lebih banyak task kecil biasanya mengalahkan sedikit task besar. Alasannya: penyebaran lebih merata antar AZ, dampak satu task mati lebih kecil, dan penskalaan lebih halus. Yang membatasi adalah aritmetika koneksi database (Fase 4) — bukan CPU.
Dan sejak Go 1.25, GOMAXPROCS membaca batas CPU cgroup, sehingga task 0,5 vCPU
tidak lagi menjadwalkan seolah punya 64 core (Fase 9).
Health check: dua jenis, dua tujuan
1. Health check CONTAINER (di task definition)
→ "prosesnya hidup?" ECS me-restart container kalau gagal
→ JANGAN menyentuh database
2. Health check TARGET GROUP (di ALB)
→ "boleh dikirimi lalu lintas?" ALB berhenti mengirim kalau gagal
→ boleh memeriksa dependensi, dan inilah yang berubah saat shutdown
// Mode health check tanpa curl — image distroless tidak punya curl.
func main() {
cekSehat := flag.Bool("health", false, "periksa kesehatan lalu keluar")
flag.Parse()
if *cekSehat {
resp, err := http.Get("http://127.0.0.1:8080/sehat")
if err != nil || resp.StatusCode != http.StatusOK {
os.Exit(1)
}
os.Exit(0)
}
...
}
Health check yang menyentuh database mengubah gangguan jadi bencana. Kalau database sempat lambat 30 detik, semua task dinyatakan tidak sehat, ECS mematikan semuanya, task baru juga gagal health check, dan aplikasimu benar-benar mati — padahal sebagian besar endpoint (yang di-cache di CDN, yang tidak menyentuh database) sebenarnya masih bisa melayani.
Beberapa service dari satu image
Service "toko-server" command: ["/server"] desiredCount: 4 di belakang ALB
Service "toko-worker" command: ["/worker"] desiredCount: 2 tanpa ALB
Service "toko-cron" command: ["/cron"] desiredCount: 1 ← WAJIB 1
desiredCount: 1 untuk penjadwal bukan pilihan. Dua task cron berarti setiap job
berjalan dua kali — email dobel, invoice dobel, laporan dobel. Alternatif yang lebih tahan: EventBridge
Scheduler yang memicu run-task sekali per jadwal, sehingga tidak ada proses yang harus tetap
hidup.
Jaringan
VPC
├── Subnet publik (2 AZ) → ALB, NAT Gateway
└── Subnet privat (2 AZ) → task ECS, RDS, ElastiCache
Security group:
sg-alb masuk : 443 dari 0.0.0.0/0 (atau hanya dari CloudFront)
sg-task masuk : 8080 HANYA dari sg-alb
sg-db masuk : 5432 HANYA dari sg-task
sg-redis masuk : 6379 HANYA dari sg-task
Rujuk security group lain, jangan rentang CIDR. sg-db yang mengizinkan
10.0.0.0/16 berarti apa pun di VPC bisa menyentuh database — termasuk instans yang dibuat
orang lain nanti. Merujuk sg-task membuat izinnya mengikuti peran, bukan alamat.
Menjalankan migrasi
# Task terpisah, dijalankan pipeline SEBELUM service diperbarui (Fase 4)
aws ecs run-task \
--cluster toko \
--task-definition toko-migrate:12 \
--launch-type FARGATE \
--network-configuration "awsvpcConfiguration={subnets=[subnet-a,subnet-b],securityGroups=[sg-task]}" \
--started-by "deploy-$COMMIT"
# Tunggu selesai, lalu periksa exit code-nya
aws ecs wait tasks-stopped --cluster toko --tasks $TASK_ARN
aws ecs describe-tasks --cluster toko --tasks $TASK_ARN \
--query 'tasks[0].containers[0].exitCode'
Periksa exit code-nya secara eksplisit. aws ecs wait tasks-stopped hanya berarti
task berhenti — bukan berhasil. Tanpa pemeriksaan ini, migrasi yang gagal akan diikuti deploy yang tetap
berjalan, dan kode baru bertemu skema lama.
Latihan: buat cluster ECS dengan satu service Fargate dari image aplikasimu. Setel
stopTimeout: 5 lalu picu deploy sambil mengirim permintaan lambat — amati permintaan
terpotong. Naikkan ke 60 dan ulangi. Lalu ubah health check container agar memeriksa database, matikan
database sebentar, dan lihat seluruh service ikut mati.
Rangkuman ini sengaja dipangkas ke bagian yang dipakai di roadmap. Buka sumber aslinya saat kamu butuh detail lengkap atau referensi parameter.