我正在尝试使用 terraform 部署一个用于 Rest api 的容器以及一个 postgres 容器。 ECS 任务定义指定可用的容器映像是公共 ECR 存储库。当我应用以下 terrafrom 时,我收到错误:
CannotPullContainerError: pull image manifest has been retried 5 time(s): failed to resolve ref public.ecr.aws/j0m1s7l0/la-client-api:latest: failed to do request: Head "https://public.ecr.aws/v2/j0m1s7l0/la-client-api/manifests/latest": dial tcp 99.83.145.10:443: i/o timeout
如果我访问 public.ecr.aws/j0m1s7l0/la-client-api:latest 我收到错误:
{"errors":[{"code":"DENIED","message":"Not Authorized"}]}
。现在我不确定这是否是存储库无法公开访问的问题,或者ECS服务是否由于网络设置而无法拉取镜像。我添加了NAT网关,因为ECS服务的子网是私有的。我现在花了两天时间,没有弄清楚 terraform 模板中的问题是什么。非常感谢任何帮助识别问题的帮助。非常感谢
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 4.16"
}
}
required_version = ">= 1.2.0"
}
provider "aws" {
region = "us-east-1"
}
resource "aws_vpc" "my_vpc" {
cidr_block = "10.0.0.0/16"
}
# Create Subnets in the VPC
resource "aws_subnet" "subnet_az1" {
vpc_id = aws_vpc.my_vpc.id
cidr_block = "10.0.0.0/24"
availability_zone = "us-east-1a"
map_public_ip_on_launch = false # Disable auto-assigning public IPs
}
resource "aws_subnet" "subnet_az2" {
vpc_id = aws_vpc.my_vpc.id
cidr_block = "10.0.1.0/24"
availability_zone = "us-east-1b"
map_public_ip_on_launch = false # Disable auto-assigning public IPs
}
resource "aws_internet_gateway" "my_igw" {
vpc_id = aws_vpc.my_vpc.id
}
# Create a new Elastic IP for the NAT Gateway
resource "aws_eip" "nat_eip" {}
# Create a public subnet in your VPC
resource "aws_subnet" "subnet_public" {
vpc_id = aws_vpc.my_vpc.id
cidr_block = "10.0.2.0/24"
availability_zone = "us-east-1c" # Choose a different AZ for the public subnet
map_public_ip_on_launch = true # Auto-assign public IPs
}
# Create a private route table for your private subnet(s)
resource "aws_route_table" "private_route_table" {
vpc_id = aws_vpc.my_vpc.id
}
# Create the NAT Gateway in a public subnet
resource "aws_nat_gateway" "my_nat_gateway" {
allocation_id = aws_eip.nat_eip.id
subnet_id = aws_subnet.subnet_public.id
}
# Update the private subnet route table to route traffic through the NAT Gateway
resource "aws_route" "private_subnet_nat_gateway_route" {
route_table_id = aws_route_table.private_route_table.id
destination_cidr_block = "0.0.0.0/0" # Route all traffic
nat_gateway_id = aws_nat_gateway.my_nat_gateway.id
}
# Associate the private route table with your private subnet(s)
resource "aws_route_table_association" "private_subnet1_association" {
subnet_id = aws_subnet.subnet_az1.id # Replace with your private subnet ID
route_table_id = aws_route_table.private_route_table.id
}
resource "aws_route_table_association" "private_subnet2_association" {
subnet_id = aws_subnet.subnet_az2.id # Replace with your private subnet ID
route_table_id = aws_route_table.private_route_table.id
}
# resource "aws_eip" "my_eip" {}
# Create a Security Group for the Database
resource "aws_security_group" "db_security_group" {
name = "db-security-group"
description = "Security group for the database"
vpc_id = aws_vpc.my_vpc.id
# Define security group rules here
# For example, allow incoming traffic on the database port
ingress {
from_port = 5432
to_port = 5432
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"] # TODO allow only traffic from ...
}
}
# Security Group for ECS tasks
resource "aws_security_group" "my_ecs_security_group" {
name = "ecs-security-group"
description = "Security group for ECS tasks"
vpc_id = aws_vpc.my_vpc.id
# Allow incoming traffic from the ALB on port 8000 (or your application port)
ingress {
from_port = 8000
to_port = 8000
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"] # TODO
# security_groups = [aws_security_group.my_alb_security_group.id]
}
# Allow outbound traffic
ingress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"] # Allow all outbound traffic to all IP addresses
# security_groups = [aws_security_group.my_alb_security_group.id]
}
}
# Security Group for ALB
resource "aws_security_group" "my_alb_security_group" {
name = "alb-security-group"
description = "Security group for ALB"
vpc_id = aws_vpc.my_vpc.id
# Allow incoming traffic from anywhere (0.0.0.0/0) on port 80 (HTTP)
ingress {
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
}
# Create an RDS PostgreSQL instance for the database
resource "aws_db_instance" "la_client_api_database" {
allocated_storage = 20
storage_type = "gp2"
engine = "postgres"
engine_version = "15.3"
instance_class = "db.m5.large"
db_name = "la_client_api_db"
username = "postgres"
password = "postgres" # Replace with a secure password
skip_final_snapshot = true
publicly_accessible = true # TODO
# vpc_security_group_ids = [aws_security_group.db_security_group.id]
tags = {
Name = "laclientapi"
}
}
resource "aws_security_group" "my_security_group" {
name_prefix = "my-security-group-"
}
resource "aws_ecs_cluster" "my_cluster" {
name = "my-cluster"
}
resource "aws_lb" "my_alb" {
name = "my-alb"
internal = false
load_balancer_type = "application"
subnets = [
aws_subnet.subnet_az1.id, # Subnet in Availability Zone 1
aws_subnet.subnet_az2.id # Subnet in Availability Zone 2
]
security_groups = [aws_security_group.my_alb_security_group.id]
enable_cross_zone_load_balancing = "true"
enable_deletion_protection = false
}
resource "aws_lb_target_group" "fargate_target_group" {
# Target group configuration here
name = "fargate-target-group"
port = 8000 # Replace with the desired target port
protocol = "HTTP"
vpc_id = aws_vpc.my_vpc.id
target_type = "ip" # Use "ip" for Fargate tasks
health_check {
path = "/"
protocol = "HTTP"
port = "8000"
interval = 30
timeout = 5
healthy_threshold = 2
unhealthy_threshold = 2
}
# Ensure this target group is associated with the ALB
depends_on = [aws_lb.my_alb]
}
# Create an ECS task definition for the API service
resource "aws_ecs_task_definition" "api_task" {
family = "api-task"
network_mode = "awsvpc"
requires_compatibilities = ["FARGATE"]
execution_role_arn = aws_iam_role.ecs_execution_role.arn
cpu = "256" # Specify the CPU value for the task in CPU units (e.g., 256)
memory = 512 # Specify the memory limit for the container in MiB
container_definitions = jsonencode([{
name = "client-api-container"
image = "public.ecr.aws/j0m1s7l0/la-client-api:latest"
portMappings = [{
containerPort = 8000,
hostPort = 8000
}]
environment = [
{ name = "DATABASE_URL", value = "postgresql://postgres:postgres@${aws_db_instance.la_client_api_database.address}:5432/la_client_api_db" },
{ name = "ROCKET_ADDRESS", value = "0.0.0.0" },
{ name = "ROCKET_ENV", value = "release" },
]
# Add an authentication block for Docker Hub with the access token
authentication = [
{
credentials_parameter = "DOCKERHUB_ACCESS_TOKEN"
authentication = "DOCKER"
auth = "dckr_pat_e-doKHRHR8CKuRw4eWvirzzNYNk"
}
]
# volume {
# name = "service-storage"
# host_path = "/ecs/service-storage"
# }
placement_constraints {
type = "memberOf"
expression = "attribute:ecs.availability-zone in [us-east-1a, us-east-1b]"
}
}])
}
resource "aws_lb_listener" "my_alb_listener" {
load_balancer_arn = aws_lb.my_alb.arn
port = 80
protocol = "HTTP"
default_action {
type = "forward"
target_group_arn = aws_lb_target_group.fargate_target_group.arn
}
depends_on = [aws_lb.my_alb, aws_lb_target_group.fargate_target_group]
}
resource "aws_lb_listener_rule" "my_listener_rule" {
listener_arn = aws_lb_listener.my_alb_listener.arn
action {
type = "forward"
target_group_arn = aws_lb_target_group.fargate_target_group.arn
}
priority = 1 # Set a priority lower than other rules
condition {
path_pattern {
values = ["/"] # This path pattern will match all paths
}
}
}
# Create an ECS service for the API task
resource "aws_ecs_service" "api_service" {
name = "api-service"
cluster = aws_ecs_cluster.my_cluster.id
task_definition = aws_ecs_task_definition.api_task.arn
desired_count = 1
launch_type = "FARGATE" # or EC2
# TODO: maybe specify LB to associate with the service
network_configuration {
subnets = [aws_subnet.subnet_az1.id, aws_subnet.subnet_az2.id]
security_groups = [aws_security_group.my_ecs_security_group.id]
assign_public_ip = true # Add this line to assign a public IP
}
load_balancer {
target_group_arn = aws_lb_target_group.fargate_target_group.arn
container_name = "client-api-container"
container_port = 8000
}
depends_on = [aws_lb_target_group.fargate_target_group, aws_ecs_cluster.my_cluster, aws_db_instance.la_client_api_database]
}
# Define an IAM role for ECS task execution
resource "aws_iam_role" "ecs_execution_role" {
name = "ecs_execution_role"
assume_role_policy = jsonencode({
Version = "2012-10-17",
Statement = [{
Action = "sts:AssumeRole",
Effect = "Allow",
Principal = {
Service = "ecs-tasks.amazonaws.com"
}
}]
})
}
# Attach the AmazonECSTaskExecutionRolePolicy policy to the execution role
resource "aws_iam_policy_attachment" "ecs_execution_role_attachment" {
name = "ecs_execution_role_attachment"
policy_arn = "arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy"
roles = [aws_iam_role.ecs_execution_role.name]
}
output "alb_dns_name" {
value = aws_lb.my_alb.dns_name
}
# output "alb_public_ip" {
# value = aws_lb.my_alb.load_balancer_type == "network" ? aws_lb.my_alb.ip_address : null
# }
我多次检查了 terraform 模板。在 docker hub 中发布镜像时出现同样的问题 (
image = "outsidethecode/la:0.1"
)
带有“允许所有出站流量”注释的安全组规则被定义为
ingress
规则,因此它实际上允许所有 入站 流量。您的安全组完全缺乏 egress
规则,因此目前所有出站流量都被阻止。