NAT Gatewayを複数のVPCで共有して課金を減らす！！！～with Transit Gateway～

# はじめに
NAT Gatewayは高い利用料金がかかる恐ろしいリソースです。
そのため、ワークロードを作成をするたびに何個もNATを作成したくはないです。
かといって1つのVPCに複数のワークロードを詰め込むのもちょっと・・・って感じですよね。

この課題を対処すべく、
**①NATゲートウェイを持つネットワーク共有用VPCを作成**し、
**②複数のVPCとこの共有VPCを統合する**アーキテクチャを作成してみます！！！

今回は、NATゲートウェイやインターネットゲートウェイを持たないVPCのEC2が、
ネットワーク共有VPCのリソースを利用し、SSMアクセス（インターネットアクセス）が可能となること、をゴールに作業していきます。

# 今回のアーキテクチャ
**①NATゲートウェイを持つネットワーク共有用VPCを作成**し、
**②複数のVPCとこの共有VPCを統合する**アーキテクチャはこんな感じです。

**アーキテクチャ図**

![arch1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3654730/20596612-ea7b-e409-2f9e-9bc7157333a9.png)


少しずつ説明します。

**①NATゲートウェイを持つネットワーク共有用VPC**

![arch1_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3654730/f0deb965-3971-4684-21f0-ee8b72596d8b.png)

図のままのVPCで、publicサブネットとprivateサブネットを持ちます。
privateサブネットのトラフィックはpublicサブネットにルートされ、NATゲートウェイにパブリックIPを貸してもらって、インターネットゲートウェイからインターネットの世界に通信します。

ネットワーク共有用のVPCなので、VPC系リソース以外は作成しません。

**ネットワークを貸してもらうVPC**

![arch1_2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3654730/dd2d1828-dfa3-d8dc-5fd3-ccfd4c4867b7.png)

図のままのVPCです。privateサブネットしか持っておらず、NATゲートウェイやインターネットゲートウェイを持っていません。
SSMエージェントのインストールされたEC2を作成しますが、インターネット接続が不可能なので、SSMログインができません。


**②複数のVPCとこの共有VPCを統合するAWS Transit Gateway**

![arch1_3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3654730/fc3b1199-a377-4408-96af-653e3c03e5aa.png)

今回の主役であるAWS Transit Gatewayです。

AWS Transit GatewayはVPCとVPCを通信可能にしてくれるサービスです。
(オンプレともごにょごにょできるが、今回は触れません)

異なるVPC間のリソース同士が通信可能にできるため、今回のようにVPCごとに責任範囲を分割しつつ、リソースを共有することができます。

細かいところは公式ドキュメントにおまかせします。

https://aws.amazon.com/jp/transit-gateway/

https://docs.aws.amazon.com/ja_jp/vpc/latest/tgw/what-is-transit-gateway.html

**最後に再整理**

![arch1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3654730/efa9da25-ef08-b022-b1d6-8cedebbba68e.png)

再度アーキテクチャを確認します。
上部に存在するEC2が、Transitゲートウェイ経由でネットワーク共有VPCにアクセスし、
NATゲートウェイを通じてインターネットと通信し、SSMアクセスが可能になります。
Transitゲートウェイがハブになってくれるので、同じように複数のVPCが共有用VPCのNATゲートウェイを使用できる感じです。

# やってみた
## 検証環境の準備
**VPCとEC2の用意**

まずはVPCとEC2を作成します。
Transitゲートウェイに重きを置きたいので、ここはさくっとterraformで作成します。

<details><summary>VPC+EC2作成Terraform</summary>

