为何切换到公有子网后ECS任务无法拉取ECR镜像?
ECS任务在公有子网无法拉取ECR镜像问题排查
问题背景
ECS任务在带NAT网关的私有子网中可正常拉取ECR镜像,切换到公有子网后无法访问Docker镜像,触发AWS报错。以下是相关组件的Terraform配置:
VPC配置
locals { region = "eu-central-1" // frankfurt } // define AWS VPC module "vpc" { source = "terraform-aws-modules/vpc/aws" version = "~> 5.0" name = "staging" cidr = "172.30.16.0/21" azs = ["${local.region}a", "${local.region}b", "${local.region}c"] public_subnets = ["172.30.16.0/24", "172.30.17.0/24", "172.30.18.0/24"] enable_vpn_gateway = false enable_dns_support = true enable_dns_hostnames = true enable_nat_gateway = false } // identify our central AWS TGW data "aws_ec2_transit_gateway" "this" { id = "tgw-0f6f58192xxxxxx" } resource "aws_ec2_transit_gateway_vpc_attachment" "this" { subnet_ids = module.vpc.public_subnets transit_gateway_id = data.aws_ec2_transit_gateway.this.id vpc_id = module.vpc.vpc_id } resource "aws_route" "name" { depends_on = [ aws_ec2_transit_gateway_vpc_attachment.this, ] route_table_id = module.vpc.public_route_table_ids[0] gateway_id = data.aws_ec2_transit_gateway.this.id destination_cidr_block = "192.168.1.0/24" }
ECR配置
locals { region = "eu-central-1" } resource "aws_ecr_repository" "app-backend" { name = "backend" }
ECS及负载均衡配置
locals { docker_registry = "redacted.dkr.ecr.eu-central-1.amazonaws.com/backend" backend_image = "3" region = "eu-central-1" prefix = "backend" } resource "aws_ecs_service" "backend" { name = local.prefix cluster = aws_ecs_cluster.backend.id task_definition = aws_ecs_task_definition.backend.arn launch_type = "FARGATE" desired_count = 1 network_configuration { subnets = data.terraform_remote_state.vpc.outputs.public_subnets security_groups = [aws_security_group.backend_security_group.id] } load_balancer { target_group_arn = tolist(module.alb.target_group_arns)[0] container_name = local.prefix container_port = 3000 } } resource "aws_ecs_cluster" "backend" { name = "${local.prefix}-cluster" } resource "aws_security_group" "backend_security_group" { name = "allow_all_traffic" description = "Allow outbound traffic to ECR registry" vpc_id = data.terraform_remote_state.vpc.outputs.vpc_id ingress { from_port = 0 to_port = 0 protocol = "-1" 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_iam_role" "ecs_execution_role" { name = "${local.prefix}-ecs-execution-role" assume_role_policy = data.aws_iam_policy_document.ecs_execution_role_assume_policy.json } data "aws_iam_policy_document" "ecs_execution_role_assume_policy" { statement { actions = ["sts:AssumeRole"] principals { type = "Service" identifiers = [ "ecs-tasks.amazonaws.com", ] } } } data "aws_iam_policy_document" "ecs_execution_role" { statement { actions = [ "ecr:GetAuthorizationToken", "ecr:BatchCheckLayerAvailability", "ecr:GetDownloadUrlForLayer", "ecr:BatchGetImage", "logs:CreateLogStream", "logs:PutLogEvents" ] effect = "Allow" resources = ["*"] } } resource "aws_iam_role_policy" "ecs_execution_role_iam_policy" { name = "${local.prefix}-ecs-execution-role" role = aws_iam_role.ecs_execution_role.id policy = data.aws_iam_policy_document.ecs_execution_role.json } resource "aws_iam_role_policy_attachment" "ecs_execution_role_ecr_policy" { role = aws_iam_role.ecs_execution_role.name policy_arn = "arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryReadOnly" } resource "aws_ecs_task_definition" "backend" { family = local.prefix requires_compatibilities = ["FARGATE"] network_mode = "awsvpc" cpu = 2048 memory = 4096 task_role_arn = aws_iam_role.backend_task_role.arn execution_role_arn = aws_iam_role.ecs_execution_role.arn container_definitions = jsonencode([ { name = local.prefix image = "${local.docker_registry}:${local.backend_image}" essential = true cpu = 2048 memory = 4096 portMappings = [ { containerPort = 3000 hostPort = 3000 } ] logConfiguration = { logDriver = "awslogs" options = { awslogs-group = aws_cloudwatch_log_group.backend_ecs_task_log_group.name awslogs-region = local.region awslogs-stream-prefix = "stream" } } environment : [] } ]) runtime_platform { cpu_architecture = "X86_64" operating_system_family = "LINUX" } } resource "aws_cloudwatch_log_group" "backend_ecs_task_log_group" { name = "${local.prefix}-ecs-task-log-group" } # Load Balancer module "alb_security_group" { source = "terraform-aws-modules/security-group/aws" version = "~> 4.0" name = "${local.prefix}-alb-security-group" vpc_id = data.terraform_remote_state.vpc.outputs.vpc_id ingress_with_cidr_blocks = [ { from_port = 80 to_port = 80 protocol = "TCP" description = "HTTP access from the internet" cidr_blocks = "0.0.0.0/0" }, { from_port = 443 to_port = 443 protocol = "TCP" description = "HTTPS access from the internet" cidr_blocks = "0.0.0.0/0" } ] egress_with_cidr_blocks = [ { from_port = 0 to_port = 0 protocol = "-1" cidr_blocks = "0.0.0.0/0" } ] } module "alb" { source = "terraform-aws-modules/alb/aws" version = "~> 8.0" name = "app-staging-alb" load_balancer_type = "application" vpc_id = data.terraform_remote_state.vpc.outputs.vpc_id subnets = data.terraform_remote_state.vpc.outputs.public_subnets security_groups = [module.alb_security_group.security_group_id] target_groups = [ { name = "app-staging-alb-tg" backend_protocol = "HTTP" backend_port = 3000 target_type = "ip" health_check = { path = "/" healthy_threshold = 3 interval = 60 unhealthy_threshold = 10 } } ] http_tcp_listeners = [ { port = 80 protocol = "HTTP" target_group_index = 0 } ] } resource "aws_iam_role" "backend_task_role" { name = "${local.prefix}-task-role" assume_role_policy = data.aws_iam_policy_document.backend_app_assume.json } resource "aws_iam_role_policy" "backend_app_iam_policy" { role = aws_iam_role.backend_task_role.id policy = data.aws_iam_policy_document.backend_app_permissions.json } resource "aws_iam_instance_profile" "backend_instances_profile" { name = "${local.prefix}-instances-profile" role = aws_iam_role.backend_task_role.name } data "aws_iam_policy_document" "backend_app_assume" { statement { actions = ["sts:AssumeRole"] principals { type = "Service" identifiers = [ "ec2.amazonaws.com", "eks-fargate-pods.amazonaws.com", "ecs-tasks.amazonaws.com", ] } } } data "aws_iam_policy_document" "backend_app_permissions" { statement { actions = [ "s3:*", ] resources = ["*"] } }
问题排查与解决方案
核心问题分析
- 公有子网路由表缺失互联网默认路由:当前仅给第一个公有路由表添加了TGW路由,其余两个公有子网的路由表无互联网访问路径;且未配置指向互联网网关(IGW)的
0.0.0.0/0默认路由。 - Fargate任务未分配公网IP:公有子网中的Fargate任务需要公网IP才能直接访问ECR等互联网服务,当前配置未开启该选项。
修复步骤
添加公有子网默认路由
为所有公有路由表添加指向IGW的默认路由,确保互联网访问路径:resource "aws_route" "public_default" { count = length(module.vpc.public_route_table_ids) route_table_id = module.vpc.public_route_table_ids[count.index] destination_cidr_block = "0.0.0.0/0" gateway_id = module.vpc.internet_gateway_id }开启Fargate任务公网IP分配
在ECS服务的network_configuration中添加assign_public_ip = true:resource "aws_ecs_service" "backend" { // 其他配置保持不变 network_configuration { subnets = data.terraform_remote_state.vpc.outputs.public_subnets security_groups = [aws_security_group.backend_security_group.id] assign_public_ip = true // 添加此行 } // 其他配置保持不变 }统一TGW路由配置
为所有公有路由表添加TGW路由,确保内部网络访问:resource "aws_route" "tgw_public" { count = length(module.vpc.public_route_table_ids) route_table_id = module.vpc.public_route_table_ids[count.index] gateway_id = data.aws_ec2_transit_gateway.this.id destination_cidr_block = "192.168.1.0/24" depends_on = [aws_ec2_transit_gateway_vpc_attachment.this] }
验证方法
应用Terraform配置后,重新启动ECS任务,通过AWS控制台查看任务状态,或检查CloudWatch日志确认镜像拉取是否成功。
内容的提问来源于stack exchange,提问作者finn
相关产品推荐
相关产品推荐

