TT Lab
はじめる
学ぶ 学習パス コース

Terraform実戦

値で畳む繰り返し、ブロックで広げる繰り返し

TT Labで続きを見る

一言でいうと

HCLの繰り返しは、値を作る側(for・flatten・setproduct)と、ブロックを作る側(dynamic)に分かれます。前者は自由に畳んだり広げたりでき、後者は、プロバイダーのスキーマが許す分だけしか作れません。

なぜ必要なのか: 繰り返しを手で展開すると起きること

3つのサービスを、3つの環境に載せるとします。手で展開すると、9つのブロックになります。最初は読みやすいです。問題は、6か月後に「すべての環境のヘルスチェックのパスを変更せよ」という要望が来たときです。9か所を直す必要があり、レビュアーは、9か所が本当に同じように変更されたかを、目で突き合わせる必要があります。1か所が漏れても、プランは通ります。

そこで、繰り返しは値として畳んでおきます。入力は1つ、ルールは1つ、結果は9つです。こうすれば、直す場所が1か所になり、レビューは「ルールが合っているか」の1点だけを見ればよくなります。その代わり、値の式を読む訓練が必要になります。このトレードオフを受け入れる価値のある地点がどこかが、このモジュールのテーマです。

どう動くのか

1. まず型を決めます。ネストしたオブジェクトの型は、ドキュメントの役割を果たします。

variable "services" {
  type = list(object({
    name = string
    port = number
    envs = list(string)
  }))
}

型を書いておけば、tfvarsでスペルが間違っていたときに、applyの前に止まります。ここにvalidationを加えて、「名前が重複してはいけない」のようなルールも、値の段階で検知できます。名前が重複すると、あとで作るfor_eachのキーが黙って1つにまとまってしまいますが、この事故は、プランには「削除1件」としか見えないため、原因を探しにくいです。

2. リストはマップに変換して、for_eachに渡します。for_eachは、セットかマップしか受け取りません。countと違って、インスタンスのアドレスがキーなので、真ん中の項目が抜けても、後ろがずれません。

3. 二重の繰り返しは、展開して1つにします。方法が2つあります。

# (가) 안에서 밖으로 — 필요한 것만 만든다
pairs = flatten([
  for s in var.services : [
    for e in s.envs : { env = e, name = s.name, port = s.port }
  ]
])

# (나) 다 만들고 거른다
combos = [
  for pair in setproduct(local.all_envs, var.services) : {
    env = pair[0], name = pair[1].name, port = pair[1].port
  } if contains(pair[1].envs, pair[0])
]

2つは同じ結果を出せますが、性質が違います。flattenのほうは、入力にあるものだけを作り、setproductのほうは、すべての組み合わせを作ってから捨てます。組み合わせの数が大きいと、後者は無駄な作業が多くなります。逆に、「抜けている組み合わせを見つけ出す」ことが目的なら、すべての組み合わせを先に作るほうが自然です。

4. dynamicは、ブロックを作ります。値ではなくネストしたブロックを、設定値から作り出す仕組みです。

dynamic "subject" {
  for_each = var.with_subject ? [1] : []
  content {
    common_name = "api.internal"
  }
}

ここで重要なのは、いくつ作れるかがプロバイダーのスキーマに依存しているという点です。スキーマが最大1つしか許可しないブロックに2つ作ると、プランは立てられますが、値の検証で「最大いくつまでなのに、いくつ来た」として止まります。ラボのステップ6で、このエラーを自分で受け取ります。

現場での姿

dynamicが実務で最もよく使われる場面は、実は繰り返しではなく、オプションのブロックです。空のコレクションを渡すと、ブロックがまったく作られないという性質を利用して、「ロギング設定はprodだけ」「暗号化ブロックは、このオプションがオンのときだけ」を表現します。以前に、条件付きブロックをまねるために、リソースを丸ごと2つ作っていた方式より、はるかに優れています。

逆に、使うべきでない場面も明確です。公式ドキュメントも、dynamicは読みにくくなるので、乱用しないようにと書いています。ブロック3つが、互いに少しずつ異なる場合、動的に作ると、「何が違うのか」がデータの中に隠れて、レビューで見えなくなります。そういうときは、単に3つのブロックを書くほうが優れています。基準は単純です。個数が入力によって変わるなら動的に、個数は固定で値だけが違うなら手で書きます。

最後に、レンダリング結果をファイルに出力する習慣が、大いに役立ちます。jsonencodeで展開した行列をファイルに書けば、人が目で確認でき、CIでdiffを取ることもできます。プランの出力だけでは、「なぜこの組み合わせが抜けたのか」を追跡しにくいです。

次のラボですること

/root/tfa-dynで、ネストしたオブジェクトの変数を受け取ってマップに変換し、同じ行列をflattenとsetproductの2つの方式で作って、結果が同じかをファイルで突き合わせます。続いて、dynamicで証明書のサブジェクトブロックをオン・オフして、実際の証明書を開いて確認し、2つ作ろうとしてスキーマに止められるエラーを受け取ったあと、展開した行列を、6つのファイルと要約レポートにレンダリングします。