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

如何禁用AWS EC2公网直访,仅通过ELB访问且保障健康检查正常?

问题描述

我已经通过ELB的DNS地址成功实现对两台EC2实例的流量负载均衡,但EC2实例也能被公网直接访问,存在安全隐患。需要修改配置,实现:

  • 禁止公网直接访问EC2实例
  • 保留ELB的正常服务
    注意:若将associate_public_ip_address设为false,目标组中的EC2实例会变为“Unhealthy”,导致无法访问任何内容。
解决方案

要实现需求,需从子网架构调整、安全组拆分、NAT网关配置三个维度修改:

1. 拆分公有/私有子网

  • 公有子网:部署ALB,关联指向Internet Gateway的路由表,确保ALB能接收公网流量
  • 私有子网:部署EC2实例,关联指向NAT Gateway的路由表,确保EC2能访问外网(如执行yum更新)但不直接暴露给公网

2. 独立配置安全组

  • ALB安全组:仅允许公网访问80端口(或443,按需),出站允许所有流量
  • EC2安全组:仅允许来自ALB安全组的80端口访问,同时仅允许指定IP的SSH访问(用于管理),出站允许所有流量

3. 配置NAT网关

为私有子网的EC2提供外网访问能力,确保user_data中的初始化脚本(如yum安装httpd)能正常执行,避免健康检查失败

修改后的Terraform代码
terraform {
  required_version = "~> 1.4.4"

  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 4.30.0"
    }
  }
}

provider "aws" {
  profile = "development"
  region  = local.region
}

# -- VARS ----------------------------------------------------------------------

locals {
  project = "api"

  region = "eu-west-1"

  vpc_cidr = "10.0.0.0/16"

  # 公有子网(用于ALB)
  public_subnets = {
    "a1" = {
      az   = "${local.region}a"
      cidr = "10.0.1.0/24"
    },
    "b1" = {
      az   = "${local.region}b"
      cidr = "10.0.2.0/24"
    }
  }

  # 私有子网(用于EC2)
  private_subnets = {
    "a2" = {
      az   = "${local.region}a"
      cidr = "10.0.3.0/24"
    },
    "b2" = {
      az   = "${local.region}b"
      cidr = "10.0.4.0/24"
    }
  }

  ec2_ami           = "ami-0fb2f0b847d44d4f0" // Amazon Linux 2023 AMI 2023.1.20230705.0 x86_64 HVM kernel-6.1
  ec2_instance_type = "t2.micro"
  # 替换为你的管理IP段,用于SSH访问EC2
  admin_ip = "192.168.1.0/24"
}

# -- VPC -----------------------------------------------------------------------

resource "aws_vpc" "vpc" {
  cidr_block           = local.vpc_cidr
  enable_dns_support   = true
  enable_dns_hostnames = true

  tags = {
    Name = "${local.project}-vpc"
  }
}

# -- INTERNET GATEWAY ----------------------------------------------------------

resource "aws_internet_gateway" "internet_gateway" {
  vpc_id = aws_vpc.vpc.id

  tags = {
    Name = "${local.project}-internet-gateway"
  }
}

# -- 公有子网路由表 -----------------------------------------------------------

resource "aws_route_table" "public" {
  vpc_id = aws_vpc.vpc.id

  route {
    destination_cidr_block = "0.0.0.0/0"
    gateway_id             = aws_internet_gateway.internet_gateway.id
  }

  tags = {
    Name = "${local.project}-public-route-table"
  }
}

# -- 私有子网路由表 -----------------------------------------------------------

resource "aws_route_table" "private" {
  vpc_id = aws_vpc.vpc.id

  tags = {
    Name = "${local.project}-private-route-table"
  }
}

# -- NAT网关配置 --------------------------------------------------------------

resource "aws_eip" "nat" {
  vpc = true

  tags = {
    Name = "${local.project}-nat-eip"
  }
}

resource "aws_nat_gateway" "nat" {
  allocation_id = aws_eip.nat.id
  subnet_id     = aws_subnet.public["a1"].id

  tags = {
    Name = "${local.project}-nat-gateway"
  }
}

# 给私有子网路由表添加NAT网关路由
resource "aws_route" "private_nat" {
  route_table_id         = aws_route_table.private.id
  destination_cidr_block = "0.0.0.0/0"
  nat_gateway_id         = aws_nat_gateway.nat.id
}

# -- SUBNET --------------------------------------------------------------------

# 公有子网
resource "aws_subnet" "public" {
  for_each = local.public_subnets

  vpc_id            = aws_vpc.vpc.id
  cidr_block        = each.value.cidr
  availability_zone = each.value.az
  # 公有子网自动分配公网IP(给NAT网关用)
  map_public_ip_on_launch = true

  tags = {
    Name = "${local.project}-public-subnet-${each.key}"
  }
}

