コードから消したら稼働中のものが壊れかけた
目標
リソースを壊さず、状態からだけ外すremovedブロックを使ってみて、このバージョンのremovedが何を受け取るかを、ツールに直接尋ねて記録し、movedで名前だけを変え、-replaceとtaintで1つだけ作り直したあと、モジュール1つをまるごと手放します。
なぜ重要なのか
インフラコードを長く運用していると、コードと実体の所有関係に手を入れる日が来ます。チームが分かれ、別のツールに移り、引き継いだものを返却します。ところが、設定からリソースブロックを削除することは、ツールには「壊せ」と読まれます。所有を手放すという意味ではありません。そのため、その意味を別に伝える宣言が必要で、コマンドではなく宣言で行う理由は、レビューと記録のためです。誰がいつ何を状態から外したのかが、コミットに残ります。ここに、もう1つ落とし穴があります。ドキュメントはいつも最新バージョンを説明しますが、使っているバージョンは、それより古いかもしれません。removedブロックが受け取る引数がバージョンごとに違うことを知り、ドキュメントではなく手元のツールに尋ねて確認する習慣が、このモジュールで学ぶ2つ目のことです。
ステップ
/root/tfa-removed/keep.tfにrandom_pet.keep(length 2)と、その名前をout/keep.txtに書くlocal_file.keepを、/root/tfa-removed/legacy.tfに、out/legacy.txtにlegacy serviceを1行書くlocal_file.legacyを置いて、init・applyしてください。そのあと、状態ファイルを/root/tfa-removed/snapshots/before.tfstateに、1回だけコピーします。/root/tfa-removed/legacy.tfを削除して、/root/tfa-removed/removed.tfに、removedブロックでlocal_file.legacyを状態から外すと宣言してください。apply前に、プランの出力を/root/tfa-removed/removed-plan.txtに保存して、applyします。out/legacy.txtは、そのまま残っている必要があります。/root/tfa-removed/removed.tfのremovedブロックの中に、lifecycleブロック(destroy = false)を一時的に入れて、tofu planを回し、結果を/root/tfa-removed/lifecycle-probe.txtに保存したあと、元に戻してください。 そのあと、/root/tfa-removed/removed-facts.tsvに、3行をタブ区切りの2列で書きます。tofu_versionは、このPodのコアバージョン、removed_lifecycleは、supportedまたはunsupported、removed_effectは、forgetまたはdestroyです。/root/tfa-removed/keep.tfのrandom_pet.keepをrandom_pet.appに変更し(参照も一緒に)、/root/tfa-removed/moved.tfに、movedブロックで、古いアドレスから新しいアドレスに移すと宣言したあと、applyしてください。そのあと、/root/tfa-removed/moved.tsvに、before(ステップ1のコピーに書かれたrandom_pet.keepのid)とafter(いまの状態のrandom_pet.appのid)の2行を、タブ区切りの2列で書きます。2つの値は、同じである必要があります。/root/tfa-removed/legacy.tfを一時的によみがえらせて(ステップ2と同じ内容)tofu planを回し、出力を/root/tfa-removed/conflict.txtに保存したあと、そのファイルを再び削除してください。removedブロックは、そのままにします。最後に、プランはきれいである必要があります。- 状態ファイルを
/root/tfa-removed/snapshots/before-replace.tfstateにコピーしたあと、tofu apply -replace=random_pet.app -auto-approveを回してください。そのあと、/root/tfa-removed/replace.tsvに、before(コピーのid)とafter(いまのid)の2行を、タブ区切りの2列で書きます。2つの値は、異なる必要があります。 tofu taint random_pet.appで印を付けたあと、状態に書かれたそのインスタンスのステータス値を、/root/tfa-removed/taint-status.txtに1行で保存し、tofu planの出力を/root/tfa-removed/taint-plan.txtに保存してください。そのあと、tofu untaintで元に戻して、プランがきれいな状態で終えます。/root/tfa-removed/modules/archive/main.tfに、out/archive-1.txt・out/archive-2.txtの2つのファイルを書くモジュールを作成し、/root/tfa-removed/archive.tfでmodule "archive"として呼び出したあと、applyしてください。そのあと、/root/tfa-removed/archive.tfを削除して、/root/tfa-removed/archive-removed.tfに、removedブロックでmodule.archiveをまるごと状態から外してください。プランの出力は/root/tfa-removed/module-plan.txtに保存し、2つのファイルは、ディスクに残っている必要があります。
参考
- Podには、OpenTofu 1.9.0とlocal・randomプロバイダーのミラーがあり、インターネットなしで動きます。
- state mv・state rmで行う状態手術は、Infrastructure as Codeの状態のラボで扱います。このラボは、同じことを宣言で行う側です。
- よくある間違い: removedブロックを入れるのに、リソースブロックを削除しないことです。そうすると、プランの段階で止まります(ステップ5で、自分の目で見ます)。
- よくある間違い: 作業を終えたあとで、removedブロックを片付けないことです。そのアドレスのリソースがいつか再び必要になったとき、止まります。
- Removing Resources・moved・Command: taint・Command: untaint・Command: apply
引き継いだものまで、1つの状態に入れる
/root/tfa-removed/keep.tfにrandom_pet.keep(length 2)と、その名前をout/keep.txtに書くlocal_file.keepを、/root/tfa-removed/legacy.tfに、out/legacy.txtにlegacy serviceを1行書くlocal_file.legacyを置いて、init・applyしてください。そのあと、状態ファイルを/root/tfa-removed/snapshots/before.tfstateに、1回だけコピーします。
あとのステップで、「このリソースが再作成されたのか、そのままなのか」を判断するには、いまの識別子をどこかに残しておく必要があります。コピーは、最初の1回だけ取ってください。あとで上書きすると、比較する原本が消えます。
状態からだけ外して、実体は残す
/root/tfa-removed/legacy.tfを削除して、/root/tfa-removed/removed.tfに、removedブロックでlocal_file.legacyを状態から外すと宣言してください。apply前に、プランの出力を/root/tfa-removed/removed-plan.txtに保存して、applyします。out/legacy.txtは、そのまま残っている必要があります。
設定からリソースブロックだけを削除すると、ツールは、それを壊せという意味に解釈します。removedブロックは、「このアドレスを状態から忘れろ」を別に伝える宣言です。プランの出力で、この動作を指す語がdestroyではない点を確認してください。
このバージョンのremovedが何を受け取るかを、ツールに尋ねる
/root/tfa-removed/removed.tfのremovedブロックの中に、lifecycleブロック(destroy = false)を一時的に入れて、tofu planを回し、結果を/root/tfa-removed/lifecycle-probe.txtに保存したあと、元に戻してください。
そのあと、/root/tfa-removed/removed-facts.tsvに、3行をタブ区切りの2列で書きます。tofu_versionは、このPodのコアバージョン、removed_lifecycleは、supportedまたはunsupported、removed_effectは、forgetまたはdestroyです。
ドキュメントは、いつも最新バージョンを説明します。いま手元にあるバージョンが何を受け取るかは、ツールに尋ねて初めてわかり、その答えを書き留めておくことが、チームの時間を節約します。効果は、ステップ2のプランの出力が、すでに語っています。
名前だけを変える。同じものを作り直さずに
/root/tfa-removed/keep.tfのrandom_pet.keepをrandom_pet.appに変更し(参照も一緒に)、/root/tfa-removed/moved.tfに、movedブロックで、古いアドレスから新しいアドレスに移すと宣言したあと、applyしてください。そのあと、/root/tfa-removed/moved.tsvに、before(ステップ1のコピーに書かれたrandom_pet.keepのid)とafter(いまの状態のrandom_pet.appのid)の2行を、タブ区切りの2列で書きます。2つの値は、同じである必要があります。
movedブロックがないと、ツールは、「古い名前が消えて、新しい名前ができた」と解釈して、壊して作り直します。movedは、同じオブジェクトのアドレスだけが変わったと伝える宣言なので、識別子がそのまま維持されます。それが本当にそうなのかを、ステップ1のコピーと比べて確認してください。
忘れろと言っておいて、再び宣言すると
/root/tfa-removed/legacy.tfを一時的によみがえらせて(ステップ2と同じ内容)tofu planを回し、出力を/root/tfa-removed/conflict.txtに保存したあと、そのファイルを再び削除してください。removedブロックは、そのままにします。最後に、プランはきれいである必要があります。
忘れろという宣言と、作れという宣言が、同じアドレスに同時にあると、ツールは何をすべきかがわかりません。このエラーは、tofu validateは通過して、planでだけ出ます。なぜそうなのかを考えてみてください(アドレスの解決は、設定の検証ではなく、プランの段階の仕事です)。
1つだけ作り直す
状態ファイルを/root/tfa-removed/snapshots/before-replace.tfstateにコピーしたあと、tofu apply -replace=random_pet.app -auto-approveを回してください。そのあと、/root/tfa-removed/replace.tsvに、before(コピーのid)とafter(いまのid)の2行を、タブ区切りの2列で書きます。2つの値は、異なる必要があります。
-replaceは、設定を変えずに、「これだけを壊して作り直せ」をプランに入れます。プロバイダーの調子が悪くなったときや、設定に現れない起動状態を初期化したいときに使います。このリソースに依存するものが、一緒に再作成されるかも、プランで見てください。
手で印を付けて、元に戻す
tofu taint random_pet.appで印を付けたあと、状態に書かれたそのインスタンスのステータス値を、/root/tfa-removed/taint-status.txtに1行で保存し、tofu planの出力を/root/tfa-removed/taint-plan.txtに保存してください。そのあと、tofu untaintで元に戻して、プランがきれいな状態で終えます。
印は、インフラには触れず、状態ファイルにだけ書かれます。次のプランがそれを読んで、置き換えを入れます。jqで、そのインスタンスのstatusを見てください。今日は、同じことを-replaceで、より安全に行えますが(状態を事前に変えないため)、このコマンドが何をするかを知っておかないと、他人が印を付けておいたものに出会ったときに慌てます。
モジュール1つをまるごと手放す
/root/tfa-removed/modules/archive/main.tfに、out/archive-1.txt・out/archive-2.txtの2つのファイルを書くモジュールを作成し、/root/tfa-removed/archive.tfでmodule "archive"として呼び出したあと、applyしてください。そのあと、/root/tfa-removed/archive.tfを削除して、/root/tfa-removed/archive-removed.tfに、removedブロックでmodule.archiveをまるごと状態から外してください。プランの出力は/root/tfa-removed/module-plan.txtに保存し、2つのファイルは、ディスクに残っている必要があります。
removedのfromには、リソースアドレスだけでなく、モジュールアドレスも指定でき、その場合、そのモジュール内のものが、一度に状態から外れます。組織を分けるとき、1つのチームの持ち分をまるごと引き渡す作業が、この形です。モジュール内でファイルパスを指定するときは、ルート基準のパスを使うほうが、混乱しません。