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

CDK重构导致ECS任务失败:无法获取ECR注册表授权

问题:ECS Fargate任务无法拉取ECR镜像(超时错误)

为提升代码模块化程度,将CDK部署代码中的Task定义与FargateService逻辑迁移至独立的EcsService类后,堆栈部署时ECS任务无法拉取ECR镜像,报错信息如下:

Task stopped at: 2023-08-31T05:55:55.882Z
ResourceInitializationError: unable to pull secrets or registry auth: execution resource retrieval failed: unable to retrieve ecr registry auth: service call has been retried 3 time(s): RequestError: send request failed caused by: Post "https://api.ecr.us-east-1.amazonaws.com/": dial tcp 44.213.79.50:443: i/o timeout. Please check your task network configuration.

重构前后代码对比

重构前代码

securityGroup.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(3000));

// Validation
if (!envJSON.ssdDockerImageTag ) {
  throw new Error('Missing ssd-fe image tag.');
}
const cluster = new ecs.Cluster(this, "ssdCluster", { vpc });

// Define the task definition with a container using an image from ECR
const taskDefinition = new ecs.FargateTaskDefinition(this, 'ssdTaskDef');
const container = taskDefinition.addContainer('ssdContainer', {
  image: ecs.ContainerImage.fromEcrRepository(
    ecr.Repository.fromRepositoryName(this, 'ssdRepo', 'ssd-fe'),
    envJSON.ssdDockerImageTag),
  memoryLimitMiB: 512,
  cpu: 256,
  portMappings: [{
    containerPort: 3000
  }],
  environment: {
    NODE_ENV: "production",
    API_BASE_URL: api.url
  }
});

// Create the Fargate Service
const service = new ecs.FargateService(this, 'ssdService', {
  cluster,
  taskDefinition,
  desiredCount: 1,
  vpcSubnets: {
    subnetType: ec2.SubnetType.PUBLIC,
  },
  securityGroups: [securityGroup],
  assignPublicIp: true,
});

LoadBalancer.getInstance(this, 'LoadBalancer', {
  vpc,
  ecsService: service,
});

重构后代码

securityGroup.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(3000));

// Validation
if (!envJSON.ssdDockerImageTag ) {
  throw new Error('Missing ssd-fe image tag.');
}
const cluster = new ecs.Cluster(this, "ssdCluster", { vpc });

// Create ECS Service
const ecsService = new EcsService(this, 'ssdService', {
  vpc,
  securityGroup: securityGroup,
  cluster: cluster,
  repoName: 'ssd-fe',
  imageTag: envJSON.ssdDockerImageTag,
  environment: {
    NODE_ENV: "production",
    API_BASE_URL: api.url
  }
});

LoadBalancer.getInstance(this, 'LoadBalancer', {
  vpc,
  ecsService: ecsService.service,
});

// EcsService class
import * as cdk from 'aws-cdk-lib';
import * as ecs from 'aws-cdk-lib/aws-ecs';
import * as ec2 from 'aws-cdk-lib/aws-ec2';
import * as ecr from 'aws-cdk-lib/aws-ecr';
import * as logs from 'aws-cdk-lib/aws-logs';
import * as iam from 'aws-cdk-lib/aws-iam';
import { Construct } from 'constructs';

interface EcsServiceProps {
  vpc: ec2.IVpc;
  securityGroup: ec2.ISecurityGroup;
  cluster: ecs.ICluster;
  repoName: string;
  imageTag: string;
  environment?: { [key: string]: string };
}

export class EcsService extends Construct {
  public readonly service: ecs.FargateService;

  constructor(scope: Construct, id: string, props: EcsServiceProps) {
    super(scope, id);
    
    const ecrRepository = ecr.Repository.fromRepositoryName(this, `${id}Repo`, props.repoName);
    
    const taskDefinition = new ecs.FargateTaskDefinition(this, `${id}TaskDef`);
    taskDefinition.addContainer(`${id}Container`, {
      image: ecs.ContainerImage.fromEcrRepository(ecrRepository, props.imageTag),
      memoryLimitMiB: 512,
      cpu: 256,
      portMappings: [{ containerPort: 3000 }],
      environment: props.environment,
    });

    this.service = new ecs.FargateService(this, id, {
      cluster: props.cluster,
      taskDefinition,
      desiredCount: 1,
      vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC },
      securityGroups: [props.securityGroup],
    });
  }
}

IAM语句变更