# 私有子网
resource "aws_subnet" "private" {
  for_each = local.private_subnets

  vpc_id            = aws_vpc.vpc.id
  cidr_block        = each.value.cidr
  availability_zone = each.value.az
  # 私有子网不自动分配公网IP
  map_public_ip_on_launch = false

  tags = {
    Name = "${local.project}-private-subnet-${each.key}"
  }
}

# 公有子网关联路由表
resource "aws_route_table_association" "public" {
  for_each = local.public_subnets

  subnet_id      = aws_subnet.public[each.key].id
  route_table_id = aws_route_table.public.id
}

# 私有子网关联路由表
resource "aws_route_table_association" "private" {
  for_each = local.private_subnets

  subnet_id      = aws_subnet.private[each.key].id
  route_table_id = aws_route_table.private.id
}

# -- SECURITY GROUPS -----------------------------------------------------------

# ALB安全组:允许公网访问80端口
resource "aws_security_group" "alb" {
  name        = "${local.project}-alb"
  description = "Allow public inbound traffic to ALB"
  vpc_id      = aws_vpc.vpc.id

  ingress {
    description = "Allow public HTTP traffic"
    from_port   = 80
    to_port     = 80
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }

  egress {
    description = "Allow all outbound traffic"
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }

  tags = {
    Name = "${local.project}-alb-sg"
  }
}

# EC2安全组:仅允许ALB访问80,以及指定IP访问SSH
resource "aws_security_group" "ec2" {
  name        = "${local.project}-ec2"
  description = "Allow traffic from ALB and admin SSH"
  vpc_id      = aws_vpc.vpc.id

  ingress {
    description = "Allow SSH from admin IP"
    from_port   = 22
    to_port     = 22
    protocol    = "tcp"
    cidr_blocks = [local.admin_ip]
  }

  ingress {
    description      = "Allow HTTP traffic from ALB security group"
    from_port        = 80
    to_port          = 80
    protocol         = "tcp"
    security_groups  = [aws_security_group.alb.id]
  }

  egress {
    description = "Allow all outbound traffic"
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }

  tags = {
    Name = "${local.project}-ec2-sg"
  }
}

# -- EC2 -----------------------------------------------------------------------

resource "aws_instance" "ec2" {
  for_each = local.private_subnets

  ami                         = local.ec2_ami
  instance_type               = local.ec2_instance_type
  subnet_id                   = aws_subnet.private[each.key].id
  vpc_security_group_ids      = [aws_security_group.ec2.id]
  # 不关联公网IP
  associate_public_ip_address = false

  user_data = <<-EOF
#!/bin/bash
sudo su
yum update -y
yum install httpd -y
service httpd start
chkconfig httpd on
echo "<html><body>Hello from private subnet ${each.key}</body></html>" > /var/www/html/index.html
  EOF

  tags = {
    Name = "${local.project}-ec2-private-${each.key}"
  }
}

# -- LOAD BALANCER -------------------------------------------------------------

resource "aws_lb" "application" {
  name = "${local.project}-application-load-balancer"

  load_balancer_type = "application"
  security_groups    = [aws_security_group.alb.id]
  # ALB部署在公有子网
  subnets            = [for sub in aws_subnet.public : sub.id]
}

resource "aws_lb_target_group" "application" {
  name = "${local.project}-http-application"

  port                          = 80
  protocol                      = "HTTP"
  vpc_id                        = aws_vpc.vpc.id
  load_balancing_algorithm_type = "least_outstanding_requests"

  health_check {
    protocol            = "HTTP"
    port                = 80
    path                = "/"
    healthy_threshold   = 3
    unhealthy_threshold = 3
    timeout             = 10
    interval            = 30
    matcher             = 200
  }
}

resource "aws_lb_target_group_attachment" "ec2" {
  for_each = aws_instance.ec2

  port             = 80
  target_id        = each.value.id
  target_group_arn = aws_lb_target_group.application.arn
}

resource "aws_lb_listener" "application" {
  load_balancer_arn = aws_lb.application.arn
  port              = "80"
  protocol          = "HTTP"

  default_action {
    type             = "forward"
    target_group_arn = aws_lb_target_group.application.arn
  }

  tags = {
    Name = "${local.project}-application-load-listener"
  }
}
关键修改说明
  • 拆分公有/私有子网,ALB部署在公有子网,EC2部署在私有子网,彻底隔离EC2的公网暴露路径
  • 独立安全组:ALB面向公网,EC2仅信任ALB的流量,同时限制SSH访问来源
  • 配置NAT网关,确保私有子网的EC2能正常访问外网执行初始化脚本,避免健康检查失败
  • EC2的associate_public_ip_address设为false,彻底移除公网IP

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.16 21:22:01