[Amazon VPC] Terraformのaws_vpcを読み解いてみた
こんにちは!コンサルティング部のくろすけです!
入社してからAWSの検証作業において、さまざまなリソースを構築しています。
もちろん常設しているとそれだけコストがかかるサービスも多いので、迅速にスクラップ・アンド・ビルドできるようにTerraformを活用しております。
そこで改めて、知らないオプションが結構多いなと思いましたのでTerraform側から読み解いていきたいと思います。
今回はaws_vpcにフォーカスしたいと思います。
前提
使用バージョン
バージョン | |
---|---|
Terraform | 1.10.4 |
Terraform AWS Provider | v5.87.0 |
Amazon VPCのTerraformドキュメントはこちら
Amazon VPCとは
Amazon Virtual Private Cloud (Amazon VPC) を使用すると、論理的に隔離されている定義済みの仮想ネットワーク内で AWS リソースを起動できます。仮想ネットワークは、お客様自身のデータセンターで運用されていた従来のネットワークによく似ていますが、AWS のスケーラブルなインフラストラクチャを使用できるというメリットがあります。
改めてですが、Amazon VPCとは仮想ネットワーク環境を作成・管理できるサービスですね。
表記について
以降 Amazon VPC は、Amazon VPC もしくは VPC と記載させていただきます。
aws_vpc リソース
以下に各オプションについて記載します。
※引用文はTerraformドキュメントの機械翻訳になります。
assign_generated_ipv6_cidr_block
(オプション) VPC の /56 プレフィックス長を持つ Amazon 提供の IPv6 CIDR ブロックをリクエストします。IP アドレスの範囲や CIDR ブロックのサイズは指定できません。デフォルトは false です。ipv6_ipam_pool_id と競合します。
VPC内でIPv6を有効/無効化するオプションです。
2021/11にはIPv6のみのサブネットおよびEC2の作成が可能となっているとのこと。
ただし、VPC自体は現在でもデュアルスタックでの作成のみとなっているようです。
Amazon Virtual Private Cloud (VPC) のお客様は、IPv6 のみのサブネットと EC2 インスタンスの作成が可能に
[アップデート] IPv6オンリーのサブネットとEC2インスタンスを作成出来るようになりました! | DevelopersIO
cidr_block
(オプション) VPC の IPv4 CIDR ブロック。CIDR は明示的に設定することも、ipv4_netmask_length を使用して IPAM から取得することもできます。
こちらは言わずもがな IPv4 CIDR ブロック ですね。
上述の通り現状VPCはIPv4なしでは構築できないため、cidr_block
もしくはipv4_netmask_length
が必須のようです。
enable_dns_hostnames
(オプション) VPC で DNS ホスト名を有効/無効にするブール フラグ。デフォルトは false です。
VPC 内の EC2 インスタンスに対して、DNS ホスト名を有効にするかどうかを制御する設定
とのこと。
こちらは有効化の前提として、後述のenable_dns_support
がtrue
である必要があります。
また、挙動の詳細は下記のようになるようです。
設定値 | 説明 |
---|---|
true |
パブリック IP アドレスを持つインスタンスに、対応するパブリック DNS ホスト名を割り当てる。 |
false (デフォルト) |
パブリック IP アドレスを持つインスタンスに、対応するパブリック DNS ホスト名を割り当てない。 |
当たり前のように有効化していましたが、このような仕組みになっていたんですね。
参考
enable_dns_support
(オプション) VPC で DNS サポートを有効/無効にするブール フラグ。デフォルトは true です。
Amazon Route 53 Resolver サーバーでDNS解決がされるようです。
確かにコンソールから Amazon Route 53 Resolver を確認したところリソースが作成されていました。(知らなかった...)
ちなみにfalse
でも Amazon Route 53 Resolver の作成自体はされていました。
挙動の詳細は下記になります。
設定値 | 説明 |
---|---|
true (デフォルト) |
Amazon Route 53 Resolver サーバーが、Amazon が提供するプライベート DNS ホスト名を解決できる。 |
false |
Amazon Route 53 Resolver サーバーが、Amazon が提供するプライベート DNS ホスト名を解決できない。 |
参考
enable_network_address_usage_metrics
(オプション) VPC でネットワーク アドレス使用率メトリックが有効になっているかどうかを示します。デフォルトは false です。
ネットワークアドレス使用状況(NAU)を計測する設定のようです。
ネットワークアドレス使用状況 (NAU) は、仮想ネットワーク内のリソースに適用されるメトリクスで、VPC のサイズを計画および監視するのに役立ちます。各 NAU ユニットは、VPC のサイズを表す合計に寄与します。
VPC のネットワークアドレスの使用状況 - Amazon Virtual Private Cloudより抜粋
とのこと。単純にプライベートipの使用率という訳では無いようです。
参考
instance_tenancy
(オプション) VPC に起動されたインスタンスのテナンシー オプション。デフォルトは default で、これにより、この VPC で起動された EC2 インスタンスは、EC2 インスタンスの起動時に指定された EC2 インスタンス テナンシー属性を使用します。他の唯一のオプションは、dedicated です。これにより、この VPC で起動された EC2 インスタンスは、起動時に指定されたテナンシー属性に関係なく、専用テナンシー インスタンスで実行されます。これには、リージョンごとに 1 時間あたり 2 ドルの専用料金と、インスタンスごとの時間単位の使用料金がかかります。
こちらはVPC内で起動するインスタンスのテナンシーの設定です。
dedicated
に設定すると強制的に全てのインスタンスが専用テナンシーなるようです。
ちょっと割高になるので、検証ではほぼ使わないかと思います。
ipv4_ipam_pool_id
(オプション) この VPC の CIDR を割り当てるために使用する IPv4 IPAM プールの ID。 IPAM は、AWS リージョンおよびアカウント全体で IP アドレスの割り当て、追跡、トラブルシューティング、監査などの IP アドレス管理ワークフローを自動化するために使用できる VPC 機能です。IPAM を使用すると、AWS 組織全体で IP アドレスの使用状況を監視できます。
初め勘違いしましたが、IPAMはVPCごとではなくアカウントもしくはワークロードに対する管理を提供するサービスのようです。
よって、CIDRの設定はIPAM側に依存するためVPCではcidr_block
もしくはipv4_netmask_length
を設定となるようです。
参考
- IPAM とは - Amazon Virtual Private Cloud
- VPC内のIPアドレスを効率的に管理するVPC IP Address Managerが発表されました #reinvent | DevelopersIO
ipv4_netmask_length
(オプション) この VPC に割り当てる IPv4 CIDR のネットマスク長。ipv4_ipam_pool_id を指定する必要があります。
こちらは上述の通り、IPAM使用時のネットマスク長ですね。
参考
- IPAM とは - Amazon Virtual Private Cloud
- VPC内のIPアドレスを効率的に管理するVPC IP Address Managerが発表されました #reinvent | DevelopersIO
ipv6_cidr_block
(オプション) IPAM プールから要求する IPv6 CIDR ブロック。明示的に設定することも、ipv6_netmask_length を使用して IPAM から取得することもできます。
IPv6のIPAM使用時のCIDRですね。
IPv6の場合は、CIDRで設定することも可能なようです。
ipv6_cidr_block_network_border_group
(オプション) デフォルトでは、IPv6 CIDR が VPC に割り当てられると、デフォルトの ipv6_cidr_block_network_border_group が VPC のリージョンに設定されます。これを変更して、パブリック IP アドレスのアドバタイズを LocalZones などの特定のネットワーク境界グループに制限することができます。
IPv6アドレスのアドバタイズする領域を制限する用途で使用するようです。
意図しないリージョンなどで使用されることを防ぐ目的があるとのこと。
ipv6_ipam_pool_id
(オプション) IPv6 プールの IPAM プール ID。assign_generated_ipv6_cidr_block と競合します。
こちらはIPv6におけるIPAM使用時のIPAM IDですね。
ipv6_netmask_length
(オプション) IPAM プールから要求するネットマスク長。 ipv6_cidr_block と競合します。allocation_default_netmask_length として IPAM プールが設定されている場合は省略できます。有効な値は 44 から 60 までで、4 ずつ増加します。
こちらはIPv6におけるIPAM使用時のネットマスク長ですね。
tags
(オプション) リソースに割り当てるタグのマップ。プロバイダーの default_tags 構成ブロックが存在するように設定されている場合、一致するキーを持つタグはプロバイダー レベルで定義されたタグを上書きします。
こちらはリソースにアタッチするお馴染みのタグですね。
やってみた
構成
下記の構成をTerraformで作成してみます。
(IPv6 や IPAM はまたの機会に記事にできるといいなと思います。)
Terraform AWS modulesもありますが、今回は使用していません。
というのも、azs, public_subets, private_subnets
がそれぞれlist
型でインデックス管理されていることで変更(特に削除)に弱いため個人的にはあまり好みでは無いためです。
Terraform
今回は下記を自作しました。
main.tf
module "vpc" {
source = "../../../modules/vpc"
name = "${var.sys_name}-${var.env}-vpc"
cidr_block = var.vpc_cidr_block
enable_dns_hostnames = true
enable_dns_support = true
igw = {
name = "${var.sys_name}-${var.env}-igw"
}
public_subnets = {
pbsb_a = {
name = "${var.sys_name}-${var.env}-pbsb-a"
cidr_block = var.pbsb_a_cidr_block
availability_zone = "ap-northeast-1a"
},
pbsb_c = {
name = "${var.sys_name}-${var.env}-pbsb-c"
cidr_block = var.pbsb_c_cidr_block
availability_zone = "ap-northeast-1c"
}
}
public_subnets_route_table = {
name = "${var.sys_name}-${var.env}-public-rtb"
}
private_subnets = {
pvsb_a = {
name = "${var.sys_name}-${var.env}-pvsb-a"
cidr_block = var.pvsb_a_cidr_block
availability_zone = "ap-northeast-1a"
},
pvsb_c = {
name = "${var.sys_name}-${var.env}-pvsb-c"
cidr_block = var.pvsb_c_cidr_block
availability_zone = "ap-northeast-1c"
}
}
private_subnets_route_tables = {
pvsb_a = {
name = "${var.sys_name}-${var.env}-pvsb-a-rtb"
},
pvsb_c = {
name = "${var.sys_name}-${var.env}-pvsb-c-rtb"
}
}
env = var.env
}
main-variable.tf
variable "sys_name" {
description = "The system name"
type = string
}
variable "vpc_cidr_block" {
description = "The CIDR block for the VPC"
type = string
}
variable "pbsb_a_cidr_block" {
description = "The CIDR block for the public subnet a"
type = string
}
variable "pbsb_c_cidr_block" {
description = "The CIDR block for the public subnet c"
type = string
}
variable "pvsb_a_cidr_block" {
description = "The CIDR block for the private subnet a"
type = string
}
variable "pvsb_c_cidr_block" {
description = "The CIDR block for the private subnet c"
type = string
}
variable "env" {
description = "The environment"
type = string
}
module.tf
################################################################################
# VPC #
################################################################################
resource "aws_vpc" "common" {
assign_generated_ipv6_cidr_block = var.assign_generated_ipv6_cidr_block
cidr_block = var.cidr_block
enable_dns_hostnames = var.enable_dns_hostnames
enable_dns_support = var.enable_dns_support
enable_network_address_usage_metrics = var.enable_network_address_usage_metrics
instance_tenancy = var.instance_tenancy
ipv4_ipam_pool_id = var.ipv4_ipam_pool_id
ipv4_netmask_length = var.ipv4_netmask_length
ipv6_cidr_block = var.ipv6_cidr_block
ipv6_cidr_block_network_border_group = var.ipv6_cidr_block_network_border_group
ipv6_ipam_pool_id = var.ipv6_ipam_pool_id
ipv6_netmask_length = var.ipv6_netmask_length
tags = {
Name = var.name
Module = "vpc"
Env = var.env
}
}
################################################################################
# Internet Gateway #
################################################################################
resource "aws_internet_gateway" "common" {
vpc_id = aws_vpc.common.id
tags = {
Name = var.igw.name
Module = "vpc"
Env = var.env
}
}
################################################################################
# Public Subnet #
################################################################################
resource "aws_subnet" "public" {
for_each = var.public_subnets
assign_ipv6_address_on_creation = each.value.assign_ipv6_address_on_creation
availability_zone = each.value.availability_zone
cidr_block = each.value.cidr_block
customer_owned_ipv4_pool = each.value.customer_owned_ipv4_pool
enable_dns64 = each.value.enable_dns64
enable_lni_at_device_index = each.value.enable_lni_at_device_index
enable_resource_name_dns_aaaa_record_on_launch = each.value.enable_resource_name_dns_aaaa_record_on_launch
enable_resource_name_dns_a_record_on_launch = each.value.enable_resource_name_dns_a_record_on_launch
ipv6_cidr_block = each.value.ipv6_cidr_block
ipv6_native = each.value.ipv6_native
map_customer_owned_ip_on_launch = each.value.map_customer_owned_ip_on_launch
map_public_ip_on_launch = each.value.map_public_ip_on_launch
outpost_arn = each.value.outpost_arn
private_dns_hostname_type_on_launch = each.value.private_dns_hostname_type_on_launch
vpc_id = aws_vpc.common.id
tags = {
Name = each.value.name
Module = "vpc"
Env = var.env
}
}
resource "aws_route_table" "public" {
vpc_id = aws_vpc.common.id
propagating_vgws = var.public_subnets_route_table.propagating_vgws
tags = {
Name = var.public_subnets_route_table.name
Module = "vpc"
Env = var.env
}
}
resource "aws_route" "public" {
route_table_id = aws_route_table.public.id
destination_cidr_block = "0.0.0.0/0"
gateway_id = aws_internet_gateway.common.id
}
resource "aws_route_table_association" "public" {
for_each = aws_subnet.public
route_table_id = aws_route_table.public.id
subnet_id = aws_subnet.public[each.key].id
}
################################################################################
# Private Subnet #
################################################################################
resource "aws_subnet" "private" {
for_each = var.private_subnets
assign_ipv6_address_on_creation = each.value.assign_ipv6_address_on_creation
availability_zone = each.value.availability_zone
cidr_block = each.value.cidr_block
customer_owned_ipv4_pool = each.value.customer_owned_ipv4_pool
enable_dns64 = each.value.enable_dns64
enable_lni_at_device_index = each.value.enable_lni_at_device_index
enable_resource_name_dns_aaaa_record_on_launch = each.value.enable_resource_name_dns_aaaa_record_on_launch
enable_resource_name_dns_a_record_on_launch = each.value.enable_resource_name_dns_a_record_on_launch
ipv6_cidr_block = each.value.ipv6_cidr_block
ipv6_native = each.value.ipv6_native
map_customer_owned_ip_on_launch = each.value.map_customer_owned_ip_on_launch
map_public_ip_on_launch = each.value.map_public_ip_on_launch
outpost_arn = each.value.outpost_arn
private_dns_hostname_type_on_launch = each.value.private_dns_hostname_type_on_launch
vpc_id = aws_vpc.common.id
tags = {
Name = each.value.name
Module = "vpc"
Env = var.env
}
}
resource "aws_route_table" "private" {
for_each = var.private_subnets_route_tables
vpc_id = aws_vpc.common.id
propagating_vgws = each.value.propagating_vgws
tags = {
Name = each.value.name
Module = "vpc"
Env = var.env
}
}
resource "aws_route_table_association" "private" {
for_each = var.private_subnets
route_table_id = aws_route_table.private[each.key].id
subnet_id = aws_subnet.private[each.key].id
}
module-variables.tf
################################################################################
# VPC #
################################################################################
variable "name" {
description = "The name of the VPC"
type = string
}
variable "assign_generated_ipv6_cidr_block" {
description = "A boolean flag to enable/disable the assignment of an IPv6 CIDR block"
type = bool
default = null
}
variable "cidr_block" {
description = "The CIDR block for the VPC"
type = string
default = null
}
variable "enable_dns_hostnames" {
description = "A boolean flag to enable/disable DNS hostnames in the VPC"
type = bool
default = true
}
variable "enable_dns_support" {
description = "A boolean flag to enable/disable DNS support in the VPC"
type = bool
default = null
}
variable "enable_network_address_usage_metrics" {
description = "A boolean flag to enable/disable network address usage metrics in the VPC"
type = bool
default = null
}
variable "instance_tenancy" {
description = "The tenancy of instances launched into the VPC"
type = string
default = null
}
variable "ipv4_ipam_pool_id" {
description = "The ID of the IPv4 IPAM pool"
type = string
default = null
}
variable "ipv4_netmask_length" {
description = "The netmask length for the IPv4 CIDR block"
type = number
default = null
}
variable "ipv6_cidr_block" {
description = "The IPv6 CIDR block"
type = string
default = null
}
variable "ipv6_cidr_block_network_border_group" {
description = "The network border group for the IPv6 CIDR block"
type = string
default = null
}
variable "ipv6_ipam_pool_id" {
description = "The ID of the IPv6 IPAM pool"
type = string
default = null
}
variable "ipv6_netmask_length" {
description = "The netmask length for the IPv6 CIDR block"
type = number
default = null
}
################################################################################
# Internet Gateway #
################################################################################
variable "igw" {
description = "The Internet Gateway"
type = object({
name = string
})
}
################################################################################
# Public Subnet #
################################################################################
variable "public_subnets" {
description = "The public subnets"
type = map(object({
name = string
assign_ipv6_address_on_creation = optional(bool, null)
availability_zone = string
cidr_block = string
customer_owned_ipv4_pool = optional(string, null)
enable_dns64 = optional(bool, null)
enable_lni_at_device_index = optional(number, null)
enable_resource_name_dns_aaaa_record_on_launch = optional(bool, null)
enable_resource_name_dns_a_record_on_launch = optional(bool, null)
ipv6_cidr_block = optional(string, null)
ipv6_native = optional(bool, null)
map_customer_owned_ip_on_launch = optional(bool, null)
map_public_ip_on_launch = optional(bool, null)
outpost_arn = optional(string, null)
private_dns_hostname_type_on_launch = optional(string, null)
}))
validation {
condition = length(keys(var.public_subnets)) > 0
error_message = "At least one public subnet must be defined"
}
validation {
condition = alltrue([
for key in keys(var.public_subnets) : can(regex("pbsb_[a-d]", key))
])
error_message = "The public subnets must be key prefixed with 'pbsb_' followed by a letter between 'a' and 'd'"
}
}
variable "public_subnets_route_table" {
description = "The route table"
type = object({
name = string
propagating_vgws = optional(list(string), null)
})
}
################################################################################
# Private Subnet #
################################################################################
variable "private_subnets" {
description = "The private subnets"
type = map(object({
name = string
assign_ipv6_address_on_creation = optional(bool, null)
availability_zone = string
cidr_block = string
customer_owned_ipv4_pool = optional(string, null)
enable_dns64 = optional(bool, null)
enable_lni_at_device_index = optional(number, null)
enable_resource_name_dns_aaaa_record_on_launch = optional(bool, null)
enable_resource_name_dns_a_record_on_launch = optional(bool, null)
ipv6_cidr_block = optional(string, null)
ipv6_native = optional(bool, null)
map_customer_owned_ip_on_launch = optional(bool, null)
map_public_ip_on_launch = optional(bool, null)
outpost_arn = optional(string, null)
private_dns_hostname_type_on_launch = optional(string, null)
}))
validation {
condition = length(keys(var.private_subnets)) > 0
error_message = "At least one private subnet must be defined"
}
validation {
condition = alltrue([
for key in keys(var.private_subnets) : can(regex("pvsb_[a-d]", key))
])
error_message = "The private subnets must be key prefixed with 'pvsb_' followed by a letter between 'a' and 'd'"
}
}
variable "private_subnets_route_tables" {
description = "The route table"
type = map(object({
name = string
propagating_vgws = optional(list(string), null)
}))
}
################################################################################
# Others #
################################################################################
variable "env" {
description = "The environment"
type = string
default = "pvt"
}
実行結果
実際に実行してみます。
tf init
tf plan
tf apply -auto-approve
下記リソースマップを確認したところ問題なさそうです。
- リソースマップ
Amazon Route 53 リゾルバー も確認してみました。
確かに作成されていますね。
- Amazon Route 53 リゾルバー
あとがき
今までその都度調査・作成する機会が多かったですが、改めて読み解いてみると引き出しを増やせたような気がしました。
今後も少しずつこちらのシリーズやっていければなと思っております。