IaCのモジュール設計 — 再利用と過剰抽象化の境界

IaCモジュールの設計判断で繰り返し目にされるのは、「共通化すればするほど保守が難しくなった」という逆転現象です。再利用を目指して抽象化を重ねた結果、呼び出し側から内部の挙動が見えなくなり、変更のたびに影響範囲の確認に工数を取られます。本記事では、どこを共通化すべきか・どこは個別に残すべきかという判断軸と、早すぎる抽象化が生む具体的なアンチパターン、バージョン管理とチーム共有の設計判断を整理します。

モジュールを切りすぎて逆に読めなくなる構造

参照の連鎖が生む認知負荷

3層や4層にわたってモジュールが入れ子になった構成では、変数の最終的な値がどこで決まるかを追うだけで相当な時間を要します。あるプロジェクトでは、EC2インスタンスを起動する処理が「ネットワーク」「セキュリティ」「コンピュート」「共通タグ」の4モジュールに分散し、タグ値を1つ変更するために4ファイルを順に確認しなければなりませんでした。認知負荷は行数ではなく「どこを見れば答えが出るかの不透明さ」によって決まります。モジュールの境界が増えるほど、コードを読み解く入り口が増えるのではなく、迷子になる分岐点が増えていきます。

変数爆発とバケツリレーの問題

モジュールを細分化するにつれ、親モジュールから子モジュールへ変数を「バケツリレー」する構造が生まれやすくなります。10環境・30リソースを管理する規模になると、呼び出し元の variables.tf に40〜50個の変数定義が並ぶことはめずらしくありません。その多くは子モジュール内部にそのまま横流しされるだけで、呼び出し元での処理は何もないケースが大半です。このバケツリレーは変更時の修正箇所を増やすだけでなく、変数名の小さなゆらぎ(たとえば subnet_ids と subnets)をインターフェース間に生み、型のズレを引き起こす温床になります。変数定義の膨張はモジュール設計の失敗を示す最初のシグナルと捉えてください。

「きれいに見える」構造が現場を止める

ディレクトリ構造が整然と並び、各モジュールのREADMEが揃っている状態は、外側からは「よく整備されたコードベース」に見えます。しかし実際に変更を加えようとすると、どのモジュールがどのリソースを作るかを把握するだけで30分以上かかるという話は現場でよく聞きます。きれいに整理されているように見えることと、変更しやすいこととはまったく別の性質です。モジュール設計の評価軸は「構造の美しさ」ではなく、「初見の担当者が次の変更を加えるまでにかかる時間」で測るべきです。その時間が30分を超えるようであれば、設計の見直しを検討する目安になります。

どこを共通化し、どこを個別に残すかの判断

変化の頻度と方向を軸にする

共通化の可否を判断する第一の軸は「変化の頻度」、第二の軸は「変化の方向の一致」です。複数の利用箇所が同じタイミングで同じ方向に変わるなら共通化の候補になります。逆に、同じに見えても変化の契機や方向がバラバラなものをまとめると、一方の変更がもう一方に意図せず影響します。ステージング環境と本番環境でVPCのCIDR設計が「今は同じ」だとしても、将来のスケールアウト戦略が分岐する可能性があるなら共通モジュールにまとめる根拠は弱いです。「現時点で一致している」という事実だけで共通化を決めるのは早計で、変化の方向が本当に同じかを問うステップを必ず置くようにします。

環境差分が大きい箇所は個別に残す

本番とステージングで設定値が20%以上異なる箇所を共通モジュールに押し込もうとすると、条件分岐変数が増殖して読めないコードになりやすいです。インスタンスタイプ・オートスケールの閾値・WAFのルールセット・バックアップ保持期間など、運用要件が環境ごとに明確に異なるリソースは個別に管理するほうが総変更コストが低くなります。差分を変数で吸収しようとするほど「共通化」の意味が薄まります。差分が多い箇所は「個別に残す」という決断を早めに下すことが、後の保守コストを大きく左右します。

共通化の判断チェックポイント

以下は設計レビュー時にも活用できる共通化可否の判断軸です。

  • 3か所以上で同一ロジックが繰り返されているか(2か所以下は複製許容が多くの現場での経験則)
  • 変化の方向が揃っているか(一方だけ変わる可能性がある場合は共通化を見送る)
  • 共通化後に追加する変数の数が、元コードの行数削減を上回らないか
  • 呼び出し側がモジュール内部を知らずに使えるか
  • モジュール単体で構文・型の検証が通るか(外部依存なし)

再利用性と可読性のトレードオフの引き方

再利用のコストを先に見積もる

モジュールを再利用可能にするには汎化コストがかかります。特定用途向けのコードを汎用化するには変数化・デフォルト値設計・バリデーション追加・ドキュメント整備が必要で、元の実装の2〜3倍の工数がかかるのが実態です。この追加コストを回収できるのは、実際に複数の箇所から呼び出された後になります。「将来きっと使いまわす」という予測だけに基づいて先行して汎化コストをかけることは、回収できないまま終わるリスクがあります。再利用性は「実際に2回目の利用要求が来たとき」に設計するという方針が、多くのプロジェクトで現実的な選択になっています。