```main.tf
############################################################################
## terraformブロック
############################################################################
terraform {
  # Terraformのバージョン指定
  required_version = "~> 1.7.0"

  # Terraformのaws用ライブラリのバージョン指定
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.33.0"
    }
  }
}

############################################################################
## providerブロック
############################################################################
provider "aws" {
  # リージョンを指定
  region = "ap-northeast-1"
}

locals {
  name = "test-transit-vpc"
}

############################################################################
## ネットワーク共有VPC
############################################################################
module "network_vpc" {
  source  = "terraform-aws-modules/vpc/aws"
  version = "5.9.0" # TODO: providerと一緒に更新する

  name = "${local.name}-network"
  cidr = "10.0.1.0/24"

  azs             = ["ap-northeast-1a", "ap-northeast-1c"]
  public_subnets = ["10.0.1.0/26", "10.0.1.64/26"]
  private_subnets = ["10.0.1.128/26", "10.0.1.192/26"]

  enable_nat_gateway = true
  single_nat_gateway = true

  tags = {
    Terraform   = "true"
    Environment = "dev"
    Group       = "${local.name}-network"
  }
}

############################################################################
## 共有ネットワーク使用VPC
############################################################################
module "private_vpc" {
  source  = "terraform-aws-modules/vpc/aws"
  version = "5.9.0" # TODO: providerと一緒に更新する

  name = "${local.name}-private"
  cidr = "10.0.2.0/24"

  azs             = ["ap-northeast-1a", "ap-northeast-1c"]
  private_subnets = ["10.0.2.0/25", "10.0.2.128/25"]

  enable_nat_gateway = false

  tags = {
    Terraform   = "true"
    Environment = "dev"
    Group       = "${local.name}-private"
  }
}

########################################
## SSM Instance Profile
########################################
data "aws_iam_policy" "ssm" {
  arn = "arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore"
}

data "aws_iam_policy_document" "assume_role_ssm" {
  statement {
    effect = "Allow"

    principals {
      type        = "Service"
      identifiers = ["ec2.amazonaws.com"]
    }

    actions = ["sts:AssumeRole"]
  }
}

resource "aws_iam_role" "ssm" {
  name               = "ec2-for-ssm"
  assume_role_policy = data.aws_iam_policy_document.assume_role_ssm.json
}

resource "aws_iam_role_policy_attachment" "ssm" {
  policy_arn = data.aws_iam_policy.ssm.arn
  role       = aws_iam_role.ssm.name
}

resource "aws_iam_instance_profile" "ec2_for_ssm" {
  name = "ec2-for-ssm-${local.name}"
  role = aws_iam_role.ssm.name
}

########################################
## EC2 Security Group
########################################
# 0.0.0.0/0を許可するセキュリティグループを作成
resource "aws_security_group" "allow_all_outbound" {
  name        = "allow_all_outbound_sg"
  description = "Allow all outbound traffic"
  vpc_id      = module.private_vpc.vpc_id

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }

  tags = {
    Name = "allow_all_outbound_sg"
  }
}

########################################
## SSM EC2 Instance
########################################
resource "aws_instance" "example" {
  ami           = "ami-0f75d1a8c9141bd00" # なんでもいいので一番上にでてきたやつ
  instance_type = "t2.micro"

  subnet_id = module.private_vpc.private_subnets[0]
  iam_instance_profile = aws_iam_instance_profile.ec2_for_ssm.name

  vpc_security_group_ids = [aws_security_group.allow_all_outbound.id]

  tags = {
    Name  = "private-instance"
    Group = "${local.name}-private"
  }
}
```
</details>

```terraform init``` ```terraform apply``` してリソースを作成します。

**作成されたリソースの確認**
以下のCIDRで2つのVPCが作成されました。

**ネットワーク共有VPC**

![cap01.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3654730/4cc3270e-3ab5-5885-c04c-dc5d681c723c.png)

分かりづらいですが、「test-transit-vpc-network」がインターネットゲートウェイで、
「test-transit-vpc-network-ap-northeast-1a」がNATゲートウェイです。

良い感じに作成できてます。

**ネットワーク使用VPC**

![cap02.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3654730/80c5ecad-9cf2-ce5e-4c74-a6e786530c1a.png)

インターネットゲートウェイへのルートを持たない2つのプライベートサブネットが作成されていることが作成できています。

**EC2の確認**

![cap03.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3654730/78672001-32d0-c3a9-7e19-8d1b6df1f2b3.png)

ネットワーク使用VPCのプライベートサブネット上に作成されています。

![cap04.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3654730/baf73eeb-b88f-b9e9-3bcd-e9c0f6927564.png)

SSMアクセスを試しますが、SSMエンドポイントに到達できないと表示されます。
VPCにはNATゲートウェイもインターネットゲートウェイも存在しないため、予想通りです。
良い感じですね！

では、Transitゲートウェイを作成していきます！

