五项 AWS 服务协同运行一个容器:ECR 存储镜像,ECS 调度任务,Fargate 无需管理服务器即可运行,ALB 分发流量,Route 53 + ACM 提供自定义域名和 HTTPS。生产级、可自动扩缩、原生 AWS——值得投入,前提是你打算长期使用;否则就是过度设计。
"为什么选 Fargate 而不是 Lightsail?"这是第一个值得追问的问题,因为如果你没有明确答案,那答案就是别用。Fargate 存在的意义在于:无需管理 EC2 即可自动扩缩、容器原生部署天然适配 CI 流水线,以及与 AWS 其他服务深度集成(IAM 任务角色、VPC 网络、ALB 健康检查、CloudWatch)。对于一个小型 Node API 加单个域名的场景,一台 $5 的 Lightsail 实例才是真正正确的答案,Fargate 纯属大材小用。但如果你的服务需要根据流量从 1 个实例弹性扩展到 100 个、并行运行多个版本、访问私有 VPC 资源,并在基础设施滚动更新时保持可用——Fargate 就是为此而生的。
让 AWS 文档变得可读的心智模型是:ECR(Elastic Container Registry)是容器镜像的存放地,类似私有的 Docker Hub,位于你的 AWS 账户内部。ECS(Elastic Container Service)是编排器——负责下达指令:"我需要 3 个这个容器的实例持续运行,任何宕掉的都要替换,流量只路由到健康实例上。"Fargate 是计算模式——不同于在你自己管理的 EC2 上运行 ECS,Fargate 意味着"AWS 负责在某处运行这些容器,你看不到底层服务器。"任务定义是规格说明书("用这些环境变量在这些端口上运行镜像 X")。服务表示"根据这个任务定义,在这个负载均衡器后面持续运行 N 个任务。"ALB(Application Load Balancer)是公共入口点,将流量分发到各个运行中的任务。ACM + Route 53 负责 TLS 和 DNS,与其他教程中一样。
本教程演示端到端的首次部署:将应用容器化、推送到 ECR、编写任务定义、在 ALB 后创建 Fargate 服务、配置 DNS 和证书、完成部署。每个步骤做过一次之后都是机械操作;第一次有很多活动部件需要应对。为首次部署预留两小时;后续部署(步骤 9)只需一条命令。
Dockerfile。
如果你还没有 Dockerfile,这是一个合理的 Node 起点:
# Dockerfile
FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --omit=dev
COPY . .
ENV PORT=8080
EXPOSE 8080
CMD ["node", "server.js"]
在本地构建并测试:
docker build -t mybrand-api:latest .
docker run -p 8080:8080 mybrand-api:latest
curl http://localhost:8080/health
如果本地跑不通,放到 Fargate 上也不会通。在往 AWS 走之前,先确保本地镜像健康。
# Create the repo
aws ecr create-repository --repository-name mybrand-api --region us-east-1
# Authenticate Docker to ECR
aws ecr get-login-password --region us-east-1 \
| docker login --username AWS --password-stdin \
<ACCOUNT_ID>.dkr.ecr.us-east-1.amazonaws.com
# Tag the local image with the ECR URL
docker tag mybrand-api:latest \
<ACCOUNT_ID>.dkr.ecr.us-east-1.amazonaws.com/mybrand-api:latest
# Push
docker push <ACCOUNT_ID>.dkr.ecr.us-east-1.amazonaws.com/mybrand-api:latest
完整的镜像 URI——<ACCOUNT_ID>.dkr.ecr.<REGION>.amazonaws.com/<REPO>:<TAG>——将被任务定义引用。记下账户 ID 和区域,后面会频繁用到。
ECS 需要两个不同的 IAM 角色,混淆它们是首次部署失败的头号原因:
ecsTaskExecutionRole)——由 ECS/Fargate 本身使用,用于从 ECR 拉取镜像并将日志写入 CloudWatch。AWS 提供了一个托管策略(AmazonECSTaskExecutionRolePolicy),正好满足需求。每个账户只需创建一次执行角色:
aws iam create-role --role-name ecsTaskExecutionRole \
--assume-role-policy-document '{
"Version": "2012-10-17",
"Statement": [{"Effect":"Allow","Principal":{"Service":"ecs-tasks.amazonaws.com"},"Action":"sts:AssumeRole"}]
}'
aws iam attach-role-policy --role-name ecsTaskExecutionRole \
--policy-arn arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy
任务角色因项目而异;如果你的服务不调用 AWS API,可以省略。
保存为 task-definition.json:
{
"family": "mybrand-api",
"networkMode": "awsvpc",
"requiresCompatibilities": ["FARGATE"],
"cpu": "256",
"memory": "512",
"executionRoleArn": "arn:aws:iam::<ACCOUNT_ID>:role/ecsTaskExecutionRole",
"containerDefinitions": [{
"name": "api",
"image": "<ACCOUNT_ID>.dkr.ecr.us-east-1.amazonaws.com/mybrand-api:latest",
"portMappings": [{"containerPort": 8080, "protocol": "tcp"}],
"essential": true,
"logConfiguration": {
"logDriver": "awslogs",
"options": {
"awslogs-group": "/ecs/mybrand-api",
"awslogs-region": "us-east-1",
"awslogs-stream-prefix": "ecs",
"awslogs-create-group": "true"
}
}
}]
}
Fargate 最小任务规格为 256 CPU 单元(¼ vCPU)+ 512 MB 内存。大多数 API 在这个配置下运行良好。注册任务定义:
aws ecs register-task-definition --cli-input-json file://task-definition.json
这部分在控制台操作确实更快。AWS 控制台 → ECS → 创建集群 → 命名为 mybrand-prod → 基础设施选择 AWS Fargate(无服务器) → 创建。
然后单独创建 Application Load Balancer:EC2 → 负载均衡器 → 创建负载均衡器 → Application Load Balancer:
mybrand-api-tg,目标类型为 IP,协议 HTTP,端口 8080,健康检查路径 /health(或你的应用返回 200 的任意路径)。创建完成后,ALB 会有类似 mybrand-prod-12345.us-east-1.elb.amazonaws.com 的 DNS 名称。记下它;步骤 8 会将你的域名指向它。
/health 端点,ALB 会将每个任务标记为不健康,Fargate 会循环地将其杀死。在部署之前先为你的应用添加一行健康端点。app.get('/health', (req, res) => res.send('ok')) 就足够了。
ECS 控制台 → 你的集群 → 创建服务:
mybrand-api(最新修订版)。mybrand-api。mybrand-api-tg。负载均衡的容器:api:8080。创建。Fargate 拉取镜像、启动任务,并将其注册到目标组。2–3 分钟后,服务显示"ACTIVE",目标组显示健康目标。测试:
curl http://<ALB_DNS_NAME>/health
# Expected: ok
与 CloudFront 不同(CloudFront 只读取 us-east-1 的证书),ALB 使用与自身同区域的证书。如果你的 ALB 在 us-east-1,证书需要在 us-east-1;如果在 eu-west-2,证书就需要在那里。
aws acm request-certificate \
--domain-name api.mybrand.com \
--validation-method DNS \
--region <SAME_REGION_AS_ALB>
如果 DNS 在同账户的 Route 53 中,控制台会提供"在 Route 53 中创建记录"来完成验证 CNAME。否则在你的 DNS 提供商处手动添加 CNAME。等待 ACM 显示"已颁发"——通常需要 5–15 分钟。
EC2 → 负载均衡器 → 你的 ALB → 监听器标签页 → 添加监听器:
mybrand-api-tg)。可选:编辑 HTTP:80 监听器,将其重定向到 HTTPS:443。保存。
现在将 DNS 指向 ALB。在 Route 53 → 你的托管区 → 创建记录:
api。保存。测试:curl -I https://api.mybrand.com/health。你应该看到 HTTP/2 200。
完成所有初始配置后,每次后续部署只需三条命令:
# Build the new image
docker build -t mybrand-api:latest .
# Push to ECR (auth refresh might be needed first)
docker tag mybrand-api:latest \
<ACCOUNT_ID>.dkr.ecr.us-east-1.amazonaws.com/mybrand-api:latest
docker push <ACCOUNT_ID>.dkr.ecr.us-east-1.amazonaws.com/mybrand-api:latest
# Tell ECS to roll out new tasks with the same task definition (which references :latest)
aws ecs update-service \
--cluster mybrand-prod \
--service mybrand-api \
--force-new-deployment
ECS 执行滚动部署:启动新任务,等待它们通过 ALB 健康检查,排空旧任务的连接,然后终止它们。默认零停机。在 ECS 控制台你的服务的部署标签页下可以观察部署进度。
回滚方法:ECS 永久保留历史任务定义修订版本。将服务更新为使用上一个修订版:aws ecs update-service --cluster mybrand-prod --service mybrand-api --task-definition mybrand-api:<PREVIOUS_REVISION>。滚动部署机制相同,只是方向反过来了。
:latest。如果推送尚未完成时滚动部署就已启动,可能会拉取到旧的或不完整的镜像。建议使用不可变标签(mybrand-api:<git-sha>),并在每次推送时更新任务定义。多一点工作量,少很多潜在事故。
2 个最小规格(0.25 vCPU + 0.5 GB 内存)的 Fargate 任务全天候运行的费用:
一个小型 API 带 2 个常驻任务的现实月费:约 $35–40/月。同等配置的 Lightsail 实例是 $5–10/月。4–8 倍的成本差价,换来的是自动扩缩、滚动部署、ALB 健康检查和其余生产级基础设施。需要时物有所值,不需要时就是冤枉钱。
单实例、无扩缩需求。Lightsail 在成本和复杂度上完胜。只有在流量真正倒逼时才升级。
突发型工作负载、大量空闲时段。Fargate 按秒计费,但常驻任务一直在计费。如果你的服务大部分时间处于空闲,可以考虑 Lambda(仅按调用次数收费)或 App Runner(Fargate 加上更简洁的操作体验以及空闲时缩容至零)。
你不想运维 AWS 网络。ECS Fargate 屏蔽了 Fargate 的计算层,但仍然把 VPC、子网、安全组、目标组、监听器暴露给你。如果这些太繁琐,AWS App Runner 是对同一底层机制更薄的抽象——旋钮更少、能力相近、控制力略低。