クラウドインフラがコードから乖離していく——その「ドリフト」を検知する方法
Infrastructure as Codeを書き、terraform plan で差分を確認し、apply して変更を適用した。クラウドコンソールにもリソースが表示されている。チームは新たなデプロイ成功を祝い、作業は完了——そう思いたいところだ。
しかし、本当にそれで終わりだろうか?
1週間後、管理者権限を持つ誰かがサーバーにログインし、緊急対応として設定を直接変更する。セキュリティチームがクラウドダッシュボードからファイアウォールルールを追加し、そのことを誰にも伝えない。クラウドプロバイダーがあるAPIエンドポイントを非推奨にし、先月まで完璧に動いていた設定が、今では静かに何も役に立たなくなる。
これらの変更は、いずれもリポジトリには一切反映されていない。コードレビューも受けていない。チームが後から確認できる記録もどこにもない。インフラは今や、コードが記述している状態とは異なっている。これこそが問題だ。
この状況には名前がある——ドリフト(Drift) だ。インフラの実際の状態が、コードで定義された望ましい状態(Desired State)と一致しなくなっていることを指す。ドリフトは危険だ。なぜなら、Infrastructure as Code の約束を静かに壊してしまうからだ。環境をゼロから再作成する必要が生じたとき、結果は異なるものになる。インシデントが発生したとき、同じコードを実行すれば安全な状態に戻せると信頼できなくなる。インフラを確実に再現する能力を失ってしまうのだ。
ドリフトはどのようにして発生するのか
ドリフトは、悪意のある行為や無能なチームが原因で起こるわけではない。ごく普通の、善意に基づいた行動がパイプラインを迂回することで発生する。
開発者が何かを素早くテストする必要があり、セキュリティグループのルールを手動で変更する。データベース管理者が急な負荷スパイクに対応するためにパラメータを調整する。プラットフォームエンジニアが、自動パッチプロセスでは時間がかかりすぎるため、サーバーに直接パッチを適用する。これらの行動は、その瞬間にはいずれも理にかなっている。しかし、それぞれがコードの記述と実際に動作しているものとの間にギャップを生み出す。
この問題は時間とともに悪化する。1回の手動変更が2回になり、やがて10回になる。数ヶ月もすれば、本番環境で動作しているインフラは、リポジトリ内のコードとはほとんど似ても似つかないものになる。次に誰かが terraform apply を実行してクリーンな状態を期待したとき、予期せぬ変更の長いリストが表示される。どの変更が意図的なもので、どれが事故なのか、誰にもわからない。インフラコードへの信頼は失われる。
問題が発生する前にドリフトを検知する
解決策は、手動変更を禁止することではない。時には迅速に行動する必要があり、パイプラインでは遅すぎることもある。解決策は、ドリフトを自動的かつ定期的に検出し、インシデントを引き起こす前に把握することだ。
ドリフト検出は、インフラの実際の状態とコード内の望ましい状態を比較するプロセスである。スケジュールに従って実行されるか、特定のイベントによってトリガーされる。差異が見つかった場合、どのリソースが変更されたか、設定のどの部分が異なるか、正しい値は何かといった詳細を記録する。
ほとんどのInfrastructure as Codeツールは、ドリフト検出をネイティブにサポートしている。Terraform には状態と設定の差分を表示する terraform plan がある。Pulumi にはプレビューコマンドがある。AWS CloudFormation にはサービスに組み込まれたドリフト検出機能がある。重要なのは、誰かが思い出したときだけ実行するのではなく、これらのチェックを自動的に実行することだ。
以下は、CIパイプラインでドリフトを自動検出するためのシンプルなコマンド例です:
#!/bin/bash
# terraform plan を実行し、ドリフト検出時に非ゼロコードで終了する
export CI=true
export TF_IN_AUTOMATION=true
terraform init -input=false
terraform plan -detailed-exitcode -input=false -out=tfplan
PLAN_EXIT_CODE=$?
if [ $PLAN_EXIT_CODE -eq 2 ]; then
echo "ドリフトを検出しました!インフラがコードと一致していません。"
# Slackやインシデント管理ツールにアラートを送信
curl -X POST -H 'Content-type: application/json' \
--data '{"text":"本番環境でドリフトを検出しました。terraform apply を実行して修正してください。"}' \
YOUR_SLACK_WEBHOOK_URL
exit 1
elif [ $PLAN_EXIT_CODE -eq 1 ]; then
echo "terraform plan の実行中にエラーが発生しました。"
exit 1
else
echo "ドリフトは検出されませんでした。インフラはコードと一致しています。"
fi
このスクリプトは -detailed-exitcode オプションを使用して、クリーンな状態(終了コード0)、エラー(終了コード1)、ドリフト(終了コード2)を区別しています。
パイプラインへのドリフト検出の組み込み
実用的なドリフト検出パイプラインは、以下のような流れになります:
以下は、ドリフト検出パイプラインの全体像を示した図です:
定期的なチェックをスケジュールする。 インフラの重要度に応じて、数時間ごとまたは毎日、ドリフト検出を実行する。本番環境ではより頻繁なチェックが望ましい。ステージング環境では、1日1回で十分な場合が多い。
不審なイベントをトリガーにする。 一部のクラウドプロバイダーは、リソースがパイプライン外で変更されたときにイベントを発行する。Webhookやイベントログを使用して、これらのイベント発生時にドリフトチェックをトリガーする。
結果を報告する。 ドリフトが検出されたら、チームに通知を送る。Slack、メール、またはトラッカーにIssueを作成する。通知には、どのリソースがドリフトしたか、期待される値は何かを含める。
チームが対応を判断できるようにする。 すべてのドリフトが悪いわけではない。手動変更が正当なものであり、コードに取り込むべき場合もある。別の変更は偶発的なものであり、元に戻す必要があるかもしれない。チームがドリフトを確認し、判断を下せるようにする必要がある。
自動修正:注意して進める
チームによっては、ドリフト検出をさらに一歩進めて、修正を自動化する場合もある。パイプラインがドリフトを検出すると、すぐに apply を実行してインフラを望ましい状態に戻す。このアプローチは、ステージング環境や厳格に管理された本番システムなど、一貫性を保たなければならない環境で有効だ。
しかし、自動修正にはリスクが伴う。オートスケーリングを考えてみよう。Infrastructure as Code ではインスタンスの最小数と最大数を定義している。オートスケーリンググループは負荷に応じてインスタンスを追加する。ドリフト検出が実行されると、追加されたインスタンスをドリフトと見なして終了させる。システムが本来の動作をしていたにもかかわらず、ユーザーはダウンタイムを経験することになる。
経験則としては、パイプライン外で決して変更されるべきでないリソースに対してのみ、自動修正を適用すること。それ以外のリソースについては、チームに通知し、判断を仰ぐ。
実践的なドリフト検出チェックリスト
以下は、ドリフト検出を始めるための短いチェックリストです:
- ドリフトチェックのスケジュールを決める(本番は6時間ごと、ステージングは1日1回など)
- 適切なチャネルへの通知を設定する(Slack、メール、インシデント管理ツール)
- 自動修正を適用する環境と、手動レビューを行う環境を決定する
- 小さな手動変更を行い、アラートが正しく発報されることを確認して、ドリフト検出プロセスをテストする
- 週に一度、チームでドリフトレポートをレビューし、パターンや繰り返し発生する問題を特定する
ドリフト検出がもたらすもの
ドリフト検出は、手動変更を防ぐものではない。インシデント対応の必要性をなくすものでもない。ドリフト検出がもたらすのは、コードと現実のギャップに対する可視性だ。ドリフトを認識していれば、それを受け入れるか、元に戻すか、コードを更新して反映させるかを判断できる。
ドリフト検出がなければ、Infrastructure as Code はフィクションに過ぎない。システムが再現可能だと信じているかもしれないが、それを検証する方法はない。ドリフト検出があれば、コードが唯一の真実の情報源(Single Source of Truth)であり続ける。なぜなら、その真実を積極的に維持しているからだ。
次に誰かが「Infrastructure as Code を使っています」と言ったら、こう尋ねてみよう。「インフラがまだコードと一致していることは、どうやって確認していますか?」答えられなければ、そこにはドリフトがある。そしてドリフトは、次のインシデントの際に爆発する時限爆弾なのだ。