状態を移し、消し、取り込む
目標
すでに適用されたリソースのアドレスを安全に移し、管理の外にあったリソースを状態に取り込み、その結果、コードと状態が食い違っていないことを終了コードで証明します。
なぜ重要なのか
状態ファイルは、ツールの記憶です。コードは「何があるべきか」を、実体は「何があるか」を語り、状態は「最後に把握したもの」を語ります。リファクタリングは、この3つのうちコードだけを変える作業なので、状態を一緒に移さないと、ツールは、古いリソースが消え、新しいリソースができたと判断して、破棄と作成をプランします。そのため、名前の変更のような些細な作業が、本番では危険な作業になります。逆に、state rmは、名前とは違って実体を削除しません。帳簿からだけ消す、管理放棄の宣言であり、コードに宣言が残っていると、次のプランが、そのリソースを再び作ろうとします。この非対称を正確に知っていれば、状態手術は怖くありません。そして、すべての手術の前には、コピーを取ってください。元に戻せる作業と戻せない作業の違いは、そのファイル1つです。
ステップ
/root/tf/surgery/main.tfに3つのリソースを宣言し、tofu initのあとに適用してください。local_file.legacy(ファイル/root/tf/surgery/files/legacy.txt)、local_file.notes(ファイル/root/tf/surgery/files/notes.txt)、random_pet.orphanです。そのあと、状態ファイルを/root/tf/surgery/backup/terraform.tfstate.bakにコピーしてください。コピーは正しいJSONで、lineageが現在の状態と同じで、リソースが3つ以上ある必要があります。tofu state listの結果を/root/tf/surgery/out/state-list.txtに(3行以上)、tofu state show local_file.legacyの結果を/root/tf/surgery/out/show-legacy.txtに保存してください。2つ目のファイルには、id属性の行が表示されている必要があります。tofu state mv local_file.legacy local_file.renamedで状態のアドレスを変更し、同じ作業の中で、コードのリソース名もrenamedに直してください。古いlocal_file.legacyブロックは、コメントアウトではなく完全に削除する必要があり、作業ディレクトリのどの.tfファイルにも残っていてはいけません。状態だけを移してコードをそのままにすると、次のプランが、古い名前のリソースを再び作ろうとします。tofu planの出力を/root/tf/surgery/out/plan-after-rename.txtに保存すると、No changesである必要があります。/root/tf/surgery/modules/archive/ディレクトリにモジュールを作成し、同じlocal_fileリソースをrenamedという名前で宣言してください(ファイルパス・内容などの引数の値が、移す前と完全に同一である必要があります)。ルートにmodule "archive"ブロックを追加してtofu initを実行したあと、tofu state mv local_file.renamed module.archive.local_file.renamedを実行し、ルートのmain.tfからは、そのリソースブロックを削除してください(ここでもコメントアウトではなく削除です。リソースの宣言は、今後はモジュールのファイルにだけあるべきです)。tofu planの出力を/root/tf/surgery/out/plan-after-module.txtに保存すると、No changesである必要があります。tofu state rm random_pet.orphanを実行したあと、tofu planの出力を/root/tf/surgery/out/plan-after-rm.txtに保存してください。プランには、random_pet.orphanを再び作成するという内容(1 to add)がある必要があります。確認できたら、main.tfからも、そのリソースブロックを削除し、/root/tf/surgery/out/rm-note.txtに、state rmとdestroyの違いを1行で書いてください。main.tfにresource "terraform_data" "adopted"を空の殻として宣言し、その上にimportブロックを置いて、to = terraform_data.adopted、id = "labhub-adopted"を指定したあと、適用してください。状態のterraform_data.adoptedのid属性が、labhub-adoptedである必要があります。main.tfにresource "terraform_data" "legacy_job"を宣言し、今回は、コマンドラインでtofu import terraform_data.legacy_job labhub-legacyを実行してください。出力を/root/tf/surgery/out/import.txtに保存すると、Import successfulという文言が表示されている必要があり、状態のterraform_data.legacy_jobのidは、labhub-legacyである必要があります。tofu plan -detailed-exitcodeを実行し、終了コードだけを/root/tf/surgery/out/final-exit.txtに書いてください(0である必要があります)。そして、/root/tf/surgery/out/inventory.jsonを作成して、addressesに現在の状態のすべてのアドレスを配列として入れ、backup_takenにtrueを入れてください。アドレスの数は、状態のインスタンス数とちょうど同じである必要があり、一覧には、module.archive.で始まるアドレスと、取り込んだadoptedのアドレスが含まれている必要があります。
参考
- このラボの状態ファイルは、
/root/tf/surgery/terraform.tfstateです。手術前のコピーは、必ず別に残してください。 tofu state mvは、古いアドレスと新しいアドレスを引数に取ります。モジュールに移すときは、新しいアドレスの前にmodule.<이름>.を付けます(プレースホルダーはモジュール名です)。- モジュールを新しく追加したら、
state mvの前に、必ずtofu initを実行してください。インストールされていないモジュールのアドレスには、移せません。 -detailed-exitcodeは、変更なしなら0、エラーなら1、変更ありなら2を返します。終了コードを取得するには、コマンドの直後に$?を読む必要があります。state mvとコードの修正は、ひと組です。どちらか一方だけだと、次のプランがすぐに食い違います。状態だけを移すと、古い名前でリソースを再び作ろうとし、コードだけを直すと、破棄と作成が並んで表示されます。そのため、実務では、両方を一度に表現できるmovedブロックが好まれます。- よくある間違い1:
state rmを削除コマンドだと誤解することです。帳簿からだけ消すので、コードのブロックも一緒に片付けないと、プランが静かになりません。 - よくある間違い2: 取り込み先のリソースブロックなしにimportを試すことです。ツールは、そのIDを入れる場所を、コードの中から探します。
手術前の状態コピーを確保する
/root/tf/surgery/main.tfに3つのリソースを宣言し、tofu initのあとに適用してください。local_file.legacy(ファイル/root/tf/surgery/files/legacy.txt)、local_file.notes(ファイル/root/tf/surgery/files/notes.txt)、random_pet.orphanです。そのあと、状態ファイルを/root/tf/surgery/backup/terraform.tfstate.bakにコピーしてください。コピーは正しいJSONで、lineageが現在の状態と同じで、リソースが3つ以上ある必要があります。
状態に触れるすべての作業の前に、ファイルをコピーします。コピーが同じ状態のものかを確認するには、リネージの値を比べてください。
状態の一覧と個別リソースを覗く
tofu state listの結果を/root/tf/surgery/out/state-list.txtに(3行以上)、tofu state show local_file.legacyの結果を/root/tf/surgery/out/show-legacy.txtに保存してください。2つ目のファイルには、id属性の行が表示されている必要があります。
一覧のコマンドと、特定のアドレスの属性をまるごと表示するコマンドは、違います。後者には、id属性が表示されている必要があります。
state mvで名前だけ変える
tofu state mv local_file.legacy local_file.renamedで状態のアドレスを変更し、同じ作業の中で、コードのリソース名もrenamedに直してください。古いlocal_file.legacyブロックは、コメントアウトではなく完全に削除する必要があり、作業ディレクトリのどの.tfファイルにも残っていてはいけません。状態だけを移してコードをそのままにすると、次のプランが、古い名前のリソースを再び作ろうとします。tofu planの出力を/root/tf/surgery/out/plan-after-rename.txtに保存すると、No changesである必要があります。
コードと状態が同じ名前を指していてこそ、プランが空になります。状態だけを移すと、次のプランが古い名前で再び作ろうとし、コードだけを直すと、破棄と作成が並んで表示されます。古い名前のブロックは、コメントではなく削除です。
リソースをモジュールの中に移す
/root/tf/surgery/modules/archive/ディレクトリにモジュールを作成し、同じlocal_fileリソースをrenamedという名前で宣言してください(ファイルパス・内容などの引数の値が、移す前と完全に同一である必要があります)。ルートにmodule "archive"ブロックを追加してtofu initを実行したあと、tofu state mv local_file.renamed module.archive.local_file.renamedを実行し、ルートのmain.tfからは、そのリソースブロックを削除してください(ここでもコメントアウトではなく削除です。リソースの宣言は、今後はモジュールのファイルにだけあるべきです)。tofu planの出力を/root/tf/surgery/out/plan-after-module.txtに保存すると、No changesである必要があります。
モジュールブロックを先に作り、initを実行してから移します。モジュール内のリソースの引数の値が、移す前と完全に同じであってこそ、プランが空になります。
state rmが実体を削除しないことを証明する
tofu state rm random_pet.orphanを実行したあと、tofu planの出力を/root/tf/surgery/out/plan-after-rm.txtに保存してください。プランには、random_pet.orphanを再び作成するという内容(1 to add)がある必要があります。確認できたら、main.tfからも、そのリソースブロックを削除し、/root/tf/surgery/out/rm-note.txtに、state rmとdestroyの違いを1行で書いてください。
帳簿からだけ消すコマンドです。コードに宣言が残っていると、次のプランが何をしようとするかを見てください。
importブロックでリソースを取り込む
main.tfにresource "terraform_data" "adopted"を空の殻として宣言し、その上にimportブロックを置いて、to = terraform_data.adopted、id = "labhub-adopted"を指定したあと、適用してください。状態のterraform_data.adoptedのid属性が、labhub-adoptedである必要があります。
取り込む先(リソースブロック)を先に作る必要があります。ブロックには、どこへ(to)、何を(id)取り込むかを書きます。
コマンドラインのimportで取り込む
main.tfにresource "terraform_data" "legacy_job"を宣言し、今回は、コマンドラインでtofu import terraform_data.legacy_job labhub-legacyを実行してください。出力を/root/tf/surgery/out/import.txtに保存すると、Import successfulという文言が表示されている必要があり、状態のterraform_data.legacy_jobのidは、labhub-legacyである必要があります。
同じことを、すぐに実行する方式です。出力に成功の文言が残るので、ファイルに保存してください。
手術後の一致を終了コードで証明する
tofu plan -detailed-exitcodeを実行し、終了コードだけを/root/tf/surgery/out/final-exit.txtに書いてください(0である必要があります)。そして、/root/tf/surgery/out/inventory.jsonを作成して、addressesに現在の状態のすべてのアドレスを配列として入れ、backup_takenにtrueを入れてください。アドレスの数は、状態のインスタンス数とちょうど同じである必要があり、一覧には、module.archive.で始まるアドレスと、取り込んだadoptedのアドレスが含まれている必要があります。
人が目で読んで判定することは、自動化ではありません。プランの終了コードで判定し、状態のアドレスを1か所に集めて、数を合わせてみてください。