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

Infrastructure as Code

属性を一つ変えただけなのにファイルが作り直された

TT Labで続きを見る

目標

本物の宣言的ツール(OpenTofu)が出すプランを読み、終了コード・保存されたプラン・プランのJSONを使って自動判定を作り、prevent_destroyが防げるものと防げないものを、直接確認します。

なぜ重要なのか

前のラボでシェルで真似したplanの記号は、実際のツールでは、プロバイダーの規則に従います。ファイルの内容を1行直しただけなのにリソースが置き換えられることはよくあり、それをプランから読み取れないと、適用したあとで初めて気づきます。自動化は、プランを人の代わりに読む必要があるので、終了コードとJSONを約束どおりに扱う必要があり、レビューしたものと適用されたものが同じだという保証は、保存されたプランから生まれます。削除の保護も、設定の中にあるときだけ効力があるという限界を知っておいてはじめて、レビューで何を見るかが決まります。

ステップ

  1. /root/iac-plan/main.tfにlocalとrandomのプロバイダーを要求し、リソースを3つ宣言してください。local_file.motdは、${path.module}/out/motd.txtにhello v1を1行(末尾に改行)書き、terraform_data.releaseはinputがv1、random_pet.suffixはkeepersがteam = "blue"です。tofu initとtofu apply -auto-approveで適用してください。
  2. /root/iac-plan/gate.sh <작업디렉터리>を作成してください(プレースホルダーは作業ディレクトリです)。そのディレクトリでtofu plan -detailed-exitcodeを実行して、変更がなければcleanを出力して0、変更があればchangesと2、エラーならerrorと1で終了します。出力はその単語1行だけです。採点ツールは、変更なし・変更あり・設定エラーの3種類の一時ディレクトリを作って、このスクリプトを実行します。
  3. main.tfで3つの値を変更してください。motdの内容はhello v2、releaseのinputはv2、suffixのkeepersのteamはgreenです。適用せずに、tofu plan -out=/root/iac-plan/change.tfplanでプランを保存したあと、/root/iac-plan/actions.txtに、変わるリソースごとに<주소> <create|update|replace|delete>を1行ずつ書いてください(プレースホルダーはリソースのアドレスです)。何がその場での更新で、何が置き換えなのかは、プランの出力の記号と、tofu show -jsonのactionsで判断します。
  4. まずtofu apply change.tfplanで、保存したプランをそのまま適用してください。そのあと、motdの内容をhello v3に変更して、tofu plan -out=/root/iac-plan/late.tfplanで保存します。このプランを適用する前に、他の人が割り込んだ状況を作ります。tofu apply -auto-approve -replace=random_pet.suffix -target=random_pet.suffixを先に実行してください(suffixだけを置き換えて、motdには触れません)。次に、tofu apply late.tfplanを実行して、標準出力とエラーを一緒に/root/iac-plan/stale.txtに保存してください。
  5. /root/iac-plan/summary.sh <계획JSON>を作成してください。tofu show -jsonの結果ファイルを受け取って、create=<수> update=<수> replace=<수> delete=<수>を1行出力します(プレースホルダーは、プランのJSONファイルと件数です)。置き換え(削除と作成が1つのリソースに一緒にあるもの)は、replace 1件としてのみ数えます。そして、現在の作業ディレクトリのプランを/root/iac-plan/now.jsonに保存し(tofu plan -outのあとにtofu show -json)、その要約を/root/iac-plan/now-summary.txtに保存してください。
  6. local_file.motdにlifecycle { prevent_destroy = true }を追加してください。内容がhello v3のままtofu planを実行して、出力(エラーを含む)を/root/iac-plan/guard.txtに保存します。そのあと、motdの内容をhello v2に戻して、プランが再びきれいになるようにしてください(作業ディレクトリのプランが変更なしでなければなりません)。採点ツールは、コピーでmotdの内容を変えたプランを作成して、保護が実際に防ぐかを確認します。
  7. /root/iac-planを、状態ファイルごと丸ごと/root/iac-plan-bypassにコピーしてください(cp -a)。コピーのmain.tfから、local_file.motdのリソースブロックを、lifecycleごと丸ごと削除して、コピーでtofu plan -out=/root/iac-plan-bypass/bypass.tfplanを保存します。適用はしません。元のものはそのままにします。
  8. /root/iac-plan/review.sh <작업디렉터리>を作成してください(プレースホルダーは作業ディレクトリです)。そのディレクトリのプランを一時ファイルに保存して、summary.shで数え、変更がなければCLEANと0、追加・更新だけならAUTOで始まる1行と2、置き換えや削除が1つでもあればREVIEWで始まる1行と3、プランが失敗したらERRORと1で終了します。作業ディレクトリにプランのファイルを残しません。採点ツールは、元のもの(CLEAN)、迂回したコピー(REVIEW)、そして自分で作った一時ディレクトリで確認します。

参考

3つのリソースを宣言して、1回適用する

/root/iac-plan/main.tfにlocalとrandomのプロバイダーを要求し、リソースを3つ宣言してください。local_file.motdは、${path.module}/out/motd.txtにhello v1を1行(末尾に改行)書き、terraform_data.releaseはinputがv1、random_pet.suffixはkeepersがteam = "blue"です。tofu initとtofu apply -auto-approveで適用してください。

localとrandomは、Pod内のミラーから取得されるので、インターネットがなくてもinitできます。terraform_dataは、プロバイダーなしで使える組み込みのリソースです。適用したあと、tofu state listで、状態が把握しているアドレスを確認してください。

終了コード3種類を単語に変えるゲートを作る

