AWS ECS 使用 EC2 Capacity Provider (Managed Instances)
上篇 AWS ECS 使用 EC2 Capacity Provider (EC2 Auto Scaling) 学习了如何在 ECS 中使用 Capacity Provider + EC2 Auto Scaling 来部署一个简单的 Web 应用,以及了解 ECS 如何管理 EC2 的 Auto Scaling Group.
ECS Capacity Providers 是于 2019 年 12 月发布的,随同的功能支持了 Fargate, Fargate Spot, 和 EC2 AutoScaling. 而 Managed Instances 在 2025-09-30 才加入的新特性,见 Announcing Amazon ECS Managed Instances.
在近六年之间, AWS 大概也理解到了 Capacity Provider + EC2 Auto Scaling 的复杂性,虽说可由 ECS 来管理 EC2 的 ASG, 但毕竟有个 ASG 在那里。 ECS 与 EC2 ASG 之间联接由 CloudWatch Metrics, Alarms, EC2 ASG 的 Dynamic scaling policies 和 Lifecycle hooks 的一整套机制协作。 经常还不得不在 EC2 ASG 与 ECS 两个界面之间来回找问题。而 Managed Instances 的出现则将 EC2 实例的管理完全透明化了,在 EC2 端压根就不存在 一个相应的 ASG, 更不需要 CloudWatch Alarms 之类的关联组件。
Managed Instances 与 EC2 Auto Scaling 之间就好比 Serverless 与 非 Serverless 的区别,用 Managed Instances 之后你只管控制好 ECS Desired Count(ECS AutoScaling), 其余的都由 Managed Instances 来管理,在界面上只需要关注 ECS Infrastructure 中的 Container Instances. 由 Managed Instances 管理的 EC2 实例,你即使用有管理员权限都无法关闭它,只能全权由 Managed Instances 来控制。试图关闭这样的 EC2 实例时报错

相比于 ECS 直接用 EC2(ASG),通过 Capacity Provider 使用 EC2 的方式,还有一个好处就是可以实现 Min and max running tasks:
100% min and 200% max 的部署方式,即启动与当前等量的任务来替换全部旧任务,即使是高峰期也能较安心的部署。而 ECS 直接用 EC2 的时只能实现
Min and max running tasks: max 最大 100%, 所以不得不在 Rolling update, Blue/green, Canary 和 Linear 之间选择。
Managed Instances 可以配置 vCPU, Memory, GPU 等约束条件,在运行时由 Capacity Provider 自动为你选择 EC2 实例类型. 而对于计算型的任务 还是较倾向于直接锁定 EC2 的实例类型, 如 AMD CPU 的 c8a.4xlarge 等。
下面是一个 Web 应用部署实例,使用 t3.medium 实例类型,它有 2 vCPU, 4 GiB 内存, Web 任务需要 1 vCPU, 1 GiB 内存,这样实际在一个
t3.medium 实例上可以运行 2 个 Web 任务。
下面是相关的 Terraform 脚本如下
capacity-provider.tf
1resource "aws_ecs_capacity_provider" "ec2" {
2 name = "ec2-capacity-provider"
3 cluster = aws_ecs_cluster.main.name
4
5 managed_instances_provider {
6 infrastructure_role_arn = aws_iam_role.ecs_infrastructure.arn
7 propagate_tags = "CAPACITY_PROVIDER"
8
9 infrastructure_optimization {
10 scale_in_after = 300
11 }
12 instance_launch_template {
13 ec2_instance_profile_arn = var.ecs_instance_profile_arn
14 storage_configuration {
15 storage_size_gib = 30
16 }
17
18 network_configuration {
19 subnets = var.subnet_ids
20 security_groups = var.ec2_security_groups
21 }
22
23 instance_requirements {
24 vcpu_count {
25 min = 2
26 }
27
28 memory_mib {
29 min = 4096
30 }
31 allowed_instance_types = ["t3.medium"]
32 burstable_performance = "included"
33 }
34 }
35 }
36 tags = {
37 Name = "test-cp"
38 }
39}
40
41data "aws_iam_policy_document" "ecs_infra_trust" {
42 statement {
43 actions = ["sts:AssumeRole"]
44 effect = "Allow"
45 principals {
46 type = "Service"
47 identifiers = ["ecs.amazonaws.com"]
48 }
49 }
50}
51
52resource "aws_iam_role" "ecs_infrastructure" {
53 name = "ECSInfraRole"
54 assume_role_policy = data.aws_iam_policy_document.ecs_infra_trust.json
55}
56
57resource "aws_iam_role_policy_attachment" "ecs_infrastructure" {
58 role = aws_iam_role.ecs_infrastructure.name
59 policy_arn = "arn:aws:iam::aws:policy/AmazonECSInfrastructureRolePolicyForManagedInstances"
60}
通过 allowed_instance_types = ["t3.medium"] 来锁定使用 t3.medium 类型,注意 cpu 和 memory 的配置不能与此有冲突,并且 t3.medium
是 burstable 的类型, 所以必须加上 burstable_performance = "included" 才能选择到.
infrastructure_role_arn 需要指定为一个有 arn:aws:iam::aws:policy/AmazonECSInfrastructureRolePolicyForManagedInstances
policy, 能被 ecs.amazonaws.com assume 的 IAM role 即可。
使用 Managed Instances 时 EC2 除了可以指定 Security group, subnets, storage 外,其他都几乎都不能自定义。比如 AMI 是由
Managed Instances 指定的,像 ecs-managed-instances-standard-x86_64-20260220222634, 它是专门优化的,并且 EC2 会每 14
天被自动更新一次,来保证安全和性能的优化。
ecs.tf
1resource "aws_ecs_cluster" "main" {
2 name = "test-cp"
3 setting {
4 name = "containerInsights"
5 value = "enabled"
6 }
7}
8
9resource "aws_ecs_service" "main" {
10 name = "my-service"
11 cluster = aws_ecs_cluster.main.id
12 task_definition = aws_ecs_task_definition.main.arn
13 desired_count = 1
14 network_configuration {
15 subnets = var.subnet_ids
16 security_groups = var.ec2_security_groups
17 }
18
19 capacity_provider_strategy {
20 capacity_provider = aws_ecs_capacity_provider.ec2.name
21 base = 1
22 weight = 1
23 }
24
25 load_balancer {
26 target_group_arn = aws_lb_target_group.main.arn
27 container_name = "my-container"
28 container_port = 80
29 }
30}
31
32resource "aws_ecs_task_definition" "main" {
33 family = "my-task"
34 requires_compatibilities = ["EC2"]
35 network_mode = "awsvpc"
36
37 container_definitions = jsonencode([
38 {
39 name = "my-container"
40 image = "strm/helloworld-http"
41 cpu = 1024
42 memory = 1024
43 stopTimeout = 5
44 portMappings = [
45 {
46 containerPort = 80
47 hostPort = 80
48 protocol = "tcp"
49 }
50 ]
51 }
52 ])
53}
54
55resource "aws_appautoscaling_target" "ecs_target" {
56 max_capacity = 10
57 min_capacity = 1
58 resource_id = "service/${aws_ecs_cluster.main.name}/${aws_ecs_service.main.name}"
59 scalable_dimension = "ecs:service:DesiredCount"
60 service_namespace = "ecs"
61}
Network 采用了 vpc, 每个容器将会从 VPC 获得自己的 IP 地址,Security Group 更易配置,初始启动一个任务。实际项目中应创建 ECS 的 AutoScaling
规则来控制 Desired Count, 后面测试将手工来调节。
elb.tf
1resource "aws_lb" "test-cp" {
2 name = "test-cp-lb"
3 internal = true
4 subnets = var.subnet_ids
5 security_groups = var.elb_security_groups
6
7}
8
9resource "aws_lb_listener" "main" {
10 port = 80
11 protocol = "HTTP"
12 load_balancer_arn = aws_lb.test-cp.arn
13 default_action {
14 type = "forward"
15 target_group_arn = aws_lb_target_group.main.arn
16 }
17}
18
19resource "aws_lb_target_group" "main" {
20 name = "test-cp-tg"
21 port = 80
22 protocol = "HTTP"
23 vpc_id = var.vpc_id
24 target_type = "ip"
25}
单纯测试 Managed Instances 类型的 Capacity Provider 可以不用建立 ELB, 作为一个较完备的应用还是加上这一层。
在运行以上的 Terraform 脚本时,请填入对应环境的以下变量
1 subnet_ids
2 ecs_instance_profile_arn
3 elb_security_groups
4 ec2_security_groups
5 vpc_id
准备好 AWS Credentials, 再补上 AWS Provider 后, 运行
1terraform init
2terraform apply-auto-approve
成功后会产生整个架构。在 ECS cluster test-cp 的 Infrastructure 将会看到

