Terraform の for_each では静的な値を利用する
はじめに
気づけば DWS に入社して 1 年以上が経過しており、時間の経つ速さに驚いているダイです。最近のプロジェクトでは、バックエンド開発に加えて Terraform を用いたインフラ構築も頻繁に行っています。今回は、Terraform の for_each の挙動について、完全に理解していなかった部分があったため、それについてまとめたいと思います。
for_eachを利用したリソースの作成
Terraform でリソースを作成する際、類似のリソースを複数作成すると、似たような resource ブロックを繰り返し記述することになり、コードが冗長になりがちです。
resource "aws_subnet" "subnet1" {
vpc_id = aws_vpc.vpc.id
cidr_block = "10.0.101.0/24"
availability_zone = "ap-northeast-1a"
tags = {
Name = "sample-subnet1"
}
}
resource "aws_subnet" "subnet2" {
vpc_id = aws_vpc.vpc.id
cidr_block = "10.0.102.0/24"
availability_zone = "ap-northeast-1c"
tags = {
Name = "sample-subnet2"
}
}
resource "aws_subnet" "subnet3" {
vpc_id = aws_vpc.vpc.id
cidr_block = "10.0.103.0/24"
availability_zone = "ap-northeast-1c"
tags = {
Name = "sample-subnet3"
}
}
そこで、サブネットの設定情報をローカル変数として定義し、コードの可読性と再利用性を高めます。
locals {
subnet_map_list = [
{ "name" = "sample-subnet1", "cidr" = "10.0.101.0/24", "az_name" = "ap-northeast-1a" },
{ "name" = "sample-subnet2", "cidr" = "10.0.102.0/24", "az_name" = "ap-northeast-1c" },
{ "name" = "sample-subnet3", "cidr" = "10.0.103.0/24", "az_name" = "ap-northeast-1c" },
]
}
resource "aws_subnet" "subnet" {
for_each = { for subnet in local.subnet_map_list : subnet.name => subnet }
vpc_id = aws_vpc.vpc.id
cidr_block = each.value.cidr
availability_zone = each.value.az_name
tags = {
Name = each.key
}
}
同種のリソースが増えるほど、for_eachで作成する恩恵も大きくなっていきいます。
問題のある動的な値を使用した例
for_eachを使用して作成した上記すべてのサブネットに VPC フローログを設定したいと思います。
resource "aws_flow_log" "subnet_flow_log" {
for_each = [for s in values(aws_subnet.subnet) : s.id]
log_destination = aws_s3_bucket.v360_buckets["v360_vpc_flow_logs"].arn
log_destination_type = "s3"
traffic_type = "ALL"
subnet_id = each.key
}
このコードでは、作成するサブネット(aws_subnet.subnet)からサブネットidを取り出し、リスト化することで利用しようとしています。
しかし、実際にterraform plan
を実行するとエラーになります。
│ The "for_each" set includes values derived from resource attributes that cannot be determined until apply, and so Terraform cannot
│ determine the full set of keys that will identify the instances of this resource.
│
│ When working with unknown values in for_each, it's better to use a map value where the keys are defined statically in your
│ configuration and where only the values contain apply-time results.
│
│ Alternatively, you could use the -target planning option to first apply only the resources that the for_each value depends on, and
│ then apply a second time to fully converge.
エラーメッセージから以下のことがわかります。
- for_each セットには、apply が行われるまで決定できない値を含めることはできない。
- for_each で未知の値を扱う際には、キーが設定内で静的に定義されている必要がある。
- terraform plan 実行時にはまだサブネットが作成されていないため、それを for_each で利用しようとするとエラーが発生する。
問題のない静的な値を使用した例
上記の問題を解決するため、VPC フローログの定義を以下のように修正してみます。
resource "aws_flow_log" "subnet_flow_log" {
for_each = { for subnet in local.subnet_map_list : subnet.name => subnet }
log_destination = aws_s3_bucket.v360_buckets["v360_vpc_flow_logs"].arn
log_destination_type = "s3"
traffic_type = "ALL"
subnet_id = aws_subnet.subnet[each.key].id
}
ここでは、local.subnet_map_list から名前をキーとした新しいマップを作成しています。このキーは sample-subnet1 や sample-subnet2 など、静的な値です。したがって、for_each からこれらを安全に利用することができます。
おわりに
Terraform での for_each の利用は、冗長性を排除し、コードの可読性と再利用性を高める大きな利点があります。しかし、そのためには使用法をしっかりと理解し、適切に適用する必要がありそうです。