## Transitゲートウェイを作成する
### リソース一覧画面へ移動する

![cap05.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3654730/8a3c5ddf-4f1b-8ad6-ea1c-ec5731eb1cbc.png)

VPC > Transit Gateway と移動します。

### 作成画面へ移動する

![cap05_1.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3654730/8ffd0d10-e6e0-2e3b-f9cd-572f43d04301.png)

Transit Gateway を作成 をクリックします。

### 名前を設定する

![cap06.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3654730/de189e90-fb13-d9d1-9ce7-875cb1237de6.png)

適当に「network-share-tgw」とします。

### Transit Gatewayの挙動を設定する

![cap07.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3654730/29e88e61-1844-1ca4-b533-44ac95e561cc.png)

「デフォルトルートテーブルの関連付け」と「デフォルトルートテーブル伝播」はこの後の手作業を一部自動で行ってくれる設定です。チェックしておきます。

その他、マルチキャストなどTransit Gatewayにはいろいろな機能が存在します。
今回のテストでは必要な機能のみチェックしておきます。

### Transit Gatewayを作成する

![cap08.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3654730/4b6fe4d2-e4da-88f8-8944-642bb383cf54.png)

その他項目はデフォルトのまま、「Transit Gatewayを作成」をクリックします。

### ちょっと待つ

![cap09.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3654730/d59c9b08-3586-2e5b-2ea0-18b018db950f.png)

一分ほどpendingします。

### 作成されたことを確認する

![cap10.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3654730/69559f88-e388-26be-7e8f-0e77af382d68.png)

きました！！

## Transitゲートウェイアタッチメントを作成する
Transitゲートウェイを作成しただけでは、まだVPC同士は統合されていません。
Transitゲートウェイアタッチメントというリソースを作成する必要があります。

Transitゲートウェイアタッチメントをネットワーク共有VPCとネットワーク使用VPCにに設定し、Transitゲートウェイと接続させます。

### リソース一覧画面へ移動する

![cap11.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3654730/9e6bfc6e-6890-cd99-8960-c6e3e89a696f.png)

VPC > Transit Gateway アタッチメント と移動します。

### 作成画面へ移動する

![cap11_1.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3654730/6278f5e7-8e78-eb33-a216-57910713e3fe.png)

Transit Gateway アタッチメントを作成 をクリックします。

### 名前、接続先Transit Gatewayを設定する

![cap13.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3654730/12c7f0ae-5e81-0f47-3d1b-a830267b4d0a.png)

名前を適当につけます。
ネットワーク共有用のprivateサブネットを接続するアタッチメントのため、「network-vpc-attachment」とします。

Transit Gatewayは先ほど作成したものを指定します。

### アタッチメントの設定を行う

![cap14.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3654730/b45e4beb-753b-2fc2-ef34-acc303619bf7.png)

今回は不要なので、チェックを外しておきます。

### VPCを設定する

![cap15.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3654730/7f6dc6ea-c737-d0ba-b47c-29cdb0397be4.png)

VPCを選択します。ネットワーク共有VPCを選択します。
（もうひとつ作るので、その時はネットワーク使用VPCを選択）

### サブネットを設定する

![cap16.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3654730/5655bce4-1bf4-38c3-1482-a7eba993e5cf.png)

アタッチメントを作成するサブネットを選択します。
アタッチメントもプライベートIPを1つ使うリソースなのでCIDRを細かく設計している場合は注意です。

![cap17.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3654730/618a8424-3339-909b-7157-58a64b1e9b92.png)

>アベイラビリティーゾーンごとに選択できるサブネットは 1 つだけですが、そのアベイラビリティーゾーンのすべてのサブネットが Transit Gateway にトラフィックを送信できます。

とあるので、今回は適当にプライベートサブネットにアタッチメントを作成します。

### アタッチメントを作成する

![cap18.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3654730/45c00537-7560-132f-81e8-0f875ec13029.png)

「Transit Gateway アタッチメントを作成」をクリックし、アタッチメントを作成します。

### ネットワーク使用VPC用のアタッチメントを作成する
同様に、ネットワーク使用VPC用のアタッチメントを作成します。

設定は同じなのでスキップします。

![cap19_1.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3654730/1b42db1b-823f-aec0-71a2-38a57b338bb0.png)

