ECR, ECS Fargate vs EC2, EKS: chạy container shop-app trên AWS

5 câu hỏi~45 phútTừ khóa: ECR, image scanning, lifecycle policy, ECS cluster, task definition, task role vs execution role, Fargate vs EC2 launch type, service, rolling deploy, circuit breaker, Service Connect, Cloud Map, EKS, Fargate profile, SAA: Domain 1 Performance / Domain 4 Cost

Tiếp đi: ECS. shop-app đã có Dockerfile và chạy ổn bằng Docker Compose ở local, còn trên AWS thì vẫn là jar chạy trên EC2 trong ASG từ bài 22.5. Mỗi lần deploy phải build AMI mới hoặc SSH vào copy jar, khá cực. Mình muốn đẩy đúng cái image đang chạy ở local lên AWS, nhưng ECS, Fargate, EKS, ECR nhiều tên quá, cái nào làm gì?

Bạn đã có image rồi thì đây là bước tự nhiên tiếp theo: đẩy image lên ECR, chạy nó bằng ECS Fargate. Bài 18.5 (bản đồ dịch vụ) đã có bảng chọn EC2 / ECS / EKS / Lambda và phần IRSA; ở đây ta không lặp mà làm thật trên shop-app: ECR, task definition, service sau ALB, rolling deploy, và chỉ so sánh với EKS (chương Kubernetes đã có đủ lý thuyết).

1. Bài toán: deploy shop-app trên EC2 đang cực

Quy trình hiện tại (bài 22.4 + 22.5):

  dev push code ──► CI build jar ──► upload S3 ──► user data EC2 tải jar khi boot
                                                        │
       deploy mới = tạo launch template version mới ──► instance refresh ASG (10–15 phút)
       rollback = đổi lại version cũ, refresh lần nữa
       local chạy Docker, AWS chạy jar: "ở máy em chạy được mà" quay lại

Thứ đã có:          Dockerfile (../docker/02-dockerfile.html), docker-compose, Helm chart (K8s local)
Thứ muốn có:        docker push ──► chạy đúng image đó trên AWS, scale theo CPU, không SSH

2. Vấn đề

3. Giải pháp: ECR + ECS (Fargate) + so sánh EKS

Amazon ECR (Elastic Container Registry): registry Docker/OCI riêng, private theo tài khoản, phân quyền bằng IAM, có image scanning (basic bằng Clair, enhanced bằng Inspector), lifecycle policy tự xóa image cũ, replication cross-region/cross-account, pull-through cache cho Docker Hub/ECR Public, và immutable tags.

Amazon ECS (Elastic Container Service): orchestrator container của riêng AWS. Các khái niệm (thuật ngữ đề thi):

ECSNghĩaTương đương Kubernetesshop-app
ClusterNhóm logic chứa service/task; với Fargate chỉ là cái tên, với EC2 là nhóm instance chạy ECS agentClustershop-cluster
Task definitionBản thiết kế (versioned): container image, CPU/memory, port, env, secrets, log driver, volume, IAM rolePod spec / Deployment templateshop-app-task:7
TaskMột lần chạy của task definition (1–10 container cùng host, cùng network namespace với awsvpc)Pod2 task đang chạy
ServiceGiữ N task luôn chạy, gắn vào target group ALB, rolling/blue-green deploy, auto scalingDeployment + Service + HPAshop-app-svc desired 2
Launch type / capacity providerFargate: AWS cấp máy ảo riêng cho từng task, bạn trả theo vCPU-giờ + GB-giờ. EC2: bạn nuôi ASG instance, ECS xếp task lênNode pool (managed / Fargate profile)Fargate
Task execution roleRole mà ECS agent dùng để pull image từ ECR, lấy secret, ghi logkubelet / node roleshop-ecs-exec-role
Task roleRole mà code trong container dùng (SDK gọi S3, SQS…)ServiceAccount + IRSAshop-app-task-role
awsvpc network modeMỗi task một ENI, IP riêng trong subnet, gắn security group riêng. Bắt buộc với FargatePod IP (VPC CNI)Task trong shop-private-a/b, SG shop-app-sg
Service Connect / Cloud MapService discovery + proxy sidecar (Envoy) để service gọi nhau bằng tênService DNS (CoreDNS)http://shop-app:8080
Deployment circuit breakerRolling deploy mà task mới cứ chết → tự dừng và rollback về task definition trướcprogressDeadlineSeconds + rollout undoBật
ECS ExecMở shell vào container qua SSM, không cần SSHkubectl execDebug

Amazon EKS (Elastic Kubernetes Service): control plane Kubernetes do AWS quản lý (API server, etcd, Multi-AZ, khoảng 0,1 USD/giờ/cluster, không Free Tier). Worker chạy trên managed node group (EC2), Fargate profile (serverless pod), hoặc Karpenter. Add-on: VPC CNI, CoreDNS, kube-proxy, EBS/EFS CSI, AWS Load Balancer Controller (tạo ALB từ Ingress). Mọi thứ bạn học ở chương Kubernetes áp dụng nguyên.

4. Code và cấu hình từng bước

Bước 1: Dockerfile đã có, chỉ chỉnh cho ECS

Dùng Dockerfile multi-stage từ bài 16.2. Hai điều chỉnh: build cho linux/arm64 nếu chạy Fargate Graviton (rẻ hơn ~20%), và để JVM đọc giới hạn container (JDK 21 mặc định đã container-aware; đặt -XX:MaxRAMPercentage=75 thay cho -Xmx cứng).

# Dockerfile (trích)
FROM eclipse-temurin:21-jre-alpine
WORKDIR /app
COPY --from=build /build/target/shop-app.jar app.jar
ENV JAVA_TOOL_OPTIONS="-XX:MaxRAMPercentage=75 -XX:+UseG1GC -Djava.security.egd=file:/dev/urandom"
EXPOSE 8080
USER 1000
ENTRYPOINT ["java","-jar","/app/app.jar"]

Bước 2: ECR repo, build, push

aws ecr create-repository --repository-name shop-app \
  --image-scanning-configuration scanOnPush=true \
  --image-tag-mutability IMMUTABLE \
  --encryption-configuration encryptionType=KMS,kmsKey=alias/shop-key \
  --tags Key=Project,Value=shop-app Key=Env,Value=lab --profile shop-lab

ECR=$ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com
aws ecr get-login-password --profile shop-lab | docker login --username AWS --password-stdin $ECR

TAG=$(git rev-parse --short HEAD)
docker buildx build --platform linux/arm64 -t $ECR/shop-app:$TAG --push .

