はじめに
Terraformを使っていると、どのプロジェクトでも流用することができる汎用的なテンプレートを作成したいと思ってしまうことが間間あります。
今回は、三項演算子を使用し、複数条件に応じたリソースを作成できるようなTerraformの記述を展開していきたいと思います。
想定シナリオ
三項演算子を活用する上でAWSリソースの中の、VPCエンドポイントを例にしてTerraformテンプレートを作成することを想定します。
VPCエンドポイントにはGateway EndpointやInterface Endpointなどを作成することで、インターネット経由で通信することなく、プライベートネットワークからAWSリソースにアクセスすることができます。
この2種類のエンドポイントには、定義するパラメータも異なってくるため、1つ1つ解説していきます。
先ずはじめに、Gateway Endpoint では、ルートテーブルにS3やDynamoDBのプレフィックスリストを指定して、
エンドポイント→ AWSサービスにアクセスすることができます。(S3やDynamoDBのゲートウェイ経由でのアクセスとなります。)
Terraformのテンプレートは、route_table_idsでルートテーブルを定義し、以下のようになります。
resource "aws_vpc_endpoint" "s3" {
vpc_id = aws_vpc.main.id
service_name = "com.amazonaws.us-west-2.s3"
route_table_ids = [
aws_route_table.pri.id
]
}
次に、Interface Endpoint では、サブネットにネットワークインターフェースを作成し、ネットワークインターフェース経由でエンドポイント→ AWSサービスにアクセスすることができます。
ネットワークインターフェースのため、作成先のサブネットIDとアタッチするセキュリティグループが必要になります。
Terraformのテンプレートは、subnet_idsとaws_security_groupを定義し、以下のようになります。
また、パラメータprivate_dns_enabledは、Interface型のエンドポイントでのみ設定が可能になります。(Gateway型の場合は定義しない。)
resource "aws_vpc_endpoint" "ec2" {
vpc_id = aws_vpc.main.id
service_name = "com.amazonaws.us-west-2.ec2"
vpc_endpoint_type = "Interface"
security_group_ids = [
aws_security_group.sg1.id,
]
subnet_ids = [
aws_subnet.edp1.id,
aws_subnet.edp2.id,
]
private_dns_enabled = true
}
このようにエンドポイントの種類ごとに設定するパラメータが異なるため、
Gateway型とInterface型で設定するパラメータが変わるようにテンプレートを作成する方法を次で解説します。
三項演算子によるTerraform での条件分岐方法について
Terraform上ではif elseによる条件分岐を使用できないため、三項演算子を使用することで条件分岐を定義することができます。
三項演算子は、分岐条件 ? 正の値 : 負の値で表現することができます。
例えば、以下の値は、変数「service_name」が、DynamoDB or S3の場合かつ、変数「rtb_ids(ルートテーブルIDの定義)」がnullでない場合、エンドポイントタイプをGatewayとしそれ以外をInterfaceとすることができます。
locals {
edp_type = ( (var.service_name == "dynamodb" || var.service_name == "s3" ) && var.rtb_ids != null ) ? "Gateway" : "Interface"
}
上記で定義したedp_typeを用いて、edp_typeがGatewayかInterfaceかで、サブネットやルートテーブルを定義します。不要な変数はnullなどを与えるようにします。
locals {
# Define Interface Endpoint
interface_edp_subnet_ids = local.edp_type == "Gateway" ? null : var.subnet_ids
interface_edp_sg_ids = local.edp_type == "Gateway" ? null : var.sg_ids
# Define Gateway Endpoint
gateway_edp_rtb_ids = local.edp_type == "Gateway" ? var.rtb_ids : null
}
Terraform 条件分岐デモ
デモとしてフォルダ構成は以下の通り、module化して呼び出しできる構成にします。
. │ ├── modules/ │ └── vpc/ │ └── endpoint/ │ ├── main.tf │ └── variables.tf └── main.tf
./modules/vpc/endpoint/variables.tfの内容は以下の通りで、Moduleに必要な変数を定義します。
variable "vpc_id" {
type = string
description = "VPC ID"
}
variable "subnet_ids" {
type = list(string)
default = null
description = "List of Subnet IDs"
}
variable "sg_ids" {
type = list(string)
default = null
description = "List of Security Group IDs"
}
variable "rtb_ids" {
type = list(string)
default = null
description = "List of Route Table IDs"
}
variable "service_name" {
type = string
description = "AWS Service Name"
}
./modules/vpc/endpoint/main.tfの内容は以下の通りです。
特にS3の場合はGateway型とInterface型の2種類存在するので、ルートテーブルを明示的に指定されたらGateway型、それ以外の場合はInterface型とします。
localsに条件分岐を記載することで、resource部分の記述をシンプルに記載が可能になります。
data "aws_region" "current" {}
locals {
edp_type = ( (var.service_name == "dynamodb" || var.service_name == "s3" ) && var.rtb_ids != null ) ? "Gateway" : "Interface"
interface_edp_subnet_ids = local.edp_type == "Gateway" ? null : var.subnet_ids
interface_edp_sg_ids = local.edp_type == "Gateway" ? null : var.sg_ids
gateway_edp_rtb_ids = local.edp_type == "Gateway" ? var.rtb_ids : null
}
resource "aws_vpc_endpoint" "main" {
vpc_id = var.vpc_id
service_name = format("com.amazonaws.%s.%s", data.aws_region.current.name, var.service_name)
vpc_endpoint_type = local.edp_type
subnet_ids = local.interface_edp_subnet_ids
security_group_ids = local.interface_edp_sg_ids
route_table_ids = local.gateway_edp_rtb_ids
private_dns_enabled = local.edp_type == "Gateway" ? null : true
tags = {
Name = format("%s-endpoint", var.service_name)
}
}
./main.tfでmoduleで定義したリソースを呼び出します。
# Gateway Endpoint
module "edp_gateway_s3" {
source = "./modules/vpc/endpoint"
vpc_id = "vpc-xxx"
rtb_ids = ["rtb-xxx"]
service_name = "s3"
}
# Interface Endpoint
module "edp_interface_ec2" {
source = "./modules/vpc/endpoint"
vpc_id = "vpc-xxx"
subnet_ids = [
"subnet-xxx",
"subnet-yyy",
]
sg_ids = [
"sg-xxx",
"sg-yyy",
]
service_name = "ec2"
}
module "edp_interface_s3" {
source = "./modules/vpc/endpoint"
vpc_id = "vpc-xxx"
subnet_ids = [
"subnet-xxx",
"subnet-yyy",
]
sg_ids = [
"sg-xxx",
"sg-yyy",
]
service_name = "s3"
}
上記の内容を全てテンプレートにて定義したら、terraform applyを実行します。
Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
+ create
Terraform will perform the following actions:
# module.edp_gateway_s3.aws_vpc_endpoint.main will be created
+ resource "aws_vpc_endpoint" "main" {
+ arn = (known after apply)
+ cidr_blocks = (known after apply)
+ dns_entry = (known after apply)
+ id = (known after apply)
+ ip_address_type = (known after apply)
+ network_interface_ids = (known after apply)
+ owner_id = (known after apply)
+ policy = (known after apply)
+ prefix_list_id = (known after apply)
+ private_dns_enabled = (known after apply)
+ requester_managed = (known after apply)
+ route_table_ids = [
+ "rtb-xxx",
]
+ security_group_ids = (known after apply)
+ service_name = "com.amazonaws.ap-northeast-1.s3"
+ service_region = (known after apply)
+ state = (known after apply)
+ subnet_ids = (known after apply)
+ tags = {
+ "Name" = "s3-endpoint"
}
+ tags_all = {
+ "Name" = "s3-endpoint"
}
+ vpc_endpoint_type = "Gateway"
+ vpc_id = "vpc-xxx"
+ dns_options {
+ dns_record_ip_type = (known after apply)
+ private_dns_only_for_inbound_resolver_endpoint = (known after apply)
}
+ subnet_configuration {
+ ipv4 = (known after apply)
+ ipv6 = (known after apply)
+ subnet_id = (known after apply)
}
}
# module.edp_interface_ec2.aws_vpc_endpoint.main will be created
+ resource "aws_vpc_endpoint" "main" {
+ arn = (known after apply)
+ cidr_blocks = (known after apply)
+ dns_entry = (known after apply)
+ id = (known after apply)
+ ip_address_type = (known after apply)
+ network_interface_ids = (known after apply)
+ owner_id = (known after apply)
+ policy = (known after apply)
+ prefix_list_id = (known after apply)
+ private_dns_enabled = true
+ requester_managed = (known after apply)
+ route_table_ids = (known after apply)
+ security_group_ids = [
+ "sg-xxx",
+ "sg-yyy",
]
+ service_name = "com.amazonaws.ap-northeast-1.ec2"
+ service_region = (known after apply)
+ state = (known after apply)
+ subnet_ids = [
+ "subnet-xxx",
+ "subnet-yyy",
]
+ tags = {
+ "Name" = "ec2-endpoint"
}
+ tags_all = {
+ "Name" = "ec2-endpoint"
}
+ vpc_endpoint_type = "Interface"
+ vpc_id = "vpc-xxx"
+ dns_options {
+ dns_record_ip_type = (known after apply)
+ private_dns_only_for_inbound_resolver_endpoint = (known after apply)
}
+ subnet_configuration {
+ ipv4 = (known after apply)
+ ipv6 = (known after apply)
+ subnet_id = (known after apply)
}
}
# module.edp_interface_s3.aws_vpc_endpoint.main will be created
+ resource "aws_vpc_endpoint" "main" {
+ arn = (known after apply)
+ cidr_blocks = (known after apply)
+ dns_entry = (known after apply)
+ id = (known after apply)
+ ip_address_type = (known after apply)
+ network_interface_ids = (known after apply)
+ owner_id = (known after apply)
+ policy = (known after apply)
+ prefix_list_id = (known after apply)
+ private_dns_enabled = true
+ requester_managed = (known after apply)
+ route_table_ids = (known after apply)
+ security_group_ids = [
+ "sg-xxx",
+ "sg-yyy",
]
+ service_name = "com.amazonaws.ap-northeast-1.s3"
+ service_region = (known after apply)
+ state = (known after apply)
+ subnet_ids = [
+ "subnet-xxx",
+ "subnet-yyy",
]
+ tags = {
+ "Name" = "s3-endpoint"
}
+ tags_all = {
+ "Name" = "s3-endpoint"
}
+ vpc_endpoint_type = "Interface"
+ vpc_id = "vpc-xxx"
+ dns_options {
+ dns_record_ip_type = (known after apply)
+ private_dns_only_for_inbound_resolver_endpoint = (known after apply)
}
+ subnet_configuration {
+ ipv4 = (known after apply)
+ ipv6 = (known after apply)
+ subnet_id = (known after apply)
}
}
Plan: 3 to add, 0 to change, 0 to destroy.
Do you want to perform these actions?
Terraform will perform the actions described above.
Only 'yes' will be accepted to approve.
Enter a value: yes
module.edp_gateway_s3.aws_vpc_endpoint.main: Creating...
module.edp_interface_s3.aws_vpc_endpoint.main: Creating...
module.edp_interface_ec2.aws_vpc_endpoint.main: Creating...
module.edp_gateway_s3.aws_vpc_endpoint.main: Creation complete after 6s [id=vpce-xxx]
module.edp_interface_ec2.aws_vpc_endpoint.main: Still creating... [10s elapsed]
module.edp_interface_s3.aws_vpc_endpoint.main: Still creating... [10s elapsed]
module.edp_interface_ec2.aws_vpc_endpoint.main: Still creating... [20s elapsed]
module.edp_interface_s3.aws_vpc_endpoint.main: Still creating... [20s elapsed]
module.edp_interface_s3.aws_vpc_endpoint.main: Still creating... [30s elapsed]
module.edp_interface_ec2.aws_vpc_endpoint.main: Still creating... [30s elapsed]
module.edp_interface_ec2.aws_vpc_endpoint.main: Still creating... [40s elapsed]
module.edp_interface_s3.aws_vpc_endpoint.main: Still creating... [40s elapsed]
module.edp_interface_s3.aws_vpc_endpoint.main: Still creating... [50s elapsed]
module.edp_interface_ec2.aws_vpc_endpoint.main: Still creating... [50s elapsed]
module.edp_interface_ec2.aws_vpc_endpoint.main: Creation complete after 53s [id=vpce-yyy]
module.edp_interface_s3.aws_vpc_endpoint.main: Still creating... [1m0s elapsed]
module.edp_interface_s3.aws_vpc_endpoint.main: Still creating... [1m10s elapsed]
module.edp_interface_s3.aws_vpc_endpoint.main: Still creating... [1m20s elapsed]
module.edp_interface_s3.aws_vpc_endpoint.main: Still creating... [1m30s elapsed]
module.edp_interface_s3.aws_vpc_endpoint.main: Still creating... [1m40s elapsed]
module.edp_interface_s3.aws_vpc_endpoint.main: Still creating... [1m50s elapsed]
module.edp_interface_s3.aws_vpc_endpoint.main: Still creating... [2m0s elapsed]
module.edp_interface_s3.aws_vpc_endpoint.main: Still creating... [2m10s elapsed]
module.edp_interface_s3.aws_vpc_endpoint.main: Still creating... [2m20s elapsed]
module.edp_interface_s3.aws_vpc_endpoint.main: Still creating... [2m30s elapsed]
module.edp_interface_s3.aws_vpc_endpoint.main: Still creating... [2m40s elapsed]
module.edp_interface_s3.aws_vpc_endpoint.main: Still creating... [2m50s elapsed]
module.edp_interface_s3.aws_vpc_endpoint.main: Still creating... [3m0s elapsed]
module.edp_interface_s3.aws_vpc_endpoint.main: Still creating... [3m10s elapsed]
module.edp_interface_s3.aws_vpc_endpoint.main: Still creating... [3m20s elapsed]
module.edp_interface_s3.aws_vpc_endpoint.main: Still creating... [3m30s elapsed]
module.edp_interface_s3.aws_vpc_endpoint.main: Still creating... [3m40s elapsed]
module.edp_interface_s3.aws_vpc_endpoint.main: Still creating... [3m50s elapsed]
module.edp_interface_s3.aws_vpc_endpoint.main: Still creating... [4m0s elapsed]
module.edp_interface_s3.aws_vpc_endpoint.main: Still creating... [4m10s elapsed]
module.edp_interface_s3.aws_vpc_endpoint.main: Still creating... [4m20s elapsed]
module.edp_interface_s3.aws_vpc_endpoint.main: Still creating... [4m30s elapsed]
module.edp_interface_s3.aws_vpc_endpoint.main: Creation complete after 4m35s [id=vpce-zzz]
Apply complete! Resources: 3 added, 0 changed, 0 destroyed.
S3のGateway型もInterface型のエンドポイントが作成されていることを確認できればOKです。
三項演算子による条件分岐によって、異なるタイプのVPCエンドポイントの作成を1つのresourceで表現することができました。