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

Terraform実戦

ライフサイクルで統制しドリフトを戻す

TT Labで続きを見る

目標

lifecycleの4つの引数で、置き換え・削除・無視を制御し、コードの外で生じた変更を検知して、2つの方向で対応したあと、その判定をスクリプトで自動化します。

なぜ重要なのか

インフラコードがずれる仕組みは、いつも同じです。急な事態が起きて、誰かがコンソールで直接直し、その変更がコードに戻ってこないことです。その状態で数日が過ぎると、まったく別の理由で実行されたapplyが、明け方の応急措置を、黙って元に戻します。そのため、ドリフト対応で本当に難しいのは、コマンドではなく、方向を決める判断です。応急措置が誤りだったなら、コードを基準に元に戻し、正しい判断だったなら、コードを実体に合わせて直す必要があります。この判断なしに自動復旧を回すのが、最も危険です。lifecycleブロックは、この流れに、あらかじめ介入する仕組みです。特に、ignore_changesは、オートスケーラーのように意図的にコードの外で変わる値を、検知の対象から外して、誤報を減らし、prevent_destroyは、削除されてはいけないリソースにブレーキをかけます。最後に、判定は、人の目ではなく-detailed-exitcodeで行ってこそ、cronとCIに載せられます。

ステップ

  1. /root/tf/lifecycle/main.tfにlocal_fileリソースendpointを宣言してください。ファイルパスは/root/tf/lifecycle/out/endpoint.txt、内容は改行のない1行の文字列です。このリソースのlifecycleブロックにcreate_before_destroy = trueを入れて、tofu initのあとに適用してください。
  2. local_fileリソースstatefile_guard(ファイル/root/tf/lifecycle/out/guard.txt)を追加し、lifecycleにprevent_destroy = trueを入れて適用してください。そのあと、そのリソースだけを対象に削除を試み(tofu destroy -target=local_file.statefile_guard)、出力を標準エラー出力まで含めて、/root/tf/lifecycle/out/prevent.txtに保存してください。拒否メッセージが残っている必要があり、状態のstatefile_guardとguard.txtは、そのまま生きている必要があります。
  3. local_fileリソース1つを、ファイル/root/tf/lifecycle/out/managed-note.txtとして作成し、内容をignored-valueにして適用してください。そのあと、lifecycleにignore_changes = [content]を入れ、コードの内容の値だけを別の文字列に変更したうえで、tofu planの出力を/root/tf/lifecycle/out/ignore-plan.txtに保存してください。プランはNo changesである必要があり、実際のファイルの内容は、依然としてignored-valueである必要があります。
  4. null_resourceリソース1つに、triggersでバージョンの値を持たせ、local_fileリソースchecksum(ファイル/root/tf/lifecycle/out/checksum.txt)に、replace_triggered_by = [null_resource.<이름>](プレースホルダーはリソース名です)をかけてください。checksum自身の引数はそのままにして、バージョンの値だけを変更し、tofu plan -out=<계획파일>(プレースホルダーはプランファイル名です)で保存したあと、tofu show -json <계획파일>の結果を/root/tf/lifecycle/out/trigger-plan.jsonに保存してください。JSONのresource_changesで、local_file.checksumのアクションが2つ(削除と作成)である必要があります。確認後に適用してください。
  5. コードを経由せずに、/root/tf/lifecycle/out/endpoint.txtを直接直して、内容にhand-editedが入るようにしてください。そのあと、tofu plan -refresh-only -detailed-exitcode -out=<계획파일>を実行して、終了コードだけを/root/tf/lifecycle/out/drift-exit.txtに書き(2である必要があります)、tofu show -json <계획파일>の結果を/root/tf/lifecycle/out/drift.jsonに保存してください。JSONのresource_driftにendpointが入っている必要があります。このプランは適用しないでください。
  6. tofu applyで実体をコードを基準に元に戻し、出力を/root/tf/lifecycle/out/remediate.txtに保存してください。Apply completeという文言が残っている必要があり、endpoint.txtからhand-editedが消え、ファイルの内容が、状態に記録されたcontentの値と、正確に同じである必要があります。
  7. local_fileリソースquota(ファイル/root/tf/lifecycle/out/quota.txt)を、内容256で適用しておいてください。今回は、ファイルを手で512に直したあと、元に戻さずに、main.tfの値を512に変更してコードに吸収し、適用してください。そのあと、tofu planの出力を/root/tf/lifecycle/out/accept-plan.txtに保存すると、No changesである必要があり、quota.txtとmain.tfの両方が、512を含んでいる必要があります。
  8. /root/tf/lifecycle/drift-report.shを作成して、実行権限を付けてください。このスクリプトは、-detailed-exitcodeを使うプランコマンドでドリフトを判定し、結果を/root/tf/lifecycle/out/drift-report.jsonに、exit_code、drift、checked_at、addressesの4つのフィールドで残す必要があります。ラボを締めくくる時点では、ドリフトがないはずなので、exit_codeは0、driftはfalseである必要があります。

参考

