1つのインフラ構成で複数環境を扱う方法
VPC、サブネット、ロードバランサーを定義したTerraform構成があるとします。開発環境では完璧に動作しています。今度は同じ構成をステージング環境と本番環境にも適用する必要が出てきました。単純な方法は、フォルダ全体を3回コピーして、いくつかの変数値を変更することです。しかし、セキュリティグループのルールを更新する必要が生じたとき、3箇所すべてに同じ変更を加えなければならず、1つも見逃さないことを願うしかありません。
問題はインフラコードを書くことではありません。問題は、すべてを複製したり、開発中に誤って本番環境を壊したりすることなく、複数の環境で同じコードを管理することです。
この問題を解決する一般的なアプローチは2つあります。ワークスペースと個別のルートモジュールです。これらは根本的に異なる方法で同じ問題を処理し、どちらを選ぶかはチームの運用方法と環境に必要な分離の度合いによって決まります。
ワークスペース:1つのコードベース、複数のステート
ワークスペースはTerraformなどのツールに組み込まれた機能で、同じソースコードを異なるステートファイルで使用できるようにします。1つの設定フォルダから3つの異なる環境を生成できると考えてください。コードは同一です。変更されるのは、デプロイされた内容を追跡するステートファイルです。
ワークフローは単純です。環境ごとにワークスペースを作成し、変更を実行する前に正しいワークスペースに切り替えます。ツールは、アクティブなワークスペースに基づいて、ステートの読み取りと書き込みを自動的に正しいバックエンドの場所にルーティングします。開発ワークスペースにいる場合、変更は開発ステートにのみ影響します。本番ステートは影響を受けません。
以下の図は、2つのアプローチの構造的な違いを示しています。
実際のワークフローは次のようになります。
# 環境ごとにワークスペースを作成 erraform workspace new dev erraform workspace new staging erraform workspace new prod
# 開発ワークスペースに切り替え
export TF_WORKSPACE=dev
# または: terraform workspace select dev
# アクティブなワークスペースのプランと適用
export TF_VAR_instance_type=t3.micro
terraform plan -out=dev.tfplan
terraform apply dev.tfplan
# 本番環境に切り替え
export TF_WORKSPACE=prod
export TF_VAR_instance_type=t3.large
terraform plan -out=prod.tfplan
terraform apply prod.tfplan
この方法は、環境が類似しており、違いが変数値に限定されている場合に適しています。開発環境では小さなインスタンスタイプ、ステージングでは中程度、本番では最大のインスタンスを使用するかもしれません。構造は同じで、数値だけが変わります。
しかし、ワークスペースには隠れたコストがあります。すべての環境が同じコードベースに存在するため、間違ったワークスペースで操作するリスクは現実のものです。開発ワークスペースで作業している開発者が、切り替えを忘れて誤って本番環境に対してプランや適用を実行する可能性があります。ツールはこれを防ぎません。アクティブなワークスペースを追跡するだけです。人間の注意力が安全障壁となります。
もう1つの制限があります。設定構造を変更すると、すべての環境が同時に変更を受けます。ステージングを先にアップグレードして動作を確認し、翌日に同じ変更を本番環境にロールアウトすることはできません。すべての環境は同じコードを共有しているため、一緒に動きます。変更が何かを壊した場合、すべての環境で同時に壊れます。
ルートモジュール:個別のフォルダ、共有ロジック
別の方法は、各環境に独自のルートモジュールを与えることです。1つのフォルダに3つのワークスペースを持つ代わりに、dev、staging、prodの3つのフォルダがあります。各フォルダには、独自のメイン設定ファイルと独自のステートバックエンド設定が含まれています。これらは完全に独立しています。
しかし、インフラロジックを3回書き直す必要はありません。各ルートモジュールは、modulesディレクトリから同じ共有モジュールを呼び出します。VPC定義、サブネットロジック、ロードバランサー設定はすべて再利用可能なモジュールにあります。ルートモジュールは、環境固有の変数でこれらのモジュールを呼び出すだけです。
利点は明らかです。開発環境で作業中に誤って本番環境を変更することはありません。なぜなら、明示的にディレクトリを変更する必要があるからです。分離は論理的だけでなく物理的です。また、環境を独立して更新できます。本番環境はモジュールの安定したバージョンを維持し、開発環境は実験的な変更を含む最新バージョンを使用できます。ステージングを先に更新してテストし、その後で本番環境に昇格できます。
欠点は重複ですが、それは構造的な重複であり、論理的な重複ではありません。環境ごとにモジュール呼び出しと変数定義を繰り返します。実際のインフラロジックは繰り返しません。モジュールにロジックが含まれており、それらは共有されます。重複は配線部分にあり、実装部分にはありません。
どちらをいつ使うか
ワークスペースは、環境が少なく、環境間の違いが最小限である小規模チームに適しています。2つの環境がほぼ同一である場合、ワークスペースはオーバーヘッドを削減します。コードを1回記述し、ワークスペースがステートの分離を処理します。
ルートモジュールは、大規模チーム、厳格な分離が必要な環境、または各環境に異なる承認ワークフローが必要な状況に適しています。本番環境の変更にプルリクエストレビュー、マネージャー承認、変更ウィンドウが必要で、開発環境の変更は直接適用できる場合、ルートモジュールはその分離を自然にします。各環境フォルダは、異なるゲートを持つ独自のCI/CDパイプラインを持つことができます。
多くのチームは、セットアップが簡単なためワークスペースから始めます。チームが成長し、環境の数が増えるにつれて、ルートモジュールに移行します。モジュールはすでに分離されているため、移行は困難ではありません。同じモジュールを呼び出す新しいルートフォルダを作成するだけです。
実用的なチェックリスト
アプローチを選択する前に、次の質問を確認してください。
- 管理する環境はいくつありますか?3つを超える場合はルートモジュールを検討します。
- 環境ごとに異なる承認プロセスが必要ですか?はいの場合はルートモジュールです。
- ある環境でのミスが別の環境に影響を与える可能性がありますか?はいの場合、ルートモジュールはそのリスクを軽減します。
- チームの規模は5人以上ですか?大規模チームは明示的なフォルダ分離の恩恵を受けます。
- 環境を異なるタイミングでアップグレードする必要がありますか?ルートモジュールは段階的なロールアウトを可能にします。
これらの質問のうち3つ以上に「はい」と答えた場合は、ルートモジュールから始めてください。ほとんどに「いいえ」と答えた場合は、ワークスペースで当面は十分でしょう。
具体的な要点
ワークスペースとルートモジュールは、異なるトレードオフで同じ問題を解決します。ワークスペースはコードの重複を減らしますが、運用リスクを増加させます。ルートモジュールは構造的な重複を追加しますが、環境間に明確な境界を提供します。チームの働き方と環境に必要な分離の度合いに基づいて選択してください。シンプルに始めますが、いつ切り替えるべきかを認識してください。目標は、初日から完璧なアプローチを選ぶことではありません。目標は、現在のアプローチが解決するよりも多くの問題を引き起こし始めたときに、それを認識することです。