使用了 Managed Instances 的 Capacity Provider 的管理界面就只要这个就行了。对于相应 EC2 的管理十分有限,不能指定 key_name,
也没有 Session Manager 可以连,不能自定义 userdata, 可以说 Managed Instances 产生的 EC2 是无法管理的。
由 Managed Instances 生成的 EC2 的 userdata 内容为
1[settings.ecs]
2cluster = 'arn:aws:ecs:us-east-1:123456789000:cluster/test-cp'
3awsvpc-block-imds = true
4[settings.two]
5platform-revision = 'Linux-X86_64-1.0.0-55'
6available-memory = 3530
下面是 Capacity Provider 的界面

下面直接改变 ECS Service my-service 的 Desired Count
- Desired Count 为 2: 因现有
t3.medium实例上还有充足的 CPU/Memory 资源,所以直接在其上运行一个新任务
- Desired Count 为 3:由于现有实例 CPU 资源不足,所以必须新启一个 EC2 实例来运行新的任务

- Desired Count 为 4 时又会在新的 EC2 实例上运行一个新任务, 这里就不再往测试了
- 现在把 Desired Count 改为 2:发现从运行两个 Task 的 EC2 上停掉了一个任务,而不是从运行一个任务的 EC2 实例上停掉任务

再等了很久,也是用两个 EC2 实例各自运行一个 Task, 从资源来讲有些浪费, 看来还是优先考虑性能,也不会把一个任务从一个 EC2 实例上挪到另一个去。
- 把 Desired Count 改为 1,

那么什么时候会把空闲的 EC2 实例关闭呢?大概等了七八分钟,没有运行任务的 EC2 上出现 Deregistrating, 并且一两秒间从该列表中消失了。

剩下的事情就是 Capacity Provider 把该 EC2 实例也结束掉。
最后只省下一个 EC2 实例

而且这个是更老的 EC2 实例,Capacity Provider` 优先关闭新的 Task.
永久链接 https://yanbin.blog/aws-ecs-capacity-provider-managed-instances/, 来自 隔叶黄莺 Yanbin's Blog[版权声明]
本文采用 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0) 进行许可。