依存グラフと状態ファイルの解剖
目標
参照が依存関係のグラフをどう作るかを、状態ファイルで直接確認し、状態を、人と機械の2つの方式で読んで、メタデータを取り出せるようになります。
なぜ重要なのか
Terraformを数日使うと、「なぜ順序がこうなのか」という疑問が、必ず出てきます。答えはいつもグラフです。ツールは、ファイルに書かれた順序を見ず、どのリソースが、どのリソースの値を使うかだけを見ます。そのため、値を参照すれば順序ができ、値をコピーして書けば順序がなくなります。depends_onは、値としては表に出ない順序を表現する抜け道ですが、習慣になると、グラフが太くなって、実行が遅くなり、再作成が横に広がります。一方、この関係は、状態ファイルにdependenciesとして記録されますが、これが、コードからリソースを削除したあとでも、正しい逆順で削除できる根拠です。状態を手で編集することがなぜ危険かも、ここで理解できます。
ステップ
/root/tf/stateに設定を作成して、初期化してください。名前がseedのrandom_petと、名前がchildのlocal_fileを宣言して、childの内容がrandom_pet.seedの値を参照するようにしてください。childにはdepends_onを使わないでください。適用のあと、状態のlocal_file.childのdependenciesにrandom_pet.seedが入っている必要があります。- 名前が
markerのlocal_fileを追加して、/root/tf/state/main.tfにdepends_on = [local_file.child]を書いて、順序を固定してください。適用のあと、状態のlocal_file.markerのdependenciesに、local_file.childが見えなければなりません。 - 状態に登録されたリソースアドレスの一覧を、
/root/tf/state/out/state-list.txtに保存してください。random_pet.seed、local_file.child、local_file.markerの3行が、それぞれまったくその文字列のまま入っている必要があります。 - 状態のJSON表現を、
/root/tf/state/out/state.jsonに保存してください。.values.root_module.resourcesの下にリソースが3個以上あり、そのうちaddressがlocal_file.childの項目がある必要があります。 /root/tf/state/out/meta.jsonを作成してください。serial、lineage、version、resource_countの4つのキーを持ち、最初の3つは/root/tf/state/terraform.tfstateの値と同じで、resource_countは状態のresources配列の長さと同じでなければなりません。childが作ったファイルを、Terraformを経由せず、シェルで直接削除したあと、その状態でプランを出力して、/root/tf/state/out/drift-plan.txtに保存してください。出力に、local_file.childを再作成するという内容があり、「変更なし」であってはいけません。/root/tf/state/.terraform.lock.hclを読んで、そこに固定されているプロバイダーの名前とバージョンを1行にまとめて、/root/tf/state/out/lock-note.txtに保存してください。localという名前と、2.5.3のような3桁のバージョン番号が、どちらも入っている必要があります。- 名前が
stage_a、stage_b、stage_cのlocal_fileを3つ追加して、stage_bはstage_aの値を、stage_cはstage_bの値を、参照するようにしてください。適用したあと、/root/tf/state/out/chain.txtに、作られる順序のとおりにlocal_file.stage_a、local_file.stage_b、local_file.stage_cを1行に1つずつ、合計3行で書いてください。
参考
terraform state listはアドレスだけを、terraform show -jsonは状態全体を、機械が読む形式で表示します。後者はjqと組み合わせて使います。- ステップ5は、
jqで.serial、.lineage、.version、(.resources|length)を取り出して、新しいJSONを組み立てれば、手でコピーするミスを避けられます。 - ステップ6のプランの出力は、標準出力に出ます。再び適用する前に、ファイルに残してください。
- よくある間違い1: 順序を合わせるために、すべてのリソースに
depends_onを付けること。参照があれば、依存関係はすでにできていて、重複したdepends_onは、グラフを太くするだけです。 - よくある間違い2: ステップ3とステップ8で、アドレスの前後に空白や引用符を残すこと。採点は、行全体が正確に一致するかを見ます。
参照だけで依存関係を作る
/root/tf/stateに設定を作成して、初期化してください。名前がseedのrandom_petと、名前がchildのlocal_fileを宣言して、childの内容がrandom_pet.seedの値を参照するようにしてください。childにはdepends_onを使わないでください。適用のあと、状態のlocal_file.childのdependenciesにrandom_pet.seedが入っている必要があります。
他のリソースの属性を式で使えば、それがそのまま順序の宣言です。このステップでは、depends_onを使わずに、状態のdependencies配列が埋まるかどうかで確認してください。
depends_onで順序を固定する
名前がmarkerのlocal_fileを追加して、/root/tf/state/main.tfにdepends_on = [local_file.child]を書いて、順序を固定してください。適用のあと、状態のlocal_file.markerのdependenciesに、local_file.childが見えなければなりません。
値は使わないのに、順序だけが必要なときに使う引数があります。値はリソースアドレスのリストで、引用符で囲みません。
状態のアドレス一覧を取り出す
状態に登録されたリソースアドレスの一覧を、/root/tf/state/out/state-list.txtに保存してください。random_pet.seed、local_file.child、local_file.markerの3行が、それぞれまったくその文字列のまま入っている必要があります。
状態に登録されたアドレスだけを、1行ずつ表示するサブコマンドがあります。ファイルには、アドレス以外の装飾が入ってはいけません。
状態をJSONで取り出す
状態のJSON表現を、/root/tf/state/out/state.jsonに保存してください。.values.root_module.resourcesの下にリソースが3個以上あり、そのうちaddressがlocal_file.childの項目がある必要があります。
人が読む出力と、機械が読む出力は、別のオプションです。JSONの中で、リソースの一覧がどのパスにあるかを、jqでたどってみてください。
状態のメタデータを記録する
/root/tf/state/out/meta.jsonを作成してください。serial、lineage、version、resource_countの4つのキーを持ち、最初の3つは/root/tf/state/terraform.tfstateの値と同じで、resource_countは状態のresources配列の長さと同じでなければなりません。
4つのキーが必要です。値を目でコピーせず、状態ファイルから直接取り出して組み立てれば、間違えることはありません。リソースの個数は、配列の長さです。
手で削除したファイルがプランで捕捉されるかを見る
childが作ったファイルを、Terraformを経由せず、シェルで直接削除したあと、その状態でプランを出力して、/root/tf/state/out/drift-plan.txtに保存してください。出力に、local_file.childを再作成するという内容があり、「変更なし」であってはいけません。
コードはそのままにして、成果物だけをなくすと、照会の段階で差が明らかになります。再び適用する前に、プランの出力を先に保存してください。
ロックファイルからプロバイダーのバージョンを読む
/root/tf/state/.terraform.lock.hclを読んで、そこに固定されているプロバイダーの名前とバージョンを1行にまとめて、/root/tf/state/out/lock-note.txtに保存してください。localという名前と、2.5.3のような3桁のバージョン番号が、どちらも入っている必要があります。
ロックファイルには、プロバイダーのブロックと、確定したバージョン、そしてハッシュがあります。ハッシュが何を保証するのかを考えながら、1行にまとめてください。
3段階の依存の連鎖を作って、順序を記録する
名前がstage_a、stage_b、stage_cのlocal_fileを3つ追加して、stage_bはstage_aの値を、stage_cはstage_bの値を、参照するようにしてください。適用したあと、/root/tf/state/out/chain.txtに、作られる順序のとおりにlocal_file.stage_a、local_file.stage_b、local_file.stage_cを1行に1つずつ、合計3行で書いてください。
前のステップの結果を、次のステップが参照するようにつなげれば、連鎖になります。記録する順序は、コードに書いた順序ではなく、作られる順序です。