create_before_destroyをかける

/root/tf/lifecycle/main.tfにlocal_fileリソースendpointを宣言してください。ファイルパスは/root/tf/lifecycle/out/endpoint.txt、内容は改行のない1行の文字列です。このリソースのlifecycleブロックにcreate_before_destroy = trueを入れて、tofu initのあとに適用してください。

lifecycleブロックは、リソースの中に入ります。置き換えが必要なときに、新しいものを先に作る引数を探してください。

prevent_destroyで削除を止める

local_fileリソースstatefile_guard(ファイル/root/tf/lifecycle/out/guard.txt)を追加し、lifecycleにprevent_destroy = trueを入れて適用してください。そのあと、そのリソースだけを対象に削除を試み(tofu destroy -target=local_file.statefile_guard)、出力を標準エラー出力まで含めて、/root/tf/lifecycle/out/prevent.txtに保存してください。拒否メッセージが残っている必要があり、状態のstatefile_guardとguard.txtは、そのまま生きている必要があります。

この引数は、プランの段階でエラーを出します。対象を1つだけ指定して削除を試み、その出力を丸ごと保存してください。

ignore_changesで特定の属性を無視する

local_fileリソース1つを、ファイル/root/tf/lifecycle/out/managed-note.txtとして作成し、内容をignored-valueにして適用してください。そのあと、lifecycleにignore_changes = [content]を入れ、コードの内容の値だけを別の文字列に変更したうえで、tofu planの出力を/root/tf/lifecycle/out/ignore-plan.txtに保存してください。プランはNo changesである必要があり、実際のファイルの内容は、依然としてignored-valueである必要があります。

先に適用して、値を状態に入れてから、コードの値を変更して初めて、効果が見えます。実体のファイルの内容は、最初に適用した値のままである必要があります。

別のリソースの変更で再作成を引き起こす

null_resourceリソース1つに、triggersでバージョンの値を持たせ、local_fileリソースchecksum(ファイル/root/tf/lifecycle/out/checksum.txt)に、replace_triggered_by = [null_resource.<이름>](プレースホルダーはリソース名です)をかけてください。checksum自身の引数はそのままにして、バージョンの値だけを変更し、tofu plan -out=<계획파일>(プレースホルダーはプランファイル名です)で保存したあと、tofu show -json <계획파일>の結果を/root/tf/lifecycle/out/trigger-plan.jsonに保存してください。JSONのresource_changesで、local_file.checksumのアクションが2つ(削除と作成)である必要があります。確認後に適用してください。

自分の引数はそのままなのに、他者の変更のせいで置き換えられることが核心です。プランをファイルに保存してから、JSONに変換してください。

コードの外での変更を、ドリフトとして検知する

コードを経由せずに、/root/tf/lifecycle/out/endpoint.txtを直接直して、内容にhand-editedが入るようにしてください。そのあと、tofu plan -refresh-only -detailed-exitcode -out=<계획파일>を実行して、終了コードだけを/root/tf/lifecycle/out/drift-exit.txtに書き(2である必要があります)、tofu show -json <계획파일>の結果を/root/tf/lifecycle/out/drift.jsonに保存してください。JSONのresource_driftにendpointが入っている必要があります。このプランは適用しないでください。

コードを経由せずに、実体を直接直してこそ、ドリフトです。リフレッシュ専用のプランと、詳細終了コードを一緒に使ってください。

コードを基準に実体を元に戻す

tofu applyで実体をコードを基準に元に戻し、出力を/root/tf/lifecycle/out/remediate.txtに保存してください。Apply completeという文言が残っている必要があり、endpoint.txtからhand-editedが消え、ファイルの内容が、状態に記録されたcontentの値と、正確に同じである必要があります。

対応の方向のうち、1つ目です。元に戻したあとは、状態に記録された値と、実体の内容が同じである必要があります。

ドリフトをコードに吸収する

local_fileリソースquota(ファイル/root/tf/lifecycle/out/quota.txt)を、内容256で適用しておいてください。今回は、ファイルを手で512に直したあと、元に戻さずに、main.tfの値を512に変更してコードに吸収し、適用してください。そのあと、tofu planの出力を/root/tf/lifecycle/out/accept-plan.txtに保存すると、No changesである必要があり、quota.txtとmain.tfの両方が、512を含んでいる必要があります。

今回は、反対の方向です。手で変更した値が正しいと判断したなら、コードをその値に直します。

ドリフト検知のスクリプトとレポートを作る

/root/tf/lifecycle/drift-report.shを作成して、実行権限を付けてください。このスクリプトは、-detailed-exitcodeを使うプランコマンドでドリフトを判定し、結果を/root/tf/lifecycle/out/drift-report.jsonに、exit_code、drift、checked_at、addressesの4つのフィールドで残す必要があります。ラボを締めくくる時点では、ドリフトがないはずなので、exit_codeは0、driftはfalseである必要があります。

判定は、人の目ではなく、終了コードで行います。set -eが、終了コード2でスクリプトを止めてしまわないように、注意してください。