TerraformでVPC Peering環境を構築してみた

TerraformでVPC Peering環境を構築してみた

2026.02.21

歴史シミュレーションゲーム好きのくろすけです!

オンプレミス環境との接続を検証する機会があったため、簡易的な検証環境として Terraform で VPC Peering を構築しました。
本記事では、Terraform で VPC Peering 環境を構築手順と、実施した内容の概要を共有します。

概要

下記が今回の構成図です。
接続するネットワーク空間は必要最小限にしようと思い、Protected Subnet 間のみ接続するようにしています。

検証では EC2 はあまり必要ではありませんでしたが、疎通確認のために作成しています。
その際 EC2 にログインする必要があるため、NATGateway を双方の環境に作成しています。
(今回は メインVPC の EC2 のみにログインして疎通確認したため、実際は 仮想オンプレミスVPC の NATGateway は不要でした。)

VPCPeering.png

やってみた

1. Terraform モジュールの準備

基本的には Terraform AWS modules を使用しています。
ただし VPC のモジュールについては、カスタマイズしたものを使用しています。

記載しようと思っていましたが、文字数の都合で記載できませんでした。
機会があれば、別の記事で紹介しようと思います。

2. Terraform テンプレート

実際の構築に使用したテンプレートは下記になります。

main.tf

################################################################################
# Data Sources                                                                 #
################################################################################
data "aws_caller_identity" "current" {}
data "aws_region" "current" {}

################################################################################
# VPC                                                                          #
################################################################################
module "vpc" {
  source     = "../../modules/vpc"
  name       = "${var.project_name}-${var.env}-vpc"
  cidr_block = var.vpc_cidr_block

  igw_name = "${var.project_name}-${var.env}-igw"

  subnets = {
    "public_a" = {
      name              = "${var.project_name}-${var.env}-subnet-publ-a"
      cidr_block        = var.public_subnets.a.cidr_block
      availability_zone = "ap-northeast-1a"

      route_tables = ["public"]
      network_acl  = "public"
    },
    "protected_a" = {
      name              = "${var.project_name}-${var.env}-subnet-prot-a"
      cidr_block        = var.protected_subnets.a.cidr_block
      availability_zone = "ap-northeast-1a"

      route_tables = ["protected_a"]
      network_acl  = "protected"
    }
  }

  route_tables = {
    "public" = {
      name                 = "${var.project_name}-${var.env}-rtb-publ",
      use_internet_gateway = true,
    },
    "protected_a" = {
      name        = "${var.project_name}-${var.env}-rtb-prot-a",
      nat_gateway = "ngw_a"
    }
  }

  nat_gateways = {
    "ngw_a" = {
      name      = "${var.project_name}-${var.env}-ngw-a"
      subnet_id = "public_a"
    }
  }

  network_acls = {
    "public" = {
      name = "${var.project_name}-${var.env}-nacl-publ"
      ingress = [
        {
          rule_no    = 100
          protocol   = "-1"
          action     = "allow"
          cidr_block = "0.0.0.0/0"
          from_port  = 0
          to_port    = 0
        }
      ]
      egress = [
        {
          rule_no    = 100
          protocol   = "-1"
          action     = "allow"
          cidr_block = "0.0.0.0/0"
          from_port  = 0
          to_port    = 0
        }
      ]
    },
    "protected" = {
      name = "${var.project_name}-${var.env}-nacl-prot"
      ingress = [
        {
          rule_no    = 100
          protocol   = "-1"
          action     = "allow"
          cidr_block = "0.0.0.0/0"
          from_port  = 0
          to_port    = 0
        }
      ]
      egress = [
        {
          rule_no    = 100
          protocol   = "-1"
          action     = "allow"
          cidr_block = "0.0.0.0/0"
          from_port  = 0
          to_port    = 0
        }
      ]
    }
  }