┌───┬───────────────────────────────────────┬────────┬───────────────────────────────────────┬───────────────────────────────────────┬───────────┐
│   │ Resource                              │ Effect │ Action                                │ Principal                             │ Condition │
├───┼───────────────────────────────────────┼────────┼───────────────────────────────────────┼───────────────────────────────────────┼───────────┤
│ - │ *                                     │ Allow  │ ecr:GetAuthorizationToken             │ AWS:${ssdTaskDefExecutionRole469C7625 │           │
│   │                                       │        │                                       │ }                                     │           │
├───┼───────────────────────────────────────┼────────┼───────────────────────────────────────┼───────────────────────────────────────┼───────────┤
│ - │ arn:aws:ecr:us-east-1:533732470418:re │ Allow  │ ecr:BatchCheckLayerAvailability       │ AWS:${ssdTaskDefExecutionRole469C7625 │           │
│   │ pository/ssd-fe                       │        │ ecr:BatchGetImage                     │ }                                     │           │
│   │                                       │        │ ecr:GetDownloadUrlForLayer            │                                       │           │
├───┼───────────────────────────────────────┼────────┼───────────────────────────────────────┼───────────────────────────────────────┼───────────┤
│ + │ ${ssdService/ssdServiceTaskDef/Execut │ Allow  │ sts:AssumeRole                        │ Service:ecs-tasks.amazonaws.com       │           │
│   │ ionRole.Arn}                          │        │                                       │                                       │           │
├───┼───────────────────────────────────────┼────────┼───────────────────────────────────────┼───────────────────────────────────────┼───────────┤
│ + │ ${ssdService/ssdServiceTaskDef/TaskRo │ Allow  │ sts:AssumeRole                        │ Service:ecs-tasks.amazonaws.com       │           │
│   │ le.Arn}                               │        │                                       │                                       │           │
├───┼───────────────────────────────────────┼────────┼───────────────────────────────────────┼───────────────────────────────────────┼───────────┤
│ + │ *                                     │ Allow  │ ecr:GetAuthorizationToken             │ AWS:${ssdService/ssdServiceTaskDef/Ex │           │
│   │                                       │        │                                       │ ecutionRole}                          │           │
├───┼───────────────────────────────────────┼────────┼───────────────────────────────────────┼───────────────────────────────────────┼───────────┤
│ + │ arn:aws:ecr:us-east-1:533732470418:re │ Allow  │ ecr:BatchCheckLayerAvailability       │ AWS:${ssdService/ssdServiceTaskDef/Ex │           │
│   │ pository/ssd-fe                       │        │ ecr:BatchGetImage                     │ ecutionRole}                          │           │
│   │                                       │        │ ecr:GetDownloadUrlForLayer            │                                       │           │
└───┴───────────────────────────────────────┴────────┴───────────────────────────────────────┴───────────────────────────────────────┴───────────┘

已尝试的解决方法

在EcsService类中显式添加执行角色权限,但问题未解决:

// Create an execution role
const executionRole = new iam.Role(this, 'ExecutionRole', {
  assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'),
});

// Grant permissions to the execution role to pull from ECR
executionRole.addToPolicy(new iam.PolicyStatement({
  actions: [
    'ecr:GetAuthorizationToken'
  ],
  resources: ['*'],
}));

const ecrRepository = ecr.Repository.fromRepositoryName(this, `${id}Repo`, props.repoName);

const taskDefinition = new ecs.FargateTaskDefinition(this, `${id}TaskDef`, {
  executionRole: executionRole
});

解决方案

核心问题:遗漏公网IP分配配置

重构前的FargateService配置包含assignPublicIp: true,但重构后的EcsService类中创建FargateService时遗漏了该参数。即使任务部署在公共子网,Fargate默认不会自动分配公网IP,导致任务无法访问ECR服务端(需要互联网连接)。

修复步骤

  1. 在EcsService类的FargateService配置中添加assignPublicIp: true:
this.service = new ecs.FargateService(this, id, {
  cluster: props.cluster,
  taskDefinition,
  desiredCount: 1,
  vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC },
  securityGroups: [props.securityGroup],
  assignPublicIp: true, // 新增此配置
});
  1. 验证安全组出站规则
    确保任务使用的安全组允许出站HTTPS(443端口)流量到互联网。默认安全组通常允许所有出站流量,若自定义了规则,需确认添加:
  • 出站规则:允许0.0.0.0/0的TCP 443端口流量
  1. IAM权限补充(可选)
    CDK的FargateTaskDefinition默认会自动创建带完整ECR拉取权限的执行角色,无需手动创建。若手动指定执行角色,需确保包含全部必要权限:
executionRole.addToPolicy(new iam.PolicyStatement({
  actions: [
    'ecr:GetAuthorizationToken',
    'ecr:BatchCheckLayerAvailability',
    'ecr:GetDownloadUrlForLayer',
    'ecr:BatchGetImage'
  ],
  resources: ['*'],
}));

建议优先依赖CDK自动生成的执行角色,避免手动配置遗漏。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.12 07:35:57