最終的にはこんな感じになりました。


## Transit Gateway ルートテーブルを準備する
もう少しだけ作業します。

Transit Gateway自体もルートテーブルを持っています。
このルートテーブルの設定も確認します。

### Transit Gateway ルートテーブルを表示する

![cap19.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3654730/f44b2509-9147-c2f6-574b-aa838f8b1652.png)

Transit Gateway画面から、「関連付けルートテーブルID」をクリックし、ルートテーブル画面に移動します。

### ルートテーブルの設定を確認する

![cap20.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3654730/0b8e690f-dde3-4c0b-9062-1a405c21e5c8.png)

「関連付け」タブに2つのアタッチメントがあることが確認できます。
Transit Gatewayを作成したときにチェックした「デフォルトルートテーブルの関連付け」のおかげで、
先ほど作成したアタッチメントが自動で関連付けられています。

![cap21.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3654730/5e3702cb-068e-44c8-4628-15ab9973fd0a.png)

「ルート」タブに2つのルートがあることが確認できます。
Transit Gatewayを作成したときにチェックした「デフォルトルートテーブル伝播」のおかげで、
先ほど作成したアタッチメントが存在するVPCへのルートが自動で関連付けられています。

### 動作イメージの確認
ここまでの設定を見て、Transit Gatewayの動きをイメージしてみます。
現在のVPC、TransitゲートウェイのRouteを図にするとこんな感じです。

![arch2_7.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3654730/8f179997-0c3a-bd49-8b30-be2a3abe233f.png)

それぞれ確認します。

**ネットワークを使用するVPCのプライベートサブネットのルート**

![arch2_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3654730/7165a02e-90c3-6b29-2f25-69cc1532bacf.png)

ネットワークを使用するVPCのプライベートサブネットのルートです。
ネットワーク共有VPCへは、Transitゲートウェイ経由でアクセスしますが、
Transitゲートウェイへのルートが存在しないため、追加する必要があります。

![arch2_4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3654730/3193f04d-d797-24a8-a7a4-773215160968.png)

インターネットへのアウトバウンドは全てネットワーク共有VPCに回したいので、
「0.0.0.0/0 → ネットワーク使用VPC内のTransitゲートウェイアタッチメント」のルートが必要です。

**Transitゲートウェイのルート**

![arch2_2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3654730/f6d7d9a2-be60-68cc-16f5-dafc3387e44c.png)

Transitゲートウェイのルートです。
アウトバウンドは全てネットワーク共有VPCに回したいので、ルートを追加する必要があります。

![arch2_5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3654730/7c0a29e7-7f1b-b742-73ef-ee378833fcb1.png)

「0.0.0.0/0 → ネットワーク共有VPC内のTransitゲートウェイアタッチメント」のルートが必要です。

**ネットワークを共有するVPCのパブリックサブネットのルート**

![arch2_3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3654730/00628d62-15f8-2601-7df5-c4e0d7468f82.png)

ネットワークを使用するVPCのパブリックサブネットのルートです。
ここまでの設定で、インターネットへ出れるようにはなったのですが、戻りのトラフィックのためのルートを用意する必要があります。

行きのトラフィックでは、EC2インスタンスのトラフィックはTransitゲートウェイを経由し、ネットワーク共有VPCでルートを探します。

その場合、「0.0.0.0/0」はNATゲートウェイにルートされるため、トラフィックはNATによってパブリックIPを与えられた後、インターネットに出ていきます。

戻りのトラフィックでは、パブリックサブネット内の「10.0.1.0/24 → local」ルートにより、トラフィックはNATへ転送されます。

NAT内でパブリックIPが元のプライベートIPへ変換されます。

この時、このプライベートIPをネットワーク使用VPCに返却するルートがパブリックサブネットのルートテーブルに必要になるわけです。

![arch2_6.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3654730/74ed08a5-9ac8-0458-bd45-4c307ca3ed93.png)

よって、ネットワーク使用VPC内のプライベートIPを全てTransitゲートウェイに投げ返す「10.0.2.0/24 → ネットワーク共有VPC内のTransitゲートウェイアタッチメント」のルートが必要です。

