子モジュールを作り複数回呼び出す
目標
小さな子モジュールを標準の構造で作り、同じモジュールを複数回呼び出してインスタンスを増やし、モジュール内の値を出力として外に取り出す流れを、身につけます。
なぜ重要なのか
モジュールを使う理由を「コードの重複の排除」としてしか教わらないと、すぐに壁にぶつかります。本当の理由は、変更の波及範囲の制限です。実装が1つなら、直す場所も1か所で、環境間の違いは、コードではなく渡す値だけに現れます。そのため、モジュール設計で最も重要な決定は、「何を変数として開放するか」です。開放しすぎると、モジュールが単なるリソースの別名になり、開放が少なすぎると、誰も使えません。もう1つ覚えておくべきことは、モジュールがカプセルだという点です。モジュール内のリソースは、外から直接参照できず、outputからしか出てきません。この制約は面倒に見えますが、おかげで、モジュールの内部を作り変えても、使う側のコードが壊れません。最後に、子モジュールの中にproviderブロックを置かないでください。そのモジュールの呼び出しを、あとでコードから削除できなくなる、元に戻しにくい事故の原因です。
ステップ
/opt/lab/fixtures/terraform/modules-starter/modules/fileboxを/root/tf/modules/modules/fileboxにコピーしてください。そのディレクトリに、main.tf、variables.tf、outputs.tfの3つのファイルがある必要があります。variables.tfにはvariable "dir"とvariable "name"があり、新しく作成するoutputs.tfにはoutput "path"(値はlocal_file.box.filename)を置いてください。モジュール内にprovider "..."ブロックを置いてはいけません。/root/tf/modules/main.tfにmodule "primary"を宣言し、source = "./modules/filebox"と指定してください。dirには/root/tf/modules/out、nameにはprimary、bodyには任意の文字列を渡します。tofu initのあとtofu applyで/root/tf/modules/out/primary.txtを作成してください。/root/tf/modules/outputs.tfにルートの出力primary_pathを作成してmodule.primary.pathを出力し、tofu output -jsonの結果を/root/tf/modules/out/outputs.jsonに保存してください。primary_pathの値は/root/tf/modules/out/primary.txtである必要があります。- 同じソースを、
module "secondary"としてもう一度呼び出してください。nameはsecondaryとし、bodyはprimaryとは異なる文字列を渡して、/root/tf/modules/out/secondary.txtの内容がprimary.txtと異なるようにします。モジュールのディレクトリをコピーしないでください。/root/tf/modules/modulesの下のディレクトリは、あとで作成するbundleを含めて、2つを超えてはいけません。 tofu state listの結果を/root/tf/modules/out/state-list.txtに保存してください。module.primary.とmodule.secondary.で始まるアドレスがそれぞれある必要があり、ルートにlocal_file.boxというアドレスが別にあってはいけません。/root/tf/modules/modules/bundle/main.tfを作成し、その中でsource = "../filebox"としてfileboxを2回呼び出してください(例:module "left"、module "right"、名前はbundle-left・bundle-right)。ルートからmodule "bundle"として呼び出して適用すると、状態にmodule.bundle.module.left....の形のアドレスが2つ以上できます。/root/tf/modules/modules/filebox/variables.tfのname変数にvalidationブロックを追加し、error_messageも一緒に書いてください(例: 小文字・数字・ハイフンのみ許可)。そのあと、どれかの呼び出しのnameに、ルールに違反する値を一時的に入れて、tofu planの出力を標準エラー出力まで含めて/root/tf/modules/out/module-error.txtに保存してください。保存した内容には、検証失敗のメッセージとfileboxのパスが一緒に表示されている必要があります。確認が終わったら、値を元に戻します。/root/tf/modules/outputs.tfにall_paths出力を追加してください。primary、secondary、bundleの3つのキーを持つマップで、primaryの値は/root/tf/modules/out/primary.txtである必要があります。bundleモジュールにもoutput "paths"を作成して内側の2つのパスを入れ、それをbundleキーに入れてください。最後に、tofu output -jsonをもう一度/root/tf/modules/out/outputs.jsonに保存します。
参考
- このラボの状態ファイルは、
/root/tf/modules/terraform.tfstateです。モジュール内のリソースも、すべてこの1つの状態に記録されます。 - この環境では、
tofuとterraformは同じコマンドです。プロバイダーはイメージ内のファイルシステムミラーから取得するので、インターネットがなくてもtofu initができます。 - ルートの
main.tfに、terraform { required_providers { local = { source = "hashicorp/local" } } }を置いてください。プロバイダーの設定は、ルートにだけ置きます。 - モジュールを新しく追加したり、
sourceを変更したりしたら、tofu initをもう一度実行する必要があります。「Module not installed」エラーの90%は、これです。 - よくある間違い1: モジュール内のリソースを、ルートから
module.primary.local_file.boxとして参照することです。そのようなアドレスは存在しません。値は必ずoutputを通す必要があります。 - よくある間違い2: インスタンスを増やすために、モジュールのディレクトリを丸ごとコピーすることです。同じ
sourceを、別の名前で呼び出してください。
モジュールを入力・実装・出力の3つのファイルに分ける
/opt/lab/fixtures/terraform/modules-starter/modules/fileboxを/root/tf/modules/modules/fileboxにコピーしてください。そのディレクトリに、main.tf、variables.tf、outputs.tfの3つのファイルがある必要があります。variables.tfにはvariable "dir"とvariable "name"があり、新しく作成するoutputs.tfにはoutput "path"(値はlocal_file.box.filename)を置いてください。モジュール内にprovider "..."ブロックを置いてはいけません。
モジュールは、ただのディレクトリです。慣例として、変数はvariables.tf、リソースはmain.tf、出力する値はoutputs.tfに置きます。子モジュールの中には、providerの設定ブロックを置かないでください。
モジュールを呼び出して適用する
/root/tf/modules/main.tfにmodule "primary"を宣言し、source = "./modules/filebox"と指定してください。dirには/root/tf/modules/out、nameにはprimary、bodyには任意の文字列を渡します。tofu initのあとtofu applyで/root/tf/modules/out/primary.txtを作成してください。
module "이름"ブロックに、sourceと変数の値を入れます(プレースホルダーはモジュールの呼び出し名です)。モジュールを追加したりソースを変更したりしたら、initをもう一度実行する必要があります。
モジュールの出力をルートに引き上げる
/root/tf/modules/outputs.tfにルートの出力primary_pathを作成してmodule.primary.pathを出力し、tofu output -jsonの結果を/root/tf/modules/out/outputs.jsonに保存してください。primary_pathの値は/root/tf/modules/out/primary.txtである必要があります。
モジュール内のリソースは、外から直接参照できません。子のoutputを、ルートのoutputが改めて受け取って初めて、tofu outputに表示されます。
同じモジュールを、異なる入力で2回呼び出す
同じソースを、module "secondary"としてもう一度呼び出してください。nameはsecondaryとし、bodyはprimaryとは異なる文字列を渡して、/root/tf/modules/out/secondary.txtの内容がprimary.txtと異なるようにします。モジュールのディレクトリをコピーしないでください。/root/tf/modules/modulesの下のディレクトリは、あとで作成するbundleを含めて、2つを超えてはいけません。
ディレクトリをコピーすると、コピペの問題に逆戻りします。同じsourceを、名前だけ変えて呼び出し、渡す値を互いに異なるものにしてください。
モジュールに属するリソースのアドレスを確認する
tofu state listの結果を/root/tf/modules/out/state-list.txtに保存してください。module.primary.とmodule.secondary.で始まるアドレスがそれぞれある必要があり、ルートにlocal_file.boxというアドレスが別にあってはいけません。
モジュール内のリソースには、module.<호출이름>.というプレフィックスが付きます(プレースホルダーは呼び出し名です)。状態の一覧を出力して、プレフィックスが実際に付いているかを見てください。
モジュールの中からモジュールを呼び出す
/root/tf/modules/modules/bundle/main.tfを作成し、その中でsource = "../filebox"としてfileboxを2回呼び出してください(例: module "left"、module "right"、名前はbundle-left・bundle-right)。ルートからmodule "bundle"として呼び出して適用すると、状態にmodule.bundle.module.left....の形のアドレスが2つ以上できます。
子モジュールから兄弟のモジュールを呼び出すときは、相対パスを使います。ネストされると、状態アドレスも二重に積み重なります。
モジュールの境界で誤った値を止める
/root/tf/modules/modules/filebox/variables.tfのname変数にvalidationブロックを追加し、error_messageも一緒に書いてください(例: 小文字・数字・ハイフンのみ許可)。そのあと、どれかの呼び出しのnameに、ルールに違反する値を一時的に入れて、tofu planの出力を標準エラー出力まで含めて/root/tf/modules/out/module-error.txtに保存してください。保存した内容には、検証失敗のメッセージとfileboxのパスが一緒に表示されている必要があります。確認が終わったら、値を元に戻します。
variableブロックの中のvalidationは、conditionとerror_messageを一緒に要求します。拒否される値をあえて一度入れて、エラーを自分の目で確認してください。
3つのモジュールの出力を1つのマップにまとめる
/root/tf/modules/outputs.tfにall_paths出力を追加してください。primary、secondary、bundleの3つのキーを持つマップで、primaryの値は/root/tf/modules/out/primary.txtである必要があります。bundleモジュールにもoutput "paths"を作成して内側の2つのパスを入れ、それをbundleキーに入れてください。最後に、tofu output -jsonをもう一度/root/tf/modules/out/outputs.jsonに保存します。
ルートのoutputの値として、オブジェクトを直接作れます。キー名と数が採点基準なので、正確に合わせてください。