ラップトップでTerraformを実行するだけでは不十分になる時

これまでターミナルからTerraformを使ってインフラを管理してきたとしましょう。自分だけが変更を加える状況では、それで十分機能します。terraform plan を実行して出力を確認し、terraform apply を実行して次に進む。ステートファイルは自分のマシンにあり、何をいつ変更したか正確に把握できています。

そこに同僚がプロジェクトに加わります。同僚がリポジトリをクローンし、terraform init を実行すると、あなたが昨日確認したのとは異なるプランが表示されます。同僚のラップトップ上のステートファイルは古くなっています。誰かが何も言わずに変更を適用してしまいます。1週間後には、誰がセキュリティグループのルールを変更したのか、なぜ変更したのか、誰も覚えていません。

これこそが、ラップトップベースのワークフローが破綻する瞬間です。問題はTerraform自体にあるのではありません。問題は、調整、可視性、説明責任にあります。インフラが共有される場合、個々のマシンからコマンドを実行すると、混乱、リスク、そして静かなドリフト(設定の乖離)が生じます。

ワークフローをパイプラインへ移行する

各メンバーのラップトップで terraform planterraform apply を実行する代わりに、そのワークフローをCI/CDパイプラインに移行できます。そうすれば、すべてのインフラ変更は同じ経路を通り、記録され、適用前にチームでレビューできるようになります。

パイプラインアプローチは、インフラ変更をアプリケーションコードの変更と同様のものに変えます。設定を書き、プルリクエストを開き、フィードバックを得て、承認された後にのみ変更が適用されます。違いは、Terraformがプランニングステップを追加し、何も実行される前にクラウドリソースに何が起こるかを正確に示す点です。

次の例は、write-plan-applyワークフローを実装する汎用的なCIパイプライン設定を示しています。

stages:
  - validate
  - plan
  - apply

validate:
  stage: validate
  script:
    - terraform fmt -check
    - terraform validate
  only:
    - merge_requests

plan:
  stage: plan
  script:
    - terraform plan -out=plan.tfplan
  artifacts:
    paths:
      - plan.tfplan
  only:
    - merge_requests

apply:
  stage: apply
  script:
    - terraform apply plan.tfplan
  when: manual
  only:
    - main

3つのステージ: Write、Plan、Apply

Terraformをパイプラインに統合する最も一般的な方法は、変更を行う自然な流れに合わせてワークフローを3つのステージに分割することです。

次のシーケンス図は、3つのステージがどのように相互作用するかを示しています。

sequenceDiagram participant Dev as 開発者 participant PR as プルリクエスト participant Pipe as CIパイプライン participant Rev as レビュアー participant Infra as インフラ Dev->>PR: 設定変更を含むPRを開く PR->>Pipe: パイプラインをトリガー Pipe->>Pipe: Writeステージ (fmt, validate) Pipe->>PR: プラン出力をコメントとして投稿 PR->>Rev: レビューを依頼 Rev->>PR: 承認または却下 alt 承認 PR->>Pipe: mainへのマージがapplyをトリガー Pipe->>Infra: terraform apply (保存されたプラン) Infra-->>Pipe: リソース更新 Pipe-->>Dev: 成功通知 else 却下 PR-->>Dev: 変更依頼 end

Write: レビュー前に問題を発見する

Writeステージは、誰かがTerraform設定ファイルを変更し、プルリクエストを開いたときに始まります。この時点で、パイプラインは自動的に基本的なチェックを実行できます。terraform fmt はコードが標準のフォーマットに従っていることを確認します。terraform validate は設定が構文的に正しく、必要な引数がすべて存在することをチェックします。

これらのチェックは、アプリケーションコードにリンターを実行するのと同等のインフラ版です。人間のレビュアーがプルリクエストを確認する時間を費やす前に、単純なミスを早期に発見します。フォーマットが間違っていたり、必須フィールドが欠けていたりすると、パイプラインは即座に失敗し、作成者は他の誰かが関与する前に修正します。

Plan: 何が変わるかを示す

プルリクエストが開かれた後、パイプラインは自動的に terraform plan を実行します。プランの出力は、プルリクエストにコメントとして投稿されます。ここでレビューが意味を持つようになります。

