Terraform AWS VPC 完整架构详细讲解
这是一个生产级标准的AWS双可用区VPC网络架构 ,也是AWS上部署任何应用的基础网络底座。我会从架构原理、代码逐行解析、流量走向、最佳实践、常见坑点五个维度进行全面讲解,让你不仅能看懂代码,还能理解"为什么这么写"。
一、整体架构概览
你这个Demo实现的是AWS最经典的"公网+私网"分层架构,逻辑拓扑如下:
互联网
↓
Internet Gateway (IGW)
↓
┌─────────────────────────────────────────────────┐
│ VPC (10.0.0.0/16) │
│ ┌─────────────┐ ┌─────────────┐ │
│ │ 可用区eu-west-1a │ │ 可用区eu-west-1b │ │
│ │ ┌─────────┐ │ │ ┌─────────┐ │ │
│ │ │公网子网1│ │ │ │公网子网2│ │ │
│ │ │10.0.1.0/24│ │ │ │10.0.2.0/24│ │ │
│ │ └─────────┘ │ │ └─────────┘ │ │
│ │ ↓ │ │ │ │
│ │ ┌─────────┐ │ │ │ │
│ │ │NAT网关 │ │ │ │ │
│ │ └─────────┘ │ │ │ │
│ │ ↓ │ │ │ │
│ │ ┌─────────┐ │ │ ┌─────────┐ │ │
│ │ │私网子网1│ │ │ │私网子网2│ │ │
│ │ │10.0.4.0/24│ │ │ │10.0.5.0/24│ │ │
│ │ └─────────┘ │ │ └─────────┘ │ │
│ └─────────────┘ └─────────────┘ │
└─────────────────────────────────────────────────┘
核心组件职责:
- VPC:你的私有网络隔离环境,完全由你控制
- 公网子网:直接暴露在互联网的子网,适合放Web服务器、负载均衡器
- 私网子网:完全隔离的内网子网,适合放数据库、应用服务器等核心服务
- Internet Gateway(IGW):VPC与互联网的出入口
- NAT Gateway:让私网资源能"单向"访问互联网(下载更新、调用外部API),但互联网无法主动访问私网资源
二、逐文件详细解析
1. versions.tf(Terraform版本约束)
你没有贴这个文件,但这是所有Terraform项目的必备文件,标准写法如下:
hcl
terraform {
required_version = ">= 1.0.0" # 要求Terraform CLI版本不低于1.0
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0" # 使用AWS Provider 5.x系列版本
}
}
}
为什么重要:
- 避免不同团队成员使用不同版本Terraform导致的兼容性问题
~> 5.0表示只接受5.x的补丁更新,不会自动升级到6.x(大版本可能有不兼容变更)
2. provider.tf(AWS提供商配置)
同样是必备文件,告诉Terraform要操作哪个云厂商的资源:
hcl
provider "aws" {
region = "eu-west-1" # AWS区域,这里是爱尔兰
# 认证方式:优先使用环境变量,然后是~/.aws/credentials文件
# 生产环境不要硬编码AK/SK!
}
3. vars.tf(变量定义)
你的Demo里把所有值都硬编码了,生产环境建议抽成变量,方便复用和修改:
hcl
variable "aws_region" {
description = "AWS区域"
type = string
default = "eu-west-1"
}
variable "vpc_cidr" {
description = "VPC的CIDR块"
type = string
default = "10.0.0.0/16"
}
variable "public_subnet_cidrs" {
description = "公网子网CIDR列表"
type = list(string)
default = ["10.0.1.0/24", "10.0.2.0/24"]
}
variable "private_subnet_cidrs" {
description = "私网子网CIDR列表"
type = list(string)
default = ["10.0.4.0/24", "10.0.5.0/24"]
}
4. vpc.tf(核心网络组件)
这是整个架构的核心文件,我们逐资源解析:
4.1 VPC本身
hcl
resource "aws_vpc" "main" {
cidr_block = "10.0.0.0/16" # VPC的IP地址范围,最多65536个IP
instance_tenancy = "default" # 实例租赁模式,default=共享硬件,dedicated=专用硬件(贵很多)
enable_dns_support = true # 启用VPC内的DNS解析(必须开)
enable_dns_hostnames = true # 让EC2实例自动获得DNS主机名(必须开,否则EC2无法解析域名)
enable_classiclink = false # 旧版功能,现在不需要
tags = {
Name = "main" # 资源名称,AWS控制台里能看到
}
}
关键参数说明:
cidr_block:AWS推荐使用10.0.0.0/8、172.16.0.0/12、192.168.0.0/16这三个私有网段enable_dns_hostnames:90%的新手都会踩这个坑!如果不开,EC2实例无法解析任何域名,包括AWS自己的服务(如S3)
4.2 公网子网
hcl
resource "aws_subnet" "main-public-1" {
vpc_id = aws_vpc.main.id # 关联到上面创建的VPC
cidr_block = "10.0.1.0/24" # 子网IP范围,256个IP
map_public_ip_on_launch = true # 自动给这个子网里的EC2分配公网IP
availability_zone = "eu-west-1a" # 所在可用区
tags = {
Name = "main-public-1"
}
}
公网子网的本质:
- 不是因为叫"公网"就自动能上网,而是因为后面会关联到公网路由表
map_public_ip_on_launch = true只是自动分配公网IP,没有路由还是上不了网- 生产环境建议关闭这个参数,只给需要公网IP的实例手动分配EIP
4.3 私网子网
hcl
resource "aws_subnet" "main-private-1" {
vpc_id = aws_vpc.main.id
cidr_block = "10.0.4.0/24"
map_public_ip_on_launch = false # 不分配公网IP,完全内网
availability_zone = "eu-west-1a"
tags = {
Name = "main-private-1"
}
}
私网子网的优势:
- 最高级别的安全隔离,互联网无法直接访问
- 所有入站流量必须通过公网的跳板机或负载均衡器转发
- 适合存放数据库、缓存、消息队列等核心敏感服务
4.4 Internet Gateway(互联网网关)
hcl
resource "aws_internet_gateway" "main-gw" {
vpc_id = aws_vpc.main.id # 一个VPC只能有一个IGW
tags = {
Name = "main"
}
}
IGW的工作原理:
- 是一个逻辑网关,不是物理设备,AWS自动维护高可用
- 负责VPC内公网IP与互联网IP之间的地址转换(NAT)
- 同时支持入站和出站流量
4.5 公网路由表与关联
hcl
# 创建公网路由表
resource "aws_route_table" "main-public" {
vpc_id = aws_vpc.main.id
# 核心路由规则:所有目标地址(0.0.0.0/0)的流量都走IGW
route {
cidr_block = "0.0.0.0/0"
gateway_id = aws_internet_gateway.main-gw.id
}
tags = {
Name = "main-public"
}
}
# 将公网子网1关联到公网路由表
resource "aws_route_table_association" "main-public-1-a" {
subnet_id = aws_subnet.main-public-1.id
route_table_id = aws_route_table.main-public.id
}
# 将公网子网2关联到公网路由表
resource "aws_route_table_association" "main-public-2-a" {
subnet_id = aws_subnet.main-public-2.id
route_table_id = aws_route_table.main-public.id
}
路由表是网络的灵魂:
- 子网本身没有"公网/私网"属性,关联了什么路由表,它就是什么子网
- 一个子网只能关联一个路由表,但一个路由表可以关联多个子网
0.0.0.0/0是默认路由,当没有更精确的路由匹配时,就走这条
5. nat.tf(NAT网关与私网路由)
NAT网关是私网子网访问互联网的唯一通道,也是生产环境的必备组件。
5.1 弹性IP(EIP)
hcl
resource "aws_eip" "nat" {
vpc = true # 这是VPC类型的EIP,不是经典网络的
}
为什么NAT网关需要EIP:
- NAT网关本身是一个AWS托管服务,需要一个固定的公网IP
- 所有私网资源访问互联网时,对外显示的都是这个EIP
- EIP是免费的,但如果没有绑定到任何资源,AWS会按小时收费
5.2 NAT网关
hcl
resource "aws_nat_gateway" "nat-gw" {
allocation_id = aws_eip.nat.id # 绑定上面创建的EIP
subnet_id = aws_subnet.main-public-1.id # NAT网关必须放在公网子网!
depends_on = [aws_internet_gateway.main-gw] # 确保IGW先创建完成
tags = {
Name = "main-nat"
}
}
关键注意事项:
depends_on非常重要!如果没有这个,Terraform可能会先创建NAT网关再创建IGW,导致NAT网关创建失败- NAT网关必须放在公网子网,因为它自己需要通过IGW访问互联网
- 你的Demo里只创建了一个NAT网关,放在eu-west-1a,这是单点故障,生产环境建议每个可用区一个
5.3 私网路由表与关联
hcl
# 创建私网路由表
resource "aws_route_table" "main-private" {
vpc_id = aws_vpc.main.id
# 核心路由规则:所有互联网流量都走NAT网关
route {
cidr_block = "0.0.0.0/0"
nat_gateway_id = aws_nat_gateway.nat-gw.id
}
tags = {
Name = "main-private"
}
}
# 将私网子网关联到私网路由表
resource "aws_route_table_association" "main-private-1-a" {
subnet_id = aws_subnet.main-private-1.id
route_table_id = aws_route_table.main-private.id
}
resource "aws_route_table_association" "main-private-2-a" {
subnet_id = aws_subnet.main-private-2.id
route_table_id = aws_route_table.main-private.id
}
公网vs私网路由表对比:
| 路由表类型 | 目标地址 | 下一跳 | 流量方向 |
|---|---|---|---|
| 公网路由表 | 0.0.0.0/0 | IGW | 双向(入站+出站) |
| 私网路由表 | 0.0.0.0/0 | NAT网关 | 单向(仅出站) |
三、完整流量走向分析
理解流量走向是掌握VPC的关键,我们分两种场景:
场景1:公网EC2实例访问互联网
- EC2实例发起请求(如ping 8.8.8.8)
- 流量到达公网子网的路由表
- 路由表匹配到0.0.0.0/0规则,将流量转发给IGW
- IGW将EC2的私网IP转换为公网IP,发送到互联网
- 互联网返回响应,IGW将公网IP转换回私网IP,转发给EC2
场景2:私网EC2实例访问互联网
- 私网EC2发起请求
- 流量到达私网子网的路由表
- 路由表匹配到0.0.0.0/0规则,将流量转发给NAT网关
- NAT网关将私网IP转换为自己的EIP,发送到IGW
- IGW将流量发送到互联网
- 互联网返回响应,IGW转发给NAT网关
- NAT网关将EIP转换回私网IP,转发给私网EC2
关键区别:互联网只能看到NAT网关的EIP,完全看不到私网EC2的存在。
四、生产环境最佳实践与改进
你的Demo是一个很好的起点,但生产环境还需要做以下优化:
1. 高可用改进
- 每个可用区部署一个NAT网关:避免单可用区故障导致所有私网资源断网
- 跨可用区部署应用:将应用和数据库分别部署在两个可用区
2. 安全改进
- 添加安全组:控制实例级别的入站出站流量
- 添加网络ACL:控制子网级别的入站出站流量(作为安全组的补充)
- 关闭公网子网的
map_public_ip_on_launch,只给需要的实例手动分配EIP - 使用跳板机 或AWS Systems Manager Session Manager访问私网实例,不要直接暴露SSH端口
3. 可维护性改进
- 将所有硬编码值抽成变量
- 添加
outputs.tf文件,输出VPC ID、子网ID、NAT网关ID等关键信息 - 将VPC架构封装成Terraform模块,方便在多个项目中复用
- 使用Terraform工作区(Workspace)区分开发、测试、生产环境
4. 成本优化
- NAT网关是按小时和流量收费的,如果私网资源不需要访问互联网,可以不创建NAT网关
- 开发环境可以使用单个NAT网关,生产环境使用多NAT网关
- 及时释放未使用的EIP,避免产生不必要的费用
五、常见坑点与排错
- EC2实例无法解析域名 :99%是因为VPC的
enable_dns_hostnames没开 - NAT网关创建失败 :检查是否加了
depends_on = [aws_internet_gateway.main-gw] - 公网EC2无法上网:检查公网子网是否关联了公网路由表,公网路由表是否有指向IGW的默认路由
- 私网EC2无法上网:检查私网子网是否关联了私网路由表,私网路由表是否有指向NAT网关的默认路由,NAT网关是否在公网子网
- Terraform销毁失败:NAT网关必须先销毁才能销毁EIP,Terraform会自动处理,但如果手动删除了资源,会导致销毁失败
六、部署与验证步骤
- 安装Terraform CLI和AWS CLI,并配置AWS认证
- 创建上述5个文件,放在同一个目录下
- 执行
terraform init初始化项目 - 执行
terraform plan查看将要创建的资源 - 执行
terraform apply确认并创建资源 - 验证:
- 在公网子网启动一个EC2,SSH登录后执行
ping 8.8.8.8,应该能通 - 在私网子网启动一个EC2,通过公网EC2跳板机登录后执行
ping 8.8.8.8,应该能通 - 尝试直接SSH登录私网EC2,应该无法连接
- 在公网子网启动一个EC2,SSH登录后执行
明确结论
你现在的代码里:只有 1 个 NAT Gateway + 1 个 Internet Gateway
总共是 2 个网关 ,但只有 1 个是 NAT。
网关讲解
① Internet Gateway(互联网网关)
hcl
resource "aws_internet_gateway" "main-gw" {
vpc_id = aws_vpc.main.id
}
✅ 数量:1 个
作用:给整个 VPC 连接互联网,一个 VPC 只需要 1 个。
② NAT Gateway(出站网关)
hcl
resource "aws_nat_gateway" "nat-gw" {
allocation_id = aws_eip.nat.id
subnet_id = aws_subnet.main-public-1.id # 只在 1a 区
}
✅ 数量:1 个
作用:给两个私网子网(1a + 1b)共用。
2. 所以总数是
- Internet Gateway:1 个
- NAT Gateway:1 个
总共 2 个网关
3. 为什么说"只有 1 个 NAT"是问题?
因为你这个 NAT 只部署在 eu-west-1a 可用区:
- 如果 1a 区故障
- 你的 两个私网子网(1a + 1b)全部无法访问互联网
这叫 单点故障,生产环境不允许。
4. 生产标准架构:应该是 2 个 NAT
高可用架构应该是:
- 1a 区 → 自己的 NAT
- 1b 区 → 自己的 NAT
各自私网子网用各自的 NAT,互不影响。
5. 极简总结(一句话)
你现在:
1 IGW + 1 NAT = 2 个网关
但 NAT 只有 1 个,存在单点故障。
先把概念对齐,再说"自带还是要配"。
一、AWS vs 阿里云
AWS
- Internet Gateway(IGW) :VPC 自带逻辑能力,但必须手动创建这个资源并关联到 VPC,否则 VPC 没有公网出入口。
- NAT Gateway:必须手动创建、放在公网子网、绑 EIP。
阿里云
- 没有叫"Internet Gateway"的东西。
- 对应公网直通能力:默认自带(不需要单独创建网关)
- 对应集中管控的网关:IPv4网关(需要手动创建+激活)
- 私网出网:公网NAT网关(必须手动创建)
二、阿里云:默认是"自带公网访问能力",不用配网关
新建一个阿里云 VPC + 交换机(子网):
- 你直接给 ECS 绑个 公网IP/EIP
- 就能上网、被外网访问
- 不需要你创建任何"互联网网关"
原理:阿里云 VPC 底层默认打通了公网通道,相当于 AWS IGW 默认隐含存在,不需要你手动建。
一句话:
阿里云默认:互联网网关能力是自带的,不用配。
三、什么时候需要手动配"IPv4网关"?
企业要统一管控所有公网出口、路由集中控制、安全合规时:
- 手动创建 IPv4网关
- 激活后,整个 VPC 公网流量都走它
- 路由表要手动加:
0.0.0.0/0 → IPv4网关
普通开发/测试、小公司:不用 IPv4 网关,直接 EIP 或 NAT 网关就行。
四、私网要上网:NAT 网关必须手动配
和 AWS 一样:
- 私网交换机(子网)里的机器不能直接上网
- 必须手动创建 公网NAT网关
- 放在一个公网交换机上,绑 EIP
- 私网路由表:
0.0.0.0/0 → NAT网关
五、对照你之前的 Terraform 架构(一句话总结)
AWS:
- IGW:手动创建
- NAT:手动创建
阿里云(普通用法,和你 demo 对等):
- "互联网网关":自带,不用创建
- NAT网关:手动创建
所以:
- 阿里云这边你不用写代码创建互联网网关
- 只需要:VPC + 公网/私网交换机 + NAT网关 + 路由表