状態ファイルを移したら同じサーバーが二台になった
目標
状態ファイルの構造を開いて見て、状態を失ったときの重複作成・lineageの安全装置・importによる復旧・同時適用のロック・状態の分割・コミットのルールを、OpenTofuで1つずつ起こして確認します。
なぜ重要なのか
状態は、コードと実際のインフラをつなぐ唯一の記録なので、事故はたいていコードではなく状態で起きます。状態がなくなると、ツールはすでにあるものを知らずにまた作り、見当違いの状態で上書きすると、他人の記録を消します。復旧はimportで1つずつ元に戻す方法しかないので、バージョン管理されたバックエンドが基本になります。ロックがあってはじめて、2つの適用が重ならず、寿命の違う層を1つの状態に置かないことで、毎日変わるほうのせいで、ほとんど変わらないほうが危険にさらされずに済みます。
ステップ
/root/iac-state/main.tfに、random_id.server(byte_length 4)と、out/server-<그 hex>.txtにserver <hex>を1行書くlocal_file.inventoryを置いて、init・applyしてください。そのあと、状態ファイルを/root/iac-state/backup/terraform.tfstateにコピーして、そのコピーから読み取った値で、/root/iac-state/anatomy.txtにlineage=<값>、serial=<값>、resources=<리소스 블록 수>の3行を書いてください(プレースホルダーは、順に、そのhex、値、リソースブロックの数です)。/root/iac-state/lost/を作成して、terraform.tfstateとterraform.tfstate.backup(あれば)をそこに移してください。tofu planの出力を/root/iac-state/lost-plan.txtに保存してから、applyしてください。採点ツールは、移した状態と新しい状態が互いに異なるlineageで、out/にサーバーのファイルが2つできたかを見ます。tofu state push lost/terraform.tfstateで、古い状態を元に戻そうとしてみてください。出力(エラーを含む)を/root/iac-state/push.txtに保存します。-forceは使いません。採点ツールは、現在の状態のlineageが、古い状態と依然として異なるかも見ます。/root/iac-state/rebuild/に同じmain.tfをコピーして、importブロックで、古い状態(lost/terraform.tfstate)のrandom_id.serverを取り込んでください。importの識別子は、古い状態のそのリソースの属性から探します。rebuildでinit・applyしたあと、プランがきれいでなければなりません。採点ツールは、rebuildの状態のhexが、古いサーバーのhexと同じかを見ます。/root/iac-state/lockdemo/main.tfに、local-execで15秒スリープするterraform_data.slowを置いて、initしてください。applyをバックグラウンドで開始して、終わる前に、同じディレクトリでtofu planを実行し、出力を/root/iac-state/lock.txtに保存してください。続けてtofu plan -lock-timeout=60sを実行して、ロックが解除されるまで待ったあとの出力を、/root/iac-state/lockwait.txtに保存します。採点ツールは、一時ディレクトリで同じ状況を直接作っても確認します。/root/iac-state/network/に、random_id.vpc(byte_length 3)と、出力vpc_id = "vpc-<hex>"を、/root/iac-state/app/には、terraform_remote_stateデータソース(localバックエンド、../network/terraform.tfstate)でその出力を読み取って、out/app.confにvpc=<vpc_id>を1行書くlocal_file.appを置いてください。networkを先に、appをあとにinit・applyします。2つのディレクトリとも、プランがきれいでなければなりません。/root/iac-state/.gitignoreを作成してください。git check-ignoreを基準に、terraform.tfstate、terraform.tfstate.backup、lost/terraform.tfstate、.terraform/(その中のファイル)、change.tfplanのようなプランファイルは無視され、.terraform.lock.hclとmain.tf、app/main.tfは無視されないようにする必要があります。採点ツールは、一時的なgitリポジトリにこの.gitignoreを置いて確認します。
参考
- Podには、OpenTofu 1.9.0とlocal・randomプロバイダーのミラーがあるので、インターネットなしで動きます。このラボのバックエンドは、すべてlocalです。
- localバックエンドのロックは、オペレーティングシステムのファイルロックなので、適用中のプロセスが死ぬと、ロックも解除されます。そのため、強制解除(force-unlock)が必要な「残ったロック」は、リモートバックエンドでのみ生じ、このPodでは再現しません。
- よくある間違い: ステップ2でterraform.tfstate.backupを残しておくこと。バックアップがあるからといって、ツールが自動的に元に戻してくれるわけではなく、あとでどちらが本物かがわからなくなります。
- よくある間違い: importで取り戻すときに、識別子にhexを入れること。リソースごとに受け取る識別子が違うので、プロバイダーのドキュメントを確認します。
- State・State Locking・tofu state push・Import・random_id (Import)・terraform_remote_state・Backend: local
状態ファイルを開いて見る
/root/iac-state/main.tfに、random_id.server(byte_length 4)と、out/server-<그 hex>.txtにserver <hex>を1行書くlocal_file.inventoryを置いて、init・applyしてください。そのあと、状態ファイルを/root/iac-state/backup/terraform.tfstateにコピーして、そのコピーから読み取った値で、/root/iac-state/anatomy.txtにlineage=<값>、serial=<값>、resources=<리소스 블록 수>の3行を書いてください(プレースホルダーは、順に、そのhex、値、リソースブロックの数です)。
状態ファイルはJSONです。lineageは、この状態が最初に作られるときに付く固有の番号で、serialは書き込むたびに上がります。jqで.lineage、.serial、.resources | lengthを見てください。
状態を失ったら、同じサーバーがもう1つできた
/root/iac-state/lost/を作成して、terraform.tfstateとterraform.tfstate.backup(あれば)をそこに移してください。tofu planの出力を/root/iac-state/lost-plan.txtに保存してから、applyしてください。採点ツールは、移した状態と新しい状態が互いに異なるlineageで、out/にサーバーのファイルが2つできたかを見ます。
ツールは、状態にないリソースを「まだ作っていないもの」と見なします。名前がランダムに決まるリソースなので、衝突することなく、静かにもう1つできます。プランの出力のPlanの行を読んでください。
古い状態を上書きしようとしたら拒否された
tofu state push lost/terraform.tfstateで、古い状態を元に戻そうとしてみてください。出力(エラーを含む)を/root/iac-state/push.txtに保存します。-forceは使いません。採点ツールは、現在の状態のlineageが、古い状態と依然として異なるかも見ます。
lineageが違う2つの状態は、「同じインフラの別の時点」ではなく、「互いに異なる2つの記録」です。ツールがこれを防いでくれる理由を考えてみてください。-forceは、この安全装置を切るオプションです。
失ったサーバーをimportで取り戻す
/root/iac-state/rebuild/に同じmain.tfをコピーして、importブロックで、古い状態(lost/terraform.tfstate)のrandom_id.serverを取り込んでください。importの識別子は、古い状態のそのリソースの属性から探します。rebuildでinit・applyしたあと、プランがきれいでなければなりません。採点ツールは、rebuildの状態のhexが、古いサーバーのhexと同じかを見ます。
random_idはimportに対応していて、識別子としてb64_urlの値を受け取ります(プロバイダーのドキュメント)。importブロックのtoとidの2つだけを使えばよく、プランに「will be imported」と表示される必要があります。新しく作成されるrandom_idがあれば、間違いです。
適用している間、他のプランはロックに阻まれる
/root/iac-state/lockdemo/main.tfに、local-execで15秒スリープするterraform_data.slowを置いて、initしてください。applyをバックグラウンドで開始して、終わる前に、同じディレクトリでtofu planを実行し、出力を/root/iac-state/lock.txtに保存してください。続けてtofu plan -lock-timeout=60sを実行して、ロックが解除されるまで待ったあとの出力を、/root/iac-state/lockwait.txtに保存します。採点ツールは、一時ディレクトリで同じ状況を直接作っても確認します。
ロックは、状態に書き込めるすべてのコマンドが、自動的に取得します。デフォルトでは、待たずにすぐに失敗し、-lock-timeoutを指定すると、その時間だけ待ちます。バックグラウンドの作業は&で起動して、waitで終了を待ちます。
寿命の違う層は、状態を分けて、出力だけでつなぐ
/root/iac-state/network/に、random_id.vpc(byte_length 3)と、出力vpc_id = "vpc-<hex>"を、/root/iac-state/app/には、terraform_remote_stateデータソース(localバックエンド、../network/terraform.tfstate)でその出力を読み取って、out/app.confにvpc=<vpc_id>を1行書くlocal_file.appを置いてください。networkを先に、appをあとにinit・applyします。2つのディレクトリとも、プランがきれいでなければなりません。
appの状態には、ネットワークのリソースは入らず、データソースだけが入ります。そのため、appを毎日変更しても、ネットワークはロックされたり、プランに含まれたりしません。その代わり、appはnetworkの出力名に依存することになります。
状態とプランはコミットせず、ロックファイルはコミットする
/root/iac-state/.gitignoreを作成してください。git check-ignoreを基準に、terraform.tfstate、terraform.tfstate.backup、lost/terraform.tfstate、.terraform/(その中のファイル)、change.tfplanのようなプランファイルは無視され、.terraform.lock.hclとmain.tf、app/main.tfは無視されないようにする必要があります。採点ツールは、一時的なgitリポジトリにこの.gitignoreを置いて確認します。
状態と保存したプランには、値が平文で入ります(次のラボで直接見ます)。ロックファイルは、プロバイダーのバージョンとハッシュを固定するので、コードと一緒にレビューされる必要があります。*.tfstate.*のようなパターンが、どこまで対象にするかを確認してください。