Terraformでコードを記述する際、ルートモジュールの分割することは多くの方が実施されていると思います。本記事では、これについての筆者の見解を紹介します。
ルートモジュールについて
そもそもルートモジュールとは何ぞや?という方に向けて、Terraformのモジュールでは以下の2つの概念があります。
- ルートモジュール
- 子モジュール
前者は皆さんが普段terraform initやterraform applyなどのコマンドを打っているディレクトリのことです。つまりTerraformコードの実行単位です。
後者は前述したルートモジュールから呼び出されるモジュールのことです。文法的にはmoduleブロックで呼び出されているものが該当します。
ルートモジュールはTerraformでのデプロイ単位と言い換えることができます。1つのルートモジュールですべてのリソースをデプロイすることもできますし、Network・Application・Databaseのようなレイヤーでルートモジュールを分割してデプロイすることもできます。
本記事ではこのルートモジュールを分割するべきか否かについて、考えてみたいと思います。
ルートモジュール分割を検討するケース
どのような状況でルートモジュールの分割を検討するのでしょうか。筆者が考える主なケースは以下の通りです。
- 同一構成のインフラを複数作成する場合(開発、ステージング、本番環境など)
- Terraformで管理するインフラが大規模な場合(複数の仮想ネットワークが存在するなど)
- デプロイ後のリソースパラメータ変更による影響範囲を最小限に抑えたい場合
※これらは、Terraformでインフラの構築から運用保守までを一貫して行うことを前提とした考えです。
ルートモジュールを分割したときの具体的なイメージ
筆者がよく使うパターンとしては以下のようなコードの構造になります。
パターン①:環境レベルで分割する
シンプルに環境(開発・ステージング・本番)で分けたパターンです。共通要素である実際のリソースの定義は子モジュール([modules]ディレクトリ)側に集約しています。Terraformを学び始めたころはよく使っていました。
[root]
|-[env] # 各環境毎のルートモジュールを配置
| |-[dev] # 開発環境のルートモジュール
| | |- main.tf # 子モジュールの呼び出し
| | |- variables.tf # 変数定義
| | |- terraform.tf # Provider定義
| | |- outputs.tf # 出力設定
| |-[stg] # ステージング環境のルートモジュール(配下のファイル構成は開発環境と同じ)
| |-[prod] # # ステージング環境のルートモジュール(配下のファイル構成は開発環境と同じ)
|
|-[modules] # ルートモジュールから呼び出される子モジュールを配置
|-[network] # ネットワークレイヤーのリソースを作成するモジュール
|-[virtualMachine] # アプリケーションレイヤーのリソースを作成するモジュール
|-[database] # DBレイヤーのリソースを作成するモジュール
パターン②:環境レベル+レイヤー毎に分割する
パターン①をさらに細分化したものです。基本的なディレクトリ構造は同じですが、環境毎のディレクトリ配下にさらにリソースの種類によるレイヤーでルートモジュールを分割しています。デプロイ順序に依存関係があるため、間違えないようにディレクトリ名に連番を入れています。またモジュール間でリソース参照の依存関係が発生するため、それらを読み込むためのlocal_otherWorkspaceInfo.tfというファイルを追加しています。
最近使い始めたパターンで、使ってみた感想としては1回あたりのデプロイ量が少ないので、開発中のトライ&エラーがしやすかったです。一方で他人にコードを説明する時は少し大変でした。
[root]
|-[env] # 各環境毎のルートモジュールを配置
| |-[dev] # 開発環境のルートモジュール
| | |-[01_network] # ネットワークレイヤーのルートモジュール
| | | |- main.tf # 子モジュールの呼び出し
| | | |- variables.tf # 変数定義
| | | |- terraform.tf # Provider定義
| | | |- outputs.tf # 出力設定
| | |-[02_virtualMachine] # アプリケーションレイヤーのルートモジュール
| | | |- main.tf # 子モジュールの呼び出し
| | | |- variables.tf # 変数定義
| | | |- terraform.tf # Provider定義
| | | |- outputs.tf # 出力設定
| | | |- local_otherWorkspaceInfo.tf # 他のルートモジュールの出力結果を読みだす設定を記述
| | |-[03_database] # DBレイヤーのルートモジュール
| | | |- main.tf # 子モジュールの呼び出し
| | | |- variables.tf # 変数定義
| | | |- terraform.tf # Provider定義
| | | |- outputs.tf # 出力設定
| | | |- local_otherWorkspaceInfo.tf # 他のルートモジュールの出力結果を読みだす設定を記述
| |-[stg] # ステージング環境のルートモジュール(配下のファイル構成は開発環境と同じ)
| |-[prod] # ステージング環境のルートモジュール(配下のファイル構成は開発環境と同じ)
|
|-[modules] # ルートモジュールから呼び出される子モジュールを配置
|-[network] # ネットワークレイヤーのリソースを作成するモジュール
|-[virtualMachine] # アプリケーションレイヤーのリソースを作成するモジュール
|-[database] # DBレイヤーのリソースを作成するモジュール
ルートモジュールを分割する・しないのメリット・デメリット
ルートモジュールを分割する・しないによるメリット・デメリットを整理してみました。
分割するか/しないか | メリット | デメリット |
---|---|---|
分割しない | ・変更内容の一元管理が可能(単一のtfstate) ※社内のエンジニアからはここがTerraformの一番のメリットだという意見をよく聞きます ・コードの構成がシンプルになる |
・1回あたりにかかるデプロイ時間が長くなる可能性 ・インフラ規模が大きくなるとルートモジュールがモノリシックな構造に陥りやすい |
分割する | ・1回あたりのデプロイ時間を短縮できる ・適切に分割することで、コード変更による影響範囲を最小化できる |
・ルートモジュール間で依存関係が発生し、複雑化する ・変更内容の管理がルートモジュール間で分散される(複数のtfstate) |
見ての通り、一長一短な結果になると思います。上記のメリット・デメリットは以下のような場面でどちらがいいか悩むことが多いです。
コードを開発・修正している時期(開発中・運用保守)
Terraformのコードの開発中は何度もapplyとdestroyを繰り返すため、開発時のデプロイ時間は短い方が開発効率が向上します。高速なトライ&エラーは開発において重要です。
一方で、Terraformでインフラの運用保守を行う場合、デプロイ時間の短縮は望ましいですが、コードの依存関係が複雑になると、運用保守担当者のスキルやコードへの理解がより求められます。筆者の所属する組織では、Terraformを熟知したメンバーがまだ少ないため、そうしたメンバーへの配慮もコード設計には必要です。
Terraformで管理するインフラの規模感
小規模なインフラであれば、無理に分割するよりも単一のルートモジュールで管理する方が、コードの開発・管理が容易です。しかし、大規模なインフラの場合、最低でも仮想ネットワーク(AWSのVPCやAzureのV-Net)単位でルートモジュールを分割しないと、コードが肥大化し、管理が困難になったり、誤ったデプロイによる影響範囲が広がるリスクがあります。
Terraformコードの実行環境
以前、HCP Terraform(旧Terraform Cloud)を利用していた際、ローカル実行と比較してplanやapplyの実行速度が著しく遅い事象に遭遇しました。この問題に対し、当時はルートモジュールを分割し、1回あたりのデプロイ量を減らすことデプロイ時間の短縮をしました。
結論
筆者の考えとしては、細かく分割しなくても、最低限の分割はすべきだと考えています。
具体的には最低でも環境(本番・ステージング・開発等)毎に分割し、次に複数の仮想ネットワークがある場合はそのレベルでも分割すべきだと考えています。
その理由は、環境や仮想ネットワークは、サービスとして互いに独立していることが一般的だからです。であれば、それらのサービスをデプロイするコードも疎結合であるべきだと考えています。疎結合にしないと、例えばサービスAのインフラ変更時に、誤ってサービスBのインフラに影響を与えてしまうといったヒューマンエラーの原因となりかねません。また、コード自体も可読性が低下する可能性が高いです。アプリケーションコードと同様に、インフラのコード(Terraform)もモノリシックに作成すると、運用保守に苦労することになります。
本記事がTerraform学習中の皆様の一助になれば幸いです。
参考記事
[1] Modules Overview - Configuration Language | Terraform
注意事項
本ブログに掲載している内容は、私個人の見解であり、所属する組織の立場や戦略、意見を代表するものではありません。あくまでエンジニアとしての経験や考えを発信していますので、ご了承ください。