VPC Endpoint共有できるってマジ！？！？

# はじめに
- 私はつい先日まで、「VPC EndpointはVPC毎に作成が必須で、異なるVPCにはそれぞれVPC Endpointを作成しないといけない」と思っていました。
- しかし、とあるお客様にて、「うちは共有のVPC Endpoint作成して、それを別のVPCへ共有する構成にしてる」というコメントをいただき、え？それができるの？と思い、実際にVPC Endpointを共有できる構成を作ってみました。
# 本ブログの構成
## 確認前提
- EC2インスタンスにセッションマネージャーで接続できることを確認します。
  - セッションマネージャーで接続するときのEndpointは以下2つが必要です。
    - com.amazonaws.<リージョン名>.ssm
    - com.amazonaws.<リージョン名>.ssmmessages
  - 以下も必要……と思われるかもしれませんが、実はこれは不要になりました。
    - com.amazonaws.<リージョン名>.ec2messages
    - [Systems Manager 用の VPC Endpointを作成する](https://docs.aws.amazon.com/ja_jp/systems-manager/latest/userguide/setup-create-vpc.html#create-vpc-endpoints)
    > com.amazonaws.region.ec2messages
    > Systems Manager は、このエンドポイントを使用して SSM Agent から Systems Manager サービスへの呼び出しを行います。SSM Agent のバージョン 3.3.40.0 以降、Systems Manager は、使用可能な場合には ec2messages:* エンドポイント (Amazon Message Delivery Service) の代わりに ssmmessages:* エンドポイント (Amazon Message Gateway Service) を使用するようになりました。
- セッションマネージャーで接続できることの確認方法は以下3つで行います。
  - セッションマネージャーでアクセスできること
    - 実際はこれさえできればOKだったりはするのですが……
  - EC2インスタンス上で、セッションマネージャーのEndpointに対して名前解決ができること
  - VPC Reachability Analyzerによって、EC2インスタンスからVPC Endpointに対して通信が到達できること
    - せっかくなので、どういう経路で接続しているかの確認を一緒にしましょう。
    - VPC Reachability Analyzerの使い方は以下ブログを参照
      - [VPC Reachability Analyzer を使ってみた](https://www.skyarch.net/blog/vpc-reachability-analyzer-%E3%82%92%E4%BD%BF%E3%81%A3%E3%81%A6%E3%81%BF%E3%81%9F/)
## 想定構成
それぞれで構成図とTerraformコードを用意していますので、お試ししやすいようにしています。
1. Single VPC
   - よくある、1VPCあたり1VPC Endpoint構成
   - 1つのVPCにEC2インスタンスとVPC Endpointを作成
   - (今回は設定しないですが)EC2インスタンスとVPC Endpointで異なるセキュリティグループをアタッチできるよう、subnetを分けて構成しています。
2. VPC Peering
   - 1つのVPCにEC2インスタンスを、もう一つのVPCにVPC Endpointを作成
   - 2つのVPCをVPC Peeringで接続
3. TransitGateway
   - A VPCにEC2インスタンスを、B VPCにVPC Endpointを作成
   - 2つのVPCをTransitGatewayで接続
# 構成① Single VPC
## 構成図
![Single VPCの構成図](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1276335/41ee349a-4d58-965a-4759-37f16ac2426f.png)
## Terraformコード
<details><summary>Single VPCのTerraformコード</summary>

```hcl
# Provider
terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "5.82.2"
    }
  }
}

provider "aws" {
}

# VPCの作成
resource "aws_vpc" "vpc_main" {
  cidr_block           = "10.0.0.0/16"
  enable_dns_support   = "true"
  enable_dns_hostnames = "true"
}

# インスタンス用subnetの作成
resource "aws_subnet" "private_subnet01" {
  vpc_id     = aws_vpc.vpc_main.id
  cidr_block = "10.0.1.0/24"

  tags = {
    Name = "Main"
  }
}

# VPC Endpoint用subnetの作成
resource "aws_subnet" "private_subnet02" {
  vpc_id     = aws_vpc.vpc_main.id
  cidr_block = "10.0.2.0/24"

  tags = {
    Name = "Main"
  }
}

# VPC Endpointの作成
resource "aws_vpc_endpoint" "ssm_endpoint" {
  vpc_id              = aws_vpc.vpc_main.id
  service_name        = "com.amazonaws.ap-northeast-1.ssm"
  private_dns_enabled = true
  vpc_endpoint_type   = "Interface"
  subnet_ids          = [aws_subnet.private_subnet02.id]
}

resource "aws_vpc_endpoint" "ssmmessage_endpoint" {
  vpc_id              = aws_vpc.vpc_main.id
  service_name        = "com.amazonaws.ap-northeast-1.ssmmessages"
  private_dns_enabled = true
  vpc_endpoint_type   = "Interface"
  subnet_ids          = [aws_subnet.private_subnet02.id]
}

# EC2インスタンスの作成
resource "aws_instance" "ec2_instance" {
  ami                  = "ami-0ab02459752898a60" #Amazon Linux 2023 AMI 2023.6.20241212.0 x86_64 HVM kernel-6.1
  instance_type        = "t3.micro"
  subnet_id            = aws_subnet.private_subnet01.id
  iam_instance_profile = aws_iam_instance_profile.ssm_access_profile.id

  user_data = <<-EOF
             #!/bin/bash
             cd /tmp
             sudo systemctl enable amazon-ssm-agent
             sudo systemctl start amazon-ssm-agent
             EOF
}

# セッションマネージャー用のインスタンスプロファイルの作成
resource "aws_iam_role" "ssm_access_role" {
  name = "ssm-managed-instance-role"

  assume_role_policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Action = "sts:AssumeRole"
        Effect = "Allow"
        Principal = {
          Service = "ec2.amazonaws.com"
        }
      }
    ]
  })
}

resource "aws_iam_role_policy_attachment" "ssm_access_policy" {
  role       = aws_iam_role.ssm_access_role.name
  policy_arn = "arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore"
}

resource "aws_iam_instance_profile" "ssm_access_profile" {
  name = "ssm-managed-instance-profile"
  role = aws_iam_role.ssm_access_role.name
}
```
</details>

## 確認結果
### セッションマネージャーでアクセス
- 画像の通りアクセスできています。
![Single VPC構成のセッションマネージャー画面](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1276335/2b56dcef-54f2-2f50-9c43-54014cef00c5.png)
### ホスト上で名前解決
- ホスト上でnslookupコマンドを実行した結果です。
- 特段Route 53などでドメイン登録をしていませんが、VPC Endpoint作成時に"private_dns_enabled"を"true"にしていることで名前解決ができるようになっています。
![Single VPC構成の名前解決](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1276335/1db08e36-6af4-2489-4fa9-a68332b11ca8.png)
### VPC Reachability Analyzerによる接続経路確認
- 例としてEC2インスタンスからssm Endpointへの接続を試しています。
![VPC Reachability Analyzer画像その1](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1276335/0389e7e3-2710-4e92-b1cc-0e118df54eda.png)
![VPC Reachability Analyzer画像その2](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1276335/5ffeb835-30b6-2ad6-97b0-5aa32a5ba1d4.png)
- いわゆる同一VPC内の通信といえばこれ！という感じになってますね。
  - 特筆すべき内容が無く、シンプルに……
# 構成② VPC Peering
## 構成図
![VPC Peeringの構成図](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1276335/d449a62a-c092-1941-6908-9185950dbcac.png)
## Terraformコード
<details><summary>VPC Peeringの場合のTerraformコード</summary>

```hcl
# Provider
terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "5.82.2"
    }
  }
}

provider "aws" {
}

# インスタンス用VPCの作成
resource "aws_vpc" "vpc01" {
  cidr_block           = "10.0.0.0/16"
  enable_dns_support   = "true"
  enable_dns_hostnames = "true"
}

# VPC Endpoint用VPCの作成
resource "aws_vpc" "vpc02" {
  cidr_block           = "10.1.0.0/16"
  enable_dns_support   = "true"
  enable_dns_hostnames = "true"
}

# インスタンス用subnetの作成
resource "aws_subnet" "private_subnet01" {
  vpc_id     = aws_vpc.vpc01.id
  cidr_block = "10.0.1.0/24"
}

# VPC Endpoint用subnetの作成
resource "aws_subnet" "private_subnet02" {
  vpc_id     = aws_vpc.vpc02.id
  cidr_block = "10.1.1.0/24"
}

# VPC Peering Connectionの作成
resource "aws_vpc_peering_connection" "vpc_peering" {
  vpc_id      = aws_vpc.vpc01.id
  peer_vpc_id = aws_vpc.vpc02.id
  auto_accept = true
}

# vpc01のルートテーブル作成
resource "aws_route_table" "rt_vpc01" {
  vpc_id = aws_vpc.vpc01.id
}

# vpc02のルートテーブル作成
resource "aws_route_table" "rt_vpc02" {
  vpc_id = aws_vpc.vpc02.id
}

# vpc01からvpc02へのルート追加
resource "aws_route" "route_vpc01_to_vpc02" {
  route_table_id            = aws_route_table.rt_vpc01.id
  destination_cidr_block    = aws_vpc.vpc02.cidr_block
  vpc_peering_connection_id = aws_vpc_peering_connection.vpc_peering.id
}

# vpc02からvpc01へのルート追加
resource "aws_route" "route_vpc02_to_vpc01" {
  route_table_id            = aws_route_table.rt_vpc02.id
  destination_cidr_block    = aws_vpc.vpc01.cidr_block
  vpc_peering_connection_id = aws_vpc_peering_connection.vpc_peering.id
}

# ルートテーブルとサブネットの関連付け(vpc01)
resource "aws_route_table_association" "rta_subnet01" {
  subnet_id      = aws_subnet.private_subnet01.id
  route_table_id = aws_route_table.rt_vpc01.id
}

# ルートテーブルとサブネットの関連付け(vpc02)
resource "aws_route_table_association" "rta_subnet02" {
  subnet_id      = aws_subnet.private_subnet02.id
  route_table_id = aws_route_table.rt_vpc02.id
}

# セキュリティグループの作成
resource "aws_security_group" "endpoint_sg" {
  name        = "vpc-endpoint-sg"
  description = "Security group for VPC endpoints"
  vpc_id      = aws_vpc.vpc02.id

  ingress {
    from_port   = 443
    to_port     = 443
    protocol    = "tcp"
    cidr_blocks = [aws_vpc.vpc01.cidr_block]
  }

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = [aws_vpc.vpc01.cidr_block]
  }
}

resource "aws_security_group" "instance_sg" {
  name        = "instance-sg"
  description = "Security group for EC2 instance"
  vpc_id      = aws_vpc.vpc01.id

  ingress {
    from_port   = 443
    to_port     = 443
    protocol    = "tcp"
    cidr_blocks = [aws_vpc.vpc02.cidr_block]
  }

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = [aws_vpc.vpc02.cidr_block]
  }
}

# VPC Endpointの作成
resource "aws_vpc_endpoint" "ssm_endpoint" {
  vpc_id              = aws_vpc.vpc02.id
  service_name        = "com.amazonaws.ap-northeast-1.ssm"
  private_dns_enabled = false
  vpc_endpoint_type   = "Interface"
  subnet_ids          = [aws_subnet.private_subnet02.id]
  security_group_ids  = [aws_security_group.endpoint_sg.id]
}

resource "aws_vpc_endpoint" "ssmmessage_endpoint" {
  vpc_id              = aws_vpc.vpc02.id
  service_name        = "com.amazonaws.ap-northeast-1.ssmmessages"
  private_dns_enabled = false
  vpc_endpoint_type   = "Interface"
  subnet_ids          = [aws_subnet.private_subnet02.id]
  security_group_ids  = [aws_security_group.endpoint_sg.id]
}

# EC2インスタンスの作成
resource "aws_instance" "ec2_instance" {
  ami                    = "ami-0ab02459752898a60" #Amazon Linux 2023 AMI 2023.6.20241212.0 x86_64 HVM kernel-6.1
  instance_type          = "t3.micro"
  subnet_id              = aws_subnet.private_subnet01.id
  iam_instance_profile   = aws_iam_instance_profile.ssm_access_profile.id
  vpc_security_group_ids = [aws_security_group.instance_sg.id]

  user_data = <<-EOF
             #!/bin/bash
             cd /tmp
             sudo systemctl enable amazon-ssm-agent
             sudo systemctl start amazon-ssm-agent
             EOF
}

resource "aws_iam_role" "ssm_access_role" {
  name = "ssm-managed-instance-role"

  assume_role_policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Action = "sts:AssumeRole"
        Effect = "Allow"
        Principal = {
          Service = "ec2.amazonaws.com"
        }
      }
    ]
  })
}

# セッションマネージャー用のインスタンスプロファイルの作成
resource "aws_iam_role_policy_attachment" "ssm_access_policy" {
  role       = aws_iam_role.ssm_access_role.name
  policy_arn = "arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore"
}

resource "aws_iam_instance_profile" "ssm_access_profile" {
  name = "ssm-managed-instance-profile"
  role = aws_iam_role.ssm_access_role.name
}

# SSM EndpointのためのRoute 53プライベートホストゾーン
## SSM EndpointのためのRoute 53プライベートホストゾーンの作成
resource "aws_route53_zone" "ssm_domain" {
  name = "ssm.ap-northeast-1.amazonaws.com"

  vpc {
    vpc_id = aws_vpc.vpc01.id
  }
}

## Route 53プライベートホストゾーンをVPC02にも関連付け
resource "aws_route53_zone_association" "ssm_domain_endpoint" {
  zone_id = aws_route53_zone.ssm_domain.zone_id
  vpc_id  = aws_vpc.vpc02.id
}

## SSM EndpointのDNSレコード
resource "aws_route53_record" "ssm" {
  zone_id = aws_route53_zone.ssm_domain.zone_id
  name    = "ssm.ap-northeast-1.amazonaws.com"
  type    = "A"

  alias {
    name                   = aws_vpc_endpoint.ssm_endpoint.dns_entry[0]["dns_name"]
    zone_id                = aws_vpc_endpoint.ssm_endpoint.dns_entry[0]["hosted_zone_id"]
    evaluate_target_health = true
  }
}

# SSMMessage EndpointのためのRoute53プライベートホストゾーン
## SSMMessage EndpointのためのRoute 53プライベートホストゾーンの作成
resource "aws_route53_zone" "ssmmessage_domain" {
  name = "ssmmessages.ap-northeast-1.amazonaws.com"

  vpc {
    vpc_id = aws_vpc.vpc01.id
  }
}

## Route 53プライベートホストゾーンをVPC02にも関連付け
resource "aws_route53_zone_association" "ssmmessage_domain_endpoint" {
  zone_id = aws_route53_zone.ssmmessage_domain.zone_id
  vpc_id  = aws_vpc.vpc02.id
}

## SSM Messages EndpointのDNSレコード
resource "aws_route53_record" "ssmmessages" {
  zone_id = aws_route53_zone.ssmmessage_domain.zone_id
  name    = "ssmmessages.ap-northeast-1.amazonaws.com"
  type    = "A"

  alias {
    name                   = aws_vpc_endpoint.ssmmessage_endpoint.dns_entry[0]["dns_name"]
    zone_id                = aws_vpc_endpoint.ssmmessage_endpoint.dns_entry[0]["hosted_zone_id"]
    evaluate_target_health = true
  }
}
```
</details>

## 確認結果
### セッションマネージャーでアクセス
- ここはSingle VPCと同じ結果になるため省略しています。
- 問題なくアクセスできました。
### ホスト上で名前解決
![VPC Peering構成の名前解決](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1276335/cc976f8b-8e93-7f91-ae20-ebd35bec52fa.png)
### VPC Reachability Analyzerによる接続経路確認
- 例としてEC2インスタンスからssm Endpointへの接続を試しています。
![VPC Reachability Analyzer画像その1](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1276335/af96e742-c3a4-4d07-be20-fe285f7f0c36.png)
![VPC Reachability Analyzer画像その2](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1276335/a8f8075e-a16e-5741-4197-182ff8edb0c6.png)
- 他の構成と異なり、VPC Peeringを経由していることがわかります。
- この構成でも問題なくセッションマネージャーによるEC2アクセスができていることが確認できました。
## 注意
- 今回は同一AWSアカウントにて2つのVPCを作成しています。
- そのため、同一のTerraformコード内で以下のようにVPC Peeringを作成時に"auto_accept"を"true"で設定しています。
  ```hcl
    resource "aws_vpc_peering_connection" "vpc_peering" {
      vpc_id      = aws_vpc.vpc01.id
      peer_vpc_id = aws_vpc.vpc02.id
      auto_accept = true
    }
  ```
- しかし、異なるAWSアカウントのVPCを接続したいことも要件としてあると思います。
- そういった場合は以下のようなコードを実行し、別途承認側のAWSアカウントでVPC Peering接続を承認してあげる必要があります。
  ```hcl
    resource "aws_vpc_peering_connection" "vpc_peering" {
      vpc_id        = aws_vpc.vpc01.id # リクエスト側のVPC ID
      peer_vpc_id   = "vpc-abcdef"  # 承認側のVPC ID
      peer_owner_id = "123456789012"   # 承認側のAWSアカウントID
      auto_accept   = false            # 異なるアカウント間では自動承認は機能しません
    }
  ```

# 構成③ TransitGateway
## 構成図
![TransitGatewayの構成図](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1276335/3086971b-9974-655d-8f86-1ab3fd98042f.png)
## Terraformコード
<details><summary>VPC Peeringの場合のTerraformコード</summary>

```hcl
# Provider
terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "5.82.2"
    }
  }
}

provider "aws" {
}

# インスタンス用VPCの作成
resource "aws_vpc" "vpc01" {
  cidr_block           = "10.0.0.0/16"
  enable_dns_support   = "true"
  enable_dns_hostnames = "true"
}

# VPC Endpoint用VPCの作成
resource "aws_vpc" "vpc02" {
  cidr_block           = "10.1.0.0/16"
  enable_dns_support   = "true"
  enable_dns_hostnames = "true"
}

# インスタンス用subnetの作成
resource "aws_subnet" "private_subnet01" {
  vpc_id     = aws_vpc.vpc01.id
  cidr_block = "10.0.1.0/24"
}

# VPC Endpoint用subnetの作成
resource "aws_subnet" "private_subnet02" {
  vpc_id     = aws_vpc.vpc02.id
  cidr_block = "10.1.1.0/24"
}

# Transit Gatewayの作成
resource "aws_ec2_transit_gateway" "tgw" {
  description = "Transit Gateway for VPC connectivity"
  auto_accept_shared_attachments = "enable"
  default_route_table_association = "enable"
  default_route_table_propagation = "enable"
}

# VPC01のTransit Gateway Attachment
resource "aws_ec2_transit_gateway_vpc_attachment" "vpc01_attachment" {
  subnet_ids         = [aws_subnet.private_subnet01.id]
  transit_gateway_id = aws_ec2_transit_gateway.tgw.id
  vpc_id            = aws_vpc.vpc01.id
}

# VPC02のTransit Gateway Attachment
resource "aws_ec2_transit_gateway_vpc_attachment" "vpc02_attachment" {
  subnet_ids         = [aws_subnet.private_subnet02.id]
  transit_gateway_id = aws_ec2_transit_gateway.tgw.id
  vpc_id            = aws_vpc.vpc02.id
}

# vpc01のルートテーブル作成
resource "aws_route_table" "rt_vpc01" {
  vpc_id = aws_vpc.vpc01.id
}

# vpc02のルートテーブル作成
resource "aws_route_table" "rt_vpc02" {
  vpc_id = aws_vpc.vpc02.id
}

# vpc01からvpc02へのルート追加（Transit Gateway経由）
resource "aws_route" "route_vpc01_to_vpc02" {
  route_table_id         = aws_route_table.rt_vpc01.id
  destination_cidr_block = aws_vpc.vpc02.cidr_block
  transit_gateway_id     = aws_ec2_transit_gateway.tgw.id
}

# vpc02からvpc01へのルート追加（Transit Gateway経由）
resource "aws_route" "route_vpc02_to_vpc01" {
  route_table_id         = aws_route_table.rt_vpc02.id
  destination_cidr_block = aws_vpc.vpc01.cidr_block
  transit_gateway_id     = aws_ec2_transit_gateway.tgw.id
}

# ルートテーブルとサブネットの関連付け(vpc01)
resource "aws_route_table_association" "rta_subnet01" {
  subnet_id      = aws_subnet.private_subnet01.id
  route_table_id = aws_route_table.rt_vpc01.id
}

# ルートテーブルとサブネットの関連付け(vpc02)
resource "aws_route_table_association" "rta_subnet02" {
  subnet_id      = aws_subnet.private_subnet02.id
  route_table_id = aws_route_table.rt_vpc02.id
}

# セキュリティグループの作成
resource "aws_security_group" "endpoint_sg" {
  name        = "vpc-endpoint-sg"
  description = "Security group for VPC endpoints"
  vpc_id      = aws_vpc.vpc02.id

  ingress {
    from_port   = 443
    to_port     = 443
    protocol    = "tcp"
    cidr_blocks = [aws_vpc.vpc01.cidr_block]
  }

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = [aws_vpc.vpc01.cidr_block]
  }
}

resource "aws_security_group" "instance_sg" {
  name        = "instance-sg"
  description = "Security group for EC2 instance"
  vpc_id      = aws_vpc.vpc01.id

  ingress {
    from_port   = 443
    to_port     = 443
    protocol    = "tcp"
    cidr_blocks = [aws_vpc.vpc02.cidr_block]
  }

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = [aws_vpc.vpc02.cidr_block]
  }
}

# VPC Endpointの作成
resource "aws_vpc_endpoint" "ssm_endpoint" {
  vpc_id              = aws_vpc.vpc02.id
  service_name        = "com.amazonaws.ap-northeast-1.ssm"
  private_dns_enabled = false
  vpc_endpoint_type   = "Interface"
  subnet_ids          = [aws_subnet.private_subnet02.id]
  security_group_ids  = [aws_security_group.endpoint_sg.id]
}

resource "aws_vpc_endpoint" "ssmmessage_endpoint" {
  vpc_id              = aws_vpc.vpc02.id
  service_name        = "com.amazonaws.ap-northeast-1.ssmmessages"
  private_dns_enabled = false
  vpc_endpoint_type   = "Interface"
  subnet_ids          = [aws_subnet.private_subnet02.id]
  security_group_ids  = [aws_security_group.endpoint_sg.id]
}

# EC2インスタンスの作成
resource "aws_instance" "ec2_instance" {
  ami                    = "ami-0ab02459752898a60" #Amazon Linux 2023 AMI 2023.6.20241212.0 x86_64 HVM kernel-6.1
  instance_type          = "t3.micro"
  subnet_id              = aws_subnet.private_subnet01.id
  iam_instance_profile   = aws_iam_instance_profile.ssm_access_profile.id
  vpc_security_group_ids = [aws_security_group.instance_sg.id]

  user_data = <<-EOF
              #!/bin/bash
              cd /tmp
              sudo systemctl enable amazon-ssm-agent
              sudo systemctl start amazon-ssm-agent
              EOF
}

resource "aws_iam_role" "ssm_access_role" {
  name = "ssm-managed-instance-role"

  assume_role_policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Action = "sts:AssumeRole"
        Effect = "Allow"
        Principal = {
          Service = "ec2.amazonaws.com"
        }
      }
    ]
  })
}

# セッションマネージャー用のインスタンスプロファイルの作成
resource "aws_iam_role_policy_attachment" "ssm_access_policy" {
  role       = aws_iam_role.ssm_access_role.name
  policy_arn = "arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore"
}

resource "aws_iam_instance_profile" "ssm_access_profile" {
  name = "ssm-managed-instance-profile"
  role = aws_iam_role.ssm_access_role.name
}

# SSM EndpointのためのRoute 53プライベートホストゾーン
## SSM EndpointのためのRoute 53プライベートホストゾーンの作成
resource "aws_route53_zone" "ssm_domain" {
  name = "ssm.ap-northeast-1.amazonaws.com"

  vpc {
    vpc_id = aws_vpc.vpc01.id
  }
}

## Route 53プライベートホストゾーンをVPC02にも関連付け
resource "aws_route53_zone_association" "ssm_domain_endpoint" {
  zone_id = aws_route53_zone.ssm_domain.zone_id
  vpc_id  = aws_vpc.vpc02.id
}

## SSM EndpointのDNSレコード
resource "aws_route53_record" "ssm" {
  zone_id = aws_route53_zone.ssm_domain.zone_id
  name    = "ssm.ap-northeast-1.amazonaws.com"
  type    = "A"

  alias {
    name                   = aws_vpc_endpoint.ssm_endpoint.dns_entry[0]["dns_name"]
    zone_id                = aws_vpc_endpoint.ssm_endpoint.dns_entry[0]["hosted_zone_id"]
    evaluate_target_health = true
  }
}

# SSMMessage EndpointのためのRoute 53プライベートホストゾーン
## SSMMessage EndpointのためのRoute 53プライベートホストゾーンの作成
resource "aws_route53_zone" "ssmmessage_domain" {
  name = "ssmmessages.ap-northeast-1.amazonaws.com"

  vpc {
    vpc_id = aws_vpc.vpc01.id
  }
}

## Route 53プライベートホストゾーンをVPC02にも関連付け
resource "aws_route53_zone_association" "ssmmessage_domain_endpoint" {
  zone_id = aws_route53_zone.ssmmessage_domain.zone_id
  vpc_id  = aws_vpc.vpc02.id
}

## SSM Messages EndpointのDNSレコード
resource "aws_route53_record" "ssmmessages" {
  zone_id = aws_route53_zone.ssmmessage_domain.zone_id
  name    = "ssmmessages.ap-northeast-1.amazonaws.com"
  type    = "A"

  alias {
    name                   = aws_vpc_endpoint.ssmmessage_endpoint.dns_entry[0]["dns_name"]
    zone_id                = aws_vpc_endpoint.ssmmessage_endpoint.dns_entry[0]["hosted_zone_id"]
    evaluate_target_health = true
  }
}
```
</details>

## 確認結果
### セッションマネージャーでアクセス
- ここはSingle VPCと同じ結果になるため省略しています。
- 問題なくアクセスできました。
### ホスト上で名前解決
![TransitGateway構成の名前解決](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1276335/6926f1d7-efda-4b55-1427-b85dccdecd15.png)
### VPC Reachability Analyzerによる接続経路確認
- 例としてEC2インスタンスからssm Endpointへの接続を試しています。
![VPC Reachability Analyzer画像その1](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1276335/b1661cf8-498a-0034-bcb3-2d5c8e19dc77.png)
![VPC Reachability Analyzer画像その2](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1276335/1466dbf6-32c0-a668-d962-8b5f644d8a34.png)
- 他の構成と異なり、Transit Gateway Attachmentを経由してTransitGatewayのRouteTableを参照、再度Transit Gateway Attachmentを経由していることがわかります。
- この構成でも問題なくセッションマネージャーによるEC2アクセスができていることが確認できました。
# 技術的解説
## エンドポイント作成時の"private_dns_enabled"オプションのtrue/false
- 以下はTransitGateway環境のssm用VPC Endpoint作成時のコードの抜粋です。
    - "private_dns_enabled"の箇所で、VPC Endpoint作成時にRoute53のホストゾーンにエントリを追加するかどうかを指定しています。
```hcl
resource "aws_vpc_endpoint" "ssm_endpoint" {
  vpc_id              = aws_vpc.vpc02.id
  service_name        = "com.amazonaws.ap-northeast-1.ssm"
  private_dns_enabled = false
  vpc_endpoint_type   = "Interface"
  subnet_ids          = [aws_subnet.private_subnet02.id]
  security_group_ids  = [aws_security_group.endpoint_sg.id]
}
```
- Single VPC環境では"true"にしています。
- 一方、VPC Peering環境、およびTransitGateway環境では"false"にしています。
  - これは、"private_dns_enabled"を"true"にし、かつRoute 53 Resolverにエントリを追加してしまうと、同一の名前解決エントリが重複してしまい予期しない動作が発生する可能性があるからです。
  - 今回に限って言うと、おそらくホストゾーンは分かれていてもエントリが同じになることが想定されるため、大きな問題にはならないと思いますが、重複はできる限り避けるべきです。
## DNS解決の仕組み
### Route 53 Resolverの基本動作
- VPC内のDNS解決は、VPCのDNSサーバー（VPC CIDR + 2のIPアドレス）が担当しています。
- Route 53 ResolverはAWSマネージドのDNSリゾルバーで、VPC内のDNS解決をハンドリングしてくれます。
- Interface型VPC EndpointはプライベートホストゾーンとDNSエントリを自動的に作成してくれます。
### プライベートホストゾーンの動作
- VPC EndpointのプライベートホストゾーンがDNSクエリをEndpointのプライベートIPにマッピングします。
  - 例：`ssm.ap-northeast-1.amazonaws.com` → VPC Endpoint のプライベートIP
- 同じVPC内では自動的にDNS解決が機能してくれますが、VPC間では追加設定が必要です。
### VPCピアリング/TransitGW環境でのDNS転送
- DNS解決の流れ
  1. EC2からのDNSクエリをVPCのDNSサーバーが受信
  2. Route 53 Resolverが該当するプライベートホストゾーンを検索
  3. 関連付けられたVPCのプライベートホストゾーンも含めて名前解決を実行
  4. 解決されたIPアドレスを返却
## パフォーマンス比較
- 接続レイテンシーの測定
  - Single VPCの場合はそのほか2つの接続方式に比べてホップ数が少ないため一番レイテンシとしては小さく動かすことができます。
  - それ以外の2つの接続方法の比較は以下ブログがありましたのでこちらをご参照ください。
    - このブログの結論として、「VPC Peeringの場合は50μs程度, TGWの場合は600μs程度のレイテンシ」という結論だそうです。
    - [AWS VPC接続方法によるレイテンシ（VPC Peering vs TGW）](https://zenn.dev/acntechjp/articles/tgw-vs-vpc-peering-latency)
- DNS解決時間の比較
  - digコマンドを5回実行し、その結果を比較使用としたのですが……
  - 有意な差が見受けられませんでした。
    ```
    # 実行例
    [ssm-user@ip-10-0-1-25 bin]$ dig vpce-0a74c5085c7087e41-bu2pk4tr.ssm.ap-northeast-1.vpce.amazonaws.com

    ; <<>> DiG 9.18.28 <<>> vpce-0a74c5085c7087e41-bu2pk4tr.ssm.ap-northeast-1.vpce.amazonaws.com
    ;; global options: +cmd
    ;; Got answer:
    ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 47791
    ;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1

    ;; OPT PSEUDOSECTION:
    ; EDNS: version: 0, flags:; udp: 4096
    ;; QUESTION SECTION:
    ;vpce-0a74c5085c7087e41-bu2pk4tr.ssm.ap-northeast-1.vpce.amazonaws.com. IN A

    ;; ANSWER SECTION:
    vpce-0a74c5085c7087e41-bu2pk4tr.ssm.ap-northeast-1.vpce.amazonaws.com. 60 IN A 10.0.2.193

    ;; Query time: 0 msec
    ;; SERVER: 10.0.0.2#53(10.0.0.2) (UDP)
    ;; WHEN: Thu Jan 02 10:47:37 UTC 2025
    ;; MSG SIZE  rcvd: 114

    [ssm-user@ip-10-0-1-25 bin]$
    ```
  - おそらく、VPC PeeringやTransitGatewayを用いた構成のほか、意識的に作成はしていないだけでSingel VPC構成もRoute 53 Resolverにエントリが存在しているということなのかなと想像します。
  - そうすれば、どの構成においても同様の通信経路になるために計測時間に差異はないということなのでしょうか。
  - もしご意見ある方いらっしゃれば、コメントいただけると大変助かります_|＼○_
  - 参考
    - [【dig】DNSの名前解決速度を測る](https://zenn.dev/k41531/articles/80f0ad25794f11)
## コスト比較
※EC2インスタンスの費用は共通で発生するため、今回は考慮外としています。
### Single VPC
- VPCエンドポイント（Interface型）: $0.014/時間 × 2エンドポイント(ssm,ssmmessage) = $0.028/時間
- 月額概算: $0.028 × 24 × 30 = VPC当たり約$20.16/月
### VPC Peering
- VPCエンドポイント（Interface型）: $0.014/時間 × 2エンドポイント(ssm,ssmmessage) = $0.028/時間
- Route 53プライベートゾーン: $0.50/ホストゾーン/月 × 2 = $1.00/月
- 月額概算: ($0.028 × 24 × 30) + $1.00 = 約$21.16/月
### Transit Gateway
- VPCエンドポイント（Interface型）: $0.014/時間 × 2エンドポイント(ssm,ssmmessage) = $0.028/時間
- Transit Gateway: $0.05/時間
- Transit Gateway アタッチメント: $0.05/アタッチメント/時間 × 2 = $0.10/時間
- Route 53プライベートゾーン: $0.50/ホストゾーン/月 × 2 = $1.00/月
- 月額概算: ($0.028 + $0.05 + $0.10) × 24 × 30 + $1.00 = 約$129.16/月
  - 接続先VPCが増えることにより増加するAWSコストは「Transit Gateway アタッチメント」部分
### コスト観点のまとめ
- とりあえず10VPCまでで、集権的なセッションマネージャー用のVPC Endpoint環境を構築した場合のコスト比較をしてみました。

| VPC数 | 構成1: Single VPC | 構成2: VPC Peering | 構成3: Transit Gateway |
|-------|----------------------------------|---------------------|----------------------|
| 1 VPC | $20.16 | - | - |
| 2 VPC | $40.32 | $21.16 | $129.16 |
| 3 VPC | $60.48 | $21.16 | $185.82 |
| 4 VPC | $80.64 | $21.16 | $242.48 |
| 5 VPC | $100.80 | $21.16 | $299.14 |
| 6 VPC | $120.96 | $21.16 | $355.80 |
| 7 VPC | $141.12 | $21.16 | $412.46 |
| 8 VPC | $161.28 | $21.16 | $469.12 |
| 9 VPC | $181.44 | $21.16 | $525.78 |
| 10 VPC | $201.60 | $21.16 | $582.44 |

- 上記はあくまで固定費のみで、通信料が含まれていない点には注意が必要です。
  - VPC Peeringの場合
    - 同一AZ間の通信費用は無料
    - 別AZ、あるいは別リージョン間の通信費用は0.01USD/GB
  - Transit Gatewayの場合
    - 同一リージョン間の通信費用は無料
    - 別リージョン間の通信費用は0.02USD/GB
- これらのことから
  - 1VPCであれば他に接続する箇所もないためSingle VPC構成のみが選ばれます。
  - 2VPCより多い場合はよっぽどの別AZ間の通信が発生しない限りVPC Peeringがコスト優位になります。
- 一方、これはあくまでAWS 利用料のみにフォーカスしています。
  - VPCを増やすと管理コストが増加してしまうため、構成が変更になった場合やVPCが追加になった場合、変更のための運用コスト観点でTransitGatewayの方がトータルコストにメリットが出てくる場合があるでしょう。
- VPC Peeringの観点を除いて、Transit GatewayとVPC Peeringのトータルコスト比較をした記事がありましたので、こちらも含めてごらんください。
  - [Transit GatewayとVPC Peeringを比較してみた](https://dev.classmethod.jp/articles/different-from-vpc-peering-and-transit-gateway-japanese/#toc-2)
# まとめ
- 目的であった「VPC Endpoint」を複数VPC間で共有する、という目的は達成することができました。
- 以前少し試そうと思ったときにVPC Peeringだけ設定してなぜか上手くいかないなー……と思っていたのですが、名前解決が必要だったんですね。
- AWSコストだけでなく運用コストを踏まえたトータルコストを検討した上で、最適な構成の検討をしていくことが重要だなと感じました。