多云网络与互联:对等、专线与统一网络模块

用 Terraform 编排多云网络:跨云 VPC 与 VNet 对等、专线与 VPN 互联、DNS 与统一出口设计,以及可复用的统一网络模块与排错方法。

1. 多云网络的挑战与目标拓扑

一句话总结: 多云网络的第一个难点不是「连不通」,而是「地址重叠加拓扑失控」,先用不重叠的 IP 规划与明确的集中式拓扑定调,再谈具体云厂商的资源。

单个云内部的网络编排已经够复杂,一旦跨到两个或三个云,问题会成倍放大:每个云都有自己的 VPC 或 VNet 概念、自己的路由模型、自己的对等语义,而它们之间的中间地带——专线、VPN、DNS、出口——恰恰是 Terraform 最需要抽象的地方。先看多云网络的四类典型挑战。

挑战典型表现应对方向
地址重叠两个云都用 10.0.0.0/16提前做 IPAM 规划
拓扑失控每加一个云就多一对对等集中式中转或网状规划
路由不透明不知道包走了哪条路统一可观测与路由审计
出口分散每个云各自出公网出口收敛到统一 NAT

1.1 IP 规划与网段不重叠

一句话总结: 多云 IP 规划的核心是「一次分配、全局唯一、留足余量」,用超网段给每个云划一块,再在云内二次切分子网。

最痛的多云事故几乎都源于地址重叠:AWS 用 10.0.0.0/16,Azure 也用 10.0.0.0/16,等到要打通时才发现无法加路由。正确做法是自上而下分配。

10.0.0.0/8 企业私有超网
 10.10.0.0/16  AWS 生产(ap-northeast-1)
 10.20.0.0/16  AWS 灾备(ap-southeast-1)
 10.30.0.0/16  Azure 生产(japaneast)
 10.40.0.0/16  GCP 生产(asia-northeast1)
 10.50.0.0/16  本地数据中心

1.2 集中式与网状拓扑

一句话总结: 两三个云可以用两两对等即网状,超过三个或需要统一策略时应改为集中式——由一个 Hub 云承担中转,其余云都只与 Hub 建链。

网状拓扑的优点是路径最短、无单点;缺点是链路数按 N 乘以 N 减一再除以二增长,且策略无处统一。集中式(Hub-and-Spoke)用一个云或一个中转 VPC 做汇聚点,链路数降到 N 减一,代价是 Hub 需要足够的带宽与高可用设计。实践中常见的做法是:先网状起步,规模到三个云以上时把其中一个升级为 Hub,其余链路逐步改接 Hub。

2. AWS VPC 与对等连接

一句话总结: AWS 侧多云网络的基座是 VPC 加子网加路由表,VPC Peering 负责点对点打通,跨账号 Peering 必须由对端接受,且 Peering 不具备传递性。

AWS 的 VPC 是区域级资源,子网绑定可用区,路由表决定子网的出向路径。跨云连接在 AWS 这一端最终都表现为「VPC 上多了一条指向某个网关或对等连接的路由」。

resource "aws_vpc" "prod" {
  cidr_block           = "10.10.0.0/16"
  enable_dns_hostnames = true
  enable_dns_support   = true

  tags = {
    Name = "vpc-prod"
    Env  = "prod"
  }
}

resource "aws_subnet" "prod_private" {
  count             = 2
  vpc_id            = aws_vpc.prod.id
  cidr_block        = cidrsubnet(aws_vpc.prod.cidr_block, 8, count.index)
  availability_zone = data.aws_availability_zones.available.names[count.index]

  tags = {
    Name = "subnet-prod-private-${count.index}"
  }
}

2.1 同账号与跨账号 Peering

一句话总结: 同账号 Peering 可以 auto_accept 一步到位;跨账号 Peering 必须由接受方在自己的 Terraform 里执行 accept,且两侧都要各自加一条路由。

resource "aws_vpc_peering_connection" "prod_to_shared" {
  vpc_id      = aws_vpc.prod.id
  peer_vpc_id = aws_vpc.shared.id
  auto_accept = true
}

