You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Terraform部署ECS任务报错STOPPED:CannotPullECRContainerError权限问题

问题描述

我正尝试通过Terraform部署网站容器,所有流程均正常执行,但任务最终失败并处于STOPPED状态,报错信息为CannotPullECRContainerError: AccessDeniedException。以下是我的Terraform脚本:

# Specify the provider and access details
provider "aws" {
  region = "${var.aws_region}"
  access_key = "${var.access_key}"
  secret_key = "${var.secret_key}"
}
## EC2
### Network
data "aws_availability_zones" "available" {}
resource "aws_vpc" "main" {
  cidr_block = "10.10.0.0/16"
}
resource "aws_subnet" "main" {
  count = "${var.az_count}"
  cidr_block = "${cidrsubnet(aws_vpc.main.cidr_block, 8, count.index)}"
  availability_zone = "${data.aws_availability_zones.available.names[count.index]}"
  vpc_id = "${aws_vpc.main.id}"
}
resource "aws_internet_gateway" "gw" {
  vpc_id = "${aws_vpc.main.id}"
}
resource "aws_route_table" "r" {
  vpc_id = "${aws_vpc.main.id}"
  route {
    cidr_block = "0.0.0.0/0"
    gateway_id = "${aws_internet_gateway.gw.id}"
  }
}
resource "aws_route_table_association" "a" {
  count = "${var.az_count}"
  subnet_id = "${element(aws_subnet.main.*.id, count.index)}"
  route_table_id = "${aws_route_table.r.id}"
}
### Compute
resource "aws_autoscaling_group" "app" {
  name = "tf-website-asg"
  vpc_zone_identifier = ["${aws_subnet.main.*.id}"]
  min_size = "${var.asg_min}"
  max_size = "${var.asg_max}"
  desired_capacity = "${var.asg_desired}"
  launch_configuration = "${aws_launch_configuration.app.name}"
}
data "template_file" "cloud_config" {
  template = "${file(\"${path.module}/cloud-config.yml\")}"
  vars {
    aws_region = "${var.aws_region}"
    ecs_cluster_name = "${aws_ecs_cluster.main.name}"
    ecs_log_level = "info"
    ecs_agent_version = "latest"
    ecs_log_group_name = "${aws_cloudwatch_log_group.ecs.name}"
  }
}
data "aws_ami" "stable_coreos" {
  most_recent = true
  filter {
    name = "description"
    values = ["CoreOS Container Linux stable *"]
  }
  filter {
    name = "architecture"
    values = ["x86_64"]
  }
  filter {
    name = "virtualization-type"
    values = ["hvm"]
  }
  owners = ["595879546273"] # CoreOS
}
resource "aws_launch_configuration" "app" {
  security_groups = [
    "${aws_security_group.instance_sg.id}",
  ]
  key_name = "${var.key_name}"
  image_id = "${data.aws_ami.stable_coreos.id}"
  instance_type = "${var.instance_type}"
  iam_instance_profile = "${aws_iam_instance_profile.app.name}"
  user_data = "${data.template_file.cloud_config.rendered}"
  associate_public_ip_address = true
  lifecycle {
    create_before_destroy = true
  }
}
### Security
resource "aws_security_group" "lb_sg" {
  description = "controls access to the application ELB"
  vpc_id = "${aws_vpc.main.id}"
  name = "tf-ecs-lbsg"
  ingress {
    protocol = "tcp"
    from_port = 80
    to_port = 80
    cidr_blocks = ["0.0.0.0/0"]
  }
  egress {
    from_port = 0
    to_port = 0
    protocol = "-1"
    cidr_blocks = [
      "0.0.0.0/0",
    ]
  }
}
resource "aws_security_group" "instance_sg" {
  description = "controls direct access to application instances"
  vpc_id = "${aws_vpc.main.id}"
  name = "tf-ecs-instsg"
  ingress {
    protocol = "tcp"
    from_port = 22
    to_port = 22
    cidr_blocks = [
      "${var.admin_cidr_ingress}",
    ]
  }
  ingress {
    protocol = "tcp"
    from_port = 80
    to_port = 80
    security_groups = [
      "${aws_security_group.lb_sg.id}",
    ]
  }
  egress {
    from_port = 0
    to_port = 0
    protocol = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }
}
## ECS
resource "aws_ecs_cluster" "main" {
  name = "terraform_website_ecs_cluster"
}
data "template_file" "task_definition" {
  template = "${file(\"${path.module}/task-definition.json\")}"
  vars {
    image_url = "xxxxx.dkr.ecr.eu-west-1.amazonaws.com/nginx:latest"
    container_name = "website"
    log_group_region = "${var.aws_region}"
    log_group_name = "${aws_cloudwatch_log_group.app.name}"
  }
}
resource "aws_ecs_task_definition" "website" {
  family = "tf_website_td"
  container_definitions = "${data.template_file.task_definition.rendered}"
}
resource "aws_ecs_service" "test" {
  name = "tf-ecs-website"
  cluster = "${aws_ecs_cluster.main.id}"
  task_definition = "${aws_ecs_task_definition.website.arn}"
  desired_count = 1
  iam_role = "${aws_iam_role.ecs_service.name}"
  load_balancer {
    target_group_arn = "${aws_alb_target_group.test.id}"
    container_name = "website"
    container_port = "80"
  }
  depends_on = [
    "aws_iam_role_policy.ecs_service",
    "aws_alb_listener.front_end",
  ]
}
## IAM
resource "aws_iam_role" "ecs_service" {
  name = "tf_website_ecs_role"
  assume_role_policy = <<EOF
{
  "Version": "2008-10-17",
  "Statement": [
    {
      "Sid": "",
      "Effect": "Allow",
      "Principal": {
        "Service": "ecs.amazonaws.com"
      },
      "Action": "sts:AssumeRole"
    }
  ]
}
EOF
}
resource "aws_iam_role_policy" "ecs_service" {
  name = "tf_website_ecs_policy"
  role = "${aws_iam_role.ecs_service.name}"
  policy = <<EOF
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "ec2:Describe*",
        "elasticloadbalancing:DeregisterInstancesFromLoadBalancer",
        "elasticloadbalancing:DeregisterTargets",
        "elasticloadbalancing:Describe*",
        "elasticloadbalancing:RegisterInstancesWithLoadBalancer",
        "elasticloadbalancing:RegisterTargets",
        "ecr:GetAuthorizationToken"
      ],
      "Resource": "*"
    }
  ]
}
EOF
}
resource "aws_iam_instance_profile" "app" {
  name = "tf-ecs-instprofile"
  role = "${aws_iam_role.app_instance.name}"
}
resource "aws_iam_role" "app_instance" {
  name = "tf-ecs-website-instance-role"
  assume_role_policy = <<EOF
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "",
      "Effect": "Allow",
      "Principal": {
        "Service": "ec2.amazonaws.com"
      },
      "Action": "sts:AssumeRole"
    }
  ]
}
EOF
}
data "template_file" "instance_profile" {
  template = "${file(\"${path.module}/instance-profile-policy.json\")}"
  vars {
    app_log_group_arn = "${aws_cloudwatch_log_group.app.arn}"
    ecs_log_group_arn = "${aws_cloudwatch_log_group.ecs.arn}"
  }
}
resource "aws_iam_role_policy" "instance" {
  name = "TfEcsInstanceRole"
  role = "${aws_iam_role.app_instance.name}"
  policy = "${data.template_file.instance_profile.rendered}"
}
## ALB
resource "aws_alb_target_group" "test" {
  name = "tf-website-ecs-website"
  port = 80
  protocol = "HTTP"
  vpc_id = "${aws_vpc.main.id}"
}
resource "aws_alb" "main" {
  name = "tf-website-alb-ecs"
  subnets = ["${aws_subnet.main.*.id}"]
  security_groups = ["${aws_security_group.lb_sg.id}"]
}
resource "aws_alb_listener" "front_end" {
  load_balancer_arn = "${aws_alb.main.id}"
  port = "80"
  protocol = "HTTP"
  default_action {
    target_group_arn = "${aws_alb_target_group.test.id}"
    type = "forward"
  }
}
## CloudWatch Logs
resource "aws_cloudwatch_log_group" "ecs" {
  name = "tf-ecs-group/ecs-agent"
}
resource "aws_cloudwatch_log_group" "app" {
  name = "tf-ecs-group/app-website"
}

