金曜の夜に手で直したルールを月曜の apply が消そうとした
目標
コードの外で生じた変更を、plan -refresh-onlyで別に読み、そのまま適用すると消えるものを、プランのJSONから見つけたうえで、吸収・元に戻す・除外の3つの解消を自分で行い、毎日回す検知スクリプトを作ります。
なぜ重要なのか
宣言的なツールは、コードを真実として扱うので、コンソールで直した正しい修正も、次の適用で元に戻します。そのため、ドリフトを見つけたら、まず何が消えるかを読んで、吸収するのか元に戻すのかを、人が決める必要があります。ignore_changesは、すべてのドリフトを隠してくれるスイッチではなく、プロバイダーが読まない属性の変更は、プランにそもそも現れません。この限界を知っていてはじめて、検知の結果が空のときに、「一致している」と「見えていない」を区別できます。
ステップ
/root/iac-drift/main.tfに、rules変数(デフォルト値["allow 443"])、ルールごとに1行ずつout/firewall.rulesに書くlocal_file.fw、out/motd.txtにmaintenance window: sun 02:00を1行書くlocal_file.motdを置いて、init・applyしてください。- 障害対応中に、誰かが
/root/iac-drift/out/firewall.rulesの末尾に、allow 8443を1行、手で追加しました(自分で追加してください)。コードには触れずに、tofu plan -refresh-onlyで、外で生じた変更だけを見た出力を、/root/iac-drift/drift.txtに保存してください。 - コードを変更せずに、
tofu plan -out=/root/iac-drift/monday.tfplanでプランを保存してください(適用しません)。そのプランがfirewall.rulesに書く内容と、現在のファイルを比較して、適用すると消える行だけを、/root/iac-drift/lost.txtに書いてください。 - 緊急の修正が正しかったと判断しました。
rulesのデフォルト値を["allow 443", "allow 8443"]に変更して、applyしてください。そのあと、firewall.rulesは2行で、プランはきれいでなければなりません。 - 今度は、誰かが
out/motd.txtをmaintenance window: noneに変更しました(自分で変更してください)。これは誤った変更です。元に戻す前に、tofu plan -refresh-onlyの出力を/root/iac-drift/revert-drift.txtに保存し、コードはそのままにして、applyして、お知らせをコードの内容に戻してください。 main.tfに、out/cache.confにcache v1を書き、lifecycle { ignore_changes = [content] }がかかったlocal_file.cacheを追加して、applyしてください。(a)ファイルを手でcache tamperedに変更したあと、tofu planの出力を/root/iac-drift/ignore.txtに保存して、applyで元に戻します。(b)コードの内容をcache v2に変更して、プランがきれいかを見ます(適用しません)。(c)chmod 600 out/firewall.rulesのあと、tofu plan -detailed-exitcodeの終了コードを、/root/iac-drift/perm.txtにperm=<코드>として書いてください(プレースホルダーはコードです)。/root/iac-drift/detect.sh <작업디렉터리>を作成してください。plan -refresh-onlyをプランファイルとして保存して、外で変更されたリソースのアドレスを集め、ドリフトがなければOKと0、あればDRIFT <주소들(공백 구분, 정렬)>を1行と2、失敗したらERRORと1で終了します(プレースホルダーは、順に作業ディレクトリと、空白区切りで並べ替えたアドレスの一覧です)。作業ディレクトリにファイルを残しません。採点ツールは、/root/iac-driftのコピーで、ファイルを変えながら確認します。
参考
- Podには、OpenTofu 1.9.0とlocalプロバイダーのミラーがあるので、インターネットなしで動きます。
- local_fileは、内容が記録と違うと、ファイルを「削除された」と報告して、作り直すプランを立てます(このPodで実測)。クラウドのリソースのプロバイダーは、通常、属性単位の差(更新)として報告するので、画面の見た目は違いますが、吸収・元に戻す・除外の判断は同じです。
- 外で生じた変更だけを見る:
tofu plan -refresh-only、それを状態にだけ取り込む:tofu apply -refresh-only。 - よくある間違い: ドリフトを見てすぐにapplyして、正しい緊急修正を消してしまうこと。よくある間違い: ignore_changesをかけたから、検知から外れると思い込むこと。
- tofu plan (-refresh-only)・tofu refresh・lifecycle (ignore_changes)・JSON Output Format (resource_drift)
ベースライン: ファイアウォールルールとお知らせをコードで適用する
/root/iac-drift/main.tfに、rules変数(デフォルト値["allow 443"])、ルールごとに1行ずつout/firewall.rulesに書くlocal_file.fw、out/motd.txtにmaintenance window: sun 02:00を1行書くlocal_file.motdを置いて、init・applyしてください。
for式でルールごとに改行を付けて、joinでつなげればよいです。適用したあと、プランが空であることが、ベースラインです。
金曜の夜の緊急修正を、プランで見る
障害対応中に、誰かが/root/iac-drift/out/firewall.rulesの末尾に、allow 8443を1行、手で追加しました(自分で追加してください)。コードには触れずに、tofu plan -refresh-onlyで、外で生じた変更だけを見た出力を、/root/iac-drift/drift.txtに保存してください。
-refresh-onlyは、コードの変更を計画せず、実際と状態の差だけを表示します。local_fileプロバイダーは、ファイルの内容が記録と違うと、そのファイルを「削除された」と報告します。属性1つが変わったようには見えないという点を、出力で確認してください。
月曜日にそのままapplyすると、何が消えるか
コードを変更せずに、tofu plan -out=/root/iac-drift/monday.tfplanでプランを保存してください(適用しません)。そのプランがfirewall.rulesに書く内容と、現在のファイルを比較して、適用すると消える行だけを、/root/iac-drift/lost.txtに書いてください。
プランのJSONのresource_changesで、local_file.fwのchange.after.contentが、適用後のファイルの内容です。現在のファイルにだけある行が、消える行です。commやgrep -vxF -fで、行単位の差を求められます。
正しい修正だった: コードに吸収する
緊急の修正が正しかったと判断しました。rulesのデフォルト値を["allow 443", "allow 8443"]に変更して、applyしてください。そのあと、firewall.rulesは2行で、プランはきれいでなければなりません。
吸収は、コードを現実に合わせることです。このプロバイダーは、ファイルを「削除された」と見なしたため、コードを直したあとのプランも、更新ではなく作成として出ます。書く内容が現在のファイルと同じなので、結果は同じです。
誤った修正だった: コードどおりに元に戻す
今度は、誰かがout/motd.txtをmaintenance window: noneに変更しました(自分で変更してください)。これは誤った変更です。元に戻す前に、tofu plan -refresh-onlyの出力を/root/iac-drift/revert-drift.txtに保存し、コードはそのままにして、applyして、お知らせをコードの内容に戻してください。
元に戻すことは、現実をコードに合わせることです。元に戻す前に、なぜそのような変更があったのかを確認することが、手順の一部です。ここでは、driftの出力を残すことで、その痕跡を残します。
ignore_changesが防ぐもの、防げないもの、まったく見えないものを確かめる
main.tfに、out/cache.confにcache v1を書き、lifecycle { ignore_changes = [content] }がかかったlocal_file.cacheを追加して、applyしてください。(a)ファイルを手でcache tamperedに変更したあと、tofu planの出力を/root/iac-drift/ignore.txtに保存して、applyで元に戻します。(b)コードの内容をcache v2に変更して、プランがきれいかを見ます(適用しません)。(c)chmod 600 out/firewall.rulesのあと、tofu plan -detailed-exitcodeの終了コードを、/root/iac-drift/perm.txtにperm=<코드>として書いてください(プレースホルダーはコードです)。
ignore_changesは、「設定に書かれた値が変わっても、更新のプランを立てない」という意味です。プロバイダーが実物を読んでいて、なくなったと判断することは、防げません。そして、プロバイダーが読まない属性の変更は、ドリフトとして検出されません。
毎朝回るドリフト検知を作る
/root/iac-drift/detect.sh <작업디렉터리>を作成してください。plan -refresh-onlyをプランファイルとして保存して、外で変更されたリソースのアドレスを集め、ドリフトがなければOKと0、あればDRIFT <주소들(공백 구분, 정렬)>を1行と2、失敗したらERRORと1で終了します(プレースホルダーは、順に作業ディレクトリと、空白区切りで並べ替えたアドレスの一覧です)。作業ディレクトリにファイルを残しません。採点ツールは、/root/iac-driftのコピーで、ファイルを変えながら確認します。
プランのJSONには、resource_changes以外にもresource_driftがあります。外で生じた変更が、そこに別に入ります。-detailed-exitcodeは、-refresh-onlyと一緒に使えます。