めっちゃ複雑なのですが、うまく伝わったでしょうか？
~~claudeに推敲させれば良いんだけどフリープラン使い切った。~~

## 各ルートの設定
### TransitゲートウェイのRouteに「0.0.0.0/0」を追加する

![cap22.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3654730/7e4c1708-c00b-f721-a6c1-b0450c07d31b.png)

ルートタブから「静的ルートを作成」をクリックします。

![cap23.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3654730/02b6b5c3-8ed5-975a-fb7a-945bf183600c.png)

「0.0.0.0/0」を入力し、ネットワーク共有VPCへのアタッチメントを選択します。
外部インターネットへのトラフィックを、ネットワーク共有VPCに向けるためですね。

![cap23.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3654730/2e51a065-d9bb-902a-fecd-3c39b92a47ac.png)

設定ができたら、「静的ルートを作成」をクリックします。

![cap24.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3654730/ed37c4ed-db21-40dc-afb1-e45c47f9238a.png)

良い感じですね。

### ネットワーク使用VPCのプライベートサブネットのRouteに「0.0.0.0/0」を追加する
ルートテーブル画面から編集します。

![cap25.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3654730/d8df307e-3757-5c33-244b-d8e4a5105e7d.png)

「ルートを編集」をクリックします。

![cap26.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3654730/c2e10adc-6704-badd-f021-0526f91bced0.png)

「0.0.0.0/0」を追加し、ターゲットからTransit Gatewayを選択します。

![cap27.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3654730/52d4dab1-3355-6c0c-8e9c-96a260b74c81.png)

リソースIDを求められるので、VPC内に作成されたアタッチメントを指定します。
「変更を保存」をクリックします。

![cap28.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3654730/a153bdb4-b0fc-0f7d-e502-7e3aed59bad0.png)

ルートが追加されました！

これで、プライベートサブネットからインターネットへの通信が、トランジットゲートウェイアタッチメントを経過し、
トランジットゲートウェイのルートテーブルで処理されるようになりました。

トランジットゲートウェイのルートテーブルでは、「0.0.0.0/0」はネットワーク共有VPCへルートさせるので、晴れてインターネットへ接続できるようになりました！！

ただ、まだ設定が必要です。次が最後です。

### ネットワーク共有VPCのパブリックサブネットのRouteに「10.0.2.0/24」を追加する
ネットワーク共有VPCのパブリックサブネットのRouteに「10.0.2.0/24」を追加します。
上記した通り、戻りトラフィックをネットワーク使用VPCにルートするためですね。

ルートテーブル画面から編集します。手順は同様ですので省略します。

![cap29.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3654730/054a2d7d-1c33-1898-688b-ebfa74cac636.png)

ルートが追加されました！

## SSMアクセスしてみる
ここまで長かった・・・。
EC2にSSMアクセスできるようになっているはずなので、コンソールからを確認します。

![cap30.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3654730/13128b18-4e75-7681-f7d2-26433601207f.png)

一応再起動させます。

![cap32.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3654730/e23cf87e-1a5a-93ad-89bb-59f66cd3bb83.png)

インスタンス画面から「接続」を選択します。
接続画面はキャッシュが強烈なので、スーパーリロードします。

![cap31.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3654730/ac90f394-45f4-97fd-0898-e9fc359a95b8.png)

接続ボタンが活性化されました！！！！
SSMアクセスしてみましょう！

![cap33.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3654730/9d1ebf1a-d2aa-4fef-75ef-ebbf96273d85.png)

うおおおインターネットにつながってますね！

ということで、上述した
**①NATゲートウェイを持つネットワーク共有用VPCを作成**し、
**②複数のVPCとこの共有VPCを統合する**アーキテクチャを作成することができました。

NATゲートウェイだけではなく、VPCエンドポイントなども同じ要領で1つのVPCに集約できるため、色々はかどりそうです。

claudeが通信帯域のひっ迫やセキュリティについて注意しなさいと言っていたので、
しっかり運用する際はそこらへんも考慮すると良さそうです。

# おわりに
SAPで何回も聞かれるTransit Gatewayの挙動を簡単な範囲で検証してみました。
戻りトラフィックのことをClaudeもGPTも私もすっかり忘れており土曜が消し飛びましたが、
良い感じに解決できてよかったです！！