解决方案

这个问题我之前也碰到过,核心原因是运行ECS任务的EC2实例没有足够权限拉取你的ECR镜像。虽然你的ECS服务角色配置了ecr:GetAuthorizationToken,但真正负责拉取镜像的是EC2实例上的ECS Agent,它使用的是EC2实例关联的IAM角色(也就是你配置的aws_iam_role.app_instance),这个角色目前缺少必要的ECR访问权限。

具体修复步骤

1. 给EC2实例角色补充ECR权限

修改你的instance-profile-policy.json文件,或者直接在Terraform的aws_iam_role_policy.instance资源中,添加以下ECR相关权限:

{
  "Version": "2012-10-17",
  "Statement": [
    // 保留你原本的CloudWatch日志相关权限
    {
      "Effect": "Allow",
      "Action": [
        "ecr:GetAuthorizationToken",
        "ecr:BatchCheckLayerAvailability",
        "ecr:GetDownloadUrlForLayer",
        "ecr:BatchGetImage"
      ],
      "Resource": "*"
    }
  ]
}

这四个权限是拉取ECR镜像的必备项:获取授权令牌、检查镜像层可用性、获取镜像层下载链接、批量获取镜像信息。

2. (可选)最小权限优化

如果想遵循最小权限原则,可以把Resource替换成你的ECR仓库的ARN,比如:

"Resource": "arn:aws:ecr:eu-west-1:xxxxx:repository/nginx"

测试阶段用*会更方便,后续再根据需求收紧权限即可。

3. 重新部署并验证

更新配置后,执行Terraform部署命令:

terraform plan
terraform apply

部署完成后,建议重启ECS服务或者替换Auto Scaling Group里的EC2实例,确保新的IAM权限生效。之后再查看ECS任务状态,应该就能正常拉取镜像启动了。

额外检查点

  • 确认你的ECR仓库资源策略没有拒绝该实例角色的访问(默认ECR仓库允许账户内所有IAM实体访问,除非你自定义了拒绝规则)。
  • 可以登录到EC2实例,执行curl http://169.254.169.254/latest/meta-data/iam/security-credentials/tf-ecs-website-instance-role,验证实例能否正常获取IAM角色凭证。

内容的提问来源于stack exchange,提问作者Wanaoishi

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.15 06:57:08