読める人が限られる構造は組織リスクになる

高度に抽象化されたモジュールは、設計意図を理解しているメンバーにしか安全に変更できない状況を生みます。設計者がプロジェクトを離れたとたん誰も手を入れられなくなる事態は、5人以下の小規模チームで特に顕在化しやすいです。可読性を犠牲にした再利用性の高さは、属人性というリスクを組織に埋め込みます。モジュールの複雑さは「設計者が理解できるか」ではなく、「次の担当者が安全に変更できるか」で評価すべきです。多少コードが重複していても読みやすい構造のほうが、組織的な安全性が高い場面は少なくありません。

用途別のトレードオフ早見表

用途・状況 再利用性を優先する判断 可読性を優先する判断
複数プロジェクト横断の基盤モジュール 利用箇所が5件以上見込める プロジェクト固有の差分が30%超
本番/ステージングの差分管理 設定差が10%未満で変化方向が同じ 運用ルールが環境ごとに明確に分岐する
チーム引き継ぎ頻度が高い インターフェースが安定・文書化済み 引き継ぎコストを下げることが最優先
週次以上の高頻度リリース 変更対象が限定的で影響範囲が明確 変更速度を落とさないことが最優先

■ この設計判断を深める一冊[PR]

実践Terraform AWSにおけるシステム設計とベストプラクティス(野村友規/NextPublishing)

本記事の設計判断を、AWS上のIaC実装に落とし込む実務書です。state設計やベストプラクティスを手を動かして確かめたい構築担当者に向いています。

Amazonで見る →

「早すぎる抽象化」が生むアンチパターン

要件確定前にモジュール設計を始める失敗

プロジェクト立ち上げ時に「将来を見越したモジュール設計」を先行させ、実際のリソース要件が固まる前に抽象化の枠組みを作り始めるケースがあります。構築を任されたある担当者は、将来50台規模を想定してモジュールを汎化したが、実際に追加されたのは3台で、汎化のための変数と条件分岐だけが残りました。要件が確定していない段階での抽象化は、実際の利用パターンと合わない構造を生む確率が高いです。「先に設計してから実装する」という方針自体は正しいのですが、抽象化の対象は「すでに2回以上実装した経験がある処理」に絞るのが安全です。

汎用化のために増える例外ハンドリング

モジュールを汎用化しようとすると、「このケースだけ違う」という例外を吸収するための条件分岐が積み重なります。dynamic ブロックや for_each による条件制御が複合すると、モジュールは設定次第で挙動が変わる複雑な機械になります。この状態では変数の組み合わせによって意図しないリソースが作られたり削除されたりするリスクが生まれます。「どのフラグをどう組み合わせたら何が起きるかを誰も正確に把握していない」という状況は現場でよく聞きます。例外ハンドリングが3つを超えたら、モジュールの分割または専用実装への切り戻しを検討するタイミングです。

ラッパーモジュールの多段構造

既存モジュールをさらにラップして「自社向けに使いやすくした」モジュールを作ることは多いです。しかしラッパーがラッパーを呼ぶ2段・3段の構造になると、実際に何が作られるかを確認するためにソースを何段も下らなければなりません。特にコミュニティモジュールをラップしている場合は、上流のバージョンアップでラッパー全体が壊れるリスクも抱えます。ラッパーを作る理由が「変数のデフォルト値を固定したいだけ」であれば、tfvarsやlocalsで対処できないかを先に検討するほうが構造はシンプルに保てます。ラッパーを作る判断はコードの複雑さへの覚悟とセットで行うべきです。

バージョン・依存をどう設計で御すか

バージョン固定の粒度と更新コスト

外部モジュールを利用する場合のバージョン固定は、マイナーバージョン以上は追随しない緩い固定か厳密なピン固定かを、モジュールの安定性と更新頻度に応じて選びます。アクティブに開発されているコミュニティモジュールではマイナーバージョン間でも破壊的変更が入ることがあります。本番環境では厳密なピン固定を基本とし、更新はステージングで検証してから反映するフローを設計に組み込むことが現実的です。バージョン管理の粒度をプロジェクト全体で統一していないと、メンバーごとに戦略がバラバラになり、後から整合させるための工数が発生します。

依存グラフの可視化と複雑度の管理

モジュール間の依存関係が複雑化すると、どのモジュールがどのモジュールに依存しているかが把握しにくくなります。依存グラフを定期的に可視化することは、依存の肥大化を早期に検知する手段として有効です(可視化の具体的な手順は手を動かす場で確認してください)。設計判断として重要なのは「循環依存が生まれていないか」と「依存の深さが3段以上になっていないか」を定期的にチェックする運用を、設計に織り込むことです。依存グラフの複雑さは、モジュール設計が持続可能かどうかを示す定量的な指標として機能します。

