TerraformでVPC Peering環境を構築してみた
歴史シミュレーションゲーム好きのくろすけです!
オンプレミス環境との接続を検証する機会があったため、簡易的な検証環境として Terraform で VPC Peering を構築しました。
本記事では、Terraform で VPC Peering 環境を構築手順と、実施した内容の概要を共有します。
概要
下記が今回の構成図です。
接続するネットワーク空間は必要最小限にしようと思い、Protected Subnet 間のみ接続するようにしています。
検証では EC2 はあまり必要ではありませんでしたが、疎通確認のために作成しています。
その際 EC2 にログインする必要があるため、NATGateway を双方の環境に作成しています。
(今回は メインVPC の EC2 のみにログインして疎通確認したため、実際は 仮想オンプレミスVPC の NATGateway は不要でした。)

やってみた
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 へ疎通できるか確認しました。
下記の通り、問題なく疎通を確認できました!

あとがき
オンプレミス環境との接続を試す第一歩として、まずは2VPC間をピアリングで接続するところを紹介しました。
DNS解決の有無など実際の環境とは異なるため、検証を行う際には実環境と比較して検証可能な範囲を確認してから使用することが必要です。
今後、オンプレミス環境との接続や VPC Peering の構築の際にはこのテンプレートを活用していこうと思います。
以上、くろすけでした!








