クラウドコンソールとIaCコードに食い違いが生じたとき:インフラストラクチャドリフトを自動検出する
数ヶ月にわたってTerraformでインフラを管理してきたとしよう。すべてはコードで定義され、プルリクエストでレビューされ、パイプラインを通じてデプロイされている。ある日、チームメンバーが簡単な設定変更を迅速に行う必要に迫られた。パイプラインを使う代わりに、クラウドコンソールにログインし、セキュリティグループのルールを手動で変更して、そのまま作業を続けた。変更はうまくいった。その後、誰もそのことを気にしなかった。
数週間後、新しいデプロイを実行する。Terraformはそのセキュリティグループの変更を元に戻そうとする。なぜならコードには古い定義が残っているからだ。もし適用すれば、誰かが意図的に行った変更が上書きされる。スキップすれば、コードは現実と一致しなくなる。これがインフラストラクチャドリフトだ。
ドリフトとは、インフラコードが存在すべきと定義している状態と、クラウド環境に実際に存在する状態との間のギャップである。これは理論上の問題ではない。規模感を持ってインフラを管理するすべてのチームで発生する。問題はドリフトが発生するかどうかではなく、どれだけ早く発見できるかである。
手動によるドリフト検出が失敗する理由
技術的には、クラウドコンソールを開き、各リソースを検査し、IaCコードと比較することでドリフトを検出できる。これは5つのリソースを管理している場合には機能する。しかし、50、500、5000のリソースを管理する場合には機能しなくなる。
手動検出には3つの問題がある。第一に、時間がかかる。1回の比較に数分かかるかもしれない。それを何百ものリソースで行えば、自動化すべきことに何時間も費やすことになる。第二に、信頼性が低い。人間の目は小さな差異を見逃しやすい。特にリソースに数十もの設定フィールドがある場合はなおさらだ。第三に、一貫性がない。チームメンバーによって確認する項目が異なったり、確認を完全に忘れたりする。
本当の問題はタイミングである。ドリフトはいつでも発生しうる。週に一度の手動チェックでは、未検出の変更が何日も放置される可能性がある。その変更がセキュリティ上の脆弱性をもたらしたり、依存関係を壊したりした場合、次のインシデントで初めて気づくことになる。
自動ドリフト検出の仕組み
自動ドリフト検出はシンプルな原則に従う。実際のインフラストラクチャの状態とIaC定義を定期的に比較する。このプロセスはドリフトスキャンと呼ばれる。ドリフトスキャンは何も変更しない。差異を見つけて報告するだけである。
最も一般的なアプローチは、インフラ管理にすでに使用しているツールをそのまま利用するものだ。例えばTerraformには、ステートファイルと実際のクラウドリソースを比較するplanコマンドがある。ステートファイルはTerraformがローカルまたはリモートに保持するレコードであり、作成されたリソースとその現在の設定を保存している。コードを一切変更せずにterraform planを実行すると、ツールはステートファイルがクラウドに実際に存在するものと一致するかを確認する。誰かがコンソールで直接リソースを変更していた場合、プランはそれをTerraformが変更しようとしている差分として表示する。
これがドリフト検出の基礎である。定期的にplanを実行し、予期しない変更がないか確認する。
以下は、ドリフトスキャンを実行し、ドリフトを検出したらチームに通知する最小限のbashスクリプトである。
#!/bin/bash
# drift-scan.sh - Terraformドリフトスキャンを実行し、変更を通知する
set -euo pipefail
cd /path/to/terraform/project
terraform init -input=false
# ライブリソースからステートをリフレッシュし、詳細な終了コード付きでプランを実行
terraform plan -refresh-only -detailed-exitcode -out=drift.tfplan
PLAN_EXIT_CODE=$?
if [ $PLAN_EXIT_CODE -eq 2 ]; then
# 終了コード2は変更があることを示す(ドリフト検出)
echo "$(date) にドリフトを検出しました"
# 人間が読めるサマリーを生成
terraform show drift.tfplan > drift-summary.txt
# Slackに通知を送信(Webhook URLを調整すること)
curl -X POST -H 'Content-type: application/json' \
--data "{\"text\":\"🚨 本番環境でドリフトを検出しました!\n$(cat drift-summary.txt)\"}" \
https://hooks.slack.com/services/YOUR/WEBHOOK/URL
elif [ $PLAN_EXIT_CODE -eq 1 ]; then
echo "プラン実行中にエラーが発生しました"
exit 1
else
echo "ドリフトは検出されませんでした"
fi
このスクリプトは、cronジョブやスケジュールされたCI/CDパイプラインから数時間おきに実行できる。
以下のフローチャートは、ドリフト検出の完全なサイクルを示している。
基本的なPlanを超えて:正確な結果を得るために
ここには微妙だが重要な詳細がある。標準のterraform planはステートファイルと実際のリソースを比較する。しかし、ステートファイル自体が古くなっていたらどうなるだろう?誰かがコンソールで変更を加えた場合、ステートファイルがそれを反映していない可能性がある。この場合、planはドリフトを検出できない。なぜなら、同期が取れていない2つのものを比較しているからだ。
一部のツールはこれをより適切に処理する。Terraform CloudやOpenTofuは、より深いドリフト検出を提供する。単にステートとリソースを比較するのではなく、まずクラウドから実際の設定を取得してステートをリフレッシュし、そのリフレッシュされたステートをコードと比較する。これにより、ステートとリソースの比較ではなく、コードと実際のリソースの比較が可能になる。結果はより正確で、ステートファイルの最終更新より前に発生した変更も捕捉できる。
この違いは重要だ。基本的なplanの出力だけに依存していると、ステートファイルが最後に更新される前に導入されたドリフトを見逃す可能性がある。適切なドリフトスキャンは、常にライブ環境からステートをリフレッシュすることから始めるべきである。
スケジューリングと通知
ドリフトスキャンはスケジュールに従って実行する必要がある。頻度は、パイプラインの外部でどの程度頻繁に変更が発生するかによる。重要な本番インフラを管理するチームは、数時間おきにスキャンを実行することが多い。活動の少ないチームは、1日1回で十分かもしれない。重要なのは一貫性である。スキャンは人間の介入なしに自動的に実行されなければならない。
スキャンは、CI/CDサーバー上のcronジョブ、スケジュールされたパイプライントリガー、またはIaCツールの組み込みスケジューラーを使用してスケジュールできる。重要なのは、スケジュールが信頼性が高く、誰かが実行を覚えている必要がないことである。
ドリフトが検出されたら、次のステップは通知である。自動アラートは、Slack、電子メール、チケットシステムなど、チームのコミュニケーションチャネルに送信されるべきである。適切な通知には、どのリソースがドリフトしたか、何が変更されたか、いつ検出されたかの3つの情報が含まれている必要がある。クラウドプロバイダーが誰が変更を行ったかをログに記録している場合は、その情報も含める。調査が大幅に迅速化される。
検出後の対応
ドリフトを見つけることは最初のステップに過ぎない。ドリフトが存在することを確認したら、2つの選択肢がある。
最初の選択肢は是正である。リソースをコードで定義された状態に戻す。これはほとんどのチームのデフォルトの対応である。terraform applyまたは同等のコマンドを実行すると、ツールが手動による変更を元に戻す。これは、ドリフトが偶発的または許可されていない場合に有効である。
2番目の選択肢は受け入れである。手動による変更が意図的であり、そのまま残すべき場合である。この場合、実際の状態に合わせてIaCコードを更新する。これは、コンソールで迅速に行われた正当な修正であり、コードが追いつく必要がある場合に適切な選択である。ただし、これを頻繁に行いすぎると、コードが信頼できる唯一の情報源(Source of Truth)ではなくなり、単なる履歴記録になってしまう危険性がある。
是正と受け入れのどちらを選択するかは、チームのポリシーに依存する。文書化された例外がない限り常に是正するチームもあれば、受け入れを許可するが、定義された期間内にフォローアップのプルリクエストを要求するチームもある。重要なのは、決定が意図的に行われ、偶然に任せないことである。
自動ドリフト検出の実践的チェックリスト
初めてドリフト検出を設定する場合の、簡単なチェックリストを以下に示す。
- 検出ツールを選択する:既存のIaCツールに組み込まれているドリフト検出機能、Terraform CloudやOpenTofuのような専用ツール、または定期的にplanを実行するカスタムスクリプトを使用する。
- スキャンスケジュールを設定する:非本番環境では1日1回、本番環境では数時間おきから始める。手動変更の頻度に基づいて調整する。
- 通知を設定する:チームチャットやチケットシステムにアラートを送信する。リソース名、具体的な変更内容、タイムスタンプを含める。
- 対応ポリシーを定義する:ドリフトを是正するか受け入れるかを決定する。プロセスを文書化し、チーム全員が同じアプローチに従うようにする。
- プロセスをテストする:ステージング環境で意図的に手動変更を加え、スキャンがそれを捕捉し、通知と対応が期待通りに機能することを確認する。
具体的な要点
インフラストラクチャドリフトは、チームが悪いという兆候ではない。チームが迅速に動いており、時折ショートカットを取っているという兆候である。問題はショートカット自体ではない。問題は、それがインシデントを引き起こすまで気づかないことである。
自動ドリフト検出は、目に見えない問題を目に見えるものに変える。コンソールでの変更を防ぐわけではない。変更が行われたときに、チームの他のメンバーが迅速にそれを知り、次に何をすべきかを決定できるようにする。コードとクラウドが一致しない場合でも、インフラの信頼性を維持するのは、その可視性なのである。