无法从本地SSH访问AWS私有子网内的EC2实例(Terraform部署)
我刚开始学习Terraform与AWS,实操中搭建了VPC、分别部署在公有/私有子网的EC2、NAT网关及Internet Gateway。目前可SSH访问公有子网EC2,且能从该实例ping通私有子网EC2,但无法从本地直接SSH连接私有子网EC2,求解决办法。
配置文件
create-vpc-main.tf
terraform{ backend "s3" { bucket = "norman-personal-s3-bucket" key = "lab3-create_vpc" region = "ap-southeast-1" } } provider "aws" { region = "us-east-1" } resource "aws_vpc" "main" { cidr_block = "10.0.0.0/16" enable_dns_support = true tags = { Name = "Lab 3 - Project VPC" } } resource "aws_security_group" "http_server_sg" { name = "http_server_sg" //vpc_id = "vpc-c49ff1be" vpc_id = aws_vpc.main.id ingress { from_port = 80 to_port = 80 protocol = "tcp" cidr_blocks = ["0.0.0.0/0"] } ingress { from_port = 22 to_port = 22 protocol = "tcp" cidr_blocks = ["0.0.0.0/0"] } egress { from_port = 0 to_port = 0 protocol = -1 cidr_blocks = ["0.0.0.0/0"] } tags = { name = "http_server_sg" } } resource "aws_subnet" "public_subnets" { count = length(var.public_subnet_cidrs) vpc_id = aws_vpc.main.id cidr_block = element(var.public_subnet_cidrs, count.index) availability_zone = element(var.azs, count.index) tags = { Name = "Lab 3 - Public Subnet" } } resource "aws_subnet" "private_subnets" { count = length(var.private_subnet_cidrs) vpc_id = aws_vpc.main.id cidr_block = element(var.private_subnet_cidrs, count.index) availability_zone = element(var.azs, count.index) tags = { Name = "Lab 3 - Private Subnet" } } resource "aws_internet_gateway" "gw" { vpc_id = aws_vpc.main.id tags = { Name = "Lab 3 - Project VPC IG" } } resource "aws_route_table" "second_rt" { vpc_id = aws_vpc.main.id route { cidr_block = "0.0.0.0/0" gateway_id = aws_internet_gateway.gw.id } tags = { Name = "Lab 3 - 2nd Route Table" } } resource "aws_route_table_association" "public_subnet_asso" { count = length(var.public_subnet_cidrs) subnet_id = element(aws_subnet.public_subnets[*].id, count.index) route_table_id = aws_route_table.second_rt.id } resource "aws_eip" "nat_gateway" { vpc = true } resource "aws_nat_gateway" "nat_gateway" { allocation_id = aws_eip.nat_gateway.id subnet_id = "${element(aws_subnet.public_subnets.*.id, 0)}" tags = { "Name" = "Lab 3 - NG" } } resource "aws_route_table" "third_rt" { vpc_id = aws_vpc.main.id route { cidr_block = "0.0.0.0/0" nat_gateway_id = aws_nat_gateway.nat_gateway.id } } resource "aws_route_table_association" "private_subnet_asso" { count = length(var.public_subnet_cidrs) subnet_id = "${element(aws_subnet.private_subnets.*.id, count.index)}" route_table_id = aws_route_table.third_rt.id }
create-ec2-main.tf
data "aws_vpc" "vpc" { filter { name = "tag:Name" values = ["Lab 3 - Project VPC"] } } data "aws_subnets" "private_subnets" { filter{ name ="vpc-id" values = [data.aws_vpc.vpc.id] } tags = { Name = "Lab 3 - Private Subnet" } } data "aws_subnet" "private_subnet" { for_each = toset(data.aws_subnets.private_subnets.ids) id = each.value } data "aws_subnets" "public_subnets" { filter{ name ="vpc-id" values = [data.aws_vpc.vpc.id] } tags = { Name = "Lab 3 - Public Subnet" } } data "aws_subnet" "public_subnet" { for_each = toset(data.aws_subnets.public_subnets.ids) id = each.value } data "aws_security_groups" "sg" { filter { name = "vpc-id" values = [data.aws_vpc.vpc.id] } } data "aws_ami" "aws_linux_2_latest" { most_recent = true owners = ["amazon"] filter { name = "name" values = ["amzn2-ami-hvm-*"] } } data "aws_ami_ids" "aws_linux_2_latest_ids" { owners = ["amazon"] } terraform{ backend "s3" { bucket = "norman-personal-s3-bucket" key = "lab3-create_ec2" region = "ap-southeast-1" } } provider "aws" { region = "us-east-1" } resource "aws_instance" "http_server" { count = length(data.aws_subnets.private_subnets.ids) #ami = "ami-062f7200baf2fa504" ami = data.aws_ami.aws_linux_2_latest.id key_name = "default-ec2" instance_type = "t2.micro" vpc_security_group_ids = data.aws_security_groups.sg.ids //subnet_id = "subnet-3f7b2563" subnet_id = element(data.aws_subnets.private_subnets.ids,count.index) connection { type = "ssh" host = self.public_ip user = "ec2-user" private_key = file(var.aws_key_pair) } tags = { Name = "Lab 3 - Private EC2 ${count.index + 1}" } # provisioner "remote-exec" { # inline = [ # "sudo yum install httpd -y", # "sudo service httpd start", # "echo Welcome to in28minutes - Virtual Server is at ${self.public_dns} | sudo tee /var/www/html/index.html" # ] # } } resource "aws_instance" "public_http_server" { count = length(data.aws_subnets.public_subnets.ids) #ami = "ami-062f7200baf2fa504" ami = data.aws_ami.aws_linux_2_latest.id key_name = "default-ec2" instance_type = "t2.micro" vpc_security_group_ids = data.aws_security_groups.sg.ids //subnet_id = "subnet-3f7b2563" subnet_id = element(data.aws_subnets.public_subnets.ids,count.index) connection { type = "ssh" host = self.public_ip user = "ec2-user" private_key = file(var.aws_key_pair) } tags = { Name = "Lab 3 - Public EC2 ${count.index + 1}" } # provisioner "remote-exec" { # inline = [ # "sudo yum install httpd -y", # "sudo service httpd start", # "echo Welcome to in28minutes - Virtual Server is at ${self.public_dns} | sudo tee /var/www/html/index.html" # ] # } } resource "aws_eip" "eip" { count = length(aws_instance.http_server) domain = "vpc" instance = aws_instance.http_server[count.index].id } resource "aws_eip_association" "eip_assoc" { count = length(aws_instance.http_server) instance_id = aws_instance.http_server[count.index].id allocation_id = aws_eip.eip[count.index].id } resource "aws_eip" "public_eip" { count = length(aws_instance.public_http_server) domain = "vpc" instance = aws_instance.public_http_server[count.index].id } resource "aws_eip_association" "public_eip_assoc" { count = length(aws_instance.public_http_server) instance_id = aws_instance.public_http_server[count.index].id allocation_id = aws_eip.public_eip[count.index].id }
问题原因分析
私有子网路由逻辑错误:私有子网的默认路由(
0.0.0.0/0)指向NAT网关,而NAT网关仅负责将私有子网的出站流量转换为自身公网IP,无法处理外部入站流量的反向路由。即使给私有EC2绑定了EIP,外部SSH请求到达实例后,响应流量会走NAT网关,源地址被替换为NAT网关的IP,本地机器收到的响应与请求目标IP不匹配,会直接丢弃,导致连接失败。违背私有子网设计原则:私有子网的核心作用是隔离内部资源,不直接暴露到互联网。直接给私有EC2绑定EIP并尝试直连,不符合AWS网络架构的最佳实践。
解决方案
方案1:通过公有子网跳板机SSH跳转(推荐)
这是访问私有子网EC2的标准方式,无需修改私有子网路由,保持网络隔离性:
步骤1:确保安全组配置正确
当前安全组已经开放22端口给所有IP,若要更安全,可将SSH ingress限制为你本地机器的公网IP。
步骤2:使用SSH代理跳转
在本地机器执行以下命令,通过公有子网的EC2作为跳板机,连接私有子网EC2:
ssh -i "default-ec2.pem" -A ec2-user@<公有EC2公网IP> # 登录跳板机后,再连接私有EC2: ssh ec2-user@<私有EC2私有IP>
或者直接用一条命令完成跳转:
ssh -i "default-ec2.pem" -o ProxyCommand="ssh -W %h:%p -i default-ec2.pem ec2-user@<公有EC2公网IP>" ec2-user@<私有EC2私有IP>
方案2:修改私有子网路由实现直连(不推荐)
若一定要直连私有EC2,需调整私有子网的路由表,让绑定EIP的实例的出站流量走Internet Gateway而非NAT网关:
修正create-vpc-main.tf中的私有子网路由表
添加一条针对EC2私有IP的路由,指向Internet Gateway,替换原有默认路由的逻辑:
resource "aws_route_table" "third_rt" { vpc_id = aws_vpc.main.id # 给绑定EIP的私有EC2单独配置路由,出站走IGW route { cidr_block = "<私有EC2的私有IP>/32" gateway_id = aws_internet_gateway.gw.id } # 其余私有子网流量仍走NAT网关 route { cidr_block = "0.0.0.0/0" nat_gateway_id = aws_nat_gateway.nat_gateway.id } }
注意:这种方式会打破私有子网的隔离性,仅适合测试场景,生产环境禁止使用。
内容的提问来源于stack exchange,提问作者Norman

