チームで使うTerraform:ステート分割、影響範囲、レビュー済みのプラン
Terraformのステートを分割し、CIでplanを実行し、レビューしたものだけを適用して、インフラ変更をリスクではなく日常業務にする方法。
例は汎用的なもので、説明を目的としています。どの顧客環境から取ったものでもありません。しきい値や設定はご自身のシステムに合わせて調整してください。
本当の境界はステートファイル
terraform applyは、対象とするステート内のあらゆるものを変更できます。つまりリスクの単位はモジュールではなくステートです。環境全体で1つのステートしかないと、DNSレコードのタイプミスも本番データベースの変更も同じplanに乗り、そのplanの計算には何分もかかります。
ライフサイクルと影響範囲で分割する
一緒に変わり、一緒に壊れるリソースをまとめます。よくある構成では、環境ごとに次のように分けます:
- 基盤:アカウント、ネットワーク、DNSゾーン——めったに変更しない。
- データ:データベース、バケット、キュー——慎重に変更し、削除から保護する。
- コンピュートとサービス:クラスタ、関数、ロードバランサー——頻繁に変更する。
各レイヤーは下位レイヤーの出力を読み取るだけで、そのリソースを管理することはありません。環境間では同じモジュールを共有しつつ、ステートと認証情報は分けます。そうすればstagingでのapplyが本番に触れることはありません。
リモートステート、ロック、バージョン
- ステートはロックとバージョニングを有効にしたリモートバックエンドに置きます。Gitにも、ノートPCにも置きません。
- 依存関係のロックファイル(.terraform.lock.hcl)をコミットし、モジュールのバージョンを固定します。今日のplanと来月のplanが同じコードを実行するようにするためです。
- ステートを持つリソースは、prevent_destroyとプロバイダー側の削除保護で守ります。
プルリクエストごとにplan、レビューしたものをapply
変更のたびにCIでterraform planを実行し、結果をプルリクエストに投稿します。承認後は再度planせずに保存済みのプランを適用し、レビュアーが読んだ内容そのものを本番に反映します。途中でステートが変わっていれば、Terraformは古いプランを拒否します。パイプラインの認証には、シークレットとして保存した長期キーではなく、OIDCフェデレーションによる短期の認証情報を使います。
terraform init -input=false
terraform plan -input=false -lock-timeout=5m -out=tfplan
terraform show -no-color tfplan > plan.txt # attach to the pull request
# after approval, applying the same saved plan:
terraform apply -input=false tfplan
定期的にドリフトを検出する
コンソールでの変更は、たいていインシデント中に、今でも起こります。terraform plan -detailed-exitcodeを定期実行するジョブがそれを見つけます。終了コード0は変更なし、2は実際のインフラがコードと異なる、1はエラーを意味します。2はチケットにし、その変更をコードに取り込むか元に戻します。
applyの前にポリシー
「公開バケット禁止」「すべてのリソースにオーナータグ」といったルールは、長い1日の終わりのレビュアーより機械のほうが確実にチェックできます。Conftest経由のOpen Policy Agentのようなポリシー・アズ・コードのツールは、CIでplanのJSON形式(terraform show -json)を評価し、マージをブロックできます。