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/Fargate 容器微服务部署链路 Dockerfile 多阶段构建 node:20-alpine USER node / EXPOSE 8080 本地镜像 order-api:1.0.0 docker build / run 本地 8080 验证 Amazon ECR order-api 仓库 镜像扫描 + 生命周期 URI 供任务定义引用 任务定义 family: order-api cpu 512 / mem 1024 awsvpc / awslogs ECS 服务定义 desiredCount=2 · launchType=FARGATE 子网 subnet-0a1b2c3d · 安全组 sg-0e9f8a7b ALB 目标组 order-api-tg → 容器 8080 引用 task definition Fargate 任务(2 副本) order-api 容器 :8080 日志 → CloudWatch /ecs/order-api Application Load Balancer 监听 80/443 → order-api-tg 健康检查 /health 返回 200 调度启动 注册目标 用户 请求 镜像标签固定版本 → 任务定义引用 → 服务保持副本数 → 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),再看日志,最后核对安全组和子网路由。

Fargate 任务默认没有公网 IP 时,必须放在私有子网并通过 NAT 网关出网,否则拉取 ECR 镜像会超时。若使用 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。把镜像标签、任务定义修订号、服务名三者对应记录在发布单里,是生产运维中最实用的习惯。