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服务端(需要互联网连接)。
修复步骤
- 在
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, // 新增此配置 });
- 验证安全组出站规则
确保任务使用的安全组允许出站HTTPS(443端口)流量到互联网。默认安全组通常允许所有出站流量,若自定义了规则,需确认添加:
- 出站规则:允许
0.0.0.0/0的TCP 443端口流量
- IAM权限补充(可选)
CDK的FargateTaskDefinition默认会自动创建带完整ECR拉取权限的执行角色,无需手动创建。若手动指定执行角色,需确保包含全部必要权限:
executionRole.addToPolicy(new iam.PolicyStatement({ actions: [ 'ecr:GetAuthorizationToken', 'ecr:BatchCheckLayerAvailability', 'ecr:GetDownloadUrlForLayer', 'ecr:BatchGetImage' ], resources: ['*'], }));
建议优先依赖CDK自动生成的执行角色,避免手动配置遗漏。
内容的提问来源于stack exchange,提问作者Pankaj Jangid
相关产品推荐
相关产品推荐

