状態の手術 — 実物に触れず記憶だけを直す
一言でいうと
state mvとstate rmは、インフラを変えません。ツールの記憶だけを変えます。この1文を誤解すると事故が起き、理解すればリファクタリングが安全になります。
なぜ必要なのか
インフラコードは、必ずリファクタリングを経験します。リソース名が気に入らなくて、リソースをいくつかモジュールにまとめたくて、状態ファイルが大きくなりすぎて分けたくて。ところが、コードで名前を変えた瞬間、ツールは古い名前のリソースが消え、新しい名前のリソースができたと判断します。プランには、破棄と作成が並んで表示されます。本番のDBだったなら、そのプランを承認した瞬間に終わりです。
反対方向の問題もあります。障害対応中に、誰かがコンソールでセキュリティグループを作りました。実体は存在するのに、コードと状態にはありません。次のapplyは、そのリソースを知らないので触りませんが、誰も管理していないリソースが1つ増えたことになります。このリソースをコード管理の下に連れてくるのが、importです。
この2つの方向、つまりアドレスを移す作業と外にあるものを連れてくる作業が、状態手術のすべてです。
どう動くのか
状態ファイルは、「私が作ったリソースのアドレスと、最後に把握した属性」を書いた帳簿です。手術の道具は4つです。
| コマンド | 役割 | 実体への影響 |
|---|---|---|
state list |
アドレスの一覧を出力します | なし |
state show <주소>(プレースホルダーはアドレスです) |
そのアドレスの属性をすべて出力します | なし |
state mv A B |
帳簿のA項目を、B名に移します | なし |
state rm A |
帳簿からA項目を消します | なし(実体はそのまま残ります) |
import |
実体を帳簿に登録します | なし |
最もよく誤解されるのが、state rmです。これは削除コマンドではなく、管理放棄の宣言です。実体はそのまま残り、ツールだけが忘れます。そのため、state rmのあとに、コードからも該当のブロックを削除しないと、次のプランは、「そのリソースがないね、新しく作ろう」と言います。実際のクラウドでは、名前が重複するリソースをまた作ろうとして失敗するか、最悪の場合、重複したリソースができます。実体まで消したいなら、destroyを使う必要があります。
state mvと同じことをコードで行うのが、movedブロックです。違いは記録にあります。state mvは、誰かのターミナルで一度実行されて消えますが、movedはコードに残り、チームメンバーがplanを回すだけでも、同じ移行が自動的に起きます。そのため、基本は、1人で使う状態にはstate mvを、複数人で使うコードにはmovedを使うことです。
importにも、2つの形態があります。コマンドラインのtofu import <주소> <ID>は、すぐに実行され、状態を変えます。import {}ブロックは、コードに宣言しておき、プランで先に確認したあと、applyで反映します。後者は、レビューが可能で履歴が残るため、協業ではブロック方式のほうが優れています。どちらも、取り込む先(リソースブロック)を、あらかじめ作っておく必要がある点は同じです。空の殻がないと、ツールは、そのIDをどこに入れればよいかがわかりません。
現場での姿
1つ目に、手術前のコピーは、交渉の余地がありません。状態に触れるすべてのコマンドの前に、ファイルをコピーしておきます。リモートバックエンドなら、state pullで取得しておきます。誤って移したアドレスは元に戻しにくいですが、コピーがあれば、そのまま元に戻せばよいです。コピーが本物かを確認する方法は、lineageの値の比較です。同じ状態のリネージを持つファイルである必要があります。
2つ目に、状態の破損は、たいてい中断されたapplyから生じます。ネットワークが切れたままapplyが落ちると、ファイルが壊れます。リモートバックエンドのバージョン管理で以前のバージョンを復元し、apply -refresh-onlyで実体と再び合わせるのが、標準の復旧手順です。ローカルのファイル1つで運用する構成が危険な本当の理由が、これです。
3つ目に、import後の最初のプランは、必ず読みます。取り込んだリソースの実際の属性がコードと違えば、プランはその差をなくそうとします。コンソールで作ったリソースの詳細設定を、コードに書き写す前にapplyすると、連れてきた途端に設定が吹き飛びます。import直後に、prevent_destroyを一時的にかけておくのも、よくある防御策です。
4つ目に、状態を移す作業は、ロックと一緒に考えます。複数人で使うリモート状態で、手術中に他人がapplyを回すと、結果を予測できません。作業時間を周知し、可能ならパイプラインを一時的に止めます。
状態に手を付ける前に、必ずすること
state rm、state mv、importは、実際のリソースには触れませんが、Terraformが世界を
見る目を変えます。間違えると、生きているリソースが管理の外に出てしまったり、次のapplyが
それを削除したりします。
まず状態を取得しておきます。リモートバックエンドでも、ローカルにコピーを残します。
terraform state pull > state.backup.json
terraform state list | tee resources.txt
元に戻す方法があるかどうかが、手を付けてよいかどうかを決めます。
importは、状態にだけ入れます。コードがないと、次のプランで「削除する」と出ます。
そのため、順序は、コードを先に書き、importし、プランが空になるかを確認することです。
プランが空にならなければ、コードが実際のリソースと違うという意味なので、コードを合わせます。
terraform plan -generate-config-out=generated.tf # 코드 초안을 만들어 준다
terraform import aws_s3_bucket.logs my-logs-bucket
terraform plan # 여기서 "No changes" 가 나와야 끝난 것이다
importブロックをコードに書く方式なら、プランで事前に確認できるため、より安全です。
import {
to = aws_s3_bucket.logs
id = "my-logs-bucket"
}
state rmは、リソースを捨てるのではなく、手放すことです。実際のリソースはそのまま
残り、Terraformだけが忘れます。ほかのスタックに移すときに使いますが、移す先にimportするまでは
誰も管理していない状態になるため、その間は誰もapplyしないようにします。
モジュールを移すときは、アドレスが丸ごと変わります。リソース1つずつstate mvする代わりに、
movedブロックを使えば、プランに現れ、レビューを受けられます。状態の操作は記録が
残りませんが、コードに書いた移動は、コミットに残ります。
手を付ける前に、ロックがあるかを確認します。CIが動いている最中に状態を直すと、双方が 互いに異なる状態をアップロードしてしまいます。
次のラボですること
まずコピーを取って状態を覗いたあと、state mvでリソース名を変えて、プランが空になることを確認します。同じコマンドでリソースをモジュールの中に移し、state rmが実体を削除しないという事実を、プランで証明します。続いて、import {}ブロックとtofu importコマンドの2つの方法で、外部のリソースを状態に取り込み、最後に-detailed-exitcodeで、コードと状態が完全に一致していることを、終了コードで証明します。