LoginSignup
2
3

More than 1 year has passed since last update.

Terraformで使った黒魔術2021

Last updated at Posted at 2021-12-15

こんにちは。
今年も私は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にはfloorceilという真逆のことをしてくれる関数があり、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を仕込んで、最初のリソースのみに反映するようにしています。

終わりに

今年、書いてみて、一番複雑にしてしまったコードを懺悔の意味も込めて書きました。
小ネタみたいな感じでしたが、変数だけでリソースを動かせるというところでは比較的堅牢なコードでもあると思うので、誰かの役に立てば幸いです。

2
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
3