はじめに
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で表現することができました。