容器服务 — CodeRoadMap容器服务
这一课你将能做到
- 把本站 Spring Boot 应用打包成 Docker 镜像,推送到 ECR 仓库
- 在 ECS Fargate 上运行容器化应用,配置任务定义、服务、自动扩缩容
- 说出 ECS、EKS、Fargate 的区别,并能选出适合本站场景的容器方案
为什么要学
容器化是现代后端的标准交付方式。本站如果迁到 AWS,Spring Boot 应用打包成 Docker 镜像后,可以在 ECS/EKS/Fargate 上运行,实现「一次打包、到处运行」。面试中「Docker 怎么打包」「ECS 和 K8s 怎么选」「Fargate 和 EC2 启动类型区别」是高频问题。
人话:Docker 是「集装箱」,把应用和依赖打包在一起,不管在哪台服务器上都能跑。ECS 是 AWS 的「集装箱码头」,负责调度这些集装箱。Fargate 是「无人码头」,连码头工人(服务器)都不需要你管。
先建立图像
AWS 容器服务对比:
┌─────────────────────────────────────────────────────────────┐
│ AWS 容器服务选型 │
├─────────────────┬─────────────────┬─────────────────────────┤
│ ECS │ EKS │ Fargate │
│ │ │ │
│ AWS 托管的 │ AWS 托管的 │ Serverless 容器运行时 │
│ Docker 编排 │ Kubernetes │ 无服务器,无节点管理 │
│ │ │ │
│ 上手简单 │ 功能最全 │ 可配给 ECS 或 EKS │
│ 与 AWS 集成好 │ 社区生态大 │ 按容器资源计费 │
│ │ │ │
│ 适合:简单应用 │ 适合:复杂微服务│ 适合:不想管服务器的场景 │
│ 快速容器化 │ 多团队多语言 │ │
└─────────────────┴─────────────────┴─────────────────────────┘
ECS + Fargate 架构图:
┌─────────────────────────────────────────┐
│ VPC │
│ ┌─────────────┐ ┌─────────────┐ │
│ │ 公有子网 A │ │ 公有子网 B │ │
│ │ │ │ │ │
│ │ ALB │────│ ALB │ │
│ └──────┬──────┘ └──────┬──────┘ │
│ │ │ │
│ ┌──────┴──────┐ ┌──────┴──────┐ │
│ │ 私有子网 A │ │ 私有子网 B │ │
│ │ │ │ │ │
│ │ Fargate×2 │────│ Fargate×2 │ │
│ │ (容器任务) │ │ (容器任务) │ │
│ └─────────────┘ └─────────────┘ │
│ │ │ │
│ └───────┬───────────┘ │
│ │ │
│ ┌────┴────┐ │
│ │ RDS │ │
│ │(主/备) │ │
│ └─────────┘ │
└─────────────────────────────────────────┘
跟着做
步骤 1:打包本站 Spring Boot 应用
:
Dockerfile
# 第一阶段:构建
FROM maven:3.9-amazoncorretto-17 AS builder
WORKDIR /app
COPY pom.xml .
RUN mvn dependency:go-offline
COPY src ./src
RUN mvn clean package -DskipTests
# 第二阶段:运行
FROM amazoncorretto:17-alpine
WORKDIR /app
COPY --from=builder /app/target/*.jar app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-Xmx512m", "-jar", "app.jar"]
# 构建镜像
docker build -t code-nav-backend:latest .
# 本地测试
docker run -p 8080:8080 -e DB_PASSWORD=secret code-nav-backend:latest
步骤 2:创建 ECR 仓库并推送镜像
# 创建仓库
aws ecr create-repository --repository-name code-nav-backend --image-scanning-configuration scanOnPush=true --encryption-configuration encryptionType=AES256
# 获取登录命令
aws ecr get-login-password --region ap-southeast-1 | docker login --username AWS --password-stdin 123456789012.dkr.ecr.ap-southeast-1.amazonaws.com
# 打标签
$ACCOUNT_ID = aws sts get-caller-identity --query Account --output text
docker tag code-nav-backend:latest "${ACCOUNT_ID}.dkr.ecr.ap-southeast-1.amazonaws.com/code-nav-backend:latest"
# 推送
docker push "${ACCOUNT_ID}.dkr.ecr.ap-southeast-1.amazonaws.com/code-nav-backend:latest"
步骤 3:创建 ECS 集群(Fargate 类型)
aws ecs create-cluster --cluster-name codenav-cluster --settings Name=containerInsights,Value=enabled --capacity-providers FARGATE,FARGATE_SPOT --default-capacity-provider-strategy capacityProvider=FARGATE,weight=1 capacityProvider=FARGATE_SPOT,weight=3
步骤 4:创建任务定义
{
"family": "code-nav-task",
"networkMode": "awsvpc",
"requiresCompatibilities": ["FARGATE"],
"cpu": "512",
"memory": "1024",
"executionRoleArn": "arn:aws:iam::123456789012:role/ecsTaskExecutionRole",
"taskRoleArn": "arn:aws:iam::123456789012:role/ecsTaskRole",
"containerDefinitions": [
{
"name": "code-nav-backend",
"image": "123456789012.dkr.ecr.ap-southeast-1.amazonaws.com/code-nav-backend:latest",
"portMappings": [
{
"containerPort": 8080,
"protocol": "tcp"
}
],
"environment": [
{"name": "SPRING_PROFILES_ACTIVE", "value": "aws"},
{"name": "DB_HOST", "value": "codenav-mysql.xxxxxxxx.ap-southeast-1.rds.amazonaws.com"}
],
"secrets": [
{
"name": "DB_PASSWORD",
"valueFrom": "arn:aws:secretsmanager:ap-southeast-1:123456789012:secret:codenav/db/password-xxxxxx"
}
],
"logConfiguration": {
"logDriver": "awslogs",
"options": {
"awslogs-group": "/ecs/code-nav",
"awslogs-region": "ap-southeast-1",
"awslogs-stream-prefix": "ecs"
}
}
}
]
}
aws ecs register-task-definition --cli-input-json file://task-definition.json
步骤 5:创建 ECS 服务
# 创建服务(2 个任务,跨两个 AZ)
aws ecs create-service --cluster codenav-cluster --service-name code-nav-service --task-definition code-nav-task --desired-count 2 --launch-type FARGATE --network-configuration "awsvpcConfiguration={subnets=[subnet-private1a,subnet-private1b],securityGroups=[sg-appxxxx],assignPublicIp=DISABLED}" --load-balancers targetGroupArn=arn:aws:elasticloadbalancing:ap-southeast-1:123456789012:targetgroup/codenav-tg/xxxxxxxx,containerName=code-nav-backend,containerPort=8080 --health-check-grace-period-seconds 60
步骤 6:配置自动扩缩容
# 注册可扩展目标
aws application-autoscaling register-scalable-target --service-namespace ecs --resource-id service/codenav-cluster/code-nav-service --scalable-dimension ecs:service:DesiredCount --min-capacity 2 --max-capacity 10
# 配置目标追踪策略(CPU > 70% 扩容)
aws application-autoscaling put-scaling-policy --service-namespace ecs --resource-id service/codenav-cluster/code-nav-service --scalable-dimension ecs:service:DesiredCount --policy-name cpu-autoscaling --policy-type TargetTrackingScaling --target-tracking-scaling-policy-configuration file://scaling-policy.json
{
"PredefinedMetricSpecification": {
"PredefinedMetricType": "ECSServiceAverageCPUUtilization"
},
"TargetValue": 70.0,
"ScaleOutCooldown": 300,
"ScaleInCooldown": 300
}
步骤 7:更新部署(蓝绿/滚动)
# 更新任务定义镜像版本,重新注册
aws ecs update-service --cluster codenav-cluster --service-name code-nav-service --task-definition code-nav-task --force-new-deployment
讲清楚要点
- Fargate 的计费方式:按 vCPU 和内存的使用时长计费(每秒计费,最少 1 分钟)。不需要预留 EC2,不存在资源浪费。但如果任务长期满载,可能比 EC2 模式贵。
- ECS 任务定义是核心:它定义了容器镜像、资源、环境变量、日志、健康检查、任务角色。版本管理靠 family:revision(如
code-nav-task:3)。
- 任务角色 vs 执行角色:执行角色(Execution Role)是 ECS 代理用的,用来拉镜像、写日志、读 Secrets。任务角色(Task Role)是容器内应用用的,用来访问 AWS 服务(如 S3、DynamoDB)。
- 健康检查:ECS 支持容器级健康检查(Docker Health Check)和服务级健康检查(Target Group)。两者结合,确保只有健康的容器接收流量。
- Fargate Spot:用 Spot 容量运行容器,便宜但可能被中断。适合无状态任务,不适合数据库或长时间计算。ECS 支持混合策略(70% Spot + 30% On-Demand)。
你可能会懵的地方
-
ECS 和 EKS 怎么选?
ECS 上手简单、与 AWS 集成好、成本低。EKS 是 Kubernetes,社区生态大、多厂商通用、适合复杂微服务。本站如果是简单 Spring Boot 应用,用 ECS Fargate 足够。如果有多个团队、多种语言、需要服务网格,选 EKS。
-
Fargate 和 EC2 启动类型怎么选?
Fargate:不想管服务器,按任务计费,适合波动大、不想运维的场景。EC2 启动类型:自己管理 ECS 节点,控制成本,适合长期满载、需要 GPU/特殊硬件的场景。
-
容器怎么访问 RDS?
Fargate 任务在 VPC 的私有子网,通过 VPC Endpoint 或 NAT Gateway 访问 RDS。安全组规则:允许 ECS 任务安全组访问 RDS 安全组的 3306 端口。
-
ECS 的日志在哪里看?
配置 awslogs 驱动后,日志自动写入 CloudWatch Logs。也可以配置 Fluent Bit 转发到第三方日志系统(如 Elasticsearch、Datadog)。
-
Docker Compose 能直接部署到 ECS 吗?
能。用 docker compose up + ECS 上下文(docker context create ecs),可以直接把 compose 文件转成 ECS 任务定义。适合开发测试,生产建议用 CloudFormation/Terraform。
错法对照
| 错法 | 问题 | 正解 |
|---|
| 把数据库密码写在环境变量 | 泄露风险,且修改需重新部署 | 用 AWS Secrets Manager + 任务定义的 secrets 字段 |
| 任务定义内存设太小 | OOM 被强制杀掉 | Spring Boot 至少 1GB,加 -Xmx 限制 |
| Fargate 任务分配公网 IP | 安全问题,且 VPC 内不需要 | 私有子网 + NAT Gateway 出网 |
| 单任务单 AZ 部署 | AZ 故障时服务中断 | 至少跨两个 AZ,DesiredCount >= 2 |
| 不配置健康检查 | 容器假死还在接收流量 | 配 Docker Health Check + Target Group 健康检查 |
卡点急救
- 任务启动后状态 PENDING 很久:通常是 ECR 镜像拉取失败(权限问题)、VPC 没配 NAT Gateway(无法拉镜像)、或任务定义的资源超过 Fargate 限制(Fargate 最大 4 vCPU / 30 GB)。
- 应用日志看不到:检查任务定义里的
logConfiguration 是否配置了 awslogs;检查执行角色是否有 logs:CreateLogStream 和 logs:PutLogEvents 权限。
- ECS 服务扩缩容不生效:检查 Application Auto Scaling 是否注册成功;检查 CloudWatch Alarm 是否正确;检查 IAM 角色是否有
application-autoscaling:* 权限。
面试常问
-
ECS、EKS、Fargate 的区别?
ECS 是 AWS 自研的容器编排服务,与 AWS 服务集成好,上手简单。EKS 是托管 Kubernetes,兼容 K8s 生态,适合复杂微服务。Fargate 是 Serverless 容器运行时,可以配给 ECS 或 EKS 使用,不需要管理节点。本站简单 Spring Boot 应用选 ECS Fargate。
-
Docker 多层构建(Multi-stage Build)的好处?
减少最终镜像大小,减少攻击面(不暴露构建工具和源码),提高部署速度。构建阶段用完整的 JDK + Maven,运行阶段只用 JRE。
-
ECS 服务发现怎么做?
用 AWS Cloud Map 或 Service Connect。Cloud Map 提供 DNS 服务发现,Service Connect 是更现代的方案,自动配置 Envoy Sidecar,支持流量管理和可观测性。
自测三问
- 任务角色(Task Role)和执行角色(Execution Role)分别给什么用的?
- 为什么 Fargate 任务不需要管理 EC2 实例?这对运维有什么好处?
- ECS Fargate 的内存和 CPU 怎么选?如果 Spring Boot 启动报 OOM,怎么调?
小结一句话
ECS 是 AWS 容器编排的核心,Fargate 免去服务器管理,任务定义定义一切,任务角色管权限,跨 AZ 部署保高可用。
延伸