# Lifecycle: giữ 10 image mới nhất, xóa untagged sau 1 ngày
aws ecr put-lifecycle-policy --repository-name shop-app --lifecycle-policy-text '{
  "rules":[
   {"rulePriority":1,"description":"untagged 1 day","selection":{"tagStatus":"untagged","countType":"sinceImagePushed","countUnit":"days","countNumber":1},"action":{"type":"expire"}},
   {"rulePriority":2,"description":"keep last 10","selection":{"tagStatus":"any","countType":"imageCountMoreThan","countNumber":10},"action":{"type":"expire"}}]}' --profile shop-lab

IMMUTABLE tag: không ai push đè :v1.2 được nữa, deploy truy vết được. Vì vậy không dùng tag latest trên ECR; CI tag bằng git SHA.

Bước 3: hai IAM role

cat > ecs-trust.json <<'EOF'
{"Version":"2012-10-17","Statement":[{"Effect":"Allow",
 "Principal":{"Service":"ecs-tasks.amazonaws.com"},"Action":"sts:AssumeRole"}]}
EOF
# Execution role: pull ECR, ghi log, đọc secret cho agent
aws iam create-role --role-name shop-ecs-exec-role --assume-role-policy-document file://ecs-trust.json \
  --tags Key=Project,Value=shop-app Key=Env,Value=lab --profile shop-lab
aws iam attach-role-policy --role-name shop-ecs-exec-role \
  --policy-arn arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy --profile shop-lab
