インフラストラクチャポリシーの実行タイミング:Plan、Apply、Post-Deploy

インフラストラクチャポリシーをコード化し、セキュリティ違反、コスト超過、命名規則違反などをチェックできるようになりました。次に実践的な問題が浮上します。パイプラインのどこでそれらのポリシーを実際に実行すべきか?

答えは「1箇所」ではありません。「3箇所」です。それぞれの場所で異なる種類の問題を捕捉し、どれか一つでも欠けるとガードレールに穴が空きます。

Plan時のポリシー:問題が発生する前に阻止する

最初のポリシー実行場所はPlanフェーズです。Terraform、Pulumi、OpenTofuなどのツールでは、Planフェーズでインフラにどのような変更が加えられるかを計算します。作成、変更、削除されるリソースの差分が表示されますが、実際にはまだ何も変更されていません。

ここでポリシーを実行することは、早期フィルターとして機能します。ポリシーエンジンは計画された変更を検査し、許可されるかどうかを判断します。開発者がセキュリティグループを0.0.0.0/0に開放しようとすれば、Plan時にポリシーがブロックします。予算上限を超える高額なインスタンスタイプが選択されれば、ポリシーがフラグを立てます。リソース名が命名規則に従っていなければ、ポリシーが拒否します。

利点は明白です。リソースが存在する前に違反を防げます。パイプラインは停止し、開発者は何がなぜブロックされたか明確なメッセージを受け取り、既に作成されたリソースをクリーンアップする必要なくコードを修正できます。ロールバックは不要、孤立リソースも発生せず、セキュリティ上の露出期間もありません。

以下は、Open Policy Agent(OPA)を使用してPlan時にパブリックセキュリティグループをブロックするポリシーを適用する具体例です。

# Terraform Planを生成しJSONに変換
export TF_VAR_region="us-east-1"
terraform plan -out=plan.tfplan
terraform show -json plan.tfplan > plan.json

# ポリシーを評価:セキュリティグループに0.0.0.0/0のインバウンドがある場合は拒否
# policy.regoには以下が含まれます: deny[msg] { ... }
opa eval --data policy.rego --input plan.json "data.terraform.deny"

# 出力にdenyメッセージが含まれていればパイプラインを失敗させる
if opa eval --data policy.rego --input plan.json "data.terraform.deny" | grep -q '"result"'; then
  echo "ポリシー違反:パブリックセキュリティグループを検出しました。デプロイをブロックします。"
  exit 1
fi

これはポリシーを適用する最も効率的な場所です。時間を節約し、無駄を減らし、インフラを最初からクリーンに保ちます。

Apply時のポリシー:最後の防衛線

2番目のポリシー実行場所はApplyフェーズです。Applyは変更が実際にインフラに反映されるタイミングです。リソースが実際に作成、変更、または削除されます。

Plan時に既にチェックしたのに、なぜApply時にも実行するのでしょうか?Plan時のチェックには盲点があるからです。Plan時はコードに書かれている内容しか見えません。パイプラインの外で実際のインフラ状態がドリフトしている可能性は見えません。誰かがコンソールから手動でリソースを変更したかもしれません。以前のデプロイで予期しない状態が残っているかもしれません。Planは特定の開始点を前提としますが、現実は異なる可能性があります。

Apply時のポリシーは、変更が実行される前に再検証します。Plan時には問題なかったが、実際のインフラ状態が違反を引き起こすケースを捕捉します。また、パイプライン外から完全に変更が来るシナリオも処理します。誰かが自分のラップトップから手動でTerraform Applyを実行した場合でも、Apply時のポリシーは発動します。

もう一つの重要なユースケースは承認ゲートです。Plan時にポリシーは違反を検出してフラグを立てられますが、続行するか停止するかの決定は多くの場合Apply時に行われます。違反に対してシニアエンジニアが例外を承認する必要があるかもしれません。変更のリスクが高く、セカンドペアの目が必要かもしれません。Apply時のポリシーはこれらのワークフローを強制できます。