resource "aws_route" "prod_to_shared" {
  route_table_id            = aws_route_table.prod_private.id
  destination_cidr_block    = aws_vpc.shared.cidr_block
  vpc_peering_connection_id = aws_vpc_peering_connection.prod_to_shared.id
}

跨账号时改为 peer_owner_id 加 peer_region,接受方执行接受动作,接受后两侧都补路由。

resource "aws_vpc_peering_connection_accepter" "peer" {
  provider                  = aws.peer_account
  vpc_peering_connection_id = aws_vpc_peering_connection.cross.id
  auto_accept               = true
}

需要提醒的是,Peering 不传递:A 与 B 对等、B 与 C 对等,并不代表 A 能到 C,跨云场景下这种非传递性会显著放大路由条目数量。

3. Azure VNet 与 GCP VPC 对等

一句话总结: Azure 的 VNet Peering 与 GCP 的 VPC Peering 语义相似但细节不同,Azure 需要显式开关转发流量与网关传递,GCP 则要单独开启自定义路由的导入导出。

Azure 侧的核心对象是资源组里的 VNet 与子网,Peering 是 VNet 级资源且双向独立配置;GCP 侧的核心是全局 VPC 与区域子网,Peering 同样双向。

3.1 Azure VNet Peering

一句话总结: Azure 每个方向的 Peering 都要单独建资源,allow_gateway_transit 只在持有网关的一侧开,allow_forwarded_traffic 让 Hub 能代为转发。

resource "azurerm_virtual_network" "hub" {
  name                = "vnet-hub"
  address_space       = ["10.30.0.0/16"]
  location            = azurerm_resource_group.net.location
  resource_group_name = azurerm_resource_group.net.name
}

resource "azurerm_virtual_network_peering" "hub_to_spoke" {
  name                         = "hub-to-spoke"
  resource_group_name          = azurerm_resource_group.net.name
  virtual_network_name         = azurerm_virtual_network.hub.name
  remote_virtual_network_id    = azurerm_virtual_network.spoke.id
  allow_virtual_network_access = true
  allow_forwarded_traffic      = true
  allow_gateway_transit        = true
}

反向的 Peering 需要单独再写一个资源,并把 use_remote_gateways 打开,这样才能从 Spoke 侧借道 Hub 的网关出去。

3.2 GCP VPC Peering 与共享 VPC

一句话总结: GCP 的 VPC Peering 默认不交换自定义路由,需要显式打开导入导出;共享 VPC 则把子网集中在宿主项目,业务项目通过子网委派消费。

resource "google_compute_network" "shared" {
  name                    = "shared-vpc"
  auto_create_subnetworks = false
}

resource "google_compute_network_peering" "app_to_shared" {
  name                 = "app-to-shared"
  network              = google_compute_network.app.self_link
  peer_network         = google_compute_network.shared.self_link
  export_custom_routes = true
  import_custom_routes = true
}

共享 VPC 场景下,宿主项目用 google_compute_shared_vpc_host_project 声明,业务项目用 google_compute_shared_vpc_service_project 加入,再按子网授予 roles/compute.networkUser。

4. 跨云专线与 VPN 互联

一句话总结: 跨云互联有两条主线——IPsec VPN 走公网加密、成本低但抖动大,专线走私有链路、稳定但周期长,生产环境通常两者并存做冗余。

跨云连通的本质是把两个云的私有网络接到同一张路由域里。VPN 用公网加隧道快速打通,专线则通过运营商或云厂商的互连产品提供确定性带宽与低延迟。

4.1 各云专线产品对照

一句话总结: AWS Direct Connect、Azure ExpressRoute、GCP Cloud Interconnect 解决的是同一个问题,差异主要在开通方式、BGP 会话模型与计费粒度。

resource "aws_dx_connection" "main" {
  name      = "dx-to-colocation"
  bandwidth = "1Gbps"
  location  = "EqTY-2"
}

resource "aws_dx_private_virtual_interface" "main" {
  connection_id      = aws_dx_connection.main.id
  name               = "vif-to-onprem"
  vlan_id            = 4094
  address_family     = "ipv4"
  bgp_asn            = 65000
  virtual_gateway_id = aws_vpn_gateway.main.id
}

