マルチVPCにおけるインターネット通信の集約構成をTerraformで構築してみた

Terraformを使ってインターネット通信の集約構成を複数パターン構築します。
2023.05.08

AWS事業本部のイシザワです。

今回はインターネット通信の集約構成をTerraformで構築してみました。

一応、以下の記事の続き物となりますが、新環境を構築するため以前の記事の内容は把握していなくても大丈夫です。

構成要素

Transit Gateway

Transit GatewayはVPCやオンプレミスネットワークの中継ハブです。VPCにTransit Gateway Attachmentを作成することで、VPCとTransit Gatewayを接続できます。

Transit Gateway経由の通信は、Transit Gatewayルートテーブルの「関連付け」と「ルート伝播」によって制御できます。

「関連付け」はVPCからTransit Gatewayへ向かう通信を制御するもので、Transit Gatewayに入った通信はTransit Gateway Attachmentに関連付けられたルートテーブルを参照して行き先を決定します。

「ルート伝播」はTransit GatewayからVPCへ向かう通信を制御するもので、ルート伝播をしたルートテーブルにはVPCのCIDRをDestinationとするルートが自動的に追加されます。

また、DestinationのCIDRブロックと宛先のTransit Gateway Attachmentを設定することで、静的にルートを作成することもできます。

VPC IP Address Manager (IPAM)

IPAMはCIDRの払い出しやIPアドレスの管理を行うリソースです。VPCにIPAMプールを設定することで、IPAMプールに事前にプロビジョニングしたIPプールからCIDRが払い出されます。 今回はVPC CIDRの重複防止とルーティング設計のためにIPAMを利用します。

IPAMプールの階層構造は以下のようになっています。

  • トップレベルIPAMプール (10.0.0.0/12)
    • 基盤IPAMプール (10.0.0.0/13)
    • ワークロードIPAMプール (10.8.0.0/13)

通信要件

今回はVPC間の通信要件ごとにTerraformコードを作成したいと思います。各パターン共通の要件は以下の通りです。

  • ワークロード用VPC (Workload-VPC) は複数ある
  • Workload-VPCからインターネットへ向かう通信は必ずインターネット集約用VPC (Internet-VPC) からインターネットへ出る
  • Workload-VPCから共通サービス用VPC (Common-VPC) は通信可能

以下はWorkload-VPC間の通信要件です。各パターンごとにネットワークを設計します。

  • パターン1:Workload-VPC間は通信可能
  • パターン2:Workload-VPC間は通信不可
  • パターン3:Workload-VPCでグループを作成する。同じグループのVPC間では通信可能、異なるグループのVPC間では通信不可

通信の到達可能性の検証方法

通信の到達可能性の検証にはVPC Reachability Analyzerを利用します。

各VPCにENIを作成してVPC Reachability AnalyzerでENI間が到達可能かを検証することで、VPC間で通信が可能かを検証します。

検証用に以下の分析パスを作成します。Transit Gatewayを経由する場合はリバースパスの検証が行われないため、行きの通信と戻りの通信両方の分析パスを作成します。

  • workloadX-to-internet
    • Workload-VPC Xとインターネットとの通信(行き)
  • workloadX-from-internet
    • Workload-VPC Xとインターネットとの通信(戻り)
  • workloadX-to-common-service
    • Workload-VPC XとCommon-VPCとの通信(行き)
  • workloadY-from-common-service
    • Workload-VPC XとCommon-VPCとの通信(戻り)
  • workloadX-to-workloadY
    • Workload-VPC XからWorkload-VPC Yへ向かう通信

パターン1:Workload-VPC間は通信可能

ルーティング設計

ルーティング設計は以下のようになっています。Internet-VPC以外のVPCのルートテーブルはWorkload-VPC0と同様のものになっています。

インターネットとの通信は次のような経路を通ります。

通信経路で特筆すべき箇所をピックアップして解説します。

  • 1:Workload-VPCのプライベートサブネットのデフォルトルートをTransit Gatewayに設定しているため、インターネットへの通信はTransit Gatewayに向かいます。
  • 3:Transit Gatewayのルートテーブルに0.0.0.0/0をDestinationとする静的ルートを設定しているため、インターネットへの通信はInternet-VPCに向かいます。
  • 7:Internet-VPCのパブリックサブネットのルートテーブルにトップレベルIPAMプールのCIDR(10.0.0.0/12)をDestinationとするルートを設定しているため、このネットワーク内のVPCに向かう通信はTransit Gatewayに向かいます。