/root/iac-plan/gate.sh <작업디렉터리>を作成してください(プレースホルダーは作業ディレクトリです)。そのディレクトリでtofu plan -detailed-exitcodeを実行して、変更がなければcleanを出力して0、変更があればchangesと2、エラーならerrorと1で終了します。出力はその単語1行だけです。採点ツールは、変更なし・変更あり・設定エラーの3種類の一時ディレクトリを作って、このスクリプトを実行します。

-detailed-exitcodeは、0・1・2の3つの値を出します。set -eをかけると、2のところでスクリプトが先に終了してしまいます。プランを立てるだけのコマンドには、-lock=falseを付けて、他の人の作業を妨げないようにできます。

属性を1つ直しただけなのに作り直される理由を読み取る

main.tfで3つの値を変更してください。motdの内容はhello v2、releaseのinputはv2、suffixのkeepersのteamはgreenです。適用せずに、tofu plan -out=/root/iac-plan/change.tfplanでプランを保存したあと、/root/iac-plan/actions.txtに、変わるリソースごとに<주소> <create|update|replace|delete>を1行ずつ書いてください(プレースホルダーはリソースのアドレスです)。何がその場での更新で、何が置き換えなのかは、プランの出力の記号と、tofu show -jsonのactionsで判断します。

~はその場での更新、-/+は削除して作り直す置き換えです。JSONでは、actionsが["update"]なのか["delete","create"]なのかで見分けられます。どの属性が置き換えを強制するかは、プロバイダーが決めます。プランの出力で、# forces replacementの表示を探してください。

レビューしたプランだけを適用し、古いプランは拒否される様子を見る

まずtofu apply change.tfplanで、保存したプランをそのまま適用してください。そのあと、motdの内容をhello v3に変更して、tofu plan -out=/root/iac-plan/late.tfplanで保存します。このプランを適用する前に、他の人が割り込んだ状況を作ります。tofu apply -auto-approve -replace=random_pet.suffix -target=random_pet.suffixを先に実行してください(suffixだけを置き換えて、motdには触れません)。次に、tofu apply late.tfplanを実行して、標準出力とエラーを一緒に/root/iac-plan/stale.txtに保存してください。

保存されたプランには、プランを立てたときの状態が一緒に入っています。その間に状態が変わっていれば、OpenTofuはプランを適用しません。-replaceは、設定を変えずに、そのリソースを置き換えさせるオプションで、-targetは、プランの範囲をそのリソースに絞ります(-targetがなければ、main.tfのv3の変更まで一緒に適用されます)。-no-colorを付けると、保存したファイルが読みやすくなります。

プランのJSONからアクションを数える

/root/iac-plan/summary.sh <계획JSON>を作成してください。tofu show -jsonの結果ファイルを受け取って、create=<수> update=<수> replace=<수> delete=<수>を1行出力します(プレースホルダーは、プランのJSONファイルと件数です)。置き換え(削除と作成が1つのリソースに一緒にあるもの)は、replace 1件としてのみ数えます。そして、現在の作業ディレクトリのプランを/root/iac-plan/now.jsonに保存し(tofu plan -outのあとにtofu show -json)、その要約を/root/iac-plan/now-summary.txtに保存してください。

resource_changesの各要素に、change.actionsの配列があります。変更のないリソースは["no-op"]なので、どの欄にも数えません。jqでは、配列どうしを==で比較できます。

保護をかけたら置き換えまで防がれることを確認する

local_file.motdにlifecycle { prevent_destroy = true }を追加してください。内容がhello v3のままtofu planを実行して、出力(エラーを含む)を/root/iac-plan/guard.txtに保存します。そのあと、motdの内容をhello v2に戻して、プランが再びきれいになるようにしてください(作業ディレクトリのプランが変更なしでなければなりません)。採点ツールは、コピーでmotdの内容を変えたプランを作成して、保護が実際に防ぐかを確認します。

local_fileのcontentは、変わると置き換えになります。置き換えには削除が含まれているので、prevent_destroyがかかったリソースでは、プラン自体がエラーで終わります。エラーメッセージから、リソースのアドレスと理由を読んでください。

ブロックを削除すると保護も一緒になくなることを確認する

/root/iac-planを、状態ファイルごと丸ごと/root/iac-plan-bypassにコピーしてください(cp -a)。コピーのmain.tfから、local_file.motdのリソースブロックを、lifecycleごと丸ごと削除して、コピーでtofu plan -out=/root/iac-plan-bypass/bypass.tfplanを保存します。適用はしません。元のものはそのままにします。

prevent_destroyは、設定の中にある間だけ効力があります。ブロックを削除すると、その設定もなくなるので、プランはエラーなしで削除を提案します。この違いが、コードレビューで何を見るべきかを教えてくれます。

破壊が混ざったプランは人に渡すゲートを作る

/root/iac-plan/review.sh <작업디렉터리>を作成してください(プレースホルダーは作業ディレクトリです)。そのディレクトリのプランを一時ファイルに保存して、summary.shで数え、変更がなければCLEANと0、追加・更新だけならAUTOで始まる1行と2、置き換えや削除が1つでもあればREVIEWで始まる1行と3、プランが失敗したらERRORと1で終了します。作業ディレクトリにプランのファイルを残しません。採点ツールは、元のもの(CLEAN)、迂回したコピー(REVIEW)、そして自分で作った一時ディレクトリで確認します。

前のステップのgate.shとsummary.shをつなぎ合わせる問題です。一時ファイルはmktempで作って、trapで削除します。プランのファイルなしでJSONを得ることはできません。-outで保存してから、showします。