Plan時のポリシーを予防、Apply時のポリシーを検証と考えてください。両方とも必要です。

デプロイ後のポリシー:ドリフトとサイレント違反を検出する

3番目のポリシー実行場所は、リソースが既に稼働している後です。これはデプロイ後の適用であり、異なる目的を果たします。

PlanとApplyのポリシーは変更の瞬間の違反を捕捉します。しかし、インフラは静的ではありません。コンソールアクセス権を持つ誰かがデプロイ後にセキュリティグループルールを変更する可能性があります。新しいコンプライアンス要件により、以前は許容されていたリソースが非準拠になる可能性があります。一時的なはずのリソースが数ヶ月後も稼働し続け、コストがかかり続けるかもしれません。

デプロイ後のポリシーはスケジュールに基づいて実行されます。毎時、毎晩、または毎週、ライブのインフラをスキャンし、ポリシールールと比較します。ルールに違反するリソースは報告されます。レポートはSlack、メール、またはダッシュボードに送信されます。チームは違反を修正するか、リソースを削除します。

これが構成ドリフトを捕捉する方法です。また、Plan時やApply時にはチェックできないポリシーを適用する方法でもあります。例えば、「どのリソースもレビューなしに90日以上経過してはならない」というポリシーは、実行中のリソースを定期的にスキャンすることでのみ適用できます。

デプロイ後のポリシーは、コンプライアンスを一度きりのゲートから継続的なプラクティスへと変えます。これらがなければ、インフラは徐々に標準からドリフトし、次の監査まで誰も気づきません。

3つのポイントがどのように連携するか

各ポイントは他のポイントが見逃すものをカバーします。Plan時のポリシーは問題が発生する前に阻止します。Apply時のポリシーはPlan時が見逃したものを捕捉し、承認ワークフローを適用します。デプロイ後のポリシーはドリフトと長期的な問題を検出します。

以下の図は、3つのポリシーチェックポイントが典型的なインフラパイプラインにどのように組み込まれるかを示しています。

flowchart TD A[コードコミット] --> B[Planフェーズ] B --> C{Plan時ポリシーチェック} C -->|合格| D[Applyフェーズ] C -->|不合格| E[ブロックして開発者に通知] E --> A D --> F{Apply時ポリシーチェック} F -->|合格| G[リソース作成/変更] F -->|不合格| H[ブロックして承認を要求] H --> D G --> I[デプロイ後ポリシースキャン] I -->|準拠| J[継続的に監視] I -->|ドリフト検出| K[チームにアラートして修復] K --> G

Plan時のみポリシーを実行すると、手動変更がルールをすり抜けます。Apply時のみ実行すると、チェックが実行される前にポリシーに違反するリソースが作成されてしまいます。デプロイ後のみ実行すると、常に問題に事後対応することになり、予防はできません。

完全なガードレールシステムには、3つすべてが必要です。

実践的なチェックリスト

パイプラインにポリシー実行ポイントを設定するためのクイックリファレンスです。

  • Planステージ: 計画された差分に対してポリシーを実行します。セキュリティ、コスト、命名規則に違反する変更をブロックします。早期にパイプラインを失敗させます。
  • Applyステージ: 変更を実行する前に再度ポリシーを実行します。ドリフトを捕捉し、高リスクの変更には手動承認を適用します。
  • デプロイ後: ライブインフラの定期的なポリシースキャンをスケジュールします。違反をチームに報告します。非準拠のリソースを修正または削除します。

まとめ

1箇所だけでポリシーを実行するのは不十分です。Plan時のチェックは問題が始まる前に防ぎます。Apply時のチェックはすり抜けたものを捕捉します。デプロイ後のチェックはインフラを長期的に正直に保ちます。これら3つすべてをパイプラインに組み込めば、ポリシーは単に誤った安心感を与えるだけでなく、実際にインフラを保護します。