他のWorkload-VPCとの通信は以下のようになっています。Common-VPCとの通信も同様です。

Terraformコード

ソースコードはGitHubに載せています。内容をピックアップして解説します。

ソースコード

Transit Gateway

今回の構成だと全てのVPC間で通信可能であるため、デフォルト関連付けルートテーブルとデフォルト伝播ルートテーブルを有効にしても問題ありません。

transit_gateway.tf

resource "aws_ec2_transit_gateway" "main" {
  default_route_table_association = "enable"
  default_route_table_propagation = "enable"
  auto_accept_shared_attachments  = "enable"
  dns_support                     = "enable"

  tags = {
    Name = "${var.system_id}-tgw"
  }
}

Transit Gatewayルートテーブル

パブリックIPへの通信をInternet-VPCに向けるためのルートをTransit Gatewayルートテーブルに追加します。

transit_gateway_rtb.tf

resource "aws_ec2_transit_gateway_route" "to_internet" {
  destination_cidr_block         = "0.0.0.0/0"
  transit_gateway_route_table_id = aws_ec2_transit_gateway.main.association_default_route_table_id
  transit_gateway_attachment_id  = aws_ec2_transit_gateway_vpc_attachment.internet.id
}

到達可能性の検証

以下の変数の値でterraform applyを行います。

terraform.tfvars

system_id          = "sample"
toplevel_pool_cidr = "10.0.0.0/12"
workload_count     = 3

Reachability Analyzerの検証パスも作成されるので、各検証パスを実行することで到達可能性の検証を行います。

Reachability Analyzerによる検証結果は以下の通りです。想定通り全ての通信パスが到達可能となっています。

パターン2:Workload-VPC間は通信不可

ルーティング設計

ルーティング設計は以下のようになっています。

Targetがblackholeとなっているのはブラックホールルートを表しています。ブラックホールルートはDestinationのCIDRに向かう通信を破棄するアクションです。

インターネットとの通信は次のような経路を通ります。

他Workload-VPCとの通信は以下のように途中で破棄されます。

ブラックホールルートのDestinationがワークロードIPAMプールのCIDR(10.8.0.0/13)となっているため、Workload-VPCから他Workload-VPCへ向かう通信はTransit Gatewayで破棄されます。

ちなみにブラックホールルートが無いとInternet-VPCを経由して他Workload-VPCに到達してしまいます。経路は以下の通りです。行きの通信と帰りの通信で図を分けています。

Common-VPCとの通信は次のような経路を通ります。

Terraformコード

ソースコードは以下になります。

ソースコード

Transit Gateway

この構成では一部のVPC間通信を遮断する必要があるので、デフォルト関連付けルートテーブルとデフォルト伝播ルートテーブルは無効としています。

transit_gateway.tf

resource "aws_ec2_transit_gateway" "main" {
  default_route_table_association = "disable"
  default_route_table_propagation = "disable"
  auto_accept_shared_attachments  = "enable"
  dns_support                     = "enable"

  tags = {
    Name = "${var.system_id}-tgw"
  }
}

Transit Gatewayルートテーブル

Transit GatewayルートテーブルはWorkload-VPC用とその他のVPC用の2つのルートテーブルを作成します。

Workload-VPC用ルートテーブルには、Internet-VPCに向かうルートとCommon-VPCに向かうルートを追加します。

transit_gateway_rtb.tf

# Route Table for Workload-VPCs
resource "aws_ec2_transit_gateway_route_table" "workload_vpc" {
  transit_gateway_id = aws_ec2_transit_gateway.main.id
}

resource "aws_ec2_transit_gateway_route" "workload_vpc_to_internet" {
  transit_gateway_route_table_id = aws_ec2_transit_gateway_route_table.workload_vpc.id
  destination_cidr_block         = "0.0.0.0/0"
  transit_gateway_attachment_id  = aws_ec2_transit_gateway_vpc_attachment.internet.id
}