  security_groups = {
    "sg_ec2" = {
      name        = "${var.project_name}-${var.env}-sg-ec2"
      description = "Security group for EC2"

      ingress = [
        {
          description = "All inbound traffic"
          from_port   = 0
          to_port     = 0
          protocol    = "-1"
          cidr_blocks = [var.vop_protected_subnets.a.cidr_block]
        }
      ]

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

module "virtual_on_premises" {
  source     = "../../modules/vpc"
  name       = "${var.project_name}-vop-${var.env}-vpc"
  cidr_block = var.vop_vpc_cidr_block

  igw_name = "${var.project_name}-vop-${var.env}-igw"

  subnets = {
    "public_a" = {
      name              = "${var.project_name}-vop-${var.env}-subnet-publ-a"
      cidr_block        = var.vop_public_subnets.a.cidr_block
      availability_zone = "ap-northeast-1a"

      route_tables = ["public"]
      network_acl  = "public"
    },
    "protected_a" = {
      name              = "${var.project_name}-vop-${var.env}-subnet-prot-a"
      cidr_block        = var.vop_protected_subnets.a.cidr_block
      availability_zone = "ap-northeast-1a"

      route_tables = ["protected_a"]
      network_acl  = "protected"
    }
  }

  route_tables = {
    "public" = {
      name                 = "${var.project_name}-vop-${var.env}-rtb-publ"
      use_internet_gateway = true
    },
    "protected_a" = {
      name        = "${var.project_name}-vop-${var.env}-rtb-prot-a"
      nat_gateway = "ngw_a"
    }
  }

  nat_gateways = {
    "ngw_a" = {
      name      = "${var.project_name}-vop-${var.env}-ngw-a"
      subnet_id = "public_a"
    }
  }

  network_acls = {
    "public" = {
      name = "${var.project_name}-vop-${var.env}-nacl-publ"
      ingress = [
        {
          rule_no    = 100
          protocol   = "-1"
          action     = "allow"
          cidr_block = "0.0.0.0/0"
          from_port  = 0
          to_port    = 0
        }
      ]
      egress = [
        {
          rule_no    = 100
          protocol   = "-1"
          action     = "allow"
          cidr_block = "0.0.0.0/0"
          from_port  = 0
          to_port    = 0
        }
      ]
    },
    "protected" = {
      name = "${var.project_name}-vop-${var.env}-nacl-prot"
      ingress = [
        {
          rule_no    = 100
          protocol   = "-1"
          action     = "allow"
          cidr_block = "0.0.0.0/0"
          from_port  = 0
          to_port    = 0
        }
      ]
      egress = [
        {
          rule_no    = 100
          protocol   = "-1"
          action     = "allow"
          cidr_block = "0.0.0.0/0"
          from_port  = 0
          to_port    = 0
        }
      ]
    }
  }

  security_groups = {
    "sg_vop_ec2" = {
      name        = "${var.project_name}-${var.env}-sg-vop-ec2"
      description = "Security group for EC2 in VOP"

      ingress = [
        {
          description = "All inbound traffic"
          from_port   = 0
          to_port     = 0
          protocol    = "-1"
          cidr_blocks = [var.protected_subnets.a.cidr_block]
        }
      ]

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

################################################################################
# VPC Peering
################################################################################

# VPC Peering接続
resource "aws_vpc_peering_connection" "main_to_vop" {
  vpc_id      = module.vpc.vpc.id
  peer_vpc_id = module.virtual_on_premises.vpc.id
  auto_accept = true

  tags = {
    Name = "${var.project_name}-${var.env}-pcx-main-to-vop"
  }
}

# メインVPC側のルート(仮想オンプレミスVPCへ)  
resource "aws_route" "main_protected_to_vop" {
  route_table_id            = module.vpc.route_tables["protected_a"].id
  destination_cidr_block    = var.vop_protected_subnets.a.cidr_block
  vpc_peering_connection_id = aws_vpc_peering_connection.main_to_vop.id
}

# 仮想オンプレミスVPC側のルート(メインVPCへ)
resource "aws_route" "vop_protected_to_main" {
  route_table_id            = module.virtual_on_premises.route_tables["protected_a"].id
  destination_cidr_block    = var.protected_subnets.a.cidr_block
  vpc_peering_connection_id = aws_vpc_peering_connection.main_to_vop.id
}

################################################################################
# EC2                                                                          #
################################################################################

# SSM エージェント入りの標準 Amazon Linux 2023 AMI を Parameter Store から取得
data "aws_ssm_parameter" "amazon_linux_2023_ami" {
  name = "/aws/service/ami-amazon-linux-latest/al2023-ami-kernel-default-x86_64"
}

# EC2用のIAMロール(モジュール使用)
module "ec2_iam_role" {
  source  = "terraform-aws-modules/iam/aws//modules/iam-role"
  version = "~> 6.4"

  name            = "${var.project_name}-${var.env}-role-ec2"
  use_name_prefix = false

  create_instance_profile = true

  # Trust policy(EC2が引き受けるロール)
  trust_policy_permissions = {
    EC2AssumeRole = {
      actions = ["sts:AssumeRole"]
      principals = [
        {
          type        = "Service"
          identifiers = ["ec2.amazonaws.com"]
        }
      ]
    }
  }

  # マネージドポリシー(SSM接続用)
  policies = {
    AmazonSSMManagedInstanceCore = "arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore"
  }

  tags = {
    Name = "${var.project_name}-${var.env}-role-ec2"
  }
}

# EC2インスタンス
module "ec2" {
  source  = "terraform-aws-modules/ec2-instance/aws"
  version = "~> 6.2"

  name = "${var.project_name}-${var.env}-ec2"

  ami                    = data.aws_ssm_parameter.amazon_linux_2023_ami.value
  instance_type          = "t3.micro"
  subnet_id              = module.vpc.subnets["protected_a"].id
  vpc_security_group_ids = [module.vpc.security_groups["sg_ec2"].id]
  create_security_group  = false

  iam_instance_profile = module.ec2_iam_role.instance_profile_name

  # EBSボリューム設定
  root_block_device = {
    volume_type = "gp3"
    volume_size = 8
    encrypted   = true
  }

  tags = {
    Name = "${var.project_name}-${var.env}-ec2"
  }
}

################################################################################
# EC2 in Virtual On-Premises VPC                                               #
################################################################################

# EC2用のIAMロール(モジュール使用)
module "vop_ec2_iam_role" {
  source  = "terraform-aws-modules/iam/aws//modules/iam-role"
  version = "~> 6.4"

  name            = "${var.project_name}-${var.env}-role-vop-ec2"
  use_name_prefix = false

  create_instance_profile = true

  # Trust policy(EC2が引き受けるロール)
  trust_policy_permissions = {
    EC2AssumeRole = {
      actions = ["sts:AssumeRole"]
      principals = [
        {
          type        = "Service"
          identifiers = ["ec2.amazonaws.com"]
        }
      ]
    }
  }

  # マネージドポリシー(SSM接続用)
  policies = {
    AmazonSSMManagedInstanceCore = "arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore"
  }

  tags = {
    Name = "${var.project_name}-${var.env}-role-vop-ec2"
  }
}

# EC2インスタンス
module "vop_ec2" {
  source  = "terraform-aws-modules/ec2-instance/aws"
  version = "~> 6.2"

  name = "${var.project_name}-${var.env}-ec2-vop"

  ami                    = data.aws_ssm_parameter.amazon_linux_2023_ami.value
  instance_type          = "t3.micro"
  subnet_id              = module.virtual_on_premises.subnets["protected_a"].id
  vpc_security_group_ids = [module.virtual_on_premises.security_groups["sg_vop_ec2"].id]
  create_security_group  = false

  iam_instance_profile = module.vop_ec2_iam_role.instance_profile_name

  # EBSボリューム設定
  root_block_device = {
    volume_type = "gp3"
    volume_size = 8
    encrypted   = true
  }

  tags = {
    Name = "${var.project_name}-${var.env}-ec2-vop"
  }
}

variables.tf

variable "env" {
  description = "Environment name"
  type        = string
  default     = "dev"
}

variable "project_name" {
  description = "Project name"
  type        = string
}

variable "vpc_cidr_block" {
  description = "VPC CIDR block"
  type        = string
}

variable "public_subnets" {
  description = "Public subnets"
  type = map(object({
    cidr_block = string
  }))
}

variable "protected_subnets" {
  description = "Protected subnets"
  type = map(object({
    cidr_block = string
  }))
}

variable "private_subnets" {
  description = "Private subnets"
  type = map(object({
    cidr_block = string
  }))
}

variable "vop_vpc_cidr_block" {
  description = "Virtual On-Premises VPC CIDR block"
  type        = string
}

variable "vop_public_subnets" {
  description = "Virtual On-Premises Public subnets"
  type = map(object({
    cidr_block = string
  }))
}

variable "vop_protected_subnets" {
  description = "Virtual On-Premises Protected subnets"
  type = map(object({
    cidr_block = string
  }))
}

output.tf
output "vpc" {
  description = "The VPC"
  value       = module.vpc
}

3. 動作確認

マネジメントコンソールより EC2 に SSMセッションマネージャーでログインし、ping で メインVPC から 仮想オンプレミスVPC の EC2 へ疎通できるか確認しました。
下記の通り、問題なく疎通を確認できました!

CleanShot20260221at17.40.37.png

あとがき

オンプレミス環境との接続を試す第一歩として、まずは2VPC間をピアリングで接続するところを紹介しました。
DNS解決の有無など実際の環境とは異なるため、検証を行う際には実環境と比較して検証可能な範囲を確認してから使用することが必要です。

今後、オンプレミス環境との接続や VPC Peering の構築の際にはこのテンプレートを活用していこうと思います。
以上、くろすけでした!

この記事をシェアする

FacebookHatena blogX

関連記事