Azure 侧对应 azurerm_express_route_circuit 加 azurerm_express_route_circuit_peering,GCP 侧对应 google_compute_interconnect_attachment 加 google_compute_router_peer。

4.2 IPsec VPN 与 BGP 路由交换

一句话总结: VPN 连接建立后,靠 BGP 会话交换路由,用 AS Path 与 local_pref 控制主备,Terraform 里通过 tunnel 参数与 BGP 配置表达。

resource "aws_customer_gateway" "onprem" {
  bgp_asn    = 65000
  ip_address = var.onprem_public_ip
  type       = "ipsec.1"
}

resource "aws_vpn_connection" "main" {
  customer_gateway_id = aws_customer_gateway.onprem.id
  transit_gateway_id  = aws_ec2_transit_gateway.hub.id
  type                = "ipsec.1"
  static_routes_only  = false
  tunnel1_ike_versions = ["IKEv2"]
}

路由交换的关键在于两侧 ASN 不能冲突,且宣告的前缀必须与实际网段一致,否则会出现「隧道通了但业务不通」的典型现象。

5. DNS 与统一出口

一句话总结: 跨云互通之后,服务发现与出网收敛是紧接着的两个问题,私有托管区解决名字解析,统一 NAT 出口解决合规与可观测。

网络打通只完成了「能到」,接下来要让应用「找得到」和「出得去」。

5.1 私有托管区与解析规则

一句话总结: 每个云各有自己的私有 DNS 产品,统一做法是把同一套内部域名在各云的私有区里各建一份记录,用 IaC 保证三者一致。

resource "aws_route53_zone" "internal" {
  name = "internal.example.com"

  vpc {
    vpc_id = aws_vpc.prod.id
  }
}

resource "azurerm_private_dns_zone" "internal" {
  name                = "internal.example.com"
  resource_group_name = azurerm_resource_group.net.name
}

resource "google_dns_managed_zone" "internal" {
  name       = "internal-zone"
  dns_name   = "internal.example.com."
  visibility = "private"
}

如果各云支持条件转发或 DNS 对等,可以只保留一份权威记录,其余云配置转发规则指向它,减少不一致。

5.2 NAT 与出口收敛

一句话总结: 出口收敛的做法是在 Hub 云集中部署 NAT 网关,其余云的默认路由指向 Hub,好处是白名单与审计只需维护一处。

resource "aws_nat_gateway" "hub" {
  allocation_id = aws_eip.hub.id
  subnet_id     = aws_subnet.hub_public.id

  tags = {
    Name = "nat-hub"
  }
}

resource "aws_route" "spoke_default" {
  route_table_id         = aws_route_table.spoke_private.id
  destination_cidr_block = "0.0.0.0/0"
  transit_gateway_id     = aws_ec2_transit_gateway.hub.id
}

集中出口的代价是 Hub 成为瓶颈与单点,需要多可用区部署 NAT 网关,并预留足够的弹性 IP 与带宽。

6. 统一网络模块设计

一句话总结: 统一网络模块的价值不是「少写几行 HCL」,而是把多云网络的拓扑决策固化成接口,让业务侧只关心「我要一个网络」而不是「AWS 还是 Azure」。

模块设计的第一原则是接口面向业务语义,而不是面向某家云的资源名。

6.1 模块输入输出与按云分支

一句话总结: 用一份变量定义网络意图,在模块内部按 cloud_provider 分支调用不同云的资源,输出统一的网络标识供上层消费。

variable "cloud_provider" {
  description = "目标云厂商:aws、azure 或 gcp"
  type        = string

  validation {
    condition     = contains(["aws", "azure", "gcp"], var.cloud_provider)
    error_message = "cloud_provider 只能是 aws、azure 或 gcp"
  }
}

variable "network_cidr" {
  description = "本网络使用的 CIDR,必须来自统一 IPAM 分配"
  type        = string
}