resource "aws_ec2_transit_gateway_route" "block_inter_workloads" {
  transit_gateway_route_table_id = aws_ec2_transit_gateway_route_table.workload_vpc.id
  destination_cidr_block         = local.workload_pool_cidr
  blackhole                      = true
}

resource "aws_ec2_transit_gateway_route_table_propagation" "workload_vpc_to_common_service_vpc" {
  transit_gateway_route_table_id = aws_ec2_transit_gateway_route_table.workload_vpc.id
  transit_gateway_attachment_id  = module.common_service_vpc.transit_gateway_attachment_id
}

その他のVPC用のルートテーブルには、Internet-VPCに向かうルートとCommon-VPCに向かうルートに加えて、各Workload-VPCに向かうルートを追加します。

transit_gateway_rtb.tf

# Route Table for Infrastructure-VPCs
resource "aws_ec2_transit_gateway_route_table" "infrastructure_vpc" {
  transit_gateway_id = aws_ec2_transit_gateway.main.id
}

resource "aws_ec2_transit_gateway_route" "infrastructure_vpc_to_internet" {
  transit_gateway_route_table_id = aws_ec2_transit_gateway_route_table.infrastructure_vpc.id
  destination_cidr_block         = "0.0.0.0/0"
  transit_gateway_attachment_id  = aws_ec2_transit_gateway_vpc_attachment.internet.id
}

resource "aws_ec2_transit_gateway_route_table_propagation" "infrastructure_vpc_to_workload_vpc" {
  count = var.workload_count

  transit_gateway_route_table_id = aws_ec2_transit_gateway_route_table.infrastructure_vpc.id
  transit_gateway_attachment_id  = module.workload_vpc[count.index].transit_gateway_attachment_id
}

resource "aws_ec2_transit_gateway_route_table_propagation" "internet_vpc_to_common_service_vpc" {
  transit_gateway_route_table_id = aws_ec2_transit_gateway_route_table.infrastructure_vpc.id
  transit_gateway_attachment_id  = module.common_service_vpc.transit_gateway_attachment_id
}

resource "aws_ec2_transit_gateway_route_table_association" "internet_vpc" {
  transit_gateway_route_table_id = aws_ec2_transit_gateway_route_table.infrastructure_vpc.id
  transit_gateway_attachment_id  = aws_ec2_transit_gateway_vpc_attachment.internet.id
}

resource "aws_ec2_transit_gateway_route_table_association" "common_service_vpc" {
  transit_gateway_route_table_id = aws_ec2_transit_gateway_route_table.infrastructure_vpc.id
  transit_gateway_attachment_id  = module.common_service_vpc.transit_gateway_attachment_id
}

到達可能性の検証

以下の変数の値でterraform applyを行います。

terraform.tfvars

system_id          = "sample"
toplevel_pool_cidr = "10.0.0.0/12"
workload_count     = 3

Reachability Analyzerによる検証結果は以下の通りです。想定通りWorkload-VPC間の通信は到達不能になっています。

パターン3:同じグループのWorkload-VPC間は通信可能

ルーティング設定

Workload-VPCのグループ分けは以下のようになっているとします。

グループ番号 所属Workload-VPC
0 Workload-VPC0, Workload-VPC1
1 Workload-VPC2

ルーティング設定は以下のようになります。

同じグループのWorkload-VPCは同じルートテーブルに関連付けとルート伝播を行います。

インターネットへの通信とCommon-VPCとの通信の通信経路はパターン2と同様です。

同じグループどうしの通信は以下の経路を通ります。

最長一致のルートが優先されるため、ブラックホールルートよりも同じグループのWorkload-VPCへのルートの方が優先されます。

異なるグループのWorkload-VPCへの通信は以下の図のように、ブラックホールルートで破棄されます。

Terraformコード

ソースコードは以下になります。

ソースコード

インプット

変数groupでWorkload-VPCのグループ分けを指定します。

variables.tf

variable "system_id" {}
variable "toplevel_pool_cidr" {}
variable "workload_count" { type = number }
variable "group" { type = list(list(number)) }

例えばWorkload-VPCを3つ作成し、以下のようなグループ分けをしたいとします。

グループ番号 所属Workload-VPC
0 Workload-VPC0, Workload-VPC1
1 Workload-VPC2

