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

Infrastructure as Code

apply するたびにサービスが再起動した

TT Labで続きを見る

目標

宣言的なツールの中に付けた副作用(再起動・一回限りの作業)がいつ起きるかを、トリガーで制御し、失敗した作業が残すtainted状態と置き換えの順序を確認したうえで、2回適用テストで冪等性を証明します。

なぜ重要なのか

ファイルを書くことは、何回行っても結果が同じですが、サービスの再起動やデータのマイグレーションは、回るたびに痕跡が残ります。そのため、「何が変わったときだけ回す」という条件が冪等性の核心で、宣言的なツールでは、その条件がトリガーの値として表現されます。トリガーに毎回変わる値を入れると、プランが決して空にならず、CIの2回適用テストが失敗し、失敗した作業はtaintedとして表示されて、次の適用でもう一度回るので、その作業自体がリトライに対して安全でなければなりません。置き換えが、停止と開始の順序で起きることも、無停止設計の出発点です。

ステップ

  1. /root/iac-prov/noisy/main.tfに、port変数(デフォルト8080)、out/app.confにport=<값>を書くlocal_file.conf、そしてtriggers_replace = timestamp()のterraform_data.restartを置いてください(プレースホルダーは値です)。restartは、作成されるときにlocal-execでout/restarts.logにrestartを1行追記します。initしたあと、applyを2回行ってください。採点ツールは、このディレクトリでプランを新しく立てて、毎回変更が出るかを見ます。
  2. /root/iac-prov/app/main.tfをステップ1と同じにして、restartのトリガーを、設定ファイルの内容のハッシュ(local_file.confのcontent_sha256)に変更してください。initしたあと、applyを2回行って、プランがきれいかを確認します。採点ツールは、コピーでportを別の値にしたプランを立てて、restartが置き換えられるか、同じ値なら何も変わらないかを見ます。
  3. /root/iac-prov/app/out/restarts.logの行数を数えたあと、applyをさらに2回行って、もう一度数え、/root/iac-prov/app/proof.txtにbefore=<수> after=<수>を1行で書いてください(プレースホルダーは件数です)。採点ツールは、コピーでapplyを2回回して、記録が実際に増えないかも確認します。
  4. /root/iac-prov/app/terraform.tfvarsにport = 8081を置いて、applyしてください。out/app.confがport=8081になり、restarts.logが、proof.txtのafterよりちょうど1行増えている必要があります。
  5. app/main.tfにterraform_data.migrateを追加してください。作成されるときに、local-execで${path.module}/ready.flagがあるかを検査し、あればout/migrate.logにmigratedを1行追記します(なければコマンドが失敗します)。ready.flagがないままapplyして、出力を/root/iac-prov/app/taint.txtに保存し、続けてtofu planの出力を/root/iac-prov/app/taint-plan.txtに保存してください。採点ツールは、コピーでready.flagなしにmigrateを再作成して、taintedになるかも見ます。
  6. /root/iac-prov/app/ready.flagを作成して、applyしてください。migrateが置き換えられ、今度は成功する必要があり、そのあとのプランはきれいでなければなりません。restartは、この過程で再び回ってはいけません(restarts.logの行数が、ステップ4のあとと同じ)。
  7. app/main.tfにworker_version変数(デフォルトv1)とterraform_data.workerを追加してください。inputとtriggers_replaceは、どちらもその変数で、作成時にout/worker.logにstart <버전>を、when = destroyのプロビジョナーでstop <버전>を追記します(プレースホルダーはバージョンです)。v1で適用したあと、terraform.tfvarsにworker_version = "v2"を追加して、もう一度適用してください。worker.logがstart v1、stop v1、start v2の順序になっている必要があります。
  8. /root/iac-prov/twice.sh <작업디렉터리>を作成してください(プレースホルダーは作業ディレクトリです)。applyしたあと、plan -detailed-exitcodeで2回目のプランを確認して、空ならidempotentと0、変更が残っていればnot-idempotentで始まる行と2、applyやplanが失敗したらerrorで始まる行と1で終了します。採点ツールは、/root/iac-prov/appと/root/iac-prov/noisyのコピー、そして設定が壊れた一時ディレクトリで確認します。

参考

適用のたびに再起動が回る様子を見る

/root/iac-prov/noisy/main.tfに、port変数(デフォルト8080)、out/app.confにport=<값>を書くlocal_file.conf、そしてtriggers_replace = timestamp()のterraform_data.restartを置いてください(プレースホルダーは値です)。restartは、作成されるときにlocal-execでout/restarts.logにrestartを1行追記します。initしたあと、applyを2回行ってください。採点ツールは、このディレクトリでプランを新しく立てて、毎回変更が出るかを見ます。