ブレーキングチェンジの吸収設計

共有モジュールを更新する際に最も難しいのは、呼び出し元への影響を最小化しながら変更を反映することです。変数名・出力名の変更は、すべての呼び出し元を同時に修正しなければデプロイできない状態を生みます。これを避けるには「非推奨変数を一定期間残しつつ新変数を追加し、移行期間後に旧変数を削除する」という2フェーズ更新の設計が有効です。チームで10件以上のモジュール呼び出しを管理している場合は、変数名の変更を明示的なメジャーバージョンアップとして扱うルールを設けるようにします。インターフェースの後方互換をどこまで守るかを設計段階で明文化しておくことが、後の混乱を防ぎます。

チームで使い回すモジュールの境界設計

呼び出し側が知るべき情報を絞る

良いモジュールのインターフェースは、呼び出し側が内部実装を知らずとも使えるよう設計されます。公開変数は「呼び出し元が必ずコントロールしたいもの」だけに絞り、内部で完結する設定はlocalsや固定値で処理します。公開変数の数が20を超えるモジュールは「何でも設定できる汎用機械」になっており、呼び出し側に過大な理解を要求している状態です。公開変数を10以下に抑えることを目標にすると設計の引き締めに効くという経験則が、複数の現場で確認されています。「何を隠すか」の設計が「何を公開するか」と同じくらい重要です。

責任境界をコードで表現する

モジュールが担う責任の範囲を明確にすることは、チーム共有の文脈では特に重要です。「このモジュールはVPCを作るがサブネットへのルーティングは担わない」という責任境界を明示しないまま共有すると、「VPCモジュールを使えばルーティングも設定されると思っていた」という誤解が生まれます。責任境界はREADMEより変数名・出力名・モジュール名に込めるほうが伝わりやすいです。コードと分離したドキュメントは更新漏れが生まれやすいため、設計の意図はコード自体に表現することを優先します。

本番利用可否を構造で区別する

チーム共有モジュールを1つのリポジトリで管理する場合、「本番で利用可能なもの」と「実験中のもの」を明確に区別する構造が必要です。実験中のモジュールが気づかないうちに本番から参照されるインシデントは珍しくありません。安定版と実験版でディレクトリを物理的に分ける、またはバージョン付きでのみ参照可能なレジストリを導入する設計が、このリスクを構造的に排除します。運用ルールではなく設計で防ぐ発想が、チームの規模が大きくなるほど重要性を増します。

モジュール設計チェックフレーム

設計・レビュー前に問う7つの判断軸

モジュールを新たに設計・リファクタリングする前に、以下の問いに答えることで過剰抽象化と過剰な個別化の両方を回避できます。

  1. 利用箇所は3件以上か:2件以下なら複製許容が経験則です。3件を超えてから共通化を検討します。
  2. 変化の方向は揃っているか:将来の変更が同一モジュール内で矛盾しないかを確認します。
  3. 公開変数は10個以下に収まるか:超える場合はモジュールが複数の責任を抱えているサインです。
  4. 例外ハンドリングは3つ以下か:超える場合はモジュール分割または専用実装への切り戻しを検討します。
  5. モジュール単体で検証が通るか:外部依存なしで確認できない構造は境界設計が不十分です。
  6. 初見の担当者が30分で変更できるか:変更に1時間以上かかる構造は可読性の限界を超えています。
  7. インターフェースは後方互換を意識しているか:変更が全呼び出し元への同時修正を要求しないかを確認します。

段階的なリファクタリング方針

モジュール設計は一度作ったら固定するものではなく、利用パターンが明確になった段階でリファクタリングするという前提で設計するほうが現実に合っています。「まず動くものを作り、2回目の利用要求が来た時点で共通化を検討し、3回目で抽象化する」という段階的なアプローチは、早すぎる抽象化を避ける実践的な方針です。リファクタリングのコストが最も低いのは、呼び出し元がまだ少ない初期段階です。呼び出し元が10件を超えた後にインターフェースを変更しようとすると、影響範囲の確認と修正だけで数日の工数が発生することがあります。設計の固定化を避け、利用実績に基づいて判断を更新していく姿勢が持続可能なモジュール管理を支えます。

▶ 手を動かして本番まで到達する

設計の型は掴めても、本番でTerraformを通しきる経験は別物です

ここまでの設計判断は、読めば腹に落ちます。ですが「自分の手で本番まで通しきる自信」は、それとは別の話になります。

2日間の集中ハンズオンで、AWS上にIaCで構築しきる経験を持ち帰れます。設計判断を、動くコードと本番環境の手応えに変える場です。

再利用できるモジュール設計を手で学ぶ2日間へ →

Terraformマスターセミナー|2日間ハンズオンでAWS上にIaCで本番構築