Links#
- Amazon ECS Developer Guide
- Task definition parameters
- Amazon ECS services
- Deploy Amazon ECS services by replacing tasks
- Deployment circuit breaker
- AWS Fargate capacity providers
- Service auto scaling
- Target tracking scaling policies
- Task IAM role
- Task execution IAM role
- aws ecs register-task-definition
- aws ecs update-service
1. Important Points#
Amazon ECS 是 AWS 原生 container orchestration 服务。它适合想运行 container application,但不想自己管理 Kubernetes control plane 的场景。
ECS 用来做:
run long-running container service
run scheduled / one-off container task
deploy app behind ALB / NLB
run worker / consumer / batch job
integrate with IAM, CloudWatch Logs, ALB, Service Discovery
ECS 不适合:
strong Kubernetes ecosystem requirement
custom scheduler / operator-heavy platform
portable K8S manifest as primary deployment artifact
complex multi-cluster service mesh governance核心原则:
prefer Fargate first:
no EC2 capacity management
simple operational model
good default for small / medium teams
use ECS on EC2 when:
special instance type / GPU / local disk
very high steady utilization
daemon / host-level integration
advanced placement or cost optimization requirement
deployment unit:
task definition revision is immutable
service points to one task definition revision
update-service creates a new deployment2. Core Concepts#
| Concept | Meaning | Production Note |
|---|---|---|
| Cluster | ECS logical scheduling boundary | usually one per environment or platform boundary |
| Task definition | immutable container spec | every deploy registers a new revision |
| Task | running copy of a task definition | one or more containers |
| Service | keeps desired number of tasks running | use for web/API/worker |
| Capacity provider | where tasks run | FARGATE, FARGATE_SPOT, or EC2 ASG |
| Task role | IAM role used by application code | least privilege per service |
| Execution role | IAM role used by ECS agent/Fargate | pull image, write logs, fetch secrets |
| Service discovery | DNS name for ECS services | Cloud Map or internal ALB |
| Deployment | replacing old tasks with new tasks | rolling update by default |
Runtime scaling topics:
request path example:
Route 53
-> ALB
-> target group
-> ECS service
-> ECS task
-> container port
worker path example:
SQS / EventBridge
-> ECS service or run-task
-> container
-> downstream AWS service3. Service Configuration#
Minimum production service config:
cluster:
environment boundary, for example prod-apps
service:
desiredCount >= 2 for production web service
deploymentCircuitBreaker enabled with rollback
healthCheckGracePeriodSeconds configured when using ALB
enableExecuteCommand only when audited and needed
task definition:
image uses immutable tag or image digest
cpu / memory explicitly set
logs go to CloudWatch Logs
secrets come from Secrets Manager / SSM Parameter Store
taskRoleArn and executionRoleArn are separated
network:
awsvpc mode
private subnets for backend service
ALB in public subnets if internet-facing
security group allows only required ingressFargate task definition skeleton:
{
"family": "order-api",
"networkMode": "awsvpc",
"requiresCompatibilities": ["FARGATE"],
"cpu": "512",
"memory": "1024",
"executionRoleArn": "arn:aws:iam::111122223333:role/ecsTaskExecutionRole",
"taskRoleArn": "arn:aws:iam::111122223333:role/order-api-task-role",
"runtimePlatform": {
"cpuArchitecture": "X86_64",
"operatingSystemFamily": "LINUX"
},
"containerDefinitions": [
{
"name": "order-api",
"image": "111122223333.dkr.ecr.ap-east-1.amazonaws.com/order-api:20260603-120000",
"essential": true,
"portMappings": [
{
"containerPort": 8080,
"protocol": "tcp"
}
],
"environment": [
{
"name": "APP_ENV",
"value": "prod"
}
],
"secrets": [
{
"name": "DATABASE_URL",
"valueFrom": "arn:aws:secretsmanager:ap-east-1:111122223333:secret:prod/order-api/database-url-AbCdEf"
}
],
"logConfiguration": {
"logDriver": "awslogs",
"options": {
"awslogs-group": "/ecs/order-api",
"awslogs-region": "ap-east-1",
"awslogs-stream-prefix": "ecs"
}
}
}
]
}restart policy#
ECS task 默认更关注 task / service 层面的替换。如果不配置 container restart policy,容器退出后,通常是 task 进入失败状态,然后由 service 再补新 task。
现在 ECS 也支持 container-level restart policy:
适合:
sidecar 偶发退出,希望就地重启
主容器短时异常退出,希望先尝试容器级恢复
不想每次都直接替换整整个 task
不适合当成万能恢复手段:
应用配置错误
启动后必然 crash
依赖长期不可用
需要靠 service deployment / rollback 解决的问题常见字段:
"restartPolicy": {
"enabled": true,
"ignoredExitCodes": [0, 143],
"restartAttemptPeriod": 180
}理解:
enabled:
是否启用 container restart policy
ignoredExitCodes:
这些退出码不触发自动重启
例如 0 常表示正常退出
143 常见于收到 SIGTERM 后的正常停止
restartAttemptPeriod:
容器至少要连续运行多久
之后退出才允许 ECS 再尝试重启它
不是“每隔多久定时重启一次”实战建议:
web / api 主容器:
先把健康检查和 graceful shutdown 做对
restart policy 只作为补充,不要掩盖真实故障
worker / sidecar:
更适合用 restart policy 吸收短时退出
不要忽略真正异常退出码:
否则问题会被静默吞掉health check#
ECS 里至少要区分三层 health:
container health check:
task definition 里的 healthCheck
由容器内命令判断进程是否健康
ALB target health check:
由 ALB 请求 HTTP path / TCP port 判断 target 是否可接流量
service deployment health:
ECS 结合 task 状态、container health、ALB target health 判断部署是否成功不要把它们混成一层。
task definition 里的 container health check 例子:
"healthCheck": {
"command": ["CMD-SHELL", "curl -f http://localhost:8080/healthz || exit 1"],
"interval": 30,
"timeout": 5,
"retries": 3,
"startPeriod": 60
}字段含义:
command:
CMD 直接执行
CMD-SHELL 用容器默认 shell 执行
interval:
两次检查之间间隔
timeout:
一次检查最多等多久
retries:
连续失败多少次才算 unhealthy
startPeriod:
启动宽限期
应用冷启动时尤其重要实战原则:
readiness path:
应返回“现在能不能接业务流量”
不只是 JVM / Node 进程活着
health check 依赖:
不要把每个弱依赖都塞进健康检查
否则一个非关键依赖抖动也会导致 task 被踢掉
startPeriod / ALB grace:
慢启动应用要同时调 task definition 的 startPeriod
以及 service 的 healthCheckGracePeriodSeconds常见错误:
health check 太早开始:
应用还没 ready 就被判失败
只检查 /:
业务根路径返回 200,不代表依赖可用
把数据库强依赖写死在 liveness:
短时数据库抖动会触发整批 task 重启docker-style container configuration#
很多 ECS task definition 字段,本质上就是 Docker runtime 参数的受控映射。理解这层映射后,排查会更顺。
常用映射:
image
对应 docker image
entryPoint
对应 Docker ENTRYPOINT / --entrypoint
command
对应 Docker CMD / docker run 后面的命令参数
workingDirectory
对应 --workdir
environment
对应 --env
environmentFiles
对应 --env-file
readonlyRootFilesystem
对应 --read-only
linuxParameters.capabilities
对应 --cap-add / --cap-drop
memory / memoryReservation
对应 hard limit / soft limit最常见的 container definition 结构:
{
"name": "order-api",
"image": "111122223333.dkr.ecr.ap-east-1.amazonaws.com/order-api:20260603-120000",
"essential": true,
"entryPoint": ["java"],
"command": ["-jar", "/app/app.jar"],
"workingDirectory": "/app",
"environment": [
{ "name": "APP_ENV", "value": "prod" }
],
"secrets": [
{
"name": "DATABASE_URL",
"valueFrom": "arn:aws:secretsmanager:ap-east-1:111122223333:secret:prod/order-api/database-url-AbCdEf"
}
],
"readonlyRootFilesystem": true
}判断原则:
image:
用 immutable tag 或 digest
不要用 latest
entryPoint vs command:
image 自带 ENTRYPOINT 时
ECS 的 command 更适合传业务参数
environment:
放非敏感配置
secrets:
放密码、token、连接串
readonlyRootFilesystem:
能开就开
但先确认应用不会写根文件系统Linux 特定配置常见于:
需要 SYS_PTRACE 做诊断
需要 drop capabilities 缩小权限
需要只读根文件系统提高约束如果你在 ECS 里遇到“本地 docker run 正常,ECS 起不来”,优先检查:
entryPoint / command 是否被覆盖错了
容器监听端口和 portMappings 是否一致
healthCheck 命令在容器里是否真的存在
只读根文件系统是否挡住了运行时写入
environment / secrets 是否齐全4. Capacity And Networking#
capacity choice#
| Option | When To Use | Notes |
|---|---|---|
| Fargate | default for most services | no node management |
| Fargate Spot | fault-tolerant worker / async job | can be interrupted |
| ECS on EC2 | special hardware or high utilization | you manage capacity |
| EC2 capacity provider | ASG-backed ECS capacity | better scaling than manual container instances |
Fargate capacity provider example:
aws ecs put-cluster-capacity-providers \
--cluster prod-apps \
--capacity-providers FARGATE FARGATE_SPOT \
--default-capacity-provider-strategy \
capacityProvider=FARGATE,weight=1,base=1 \
capacityProvider=FARGATE_SPOT,weight=1Production networking:
public web API:
ALB in public subnets
ECS tasks in private subnets
task security group allows inbound only from ALB security group
outbound restricted when possible
internal service:
internal ALB or Cloud Map
private subnets only
no public IP
egress:
use VPC endpoints for ECR, CloudWatch Logs, Secrets Manager, SSM, STS when possible
use NAT Gateway only for required internet egress5. IAM#
Task role trust policy:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": "ecs-tasks.amazonaws.com"
},
"Action": "sts:AssumeRole"
}
]
}Task role example for an app that reads one secret and one S3 prefix:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "ReadAppSecret",
"Effect": "Allow",
"Action": [
"secretsmanager:GetSecretValue"
],
"Resource": "arn:aws:secretsmanager:ap-east-1:111122223333:secret:prod/order-api/*"
},
{
"Sid": "ReadOrderBucketPrefix",
"Effect": "Allow",
"Action": [
"s3:GetObject"
],
"Resource": "arn:aws:s3:::company-prod-orders/order-api/*"
}
]
}Deploy principal policy for CI/CD:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "DeployEcsService",
"Effect": "Allow",
"Action": [
"ecs:DescribeServices",
"ecs:DescribeTaskDefinition",
"ecs:DescribeTasks",
"ecs:ListTasks",
"ecs:RegisterTaskDefinition",
"ecs:UpdateService"
],
"Resource": "*"
},
{
"Sid": "PassOnlyEcsTaskRoles",
"Effect": "Allow",
"Action": "iam:PassRole",
"Resource": [
"arn:aws:iam::111122223333:role/ecsTaskExecutionRole",
"arn:aws:iam::111122223333:role/order-api-task-role"
],
"Condition": {
"StringEquals": {
"iam:PassedToService": "ecs-tasks.amazonaws.com"
}
}
}
]
}security rule:
app permissions go to task role
image pull / log / secret injection permissions go to execution role
CI/CD role can register task definition and update service
CI/CD role should only pass approved ECS roles6. Deployment Strategy#
ECS rolling update is the default deployment controller for most services.
rolling update:
update-service points service to new task definition
ECS starts new tasks and drains old tasks based on deployment config
minimumHealthyPercent controls lower healthy task bound
maximumPercent controls temporary upper task count
deployment circuit breaker:
detects service deployment failure
can rollback to last completed deployment
works with ECS rolling update controller
blue/green:
use CodeDeploy or ECS blue/green when traffic shifting / validation hooks are needed
more moving parts than normal rolling updateCreate service with circuit breaker:
aws ecs create-service \
--cluster prod-apps \
--service-name order-api \
--task-definition order-api:1 \
--desired-count 2 \
--capacity-provider-strategy capacityProvider=FARGATE,weight=1 \
--deployment-controller type=ECS \
--deployment-configuration "deploymentCircuitBreaker={enable=true,rollback=true},maximumPercent=200,minimumHealthyPercent=100" \
--network-configuration "awsvpcConfiguration={subnets=[subnet-aaa,subnet-bbb],securityGroups=[sg-task],assignPublicIp=DISABLED}" \
--load-balancers "targetGroupArn=arn:aws:elasticloadbalancing:ap-east-1:111122223333:targetgroup/order-api/abc123,containerName=order-api,containerPort=8080" \
--health-check-grace-period-seconds 60 \
--region ap-east-17. CLI Deploy#
这个 section 的目标是给 Jenkins、Ansible、GitHub Actions、GitLab CI 这种系统直接集成,不依赖 AWS Console。
deploy flow#
1. build image
2. push image to ECR
3. render task definition with new image
4. register new task definition revision
5. update ECS service to new revision
6. wait services-stable
7. verify running task image / ALB target health
8. print diagnostics on failurefiles#
deployment/ecs/order-api/prod/
├── metadata.env
└── task-definition.jsonmetadata.env:
AWS_REGION=ap-east-1
AWS_ACCOUNT_ID=111122223333
ECR_REPOSITORY=order-api
ECS_CLUSTER=prod-apps
ECS_SERVICE=order-api
CONTAINER_NAME=order-api
TARGET_GROUP_ARN=arn:aws:elasticloadbalancing:ap-east-1:111122223333:targetgroup/order-api/abc123task-definition.json keeps image as a placeholder:
{
"family": "order-api",
"networkMode": "awsvpc",
"requiresCompatibilities": ["FARGATE"],
"cpu": "512",
"memory": "1024",
"executionRoleArn": "arn:aws:iam::111122223333:role/ecsTaskExecutionRole",
"taskRoleArn": "arn:aws:iam::111122223333:role/order-api-task-role",
"containerDefinitions": [
{
"name": "order-api",
"image": "__IMAGE_URI__",
"essential": true,
"portMappings": [
{
"containerPort": 8080,
"protocol": "tcp"
}
],
"logConfiguration": {
"logDriver": "awslogs",
"options": {
"awslogs-group": "/ecs/order-api",
"awslogs-region": "ap-east-1",
"awslogs-stream-prefix": "ecs"
}
}
}
]
}deploy script#
#!/usr/bin/env bash
set -euo pipefail
export AWS_PAGER=""
SERVICE_DIR="${1:?usage: deploy-ecs.sh <service-dir> <image-tag>}"
IMAGE_TAG="${2:?usage: deploy-ecs.sh <service-dir> <image-tag>}"
source "${SERVICE_DIR}/metadata.env"
IMAGE_URI="${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com/${ECR_REPOSITORY}:${IMAGE_TAG}"
TMP_TASK_DEF="$(mktemp /tmp/ecs-task-definition.XXXXXX.json)"
trap 'rm -f "${TMP_TASK_DEF}"' EXIT
print_service_diagnostics() {
aws ecs describe-services \
--cluster "${ECS_CLUSTER}" \
--services "${ECS_SERVICE}" \
--region "${AWS_REGION}" \
--query 'services[0].deployments[*].[status,taskDefinition,desiredCount,runningCount,pendingCount,failedTasks,rolloutState,rolloutStateReason]' \
--output table
aws ecs describe-services \
--cluster "${ECS_CLUSTER}" \
--services "${ECS_SERVICE}" \
--region "${AWS_REGION}" \
--query 'services[0].events[0:10].[createdAt,message]' \
--output table
}
print_target_health() {
if [[ -z "${TARGET_GROUP_ARN:-}" ]]; then
return 0
fi
aws elbv2 describe-target-health \
--target-group-arn "${TARGET_GROUP_ARN}" \
--region "${AWS_REGION}" \
--query 'TargetHealthDescriptions[*].[Target.Id,Target.Port,TargetHealth.State,TargetHealth.Reason,TargetHealth.Description]' \
--output table
}
echo "Render task definition: ${IMAGE_URI}"
jq --arg image "${IMAGE_URI}" --arg name "${CONTAINER_NAME}" '
.containerDefinitions |= map(
if .name == $name then .image = $image else . end
)
' "${SERVICE_DIR}/task-definition.json" > "${TMP_TASK_DEF}"
jq empty "${TMP_TASK_DEF}"
echo "Register task definition"
TASK_DEF_ARN="$(
aws ecs register-task-definition \
--region "${AWS_REGION}" \
--cli-input-json "file://${TMP_TASK_DEF}" \
--query 'taskDefinition.taskDefinitionArn' \
--output text
)"
echo "Update service: ${ECS_CLUSTER}/${ECS_SERVICE} -> ${TASK_DEF_ARN}"
aws ecs update-service \
--cluster "${ECS_CLUSTER}" \
--service "${ECS_SERVICE}" \
--task-definition "${TASK_DEF_ARN}" \
--force-new-deployment \
--region "${AWS_REGION}" \
--output json > /dev/null
echo "Wait services-stable"
if ! aws ecs wait services-stable \
--cluster "${ECS_CLUSTER}" \
--services "${ECS_SERVICE}" \
--region "${AWS_REGION}"; then
echo "ECS service failed to stabilize."
print_service_diagnostics
print_target_health
exit 1
fi
echo "Verify service deployment"
aws ecs describe-services \
--cluster "${ECS_CLUSTER}" \
--services "${ECS_SERVICE}" \
--region "${AWS_REGION}" \
--query 'services[0].{serviceName:serviceName,taskDefinition:taskDefinition,desiredCount:desiredCount,runningCount:runningCount,pendingCount:pendingCount,rolloutState:deployments[0].rolloutState,rolloutStateReason:deployments[0].rolloutStateReason}' \
--output table
print_target_health
echo "Deployment completed: ${IMAGE_URI}"Run locally or from Jenkins:
chmod +x deploy-ecs.sh
./deploy-ecs.sh deployment/ecs/order-api/prod 20260603-120000rollback#
Rollback means update the service to a previous task definition revision.
aws ecs list-task-definitions \
--family-prefix order-api \
--sort DESC \
--region ap-east-1 \
--max-items 10
aws ecs update-service \
--cluster prod-apps \
--service order-api \
--task-definition order-api:42 \
--force-new-deployment \
--region ap-east-1
aws ecs wait services-stable \
--cluster prod-apps \
--services order-api \
--region ap-east-1Jenkins pattern#
pipeline {
agent any
parameters {
string(name: 'IMAGE_TAG', defaultValue: '', description: 'ECR image tag')
choice(name: 'ENV', choices: ['dev', 'uat', 'prod'], description: 'deploy env')
}
stages {
stage('Deploy ECS') {
steps {
sh '''
set -euo pipefail
./deploy-ecs.sh "deployment/ecs/order-api/${ENV}" "${IMAGE_TAG}"
'''
}
}
}
}Ansible pattern#
---
- name: Deploy ECS service
hosts: localhost
gather_facts: false
tasks:
- name: Run ECS deploy script
ansible.builtin.command:
cmd: ./deploy-ecs.sh deployment/ecs/order-api/prod 20260603-120000
changed_when: true8. Hands-on#
Create log group:
aws logs create-log-group \
--log-group-name /ecs/order-api \
--region ap-east-1Create cluster:
aws ecs create-cluster \
--cluster-name prod-apps \
--region ap-east-1Register task definition:
aws ecs register-task-definition \
--cli-input-json file://task-definition.json \
--region ap-east-1Run one task for smoke test:
aws ecs run-task \
--cluster prod-apps \
--task-definition order-api:1 \
--capacity-provider-strategy capacityProvider=FARGATE,weight=1 \
--network-configuration "awsvpcConfiguration={subnets=[subnet-aaa],securityGroups=[sg-task],assignPublicIp=DISABLED}" \
--region ap-east-1Check service:
aws ecs describe-services \
--cluster prod-apps \
--services order-api \
--region ap-east-1 \
--query 'services[0].[status,desiredCount,runningCount,pendingCount,taskDefinition]'Check stopped task reason:
aws ecs list-tasks \
--cluster prod-apps \
--service-name order-api \
--desired-status STOPPED \
--region ap-east-1
aws ecs describe-tasks \
--cluster prod-apps \
--tasks "arn:aws:ecs:ap-east-1:111122223333:task/prod-apps/example" \
--region ap-east-1 \
--query 'tasks[0].{lastStatus:lastStatus,stoppedReason:stoppedReason,containers:containers[*].[name,lastStatus,exitCode,reason]}'9. Monitoring#
Must-have signals:
| Signal | Where | Alert |
|---|---|---|
| CPU / memory utilization | ECS / CloudWatch metrics | sustained high usage |
| running task count | ECS service metrics | running < desired |
| pending task count | ECS service metrics | pending > 0 for long time |
| task stopped reason | ECS task state change event | unexpected stop |
| deployment failed | ECS service deployment event | immediate alert |
| ALB target unhealthy | ALB target group metrics | unhealthy > 0 |
| HTTP 5xx | ALB / app logs | error budget burn |
| log error rate | CloudWatch Logs / log backend | app regression |
EventBridge rule for failed deployment:
{
"source": ["aws.ecs"],
"detail-type": ["ECS Deployment State Change"],
"detail": {
"eventName": ["SERVICE_DEPLOYMENT_FAILED"]
}
}Operational commands:
aws ecs describe-services \
--cluster prod-apps \
--services order-api \
--region ap-east-1 \
--query 'services[0].events[0:20].[createdAt,message]' \
--output table
aws logs tail /ecs/order-api \
--since 30m \
--follow \
--region ap-east-110. Security Best Practices#
image:
use ECR
scan images
avoid latest tag in production
prefer immutable version tag or digest
network:
tasks in private subnets
no public IP for backend services
security group ingress only from ALB or trusted service
use VPC endpoints for AWS APIs where possible
identity:
separate task role and execution role
least privilege per service
CI/CD uses OIDC assume role, not long-lived access keys
restrict iam:PassRole
secrets:
use Secrets Manager or SSM Parameter Store
do not bake secrets into image
do not store secrets in task definition environment values
operations:
enable deployment circuit breaker with rollback
log to central log backend
alert on failed deployments and stopped tasks11. Scaling And Cost#
Service autoscaling:
scale on:
CPUUtilization
MemoryUtilization
ALBRequestCountPerTarget
SQS ApproximateNumberOfMessagesVisible for workers
custom business metric when possible
avoid:
scaling only on CPU for I/O-bound app
Fargate Spot for stateful or latency-critical service
desiredCount=1 for production web APICost controls:
right size:
set task CPU/memory from real metrics
review p95 CPU/memory every release cycle
Fargate Spot:
good for workers and retryable jobs
handle SIGTERM and stopTimeout
logs:
set CloudWatch Logs retention
avoid debug logs in production
network:
compare NAT Gateway cost vs VPC endpoint cost
avoid cross-AZ target / NAT traffic surprises12. Production Checklist#
service:
desired count >= 2 for production web service
deployment circuit breaker enabled
rollback tested
health check path returns app readiness
graceful shutdown implemented
task definition:
immutable image tag or digest
CPU/memory configured
awslogs configured
secrets injected from Secrets Manager / SSM
task role least privilege
execution role minimal and standard
network:
private subnets for tasks
security group scoped
ALB target health checks correct
VPC endpoints configured for private AWS API access when needed
deploy:
CLI deploy script stored with app or ops repo
register-task-definition and update-service are automated
services-stable wait is required
failed deploy prints ECS events and target health
rollback command documented
monitoring:
ECS deployment failure EventBridge alert
running task count alert
ALB 5xx / target unhealthy alert
app logs searchable by service/env/version13. Common Mistakes#
mistake:
use latest image tag in production
result:
rollback and audit become unreliable
mistake:
put app AWS permissions on execution role
result:
role boundary becomes unclear and over-permissioned
mistake:
deploy only with update-service --force-new-deployment and same image tag
result:
hard to know which image is running
mistake:
no circuit breaker / no service-stable wait
result:
pipeline reports success while service is unhealthy
mistake:
task health check starts before app is ready
result:
deployment flaps and rolls back
mistake:
Fargate tasks need AWS APIs but private subnet has no NAT or VPC endpoints
result:
image pull, logs, secrets, or STS calls fail