Terraformのfor_eachとnullで、効率的にAWSのセキュリティグループを定義する
ちゃだいん(@chazuke4649)です。
普段、IaCでAWSリソースを管理している人は、セキュリティグループについて効率よく書けないか、一度は頭をひねった経験があるのではないでしょうか?
自分もその一人でして、今回Terraformでセキュリティグループを定義するにあたり、いろんな方の記事をありがたく参考にしつつ、改善することができたので紹介します。
改善前
改善前は以下のようにセキュリティグループを定義していました。
resource "aws_security_group" "test" { vpc_id = aws_vpc.main.id name = "test-sg" description = "For test" tags = { Name = "test-sg" } } ## Inbound resource "aws_security_group_rule" "test_inbound_01" { security_group_id = aws_security_group.test.id type = "ingress" description = "HTTP from Internet" protocol = "tcp" from_port = "80" to_port = "80" cidr_blocks = ["0.0.0.0/0"] } resource "aws_security_group_rule" "test_inbound_02" { security_group_id = aws_security_group.test.id type = "ingress" description = "SSH From Bastion" protocol = "tcp" from_port = "22" to_port = "22" source_security_group_id = ["sg-1234567890"] } ## Outbound resource "aws_security_group_rule" "test_outbound_01" { security_group_id = aws_security_group.test.id type = "egress" description = "Allow any outbound traffic" protocol = "-1" from_port = "0" to_port = "0" cidr_blocks = ["0.0.0.0/0"] }
これを修正し、結果的に以下のようになりました。
改善後
locals { test_sg = { ## [ type, from_port, to_port, protocol, sg-id, cidr_blocks, description ] "rule_1" = ["ingress", 80, 80, "tcp", null, ["0.0.0.0/0"], "HTTP from Internet"], "rule_2" = ["ingress", 22, 22, "tcp", "sg-1234567890", null, "SSH from Bastion"], "rule_3" = ["egress", 0, 0, "-1", null, ["0.0.0.0/0"], "Allow any outbound traffic"] } } resource "aws_security_group" "test_sg" { vpc_id = aws_vpc.main.id description = "For test" name = "test-sg" tags = { Name = "test-sg" } } resource "aws_security_group_rule" "test" { security_group_id = aws_security_group.test_sg.id for_each = local.test_sg type = each.value[0] from_port = each.value[1] to_port = each.value[2] protocol = each.value[3] source_security_group_id = each.value[4] cidr_blocks = each.value[5] description = each.value[6] }
解説
改善するにあたり、冗長的な記述になっているセキュリティグループルールをコンパクトにできないか考えます。
for_each
を使うと、例えばresourceブロックを繰り返し処理で複数作ることができます。こちらは比較的人気かと思いますので説明は割愛します。詳しくは参考ブログをご覧ください。
for_each
によって複数作れそうですが、ルールの内容によって一部引数に差異があります。ここをどう解決するかが肝になりそうです。条件分岐を入れると複雑化しそうなので、できるだけシンプルな方法を検討しました。
今回のポイントは null 型です。
null: 不在または省略を表す値。リソースの引数をnullにすると、Terraformは完全に省略したかのように振る舞います - 引数にデフォルト値がある場合はそれを使用し、引数が必須の場合はエラーを発生させます。nullは条件式で最も有用なので、条件が満たされない場合に動的に引数を省略することができます。(意訳)
Types and Values - Configuration Language | Terraform by HashiCorp
今回の場合、aws_security_group_rule
ブロックの引数source_sercurity_group_id
とcidr_blocks
は、両方入力することができず、いずれか一方のみしか入力できません。
例えばインバウンドの80番ポートでcidr_blocks
を0.0.0.0/0
で開放する場合、source_security_group_id
に""
とブランクにしてplanするとエラーになります。
│ Error: Conflicting configuration arguments │ │ with aws_security_group_rule.test["rule_1"], │ on security_group.tf line 217, in resource "aws_security_group_rule" "test": │ 217: source_security_group_id = each.value[4] │ │ "source_security_group_id": conflicts with cidr_blocks
そこでブランクの代わりにnull
を入力すると、対象の引数は省略されたものとみなし、競合しなくなるというわけです。
null
の活用により、単一のセキュリティグループルールをガワだけ作り、実際の値はローカル変数にてMAP型の行を追加するだけで、ルールを追加することができます。
これによって、修正前よりコード量を減らし同等の構成を再現することができました。
紹介としては以上となります。もっといい方法があったらぜひTwitterなどでこそっと教えてください。
参考URL
aws_security_group | Resources | hashicorp/aws | Terraform Registry
aws_security_group_rule | Resources | hashicorp/aws | Terraform Registry
以下の記事を参考にしました。ありがとうございます!
Terraformerとしてコードを書いて思うこと | フューチャー技術ブログ
TerraformでAWSのセキュリティグループをListで定義してみた - Kumanote corporate website