output "network_id" {
  description = "统一网络标识,屏蔽各云差异"
  value = (
    var.cloud_provider == "aws" ? aws_vpc.this[0].id :
    var.cloud_provider == "azure" ? azurerm_virtual_network.this[0].id :
    google_compute_network.this[0].id
  )
}

模块内部用 count 或 for_each 控制各云资源是否创建,避免在 AWS 环境里出现 Azure 的 provider 依赖。

6.2 命名与标签规范

一句话总结: 多云环境下命名规范必须跨云统一,因为标签体系各不相同,只有命名是三家都支持的共同语言。

规范项约定说明
网络名net-用途-环境-区域跨云完全一致
子网名snet-用途-环境-序号便于对照
标签键env、owner、cost-center三家通用键
云专有标签映射到各家原生字段由模块内部翻译

模块内部把统一的 common_tags 映射到 AWS 的 tags、Azure 的 tags 与 GCP 的 labels,业务侧只维护一份键值,避免三家各写一遍。

7. 可观测与排错

一句话总结: 多云网络的排错难点在于「没有一张全局视图」,只能靠 Flow Logs 加路由核对加连通性探测三段式方法逐步定位。

跨云问题往往在「我的云能到、你的云没回包」之间来回踢皮球,因此每一段都要有独立的证据。

7.1 Flow Logs 与路由验证

一句话总结: 三家的流日志产品都支持把连接记录投递到对象存储,用 Terraform 统一开启,再配合路由表导出做交叉比对。

resource "aws_flow_log" "vpc" {
  log_destination      = aws_s3_bucket.flow_logs.arn
  log_destination_type = "s3"
  traffic_type         = "ALL"
  vpc_id               = aws_vpc.prod.id
}

resource "azurerm_network_watcher_flow_log" "vnet" {
  name                 = "flowlog-vnet"
  network_watcher_name = azurerm_network_watcher.main.name
  resource_group_name  = azurerm_resource_group.net.name
  target_resource_id   = azurerm_virtual_network.hub.id
  storage_account_id   = azurerm_storage_account.logs.id
  enabled              = true
}
aws ec2 describe-route-tables --query "RouteTables[].Routes" \
  --filters Name=vpc-id,Values=vpc-0abc123
az network vnet peering list -g rg-net --vnet-name vnet-hub -o table
gcloud compute routes list --filter="network:shared-vpc"

7.2 MTU 与重叠网段排查

一句话总结: 隧道封装会吃掉 MTU,跨云链路出现「小包通、大包断」几乎一定是 MTU 问题;网段重叠则表现为路由加不上或加上了也不生效。

排查清单如下:

现象可能原因处理方式
小包通大包断隧道封装压低了 MTU调低接口 MTU 或开启 MSS 钳制
路由加不上两侧网段重叠回到 IPAM 重新分配
单向不通一侧缺回程路由补齐对端路由表
间歇性超时BGP 会话抖动检查链路质量与 ASN 配置

8. 总结

环节要点
IP 规划自上而下分配,全局唯一且留足余量
拓扑选型两三个云网状,三个以上转集中式 Hub
对等连接Peering 不传递,跨账号需对端接受
专线与 VPNVPN 快速打通,专线保稳定,双链路冗余
DNS 与出口私有托管区统一命名,NAT 集中收敛出口
模块设计接口面向业务语义,内部按云分支
可观测Flow Logs 加路由核对加连通性探测
排错先看 MTU 与网段,再看单向回程路由

多云网络最难的部分从来不是某一家云的资源写法,而是把三套语义不同的网络模型收敛到一套拓扑与一套命名上。Terraform 在这里的作用是把「拓扑决策」变成「模块接口」,让新增一个云从「重新设计一遍网络」变成「换一个 provider 参数」。下一篇文章会把视角从网络转向算力,讨论如何用 Terraform 编排 AI 与机器学习基础设施,包括 GPU 节点池、模型存储与推理服务的弹性伸缩。

延伸阅读

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「terraform」更多文章

  1. Helm Provider 与应用发布:值注入与回滚
  2. 模块注册表与分发:版本、文档与测试
  3. DNS 与证书编排:托管区域与自动验证