aws iam put-role-policy --role-name shop-ecs-exec-role --policy-name read-shop-secrets --policy-document "{
 \"Version\":\"2012-10-17\",\"Statement\":[
  {\"Effect\":\"Allow\",\"Action\":[\"secretsmanager:GetSecretValue\"],
   \"Resource\":\"arn:aws:secretsmanager:$AWS_REGION:$ACCOUNT_ID:secret:shop/db-*\"},
  {\"Effect\":\"Allow\",\"Action\":[\"kms:Decrypt\"],
   \"Resource\":\"arn:aws:kms:$AWS_REGION:$ACCOUNT_ID:key/*\",
   \"Condition\":{\"ForAnyValue:StringEquals\":{\"kms:ResourceAliases\":\"alias/shop-key\"}}}]}" --profile shop-lab

# Task role: quyền của code (S3 assets, SQS)
aws iam create-role --role-name shop-app-task-role --assume-role-policy-document file://ecs-trust.json \
  --tags Key=Project,Value=shop-app Key=Env,Value=lab --profile shop-lab
aws iam put-role-policy --role-name shop-app-task-role --policy-name shop-app-access --policy-document "{
 \"Version\":\"2012-10-17\",\"Statement\":[
  {\"Effect\":\"Allow\",\"Action\":[\"s3:GetObject\",\"s3:PutObject\"],\"Resource\":\"arn:aws:s3:::shop-app-assets-$ACCOUNT_ID/*\"},
  {\"Effect\":\"Allow\",\"Action\":[\"sqs:SendMessage\"],\"Resource\":\"arn:aws:sqs:$AWS_REGION:$ACCOUNT_ID:shop-order-events\"}]}" --profile shop-lab

Hai role khác nhau là câu hỏi phỏng vấn kinh điển: execution role làm việc trước khi container chạy (pull image, lấy secret bơm vào env, mở log stream); task role là identity của code bên trong. SDK v2 trong container tự lấy credential của task role qua endpoint 169.254.170.2 (biến AWS_CONTAINER_CREDENTIALS_RELATIVE_URI), nằm trong default provider chain, code không đổi gì.

Bước 4: task definition

cat > shop-app-task.json <<EOF
{
  "family": "shop-app-task",
  "networkMode": "awsvpc",
  "requiresCompatibilities": ["FARGATE"],
  "cpu": "512", "memory": "1024",
  "runtimePlatform": { "cpuArchitecture": "ARM64", "operatingSystemFamily": "LINUX" },
  "executionRoleArn": "arn:aws:iam::$ACCOUNT_ID:role/shop-ecs-exec-role",
  "taskRoleArn": "arn:aws:iam::$ACCOUNT_ID:role/shop-app-task-role",
  "containerDefinitions": [{
    "name": "shop-app",
    "image": "$ECR/shop-app:$TAG",
    "essential": true,
    "portMappings": [{ "containerPort": 8080, "protocol": "tcp", "name": "http" }],
    "environment": [
      { "name": "SPRING_PROFILES_ACTIVE", "value": "aws" },
      { "name": "AWS_REGION", "value": "$AWS_REGION" },
      { "name": "SPRING_DATASOURCE_URL", "value": "jdbc:postgresql://shop-db.xxxx.$AWS_REGION.rds.amazonaws.com:5432/shop" }
    ],
    "secrets": [
      { "name": "SPRING_DATASOURCE_USERNAME",
        "valueFrom": "arn:aws:secretsmanager:$AWS_REGION:$ACCOUNT_ID:secret:shop/db:username::" },
      { "name": "SPRING_DATASOURCE_PASSWORD",
        "valueFrom": "arn:aws:secretsmanager:$AWS_REGION:$ACCOUNT_ID:secret:shop/db:password::" }
    ],
    "healthCheck": {
      "command": ["CMD-SHELL", "wget -qO- http://localhost:8080/actuator/health || exit 1"],
      "interval": 30, "timeout": 5, "retries": 3, "startPeriod": 60
    },
    "logConfiguration": {
      "logDriver": "awslogs",
      "options": {
        "awslogs-group": "/shop/app",
        "awslogs-region": "$AWS_REGION",
        "awslogs-stream-prefix": "ecs",
        "awslogs-create-group": "true"
      }
    },
    "ulimits": [{ "name": "nofile", "softLimit": 65536, "hardLimit": 65536 }],
    "readonlyRootFilesystem": false
  }],
  "tags": [{ "key": "Project", "value": "shop-app" }, { "key": "Env", "value": "lab" }]
}
EOF
aws ecs register-task-definition --cli-input-json file://shop-app-task.json --profile shop-lab

Vài điểm Java: cpu 512 = 0,5 vCPU, memory 1024 MB → với MaxRAMPercentage=75 heap tối đa ~768 MB, phần còn lại cho metaspace, thread stack, off-heap. Fargate chỉ cho các cặp CPU/memory hợp lệ (0,25 vCPU: 0,5–2 GB; 0,5 vCPU: 1–4 GB; 1 vCPU: 2–8 GB…). startPeriod 60 để Spring Boot có thời gian khởi động trước khi health check tính fail. Cú pháp secret arn:…:secret:shop/db:password:: lấy đúng một key trong JSON secret (xem bài 22.14).

Bước 5: cluster, log group, service sau ALB

aws ecs create-cluster --cluster-name shop-cluster \
  --capacity-providers FARGATE FARGATE_SPOT \
  --default-capacity-provider-strategy capacityProvider=FARGATE,weight=1 \
  --settings name=containerInsights,value=enabled \
  --tags key=Project,value=shop-app key=Env,value=lab --profile shop-lab

# Target group mới kiểu ip (task Fargate không có instance id)
TG_ARN=$(aws elbv2 create-target-group --name shop-ecs-tg --protocol HTTP --port 8080 \
  --vpc-id $VPC_ID --target-type ip --health-check-path /actuator/health \
  --health-check-interval-seconds 15 --healthy-threshold-count 2 \
  --tags Key=Project,Value=shop-app Key=Env,Value=lab \
  --query 'TargetGroups[0].TargetGroupArn' --output text --profile shop-lab)

# Đổi listener 80 của shop-alb (bài 22.5) sang target group này
aws elbv2 modify-listener --listener-arn $LISTENER_ARN \
  --default-actions Type=forward,TargetGroupArn=$TG_ARN --profile shop-lab

aws ecs create-service --cluster shop-cluster --service-name shop-app-svc \
  --task-definition shop-app-task --desired-count 2 --launch-type FARGATE \
  --platform-version LATEST \
  --network-configuration "awsvpcConfiguration={subnets=[$PRIV_A,$PRIV_B],securityGroups=[$APP_SG],assignPublicIp=DISABLED}" \
  --load-balancers "targetGroupArn=$TG_ARN,containerName=shop-app,containerPort=8080" \
  --health-check-grace-period-seconds 90 \
  --deployment-configuration "maximumPercent=200,minimumHealthyPercent=100,deploymentCircuitBreaker={enable=true,rollback=true}" \
  --enable-execute-command \
  --propagate-tags SERVICE --tags key=Project,value=shop-app key=Env,value=lab --profile shop-lab

Task ở private subnet, không public IP. Để pull image từ ECR và gọi Secrets Manager / CloudWatch, nó cần đường ra: hoặc NAT Gateway (bài 22.3, tốn tiền) hoặc VPC endpoint cho ecr.api, ecr.dkr, s3 (gateway, cho layer), logs, secretsmanager. Đề thi hay hỏi chính xác chỗ này: "Fargate task trong private subnet không pull được image" → thiếu NAT hoặc endpoint.

Bước 6: auto scaling theo CPU

aws application-autoscaling register-scalable-target --service-namespace ecs \
  --resource-id service/shop-cluster/shop-app-svc --scalable-dimension ecs:service:DesiredCount \
  --min-capacity 2 --max-capacity 6 --profile shop-lab
aws application-autoscaling put-scaling-policy --service-namespace ecs \
  --resource-id service/shop-cluster/shop-app-svc --scalable-dimension ecs:service:DesiredCount \
  --policy-name shop-app-cpu60 --policy-type TargetTrackingScaling \
  --target-tracking-scaling-policy-configuration '{
    "TargetValue": 60.0,
    "PredefinedMetricSpecification": {"PredefinedMetricType": "ECSServiceAverageCPUUtilization"},
    "ScaleOutCooldown": 60, "ScaleInCooldown": 300 }' --profile shop-lab

Bước 7: deploy phiên bản mới = register task definition mới + update service

TAG=$(git rev-parse --short HEAD)
docker buildx build --platform linux/arm64 -t $ECR/shop-app:$TAG --push .
sed "s#shop-app:[a-f0-9]*\"#shop-app:$TAG\"#" shop-app-task.json > td.json
aws ecs register-task-definition --cli-input-json file://td.json --profile shop-lab
aws ecs update-service --cluster shop-cluster --service shop-app-svc \
  --task-definition shop-app-task --profile shop-lab        # không ghi :N = lấy revision mới nhất
aws ecs wait services-stable --cluster shop-cluster --services shop-app-svc --profile shop-lab

Trong CI (bài 18.2) đúng ba lệnh đó, hoặc dùng aws-actions/amazon-ecs-deploy-task-definition. Muốn blue/green với test listener và rollback theo alarm thì dùng CodeDeploy deployment controller (hoặc ECS blue/green native, 2025).

5. Cách hoạt động

Push và deploy:

 dev ──docker push──► ECR shop-app:abc123 ──scan──► findings (Inspector)
                           │
 register-task-definition shop-app-task:8 (image abc123)
                           │
 update-service ──► ECS scheduler ──► Fargate: tạo microVM, ENI trong shop-private-a/b
                                        │  exec role: pull image, GetSecretValue, tạo log stream
                                        │  task role: credential cho SDK trong app
                                        ▼
                               task mới ──► đăng ký vào shop-ecs-tg ──► ALB health check /actuator/health
                                        │ healthy
                                        ▼
                               task cũ: deregister (draining 300s mặc định) ──► SIGTERM ──► 30s ──► SIGKILL

Rolling: desired 2, min 100%, max 200%
   t0: [old][old]
   t1: [old][old][new][new]      ← chạy 4 task trong lúc chuyển (trả tiền 4)
   t2: [new][new]                ← old drain xong
   Circuit breaker: new fail health N lần ──► dừng, rollback về :7

Request:

 Internet ──► shop-alb (public subnet, shop-alb-sg) ──► target ip 10.0.11.x:8080 (task, shop-app-sg)
                                                           │ task role
                                                           ├──► RDS shop-db (shop-db-sg)
                                                           ├──► ElastiCache shop-cache
                                                           └──► S3 / SQS / Secrets Manager qua VPC endpoint hoặc NAT

ECS vs EKS (cùng một image):

 ECS:  task definition JSON ──► service ──► ALB      (AWS API, IAM, CloudWatch sẵn)
 EKS:  Deployment YAML ──► Service ──► Ingress ──► AWS LB Controller tạo ALB
       + Helm, Argo CD, HPA, NetworkPolicy, mọi thứ ở chương Kubernetes

6. So sánh

Fargate vs EC2 launch type (đề thi hỏi nhiều nhất trong bài):

FargateEC2 launch type
Bạn quản lýChỉ task definitionASG, AMI ECS-optimized, patch, ECS agent, bin-packing
Tính tiềnvCPU-giờ + GB-giờ theo task (làm tròn phút, tối thiểu 1 phút); không Free TierTheo instance, dù task có chạy hay không
Rẻ hơn khiTải thay đổi, ít task, không muốn vận hànhTải ổn định cao, xếp chặt được, có Reserved/Savings Plans, Spot
Giảm giáFargate Spot (tới ~70%), Compute Savings PlansSpot, RI, Savings Plans
GPU, Windows, instance store, đặc quyền (privileged), daemonKhông GPU (một số region có), không privileged, không daemon schedulingCó đủ
Network modeChỉ awsvpcawsvpc, bridge, host
StorageEphemeral 20–200 GB, EFSEBS của instance, EFS
Cô lậpMỗi task một microVMNhiều task chung kernel instance

ECS vs EKS (cho shop-app, chi tiết hơn bảng tổng quát ở bài 18.5):

ECSEKS
Control planeMiễn phíKhoảng 0,1 USD/giờ/cluster (≈ 73 USD/tháng) + node
Đường cong họcThấp: IAM + JSON + ALB quen thuộcCao: K8s + add-on + IAM mapping (IRSA/Pod Identity)
Hệ sinh tháiAWS-only; ít tool bên thứ baHelm, Argo CD, Istio, Prometheus Operator, mọi thứ CNCF
PortabilityKhóa vào AWSManifest chạy được trên GKE/AKS/on-prem
Service discoveryService Connect / Cloud MapCoreDNS, Service, Ingress
SecretSecrets Manager/SSM bơm env trực tiếpSecret K8s + External Secrets Operator / CSI driver
Hợp vớiTeam nhỏ–trung, 1–20 service, muốn nhanhTeam đã làm K8s, nhiều service, multi-cloud, cần CRD/operator

Bốn chỗ chạy container trên AWS: App Runner (chỉ đưa image/repo, có HTTPS, scale, không VPC phức tạp; cho web app đơn giản) → ECS Fargate → ECS EC2 → EKS. Và Lambda container image cho job ngắn (bài 22.12).

7. Khi nào dùng / không dùng

ECR: luôn dùng khi chạy container trên AWS (pull nhanh, không rate limit Docker Hub, IAM, scan). Bật pull-through cache nếu base image từ Docker Hub.

ECS Fargate: shop-app giai đoạn này: vài service, team 3–5 người, muốn deploy bằng CI trong 5 phút, không ai muốn nuôi K8s. Dùng Fargate Spot cho worker chịu được bị dừng (consumer SQS).

ECS EC2: khi cần GPU, cần xếp chặt hàng chục task nhỏ trên instance Reserved đã trả tiền, cần daemon (agent log/monitoring mỗi node), hoặc cần instance store NVMe.

EKS: khi team đã có Helm chart + Argo CD (như bài 18.3), cần operator (Kafka Strimzi, Postgres operator), cần chạy đồng thời on-prem/cloud khác, hoặc hơn 20–30 service với nhiều team. Chọn EKS rồi vẫn nên cân nhắc Fargate profile cho workload không cần node tùy biến.

Không dùng container cho: job sự kiện ngắn thưa (Lambda rẻ hơn), app cần kernel module/driver lạ, database stateful lớn (dùng RDS, bài 22.7).

Service của mình cứ tạo task rồi task chết ngay với CannotPullContainerError: … i/o timeout. Task ở private subnet như bạn bảo, security group shop-app-sg cho phép 8080 từ ALB. Mình kiểm tra thì image có trên ECR rồi. Thiếu gì?

Bạn làm đúng phần security group inbound; vấn đề nằm ở đường ra. Fargate task ở private subnet không có public IP; để pull image nó phải gọi ba nơi: ecr.api (lấy auth token, manifest), ecr.dkr (registry), và S3 (ECR lưu layer trong S3). Rồi để chạy được còn cần logs (awslogs driver) và secretsmanager (secret trong task definition). "i/o timeout" là không có route ra Internet và cũng không có VPC endpoint.

Hai cách sửa:

NAT GatewayVPC endpoint (PrivateLink + Gateway)
Việc làmTạo NAT GW ở public subnet, route 0.0.0.0/0 của private route table trỏ vào (bài 22.3)Tạo interface endpoint com.amazonaws.$AWS_REGION.ecr.api, .ecr.dkr, .logs, .secretsmanager trong 2 private subnet + gateway endpoint .s3 gắn vào route table
Chi phíTheo giờ + theo GB qua NAT (pull image 300 MB × nhiều task × nhiều deploy cộng lại đáng kể); không Free TierInterface endpoint tính theo giờ mỗi AZ + GB (rẻ hơn NAT về data); gateway endpoint S3 miễn phí
Bảo mậtTask có thể gọi bất kỳ đâu trên InternetTraffic không rời mạng AWS; endpoint policy giới hạn được bucket/repo
Đề thi chọnKhi app thật sự cần gọi API ngoài"Traffic must not traverse the Internet", "reduce NAT data processing charges"
# Cách 2: endpoint. Security group cho endpoint: cho 443 từ shop-app-sg
EP_SG=$(aws ec2 create-security-group --group-name shop-vpce-sg --description "VPC endpoints" --vpc-id $VPC_ID \
  --tag-specifications 'ResourceType=security-group,Tags=[{Key=Project,Value=shop-app},{Key=Env,Value=lab}]' \
  --query GroupId --output text --profile shop-lab)
aws ec2 authorize-security-group-ingress --group-id $EP_SG --protocol tcp --port 443 --source-group $APP_SG --profile shop-lab

for svc in ecr.api ecr.dkr logs secretsmanager; do
  aws ec2 create-vpc-endpoint --vpc-id $VPC_ID --vpc-endpoint-type Interface \
    --service-name com.amazonaws.$AWS_REGION.$svc --subnet-ids $PRIV_A $PRIV_B \
    --security-group-ids $EP_SG --private-dns-enabled \
    --tag-specifications 'ResourceType=vpc-endpoint,Tags=[{Key=Project,Value=shop-app},{Key=Env,Value=lab}]' --profile shop-lab
done
aws ec2 create-vpc-endpoint --vpc-id $VPC_ID --vpc-endpoint-type Gateway \
  --service-name com.amazonaws.$AWS_REGION.s3 --route-table-ids $PRIV_RT --profile shop-lab

Danh sách kiểm tra khi task Fargate không lên, theo thứ tự thường gặp:

  1. CannotPullContainerError → đường ra (NAT/endpoint) hoặc execution role thiếu ecr:GetAuthorizationToken/BatchGetImage; hoặc image build sai kiến trúc (exec format error: build amd64 mà task ARM64).
  2. ResourceInitializationError: unable to pull secrets → execution role thiếu secretsmanager:GetSecretValue hoặc kms:Decrypt, hoặc không tới được endpoint secretsmanager.
  3. Task chạy rồi STOPPED (Essential container exited) → app crash; xem log group /shop/app. Hay gặp: thiếu env, DB không tới được (SG shop-db-sg phải cho 5432 từ shop-app-sg), OOM (exit 137: tăng memory hoặc giảm MaxRAMPercentage).
  4. Task healthy nhưng ALB báo unhealthy → target group health check path sai, hoặc grace period ngắn hơn thời gian Spring Boot khởi động.
# Lệnh chẩn đoán nhanh
aws ecs describe-services --cluster shop-cluster --services shop-app-svc \
  --query 'services[0].events[0:5].message' --profile shop-lab
TASK=$(aws ecs list-tasks --cluster shop-cluster --service-name shop-app-svc --desired-status STOPPED \
  --query 'taskArns[0]' --output text --profile shop-lab)
aws ecs describe-tasks --cluster shop-cluster --tasks $TASK \
  --query 'tasks[0].{stop:stoppedReason,containers:containers[].{name:name,reason:reason,exit:exitCode}}' --profile shop-lab

Fargate có vẻ đắt: 2 task 0,5 vCPU / 1 GB chạy 24/7 tính ra có khi hơn cả 2 EC2 t3.micro trong ASG mình đang có. Mình đổi lấy gì cho khoản chênh đó, và có cách nào rẻ hơn không?

Bạn tính không sai. Với tải nhỏ và ổn định, Fargate on-demand thường đắt hơn EC2 cùng cỡ khoảng 20–40%, vì bạn trả cho việc AWS cấp microVM riêng, không chia sẻ, không phải nuôi. Khoản chênh đổi lấy:

Cách rẻ hơn, theo thứ tự ít rủi ro:

CáchGiảmĐổi lấy
Graviton (ARM64)~20%Build multi-arch; Java chạy tốt
Right-size task: đo CpuUtilized/MemoryUtilized trong Container Insights, hạ 0,5→0,25 vCPU nếu idleTới 50%Spring Boot khởi động chậm hơn với 0,25 vCPU (1–2 phút); tăng grace period
Compute Savings Plans (cam kết 1–3 năm USD/giờ, áp cho Fargate, Lambda, EC2)Tới ~50%Cam kết; linh hoạt hơn RI
Fargate Spot cho worker/consumer (capacity provider strategy: 1 task base FARGATE, phần còn lại FARGATE_SPOT)Tới ~70% phần SpotTask có thể bị dừng với 2 phút báo trước (SIGTERM); không dùng cho task nhận request web duy nhất
Scale-to-min ngoài giờ: scheduled scaling desired 1 ban đêm cho môi trường lab/stagingTheo giờMất redundancy ban đêm
Chuyển EC2 launch type với instance Savings Plans/Spot, xếp chặtNhiều nhất khi tải lớn ổn địnhVận hành lại ASG, AMI, agent
# Spot cho worker: base 1 on-demand, còn lại Spot
aws ecs create-service --cluster shop-cluster --service-name shop-worker-svc \
  --task-definition shop-worker-task --desired-count 4 \
  --capacity-provider-strategy capacityProvider=FARGATE,base=1,weight=1 capacityProvider=FARGATE_SPOT,weight=3 \
  --network-configuration "awsvpcConfiguration={subnets=[$PRIV_A,$PRIV_B],securityGroups=[$APP_SG]}" --profile shop-lab

Và lời khuyên cho lab/thi: câu hỏi "MOST cost-effective" với container trên AWS thường đáp án là Fargate Spot cho workload chịu gián đoạn, EC2 Spot/Savings Plans cho tải ổn định lớn, Fargate on-demand khi đề nhấn "least operational overhead" hơn là giá. Luôn nhắc kiểm tra pricing page vì giá theo region khác nhau.

Cho mình bài lab để tự tay làm phần này nhé.

Lab 22.13: Đưa image shop-app lên ECR và chạy 2 task Fargate sau shop-alb, rolling deploy

Mục tiêu

Chi phí và thời gian

Chuẩn bị

export AWS_REGION=ap-southeast-1
export ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text --profile shop-lab)
export ECR=$ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com
export VPC_ID=$(aws ec2 describe-vpcs --filters Name=tag:Name,Values=shop-vpc --query 'Vpcs[0].VpcId' --output text --profile shop-lab)
export PRIV_A=$(aws ec2 describe-subnets --filters Name=tag:Name,Values=shop-private-a --query 'Subnets[0].SubnetId' --output text --profile shop-lab)
export PRIV_B=$(aws ec2 describe-subnets --filters Name=tag:Name,Values=shop-private-b --query 'Subnets[0].SubnetId' --output text --profile shop-lab)
export PRIV_RT=$(aws ec2 describe-route-tables --filters Name=vpc-id,Values=$VPC_ID Name=tag:Name,Values=shop-private-rt --query 'RouteTables[0].RouteTableId' --output text --profile shop-lab)
export APP_SG=$(aws ec2 describe-security-groups --filters Name=group-name,Values=shop-app-sg --query 'SecurityGroups[0].GroupId' --output text --profile shop-lab)
export ALB_ARN=$(aws elbv2 describe-load-balancers --names shop-alb --query 'LoadBalancers[0].LoadBalancerArn' --output text --profile shop-lab)
export ALB_DNS=$(aws elbv2 describe-load-balancers --names shop-alb --query 'LoadBalancers[0].DNSName' --output text --profile shop-lab)
export LISTENER_ARN=$(aws elbv2 describe-listeners --load-balancer-arn $ALB_ARN --query 'Listeners[?Port==`80`].ListenerArn' --output text --profile shop-lab)
echo $VPC_ID $PRIV_A $PRIV_B $APP_SG $ALB_DNS

Các bước

Bước 1: ECR repo + push image. Chạy các lệnh ở mục 4 Bước 2 (create-repository, login, buildx push, lifecycle policy). Chú ý --encryption-configuration dùng alias/shop-key chỉ khi key đã có (bài 22.14); nếu chưa, bỏ tham số đó để dùng AES-256 mặc định.

aws ecr describe-images --repository-name shop-app --image-ids imageTag=$TAG \
  --query 'imageDetails[0].{size:imageSizeInBytes,pushed:imagePushedAt,scan:imageScanStatus.status}' --profile shop-lab
aws ecr describe-image-scan-findings --repository-name shop-app --image-id imageTag=$TAG \
  --query 'imageScanFindings.findingSeverityCounts' --profile shop-lab
# {"HIGH": 2, "MEDIUM": 9, ...} tùy base image

Console: ECR → Repositories → Create → Immutable tags, Scan on push → vào repo → Images → Vulnerabilities.

Bước 2: hai IAM role theo mục 4 Bước 3. Nếu secret shop/db mã hóa bằng key mặc định aws/secretsmanager thì bỏ statement kms:Decrypt.

Console: IAM → Roles → Create role → Elastic Container Service → Elastic Container Service Task → policy AmazonECSTaskExecutionRolePolicy.

Bước 3: đường ra cho private subnet. Nếu đã có NAT Gateway từ bài 22.3 và route 0.0.0.0/0 trong $PRIV_RT, bỏ qua. Nếu không, tạo 4 interface endpoint + 1 gateway endpoint như ở câu hỏi phụ trên. Kiểm tra:

aws ec2 describe-vpc-endpoints --filters Name=vpc-id,Values=$VPC_ID \
  --query 'VpcEndpoints[].{svc:ServiceName,state:State}' --output table --profile shop-lab
# tất cả available (interface endpoint mất 2–3 phút)

Console: VPC → Endpoints → Create endpoint → AWS services → tìm ecr.api → chọn VPC, 2 private subnet, SG → Enable DNS name.

Bước 4: task definition. Dùng JSON ở mục 4 Bước 4, thay SPRING_DATASOURCE_URL bằng endpoint RDS thật:

RDS_EP=$(aws rds describe-db-instances --db-instance-identifier shop-db \
  --query 'DBInstances[0].Endpoint.Address' --output text --profile shop-lab)
sed -i "s#shop-db.xxxx.$AWS_REGION.rds.amazonaws.com#$RDS_EP#" shop-app-task.json
aws ecs register-task-definition --cli-input-json file://shop-app-task.json \
  --query 'taskDefinition.{family:family,rev:revision,cpu:cpu,mem:memory}' --profile shop-lab
# {"family":"shop-app-task","rev":1,"cpu":"512","mem":"1024"}

Console: ECS → Task definitions → Create new → Fargate, Linux/ARM64, 0.5 vCPU, 1 GB → Container: image URI, port 8080, env, secrets (ValueFrom), Log collection awslogs.

Bước 5: cluster, target group, service theo mục 4 Bước 5. Sau create-service:

aws ecs wait services-stable --cluster shop-cluster --services shop-app-svc --profile shop-lab   # 3–5 phút
aws ecs describe-services --cluster shop-cluster --services shop-app-svc \
  --query 'services[0].{desired:desiredCount,running:runningCount,pending:pendingCount,deploy:deployments[0].rolloutState}' --profile shop-lab
# {"desired":2,"running":2,"pending":0,"deploy":"COMPLETED"}

Console: ECS → Clusters → Create cluster (Fargate) → Services → Create → Launch type Fargate, Family shop-app-task, Desired 2, Networking: private subnets, shop-app-sg, public IP off, Load balancing: existing ALB shop-alb, listener 80, target group mới (ip), Deployment options: circuit breaker + rollback.

Bước 6: auto scaling theo mục 4 Bước 6 (min 2, max 6, CPU 60%).

Kiểm tra kết quả

# 1. Target healthy
aws elbv2 describe-target-health --target-group-arn $TG_ARN \
  --query 'TargetHealthDescriptions[].{ip:Target.Id,state:TargetHealth.State}' --output table --profile shop-lab
# 2 dòng healthy, IP 10.0.11.x và 10.0.12.x (mỗi AZ một task)

# 2. Qua ALB
curl -s http://$ALB_DNS/actuator/health
# {"status":"UP"}
curl -s http://$ALB_DNS/api/products | head -c 200

# 3. Log trong CloudWatch
aws logs tail /shop/app --since 10m --profile shop-lab | head -20
# ecs/shop-app/<taskId> ... Started ShopApplication in 18.4 seconds

# 4. Credential bên trong là task role (không có access key)
TASK=$(aws ecs list-tasks --cluster shop-cluster --service-name shop-app-svc --query 'taskArns[0]' --output text --profile shop-lab)
aws ecs execute-command --cluster shop-cluster --task $TASK --container shop-app \
  --interactive --command "/bin/sh" --profile shop-lab
# trong shell:  env | grep AWS_CONTAINER   → AWS_CONTAINER_CREDENTIALS_RELATIVE_URI=/v2/credentials/...
#               wget -qO- http://169.254.170.2$AWS_CONTAINER_CREDENTIALS_RELATIVE_URI | head -c 120
#               exit

Nếu execute-command báo lỗi: task role cần thêm policy ssmmessages:CreateControlChannel/CreateDataChannel/OpenControlChannel/OpenDataChannel, và service phải tạo với --enable-execute-command (task tạo trước khi bật không có agent; force new deployment).

Thử thêm

1. Rolling deploy không downtime. Mở terminal 2 chạy vòng lặp, terminal 1 deploy:

# terminal 2
while true; do curl -s -o /dev/null -w '%{http_code} ' http://$ALB_DNS/actuator/health; sleep 0.5; done

# terminal 1: đổi gì đó nhỏ (ví dụ info.app.version), build tag mới, register, update
TAG=v2-$(date +%s); docker buildx build --platform linux/arm64 -t $ECR/shop-app:$TAG --push .
sed "s#shop-app:[^\"]*\"#shop-app:$TAG\"#" shop-app-task.json > td.json
aws ecs register-task-definition --cli-input-json file://td.json --query taskDefinition.revision --profile shop-lab
aws ecs update-service --cluster shop-cluster --service shop-app-svc --task-definition shop-app-task --profile shop-lab
watch -n 5 "aws ecs describe-services --cluster shop-cluster --services shop-app-svc \
  --query 'services[0].deployments[].{rev:taskDefinition,run:runningCount,state:rolloutState}' --output table --profile shop-lab"

Kỳ vọng: terminal 2 toàn 200; bảng deployments có hai dòng (PRIMARY rev mới, ACTIVE rev cũ) rồi còn một. Nếu thấy vài lỗi 502 lúc task cũ rút: giảm deregistration_delay của target group xuống 30 s và chắc rằng Spring Boot có server.shutdown=graceful (bài 16.6 về PID 1 và SIGTERM: docker/06).

2. Deploy hỏng để xem circuit breaker. Đổi health check command thành exit 1 trong td.json, register, update-service. Sau vài phút: rolloutState: FAILED, rolloutStateReason: "ECS deployment circuit breaker: tasks failed to start", service tự quay về revision trước, curl vẫn 200 suốt.

3. Scale theo tải. Chạy hey -z 3m -c 50 http://$ALB_DNS/api/products (hoặc ab). Sau ~3 phút Container Insights thấy CPU > 60%, service lên 3–4 task; dừng tải, 5 phút sau scale in về 2.

4. Fargate Spot. update-service --capacity-provider-strategy capacityProvider=FARGATE,base=1,weight=1 capacityProvider=FARGATE_SPOT,weight=1 --force-new-deployment; describe-tasks thấy capacityProviderName: FARGATE_SPOT cho một task.

Dọn tài nguyên

# 1. Service về 0 rồi xóa (xóa service đang chạy task sẽ bị từ chối nếu không --force)
aws application-autoscaling deregister-scalable-target --service-namespace ecs \
  --resource-id service/shop-cluster/shop-app-svc --scalable-dimension ecs:service:DesiredCount --profile shop-lab
aws ecs update-service --cluster shop-cluster --service shop-app-svc --desired-count 0 --profile shop-lab
aws ecs delete-service --cluster shop-cluster --service shop-app-svc --force --profile shop-lab
aws ecs wait services-inactive --cluster shop-cluster --services shop-app-svc --profile shop-lab
# 2. Cluster (Fargate: không có instance; EC2 launch type thì phải xóa ASG trước)
aws ecs delete-cluster --cluster shop-cluster --profile shop-lab
# 3. Task definition: deregister từng revision (từ 2023 có thể delete hẳn)
for r in $(aws ecs list-task-definitions --family-prefix shop-app-task --query 'taskDefinitionArns[]' --output text --profile shop-lab); do
  aws ecs deregister-task-definition --task-definition $r --profile shop-lab >/dev/null; done
# 4. Trả listener ALB về shop-tg cũ (hoặc xóa ALB nếu không còn dùng, tốn tiền theo giờ), xóa target group
aws elbv2 modify-listener --listener-arn $LISTENER_ARN --default-actions Type=forward,TargetGroupArn=$OLD_TG_ARN --profile shop-lab
aws elbv2 delete-target-group --target-group-arn $TG_ARN --profile shop-lab
# 5. VPC endpoint (tốn tiền theo giờ!) hoặc NAT Gateway nếu tạo riêng cho lab
for ep in $(aws ec2 describe-vpc-endpoints --filters Name=vpc-id,Values=$VPC_ID Name=tag:Env,Values=lab --query 'VpcEndpoints[].VpcEndpointId' --output text --profile shop-lab); do
  aws ec2 delete-vpc-endpoints --vpc-endpoint-ids $ep --profile shop-lab; done
aws ec2 delete-security-group --group-id $EP_SG --profile shop-lab
# 6. ECR (--force xóa cả image), log group
aws ecr delete-repository --repository-name shop-app --force --profile shop-lab
aws logs delete-log-group --log-group-name /shop/app --profile shop-lab
# 7. Role (giữ nếu 22.14 còn dùng)
aws iam delete-role-policy --role-name shop-ecs-exec-role --policy-name read-shop-secrets --profile shop-lab
aws iam detach-role-policy --role-name shop-ecs-exec-role --policy-arn arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy --profile shop-lab
aws iam delete-role --role-name shop-ecs-exec-role --profile shop-lab
aws iam delete-role-policy --role-name shop-app-task-role --policy-name shop-app-access --profile shop-lab
aws iam delete-role --role-name shop-app-task-role --profile shop-lab
# Kiểm tra: Fargate task còn chạy không (tốn tiền từng phút)
aws ecs list-clusters --profile shop-lab
aws resourcegroupstaggingapi get-resources --tag-filters Key=Env,Values=lab \
  --resource-type-filters ecs ecr ec2:vpc-endpoint --query 'ResourceTagMappingList[].ResourceARN' --profile shop-lab

Phỏng vấn và đề thi SAA hỏi về ECR, ECS, EKS thì sao?

Q1. Task role và task execution role trong ECS khác nhau thế nào?

🇻🇳 Tiếng Việt:

Execution role là role ECS agent dùng thay mình trước khi container chạy: pull image từ ECR, lấy secret từ Secrets Manager hoặc Parameter Store để bơm vào env, tạo log stream CloudWatch. Task role là identity của code trong container: SDK lấy credential tạm từ endpoint metadata của task và dùng để gọi S3, SQS, DynamoDB. Tách hai role để least privilege: app không cần quyền pull image, agent không cần quyền ghi S3. Tương đương trên EKS là node role với IRSA hoặc Pod Identity.

🇬🇧 English:

The execution role is what the ECS agent uses on our behalf before the container runs: pulling the image from ECR, fetching secrets from Secrets Manager or Parameter Store to inject as environment variables, and creating CloudWatch log streams. The task role is the identity of the code inside the container: the SDK obtains temporary credentials from the task metadata endpoint and uses them to call S3, SQS or DynamoDB. Separating them gives least privilege: the app does not need image-pull rights and the agent does not need S3 write access. On EKS the equivalent is the node role versus IRSA or Pod Identity.

Q2. Khi nào chọn Fargate, khi nào EC2 launch type cho ECS?

🇻🇳 Tiếng Việt:

Em chọn Fargate mặc định: không quản lý instance, mỗi task một microVM cô lập, tính tiền theo vCPU và memory của task, scale theo task. Em chuyển sang EC2 launch type khi cần GPU, cần daemon chạy mỗi node, cần instance store hay network mode host, hoặc khi tải lớn và ổn định đến mức xếp chặt task lên instance có Savings Plans hay Spot rẻ hơn rõ rệt. Để giảm giá Fargate em dùng Graviton, right-size, Compute Savings Plans và Fargate Spot cho worker chịu được gián đoạn.

🇬🇧 English:

Fargate is my default: no instances to manage, each task in its own isolated microVM, billed by task vCPU and memory, scaling per task. I switch to the EC2 launch type when I need GPUs, daemons on every node, instance store or host networking, or when the load is large and steady enough that bin-packing tasks onto instances covered by Savings Plans or Spot is clearly cheaper. To lower Fargate cost I use Graviton, right-sizing, Compute Savings Plans and Fargate Spot for interruption-tolerant workers.

Q3. Team đã có Helm chart và Argo CD, nên chọn ECS hay EKS? Vì sao?

🇻🇳 Tiếng Việt:

Nếu team đã đầu tư vào Kubernetes thì EKS để tái dùng Helm, Argo CD, HPA, operator và kỹ năng sẵn có, và giữ portability. Chi phí thêm là control plane theo giờ, add-on, nâng cấp version mỗi năm, IAM mapping qua IRSA hoặc Pod Identity. Nếu team nhỏ, chưa có Kubernetes, chỉ vài service, ECS Fargate đơn giản và rẻ hơn: không có control plane phí, IAM, ALB, CloudWatch tích hợp sẵn. Cùng một image chạy được cả hai nên quyết định này đổi được sau.

🇬🇧 English:

If the team has already invested in Kubernetes, I would pick EKS to reuse Helm, Argo CD, HPA, operators and existing skills, and keep portability. The extra cost is the hourly control plane, add-ons, yearly version upgrades and IAM mapping through IRSA or Pod Identity. If the team is small, new to Kubernetes and runs a handful of services, ECS Fargate is simpler and cheaper: no control-plane fee, with IAM, ALB and CloudWatch integrated out of the box. The same image runs on both, so the decision is reversible.

Q4. Rolling deploy trên ECS hoạt động thế nào, làm sao không mất request?

🇻🇳 Tiếng Việt:

Register task definition mới rồi update service. ECS tạo task mới theo maximumPercent, chờ ALB health check pass, rồi deregister task cũ khỏi target group, chờ connection draining, gửi SIGTERM và sau stopTimeout thì SIGKILL. Để không mất request em để minimumHealthyPercent 100, grace period đủ cho Spring Boot khởi động, bật graceful shutdown trong app để xử lý xong request đang chạy, giảm deregistration delay hợp lý, và bật deployment circuit breaker với rollback để bản hỏng tự quay về.

🇬🇧 English:

We register a new task definition and update the service. ECS starts new tasks up to maximumPercent, waits for the ALB health check to pass, then deregisters old tasks from the target group, waits for connection draining, sends SIGTERM and after stopTimeout SIGKILL. To avoid dropped requests I keep minimumHealthyPercent at 100, set a grace period long enough for Spring Boot to start, enable graceful shutdown in the app so in-flight requests finish, tune the deregistration delay, and enable the deployment circuit breaker with rollback so a bad version reverts automatically.

Góc thi SAA-C03

Câu 1: Một công ty chạy ứng dụng container trên ECS Fargate trong private subnet. Task không khởi động được với lỗi không pull được image từ ECR. Yêu cầu: traffic giữa task và ECR KHÔNG đi qua Internet. Giải pháp nào?
A. Thêm NAT Gateway vào public subnet và route private subnet qua đó.
B. Gán public IP cho task và mở security group.
C. Tạo interface VPC endpoint cho ecr.api và ecr.dkr, và gateway endpoint cho S3.
D. Chuyển task sang public subnet.

Đáp án: C. Interface endpoint cho ECR API và registry cộng gateway endpoint S3 (layer lưu trong S3) cho phép pull image hoàn toàn trong mạng AWS. A hoạt động nhưng traffic đi ra Internet qua NAT, vi phạm yêu cầu. B và D đưa task ra public, vi phạm yêu cầu và giảm bảo mật.

Câu 2: Một hệ thống xử lý ảnh chạy trên ECS, tải thay đổi lớn theo giờ, các job có thể chạy lại nếu bị gián đoạn. Công ty muốn giảm chi phí NHIỀU NHẤT mà không phải quản lý EC2. Chọn gì?
A. ECS trên EC2 với Reserved Instances.
B. ECS Fargate với capacity provider FARGATE_SPOT.
C. ECS Fargate on-demand với Compute Savings Plans 3 năm.
D. Chuyển sang EKS với managed node group Spot.

Đáp án: B. Job chịu được gián đoạn + không muốn quản EC2 + giảm nhiều nhất = Fargate Spot (tới ~70%). A và D đều phải quản lý EC2 (node group vẫn là EC2). C giảm ít hơn và cam kết 3 năm không hợp tải thay đổi.

Câu 3: Một ứng dụng trên ECS cần đọc object từ một bucket S3 cụ thể. Kiến trúc sư phải cấp quyền theo least privilege và không lưu credential trong image hay env. Làm thế nào?
A. Tạo IAM user, đặt access key vào environment của task definition.
B. Gắn policy cho phép s3:GetObject trên bucket đó vào task execution role.
C. Gắn policy cho phép s3:GetObject trên bucket đó vào task role (taskRoleArn) của task definition.
D. Gắn policy vào instance profile của container instance.

Đáp án: C. Task role là identity của code trong container, SDK tự lấy credential tạm. A hardcode key dài hạn, vi phạm. B sai role: execution role dành cho agent (pull image, secret, log), cấp quyền S3 ở đây là thừa quyền và không phải chỗ app lấy credential theo thiết kế. D chỉ áp dụng với EC2 launch type và cấp cho mọi task trên instance, không least privilege (và không có với Fargate).

Cách nhớ ngắn gọn

Từ khóa trong đềNghĩ tới
"containers", "no server management", "least operational overhead"ECS Fargate (hoặc EKS Fargate profile nếu đã dùng K8s)
"Kubernetes", "kubectl", "Helm", "portability", "existing K8s"EKS
"GPU", "privileged", "daemon on every host", "instance store"ECS EC2 launch type
"interruption-tolerant", "batch", "lowest cost containers"Fargate Spot / EC2 Spot
"cannot pull image", "private subnet", "no Internet"VPC endpoint ECR + S3 (hoặc NAT nếu được phép)
"container needs S3 access", "no credentials in image"Task role (ECS) / IRSA, Pod Identity (EKS)
"scan images for vulnerabilities", "registry"ECR scan on push / enhanced scanning (Inspector)
"web app from source/image, simplest, HTTPS included"App Runner
"service-to-service discovery in ECS"Service Connect / Cloud Map

Tự kiểm tra

Vì sao target group cho ECS Fargate phải là target-type ip?

Task Fargate dùng awsvpc, mỗi task có ENI và IP riêng, không có instance ID để đăng ký kiểu instance. ECS tự đăng ký/gỡ IP của task vào target group.

Tag image latest trên ECR có vấn đề gì với ECS?

Không truy vết được bản nào đang chạy, rollback mơ hồ, và với immutable tag thì không push đè được. Dùng git SHA hoặc version; mỗi deploy register task definition mới trỏ vào tag mới.

Task bị dừng với exit code 137 nghĩa là gì, sửa thế nào với Java?

OOM kill: container vượt memory của task definition. Với Java: đặt -XX:MaxRAMPercentage khoảng 70–75 thay cho -Xmx cứng, tính thêm metaspace và thread; hoặc tăng memory của task.

Deployment circuit breaker làm gì?

Khi rolling deploy mà task mới liên tục không healthy, ECS dừng deploy, và nếu bật rollback thì tự quay về task definition trước đang chạy ổn, tránh vòng lặp tạo task hỏng.

Ba đích mạng nào Fargate task cần tới được để pull image từ ECR?

ecr.api (token, manifest), ecr.dkr (registry), và S3 (layer). Trong private subnet: qua NAT Gateway hoặc hai interface endpoint + gateway endpoint S3. Thêm logs và secretsmanager nếu dùng awslogs và secrets.

🧪 Thực hành bài này: Challenge: Kiến trúc tham chiếu + DR + cost (chưa triển khai) · lab labs/aws