初めに
「CYBIRD Advent Calendar 2023」 の19日目の記事を担当する@gongon282828です。
18日目の記事は、@cy-yuka-WPさんの「Google Apps Scriptを利用したスケジュール管理」でした。
この記事では、外部ネットワークとAWSをSite-to-Site VPNで疎通し、AWS側では複数のVPNに対して疎通をフレキシブルにとれるように、トランジェントゲートウェイを利用した構築方法を説明します。
前提
サイバードでは、AWSの開発にはIaC(Infrastructure as Code)の実用として、Terraformを利用して構築しているため、その方法について記載します。
その他、諸条件については以下になります。
- Site-to-Site VPNと疎通をとる対象となる外部ネットワーク(オンプレミス環境や他パブリッククラウド等)のIPアドレスやネットワークは事前に用意してあるものとし、割愛します。
- AWS側のVPCは記事の冗長化を避ける目的で、2つのVPCのみ構築する内容で記載し、クロスアカウントでのVPCでのネットワーク疎通については記載しません。
- VPC内のサブネットは、プライベートサブネットの指定のみとします。
- 実際に疎通するAWSリソース(EC2やRDS等)へのセキュリティグループの設定の許可については割愛し、各ネットワークのルートテーブルまでの構築方法について記載します。
ネットワーク構成
以下、ネットワーク構成を示します。
Site-to-Site VPNで外部ネットワークと疎通させ、AWS側はトランジェントゲートウェイで疎通をとっています。
TerraformでのAWSリソースの構築
ここではTerraformで構築する方法を示します。
トランジェントゲートウェイとカスタムゲートウェイの生成
ここでは、トランジェントゲートウェイとカスタムゲートウェイを生成します。
locals{
 external_network_public_ip = "x.x.x.x/32"
}
resource "aws_ec2_transit_gateway" "main" {
  vpn_ecmp_support                = "enable"
  default_route_table_association = "disable"
  default_route_table_propagation = "disable"
  auto_accept_shared_attachments  = "disable"
  tags = { Name = "tgw" }
}
resource "aws_customer_gateway" "main" {
  ip_address = local.external_network_public_ip
  bgp_asn = "65000"
  type = "ipsec.1"
  tags = { "cgw" }
}
今回は、VPNとトランジェントゲートウェイの疎通をとるので、aws_ec2_transit_gatewayのvpn_ecmp_supportはenableにし、それ以外はdisableにします。
aws_customer_gateway(実質的には、これがSite-to-Site VPNのリソース)では、外部ネットワーク内のIPアドレスを指定します。
トランジェントゲートウェイとカスタムゲートウェイの疎通
resource "aws_vpn_connection" "main" {
  transit_gateway_id  = aws_ec2_transit_gateway.main.id
  customer_gateway_id = aws_customer_gateway.main.id
  type = "ipsec.1"
  static_routes_only = true
  tags = { Name = "tgw-cgw-connection" }
}
resource "aws_ec2_transit_gateway_route_table" "main" {
  transit_gateway_id = aws_ec2_transit_gateway.main.id
  tags               = { Name = "tgw-route-table" }
}
data "aws_ec2_transit_gateway_vpn_attachment" "main" {
  transit_gateway_id = aws_ec2_transit_gateway.main.id 
  vpn_connection_id  = aws_vpn_connection.main.id
  tags = { Name = "twg-vpn-attachment" }
}
resource "aws_ec2_transit_gateway_route_table_association" "vpn" {
  transit_gateway_attachment_id  = data.aws_ec2_transit_gateway_vpn_attachment.main.id
  transit_gateway_route_table_id = aws_ec2_transit_gateway_route_table.main.id
}
resource "aws_ec2_transit_gateway_route_table_propagation" "vpn" {
  transit_gateway_attachment_id  = data.aws_ec2_transit_gateway_vpn_attachment.main.id
  transit_gateway_route_table_id = aws_ec2_transit_gateway_route_table.main.id
}
Site-to-Site VPN側では、aws_vpn_connectionでトランジェントゲートウェイと紐づけるのですが、外部IPは事前に作成している前提になるので、static_routes_onlyはtrueにします。
トランジェントゲートウェイ側では、トランジェントゲートウェイ用のルートテーブルを作成した上で、Site-to-Site VPNをアタッチさせます。その上で、トランジェントゲートウェイのルートテーブルの関連付け(association)と伝番(propagation)の設定を入れています。
トランジェントゲートウェイとVPCの疎通
トランジェントゲートウェイと各VPCを疎通させます。
ここでは、AWSのVPCの各リソースについては、localで変数化したものを参照させています。実際に構築する際には、他moduleで生成し参照させる方法が一般的かと思います。
locals{
 external_network = "x.x.x.x/16"
 aws_network = {
   env_1 = {
     env                     = "env_1"
    vpc_id                  = "x.x.x.x/16"
    private_subnet_ids      = ["x.x.0.x/24","x.x.1.x/24"]
     private_route_table_ids = "rtb-xxxxxxxxxx"
   }
   env_2 = {
     env                     = "env_2"
    vpc_id                  = "x.x.x.x/16"
    private_subnet_ids      = ["x.x.0.x/24","x.x.1.x/24"]
     private_route_table_ids = "rtb-xxxxxxxxxx"
   }
 }
}
resource "aws_ec2_transit_gateway_vpc_attachment" "main" {
 count = length(local.aws_network[*])
  transit_gateway_id = aws_ec2_transit_gateway.main.id
  vpc_id             = local.aws_network[count.index].vpc_id
  subnet_ids         = local.aws_network[count.index].private_subnet_ids
  transit_gateway_default_route_table_association = false
  transit_gateway_default_route_table_propagation = false
  tags = {
    Name = "${local.aws_network[count.index].env}-twg-vpc-attachment"
  }
}
resource "aws_ec2_transit_gateway_route_table_association" "aws" {
  count = length(aws_ec2_transit_gateway_vpc_attachment.main[*]) 
  
  transit_gateway_attachment_id  = element(aws_ec2_transit_gateway_vpc_attachment.main, count.index)
  transit_gateway_route_table_id = aws_ec2_transit_gateway_route_table.main.id
}
resource "aws_route" "main" {
  count = length(local.aws_network[*])
   
  route_table_id         = local.aws_network[count.index].private_route_table_ids
  transit_gateway_id     = aws_ec2_transit_gateway.main.id
  destination_cidr_block = locals.external_network
}
まず、aws_ec2_transit_gateway_vpc_attachmentでトランジェントゲートウェイと各VPCの紐づけを、サブネット単位で行います。
次に、トランジェントゲートウェイ側のルートテーブルに関連付けと伝番を設定します。
最後に、各VPCのルートテーブル側で、外部ネットワークのciderを登録します。
以上が、Terraformでの構築方法になります。
補足
実践の場では、外部ネットワーク側のルーティングについては仮想ルーターでOSSとなるVyOSを活用して外部ネットワークの疎通リソースを構築しました。
また、VyOS側の作業として、AWSコンソール上で出力される設定情報を流し込む必要があります。
なお、VyOSはLTSバージョンは有償にはなるのですが、isoイメージをビルドするソースは公開されているので、isoイメージについても別途作成する必要があります。
最後に
トランジェントゲートウェイを利用することで、例えば環境の部分移管やAWS側の一部VPCを廃止させたい時など、稼働しているネットワークに影響を与えずに、ネットワーク構成をフレキシブルに変更することができ、便利に活用しています。
また、実はいうとこの記事は本当は書く予定はなかったのですが、アドベントカレンダーの記事はQiita内で重複参照できないことを前日朝に気づき(絶望...)、急遽、本記事を用意しました。
「AWS for Games Advent Calendar 2023」 の19日目の記事「ECS Fargateでのx86_64とarmのマルチアーキテクチャ実装概略とTerraformでの構築」も担当しているので、ぜひそちらもチェックいただければ嬉しいです!
「CYBIRD Advent Calendar 2023」 、20日目は@ice_matcha3さんの「ロードバランサの流量調整を動的にやってみた!」です。
サイバードのアドベントカレンダーは明日も引き続きインフラトピックになります!
乞うご期待!!