timestamp()は、実行するたびに違う値なので、トリガーがいつも変わります。triggers_replaceが変わると、terraform_dataは置き換えられ、置き換えはすなわち作成なので、作成時のプロビジョナーがもう一度回ります。

設定が変わるときだけ再起動するようにトリガーを設定する

/root/iac-prov/app/main.tfをステップ1と同じにして、restartのトリガーを、設定ファイルの内容のハッシュ(local_file.confのcontent_sha256)に変更してください。initしたあと、applyを2回行って、プランがきれいかを確認します。採点ツールは、コピーでportを別の値にしたプランを立てて、restartが置き換えられるか、同じ値なら何も変わらないかを見ます。

local_fileは、content_sha256のような計算された属性を出力します。トリガーに使う値が、「何が変わったら副作用が必要か」の定義です。

2回さらに適用しても再起動の記録が増えないことを残す

/root/iac-prov/app/out/restarts.logの行数を数えたあと、applyをさらに2回行って、もう一度数え、/root/iac-prov/app/proof.txtにbefore=<수> after=<수>を1行で書いてください(プレースホルダーは件数です)。採点ツールは、コピーでapplyを2回回して、記録が実際に増えないかも確認します。

変更のない適用では、どのリソースも作成されないので、作成時のプロビジョナーも回りません。行数は、wc -l < 파일で数字だけを得られます(プレースホルダーはファイルです)。

設定が本当に変わったら、ちょうど1回だけ再起動する

/root/iac-prov/app/terraform.tfvarsにport = 8081を置いて、applyしてください。out/app.confがport=8081になり、restarts.logが、proof.txtのafterよりちょうど1行増えている必要があります。

terraform.tfvarsは自動的に読み込まれます。プランで、local_file.confとterraform_data.restartが一緒に置き換えられることを確認してください。

失敗したプロビジョナーはリソースをtaintedのまま残す

app/main.tfにterraform_data.migrateを追加してください。作成されるときに、local-execで${path.module}/ready.flagがあるかを検査し、あればout/migrate.logにmigratedを1行追記します(なければコマンドが失敗します)。ready.flagがないままapplyして、出力を/root/iac-prov/app/taint.txtに保存し、続けてtofu planの出力を/root/iac-prov/app/taint-plan.txtに保存してください。採点ツールは、コピーでready.flagなしにmigrateを再作成して、taintedになるかも見ます。

作成時のプロビジョナーが失敗すると、リソース自体は作られていても、「正しく終わっていないもの」として表示され、次のプランはそれを置き換えようとします。tofu state showや、状態のJSONのinstances[].statusでも見えます。

原因を直して再適用すると、1回だけ置き換えられる

/root/iac-prov/app/ready.flagを作成して、applyしてください。migrateが置き換えられ、今度は成功する必要があり、そのあとのプランはきれいでなければなりません。restartは、この過程で再び回ってはいけません(restarts.logの行数が、ステップ4のあとと同じ)。

taintedのリソースは、次の適用で置き換えられます。リトライが安全であるためには、その作業が何回回っても問題ない必要があります。このmigrateが2回回ると何が残るかを、migrate.logで確認してください。

置き換えは、古いものの停止のあとに新しいものの開始

app/main.tfにworker_version変数(デフォルトv1)とterraform_data.workerを追加してください。inputとtriggers_replaceは、どちらもその変数で、作成時にout/worker.logにstart <버전>を、when = destroyのプロビジョナーでstop <버전>を追記します(プレースホルダーはバージョンです)。v1で適用したあと、terraform.tfvarsにworker_version = "v2"を追加して、もう一度適用してください。worker.logがstart v1、stop v1、start v2の順序になっている必要があります。

destroyプロビジョナーは、自分自身(self)しか参照できません。変数を直接使うとエラーです。デフォルト値のcreate_before_destroy=falseでは、古いオブジェクトを先に削除して、新しいオブジェクトを作ります。

CIに入れる2回適用テストを作る

/root/iac-prov/twice.sh <작업디렉터리>を作成してください(プレースホルダーは作業ディレクトリです)。applyしたあと、plan -detailed-exitcodeで2回目のプランを確認して、空ならidempotentと0、変更が残っていればnot-idempotentで始まる行と2、applyやplanが失敗したらerrorで始まる行と1で終了します。採点ツールは、/root/iac-prov/appと/root/iac-prov/noisyのコピー、そして設定が壊れた一時ディレクトリで確認します。

理論で述べた「CIでプレイブックを2回回すチーム」を、宣言的なツールに置き換えたものです。set -eを使うと、2のところで先に終了してしまうので、終了コードを受け取って判断してください。