このとき、変数の値を以下のように設定します。

terraform.tfvars

workload_count = 3
group          = [[0, 1], [2]]

Transit Gateway

パターン2と同様に一部のVPC間通信を遮断する必要があるので、デフォルト関連付けルートテーブルとデフォルト伝播ルートテーブルは無効としています。

Transit Gatewayルートテーブル

Workload-VPC用のTransit Gatewayルートテーブルをグループごとに作成します。各ルートテーブルについて以下を設定しています。

  • パブリックIPへの通信をInternet-VPCに向ける静的ルート(aws_ec2_transit_gateway_route.workload_vpc_to_internet
  • Common-VPCのルート伝播(aws_ec2_transit_gateway_route_table_propagation.workload_vpc_to_common_service_vpc
  • ワークロードIPAMプールのCIDRへの通信を破棄するブラックホールルート(aws_ec2_transit_gateway_route.block_inter_workloads
  • グループに所属するWorkload-VPCとの関連付け(aws_ec2_transit_gateway_route_table_association.workload_vpc
  • グループに所属するWorkload-VPCのルート伝播(aws_ec2_transit_gateway_route_table_propagation.workload_vpc

transit_gateway_rtb.tf

# Route Table for Workload-VPCs
resource "aws_ec2_transit_gateway_route_table" "workload_vpc" {
  count = length(var.group)

  transit_gateway_id = aws_ec2_transit_gateway.main.id
}

resource "aws_ec2_transit_gateway_route" "workload_vpc_to_internet" {
  count = length(var.group)

  transit_gateway_route_table_id = aws_ec2_transit_gateway_route_table.workload_vpc[count.index].id
  destination_cidr_block         = "0.0.0.0/0"
  transit_gateway_attachment_id  = aws_ec2_transit_gateway_vpc_attachment.internet.id
}

resource "aws_ec2_transit_gateway_route" "block_inter_workloads" {
  count = length(var.group)

  transit_gateway_route_table_id = aws_ec2_transit_gateway_route_table.workload_vpc[count.index].id
  destination_cidr_block         = local.workload_pool_cidr
  blackhole                      = true
}

resource "aws_ec2_transit_gateway_route_table_propagation" "workload_vpc_to_common_service_vpc" {
  count = length(var.group)

  transit_gateway_route_table_id = aws_ec2_transit_gateway_route_table.workload_vpc[count.index].id
  transit_gateway_attachment_id  = module.common_service_vpc.transit_gateway_attachment_id
}

resource "aws_ec2_transit_gateway_route_table_association" "workload_vpc" {
  for_each = merge([for v in [for k_1, v_1 in zipmap(range(length(var.group)), var.group) : [for v_2 in v_1 : [tonumber(k_1), v_2]]] : { for v_3 in v : join(",", v_3) => v_3 }]...)

  transit_gateway_route_table_id = aws_ec2_transit_gateway_route_table.workload_vpc[each.value[0]].id
  transit_gateway_attachment_id  = module.workload_vpc[each.value[1]].transit_gateway_attachment_id
}

resource "aws_ec2_transit_gateway_route_table_propagation" "workload_vpc" {
  for_each = merge([for v in [for k_1, v_1 in zipmap(range(length(var.group)), var.group) : [for v_2 in v_1 : [tonumber(k_1), v_2]]] : { for v_3 in v : join(",", v_3) => v_3 }]...)

  transit_gateway_route_table_id = aws_ec2_transit_gateway_route_table.workload_vpc[each.value[0]].id
  transit_gateway_attachment_id  = module.workload_vpc[each.value[1]].transit_gateway_attachment_id
}

到達可能性の検証

以下の変数の値でterraform applyを行います。

terraform.tfvars

system_id          = "sample"
toplevel_pool_cidr = "10.0.0.0/12"
workload_count     = 3
group              = [[0, 1], [2]]

Reachability Analyzerによる検証結果は以下の通りです。想定通り同じグループのVPC間は到達可能で、異なるグループのVPC間では到達不能であることが確認できます。

まとめ

マルチVPCにおけるインターネットの集約構成をTerraformで3パターンに分けて構築してみました。インターネットへの通信の集約をするときの参考になれば幸いです。