こんにちは。
今年も私はTerraformをひたすら書いていました。毎週どこかでは必ず触り、ほぼ1人で基盤を作り込んだくらい、クラウドというかTerraformにどっぷりつかり込んだ1年でした。そのおかげもあり、かなり理解は深まったり、会社の技術ブログでもちょっとアツ目な記事を書きました。
ただ、とはいえ、100%きれいに書けた自信はないですし、明らかに汚くなってしまった書き方もあるので、今回はそれらを供養する意味を込めてこちらを書きます。
いろいろ行き詰まった時にこの記事を読んでいただければ幸いです。
countとfor_eachの合わせ技
複数リソースを使うケースとして、countとfor_eachがあり、さらにfor_eachはリソースないで特定のブロックを複製したりできる便利なものですが、やむなくこの2つを組み合わせないといけない時があり、以下のコードを書きました
locals {
env = "stg"
ip_list = [
"xx.xx.xx.xx/",
"xx.xx.xx.xx/",
"xx.xx.xx.xx/",
"xx.xx.xx.xx/",
"xx.xx.xx.xx/",
....
]
ingress_selfs = [
["0", "0", "-1"]
]
ingress_cidrs = [
["8080", "8080", "tcp", flatten(concat(local.ip_list))],
]
ingress_security_groups = [
["8080", "8080", "tcp", flatten([
aws_security_group.this.id,
aws_security_group.hoge.id,
aws_security_group.huga.id,])
]
]
}
resource "aws_security_group" "this" {
count = floor((length(local.ip_list) / 50) + 1)
name = format("sample-sg0%d", count.index + 1)
description = format("sample-sg0%d", count.index + 1)
vpc_id = aws_vpc.this.id
tags = local.tags
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
dynamic "ingress" {
for_each = local.ingress_cidrs
content {
from_port = ingress.value[0]
to_port = ingress.value[1]
protocol = ingress.value[2]
cidr_blocks = element(chunklist(ingress.value[3], 40), count.index)
}
}
dynamic "ingress" {
for_each = count.index == 0 ? local.ingress_security_groups : []
content {
from_port = ingress.value[0]
to_port = ingress.value[1]
protocol = ingress.value[2]
security_groups = ingress.value[3]
}
}
dynamic "ingress" {
for_each = count.index == 0 ? local.ingress_selfs : []
content {
description = ""
from_port = ingress.value[0]
to_port = ingress.value[1]
protocol = ingress.value[2]
self = true
}
}
}
AWSのセキュリティグループは、デフォルトでは1つに対して、60の別のセキュリティグループやCIDRなどのルールをアタッチできますが、上記のようにip_list
などでリスト更新をした場合、頭打ちになるとエラーになってしまいます。それを回避するための策として捻り出したコードが上のものです。少し細かくみます。
floor関数
count = floor((length(local.ip_list) / 50) + 1)
Terraformにはfloor
とceil
という真逆のことをしてくれる関数があり、floor
は小数点以下を切り捨て、ceil
は切り上げしてくれます。
滅多に使う機会がないですが、ことに上記のようなパターンでは登場させると便利です。
(今回の場合ではceilではなくてfloorにしている理由がありましたが、記憶を探っている途中)
dynamicブロック
dynamic "ingress" {
for_each = count.index == 0 ? local.ingress_security_groups : []
content {
from_port = ingress.value[0]
to_port = ingress.value[1]
protocol = ingress.value[2]
security_groups = ingress.value[3]
}
}
dynamicブロックは、同一のリソースで同じブロックを利用する時に、コードを無意に長くしない、ハードコードを軽減できるなどいいことがたくさんあります。そのdynamicブロックで使うfor_eachの中にcountを仕込みました。
2つ以上countで作るときには、2つ目以降はip_list
以外のブロックを受け付けないようにするために、count.index == 0
を仕込んで、最初のリソースのみに反映するようにしています。
終わりに
今年、書いてみて、一番複雑にしてしまったコードを懺悔の意味も込めて書きました。
小ネタみたいな感じでしたが、変数だけでリソースを動かせるというところでは比較的堅牢なコードでもあると思うので、誰かの役に立てば幸いです。