プラン出力は、Terraformが何を行うかを正確に示します。どのリソースが作成され、どのリソースが変更され、どのリソースが破棄されるかです。レビュアーは、プルリクエストがサーバーのインスタンスタイプをsmallからmediumに変更すること、新しいファイアウォールルールを追加すること、古いデータベースサブネットグループを削除することを確認できます。もし何かおかしな点があれば、変更が本番環境に到達する前に、レビュアーはプルリクエストを却下します。

このステージは、インフラレビューを「私がプランを実行したので信じてください」から「これがプランです。自分で確認してください」へと移行させるため、極めて重要です。プランはプルリクエストの会話の一部となり、承認または却下の判断は、信仰ではなく具体的な出力に基づいて行われます。

Apply: 承認後にのみ実行する

Applyステージは、プルリクエストが承認され、メインブランチにマージされた後にのみ実行されます。メインブランチ上のパイプラインは、すでにレビューされたプランを使用して terraform apply を実行します。

これを行う最も安全な方法は、前のステージからのプラン出力をファイルとして保存し、そのファイルを terraform apply に渡すことです。このテクニックは保存されたプランと呼ばれます。これにより、レビューされたものとまったく同じ変更が適用されることが保証され、レビューと適用の間に誰かが別のコミットをプッシュしたために異なる可能性がある新しいプランが実行されることはありません。

保存されたプランがない場合、パイプラインはApplyステージで再度 terraform plan を実行することになります。その間に設定が変更されていれば、新しいプランはレビューされたものと異なる可能性があります。それでは、そもそもレビューを行う意味がなくなります。

パイプラインでの状態管理

Terraformがパイプラインで実行される場合、状態管理は簡単になります。ステートファイルは、チームが一度設定するクラウドストレージバケットやTerraformバックエンドなどの共有ロケーションに保存する必要があります。パイプラインがplanまたはapplyを実行するたびに、古くなっている可能性のあるローカルコピーではなく、その共有ロケーションからステートを取得します。

パイプラインはステートロックも有効にする必要があります。Terraformはplanまたはapplyの実行中にステートファイルをロックし、2つのプロセスが同時にステートを変更するのを防ぎます。ロックがないと、2つのパイプラインが同時にapplyを実行し、破損や予期しない結果を引き起こす可能性があります。

このワークフローがもたらすもの

write-plan-applyワークフローがパイプラインで実行されるようになると、インフラ変更はアプリケーションコードの変更と同じ規律に従うようになります。すべての変更はプルリクエストを通り、チームメンバーによるレビューを受け、自動化されたプランでテストされ、承認後にのみ実行されます。変更の全履歴はGitとパイプラインログに記録されます。

誰がサーバーを変更したのか、いつそれが起こったのか、もう疑問に思う必要はありません。誰かのラップトップにある古いステートファイルを心配する必要もありません。インフラに関して「自分のマシンでは動いたんです」という言葉を聞くこともなくなります。

設定のための実践的チェックリスト

  • パイプラインを設定する前に、ステートストレージ用のリモートバックエンドを設定してください。バックエンドはパイプラインランナーからアクセス可能である必要があります。
  • ステートロックを設定してください。DynamoDBを使用するS3や、オブジェクトバージョニングを使用するGCSなど、ほとんどのバックエンドはこれをネイティブにサポートしています。
  • terraform fmtterraform validate をプルリクエストパイプラインの初期チェックとして追加してください。
  • すべてのプルリクエストで terraform plan を実行し、出力をコメントとして投稿してください。
  • 保存されたプランを使用してください。プランファイルをパイプラインアーティファクトとして保存し、Applyステージに渡してください。
  • terraform apply はマージ後のメインブランチでのみ実行されるように制限してください。
  • パイプラインランナーがplanとapplyを実行するために必要な最小限の権限を持っていることを確認してください。管理者認証情報は使用しないでください。

次に来るもの

パイプラインがwrite-plan-applyワークフローを自動的に処理するようになった後、次の課題は異なる環境をどのように管理するかです。ステージングと本番環境で同じ設定値を使用することはほとんどありません。パイプラインは、環境ごとにワークフロー全体を複製することなく、環境固有の変数、ステートファイル、承認ゲートを処理する必要があります。

しかし、それは後で考える問題です。今のところ重要なステップは、ラップトップでTerraformを実行するのをやめ、インフラ変更をコード変更のように扱い始めることです。パイプラインは、何が変更され、誰が承認し、いつ適用されたかについての単一の真実の情報源を提供します。これだけで、共有インフラに伴う混乱のほとんどを排除できます。