3. 容器化微服务与Amazon ECS/Fargate实战
把微服务跑在 AWS 上,最省心的路径通常不是直接上 EKS,而是先用 Amazon ECS 配合 Fargate 启动模式。ECS 负责“把容器放到哪里、放几个、怎么连网络”,Fargate 负责“不用你管 EC2 实例”。这一节我们围绕一条真实可复现的链路展开:写 Dockerfile → 构建镜像 → 推送到 ECR → 写任务定义 → 写服务定义 → 部署并验证第一个无服务器容器微服务。
一、Docker 镜像构建最佳实践
镜像构建的质量直接决定部署速度、冷启动时间和安全面。以本节示例服务 order-api(一个基于 Node.js 的订单查询微服务)为例,推荐使用多阶段构建:构建阶段装依赖、编译,运行阶段只保留产物和运行时。
# 构建阶段
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --omit=dev
COPY . .
# 运行阶段
FROM node:20-alpine
WORKDIR /app
ENV NODE_ENV=production
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/src ./src
USER node
EXPOSE 8080
CMD ["node", "src/server.js"]
几个关键点:基础镜像选 alpine 或 distroless,体积小、攻击面小;先 COPY package.json 再 npm ci,利用 Docker 层缓存,代码改动不会触发依赖重装;用 USER node 切换非 root 用户,满足容器安全基线;只暴露一个端口,Fargate 任务定义里端口映射要与之对应。构建命令:
docker build -t order-api:1.0.0 .
docker run -p 8080:8080 order-api:1.0.0
本地跑通后,用 docker inspect 或 docker history 检查镜像层,确认没有把 .env、密钥、测试文件打进镜像。镜像标签不要只用 latest,建议 order-api:1.0.0 或 order-api:git-abc1234,便于回滚和追踪。
二、ECR 镜像仓库
Amazon ECR 是托管镜像仓库,与 ECS 天然集成。创建仓库并推送:
aws ecr create-repository --repository-name order-api
aws ecr get-login-password --region ap-northeast-1 \
| docker login --username AWS --password-stdin \
123456789012.dkr.ecr.ap-northeast-1.amazonaws.com
docker tag order-api:1.0.0 \
123456789012.dkr.ecr.ap-northeast-1.amazonaws.com/order-api:1.0.0
docker push \
123456789012.dkr.ecr.ap-northeast-1.amazonaws.com/order-api:1.0.0
生产上建议开启镜像扫描(scan on push)和生命周期策略,例如只保留最近 10 个镜像,自动清理旧版本,避免仓库无限膨胀。ECR 的 URI 就是任务定义中 image 字段的值。
三、ECS 任务定义与服务定义
任务定义(Task Definition)描述“一个容器长什么样”:镜像、CPU/内存、端口、环境变量、日志、执行角色。服务定义(Service)描述“要跑几个、怎么保持、怎么接流量”。
示例任务定义关键字段:
{
"family": "order-api",
"networkMode": "awsvpc",
"requiresCompatibilities": ["FARGATE"],
"cpu": "512",
"memory": "1024",
"executionRoleArn": "arn:aws:iam::123456789012:role/ecsTaskExecutionRole",
"containerDefinitions": [{
"name": "order-api",
"image": "123456789012.dkr.ecr.ap-northeast-1.amazonaws.com/order-api:1.0.0",
"portMappings": [{"containerPort": 8080, "protocol": "tcp"}],
"environment": [{"name": "NODE_ENV", "value": "production"}],
"logConfiguration": {
"logDriver": "awslogs",
"options": {
"awslogs-group": "/ecs/order-api",
"awslogs-region": "ap-northeast-1",
"awslogs-stream-prefix": "ecs"
}
}
}]
}
注意 networkMode 必须是 awsvpc,Fargate 只支持这种模式;executionRoleArn 用于拉取 ECR 镜像和写 CloudWatch Logs,不要和任务角色(task role)混淆——任务角色是给容器内代码访问 AWS 资源用的。
服务定义通过 aws ecs create-service 创建,核心参数包括:desiredCount(期望副本数)、launchType FARGATE、networkConfiguration(子网 + 安全组)、loadBalancers(挂 ALB 目标组)。示例:
aws ecs create-service \
--cluster prod-cluster \
--service-name order-api-svc \
--task-definition order-api:1 \
--desired-count 2 \
--launch-type FARGATE \
--network-configuration "awsvpcConfiguration={subnets=[subnet-0a1b2c3d],securityGroups=[sg-0e9f8a7b],assignPublicIp=DISABLED}" \
--load-balancers "targetGroupArn=arn:aws:elasticloadbalancing:...:targetgroup/order-api-tg,containerName=order-api,containerPort=8080"
下面这张图把“镜像构建 → ECR → 任务定义 → 服务 → Fargate 运行 → ALB 流量”的完整链路和关键参数关系画出来,便于对照理解各对象之间的依赖。
四、部署与验证
服务创建后,ECS 会按 desiredCount 拉起 Fargate 任务。观察部署状态:
aws ecs describe-services --cluster prod-cluster --services order-api-svc \
--query "services[0].{status:status,running:runningCount,desired:desiredCount,events:events[0:3]}"
当 runningCount 等于 desiredCount,且 ALB 目标组中两个目标都显示 healthy,说明部署成功。此时用 ALB DNS 访问:
curl http://order-api-alb-123456.ap-northeast-1.elb.amazonaws.com/health
# {"status":"ok","version":"1.0.0"}
如果任务反复重启,优先看 CloudWatch Logs 中 /ecs/order-api 的日志流,常见原因有三类:镜像拉取失败(execution role 缺 ECR 权限)、容器端口与任务定义不一致、健康检查路径返回非 200。排障顺序建议:先看任务停止原因(stoppedReason),再看日志,最后核对安全组和子网路由。
assignPublicIp=ENABLED,则任务需放在公有子网。五、更新与回滚
发布新版本时,构建并推送 order-api:1.0.1,注册新任务定义修订版,然后更新服务:
aws ecs update-service --cluster prod-cluster --service order-api-svc \
--task-definition order-api:2 --force-new-deployment
ECS 默认采用滚动更新,配合 ALB 健康检查实现零停机。若新版本异常,回滚只需把服务指回上一个任务定义修订版 order-api:1,再执行一次 update-service。把镜像标签、任务定义修订号、服务名三者对应记录在发布单里,是生产运维中最实用的习惯。