Terraform

Terraformモジュール合成パターン実践ガイド: 複数モジュールの組合せを安全に設計・運用する

2026-04-19
NicheeLab編集部

ハブ記事: Terraform モジュール 完全ガイド

設計・配布・運用まで Terraform モジュールの全体像を一望できるハブ記事

モジュールはTerraformの再利用単位ですが、真価が出るのは「合成」した時です。ネットワーク、セキュリティ、アプリ基盤など複数のモジュールを安全に組み合わせるには、依存関係の表現、プロバイダ伝播、入力・出力の設計、バージョン固定など、いくつか外せないポイントがあります。

本稿では、公式ドキュメントの挙動に基づき、壊れにくい合成パターンを解説します。試験対策として問われやすい論点(module間のdepends_on、for_eachのキー安定性、providerのエイリアス伝播、バージョン制約の書き方等)も併せて確認します。

ルートモジュールで合成する基本設計

複数モジュールの合成は通常、ルートモジュールで行います。各子モジュールは明確な境界(入力variablesと出力outputs)を持ち、ルートがそれらを配線します。入力の型を厳密に定義し、出力は後続のモジュールが使いやすい最小限の形に整形します。

モジュールのsourceはレジストリ、VCS、ローカルパスのいずれかを用い、安定運用のためversionを明示します。プロバイダはルートで初期化し、必要に応じて子へエイリアスをマッピングします。

  • 入力は型・デフォルト・検証条件(validation)を明記して境界を固くする
  • 出力は後段が必要とするID・ARNなど最小集合に限定し、構造は安定させる
  • moduleブロックはsourceとversionを固定し、リリース単位で上げる
  • プロバイダはルートで定義し、子モジュールにはproviders引数で明示伝播する
パターン概要強み注意点
ルート合成(Flat)ルートで複数子モジュールを直接配線見通しがよくデバッグ容易ルートが肥大化しやすい
ラッパーモジュール(Facade)一連の構築を1つの再利用モジュールに束ねる再利用性と標準化が高い内部差し替え時はバージョン管理を厳密に
環境ごとスタック分割env別ディレクトリで同じ合成を繰り返す状態と責任分離が明確定義の重複はテンプレ化や共通ラッパーで吸収

典型的なモジュール合成(ルートでの配線)

root modulevariables.tfvarsnetworkoutputssecurityoutputsapp platformoutputs 連結先root module で variables を配線 → network / security → app platform

ルートでの合成例(sourceの固定と出力配線)

terraform {
  required_version = ">= 1.5.0"
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
  }
}

provider "aws" {
  region = var.region
}

module "network" {
  source  = "terraform-aws-modules/vpc/aws"
  version = "~> 5.0"
  name    = var.name
  cidr    = var.vpc_cidr
}

module "security" {
  source  = "git::https://example.com/org/security-group.git?ref=v1.4.2"
  vpc_id  = module.network.vpc_id
}

module "app" {
  source          = "./modules/app"
  subnet_ids      = module.network.private_subnets
  security_groups = [module.security.sg_id]
}

output "app_endpoint" {
  value = module.app.endpoint
}

出力と入力の連結を安定化する

モジュール間の結合は出力→入力の受け渡しで行います。参照がある限りTerraformは暗黙依存を解決して評価順を組み立てます。中間整形はlocalsを使い、後段モジュールの入力仕様に合わせた形にします。

出力のsensitiveは表示を抑制しますが、状態に平文で保持される点は変わりません。秘匿値はプロバイダ側の機密管理や外部シークレットストアの活用を検討してください。

  • 出力の命名は安定性重視。破壊的変更はメジャーバージョンで行う
  • localsで構造を整形し、子モジュールの入力スキーマをシンプルに保つ
  • sensitiveは可視性の制御であり、状態の暗号化はバックエンドで行う

出力を配線するためのlocals活用

module "vpc" {
  source = "terraform-aws-modules/vpc/aws"
  name   = var.name
  cidr   = var.vpc_cidr
}

locals {
  app_subnet_ids = slice(module.vpc.private_subnets, 0, 2)
}

module "db" {
  source     = "./modules/rds"
  subnet_ids = local.app_subnet_ids
}

output "db_endpoint" {
  value     = module.db.endpoint
  sensitive = true
}

依存関係と評価順序の制御

Terraformは参照(例: resourceまたはmoduleの出力参照)から暗黙の依存グラフを構築します。参照がないが手続き上の順序が必要な場合のみ、moduleブロックのdepends_onメタ引数で明示依存を付与できます。

データソースは読み取り専用であり、計画時に評価されます。副作用や順序制御のためにデータソースへ不必要なdepends_onを付けると複雑化の原因になります。まずは参照による暗黙依存で足りるかを検討してください。

  • moduleブロックはdepends_onメタ引数をサポート(Terraform 0.13+)
  • 参照があるならdepends_onは不要。重複依存は計画時間増大の原因
  • 副作用のみのモジュール(例: 監査設定)に限り明示依存を検討

module間の明示依存(暗黙参照がない場合)

module "iam" {
  source = "./modules/iam-baseline"
}

module "eks" {
  source     = "terraform-aws-modules/eks/aws"
  version    = "~> 20.0"
  cluster_name = var.name
  # このモジュールはiamの出力を直接参照しないが、事前適用が必要
  depends_on = [module.iam]
}

モジュールの複製: countとfor_eachの使い分け

同一モジュールを複数インスタンス化する場合、countまたはfor_eachが使用できます。安定したキーによるアドレッシングと差分制御の観点から、実務ではfor_eachを優先するのが一般的です。

for_eachのキーは将来も安定する識別子(論理名やID)を用い、並べ替えや削除時の破壊的置換を避けます。

  • countはインデックス依存のため挿入でズレやすい
  • for_eachはキーが安定していれば差分が最小化される
  • 集約出力は内包表記でマップ化すると呼び出し側が扱いやすい

for_eachでサブネットモジュールを複製し、出力を集約

variable "subnets" {
  type = map(object({
    cidr = string
    az   = string
  }))
}

module "subnet" {
  source = "./modules/subnet"
  for_each = var.subnets

  name = each.key
  cidr = each.value.cidr
  az   = each.value.az
}

# 呼び出し側が使いやすいように、キー付きで出力を束ねる
output "subnet_ids" {
  value = { for k, m in module.subnet : k => m.id }
}

プロバイダ伝播とバージョン固定

子モジュールは呼び出し元のプロバイダを継承します。エイリアス付きのプロバイダを子へ渡す場合は、moduleブロックのproviders引数でマッピングします。子側で期待するプロバイダ名(エイリアス含む)に対し、呼び出し元の定義を割り当てます。

required_providersでプロバイダのバージョン範囲を宣言し、モジュールsourceではversionを固定します。リリースの互換性ポリシーに沿って~>などの範囲指定を使い分けます。

  • providers引数は「子が期待するプロバイダ名」に対して「親の構成」を割り当てる
  • moduleのversionは必ず固定。providerはrequired_providersで範囲指定
  • レジストリsource(org/name/provider)の三段式はキャッシュと検証に有利

プロバイダのエイリアス伝播とバージョン制約

terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = ">= 5.0, < 6.0"
    }
  }
}

provider "aws" {
  alias  = "use1"
  region = "us-east-1"
}

provider "aws" {
  alias  = "usw2"
  region = "us-west-2"
}

# 子モジュールはデフォルト名awsを期待している想定
module "logs" {
  source = "./modules/logs"
  providers = {
    aws = aws.usw2
  }
}

# レジストリのモジュールはversionで固定
module "vpc" {
  source  = "terraform-aws-modules/vpc/aws"
  version = "~> 5.1"
  name    = var.name
  cidr    = var.vpc_cidr
}

環境分離と状態の配置

本番・検証などの環境は、別ディレクトリや別バックエンドで状態を分離するのが実務の基本です。ワークスペースは軽量なバリアントには有用ですが、環境境界や権限分離の代替にはなりません。

環境間で同一の合成を適用する場合、共通のラッパーモジュールを作り、envごとに入力値とバックエンド設定だけを変えると管理が容易です。リファクタ時はmovedブロックでリソースアドレスの移動を明示し、状態破壊を避けます。

  • 環境は状態ファイルを分離し、plan/applyは環境単位で実行する
  • バックエンドの認証・暗号化設定は環境ごとに適用
  • 大規模ではディレクトリ分割+共通ラッパーが見通し良い

環境ディレクトリ分割の最小例

prod/
  main.tf
  variables.tf
  backend.hcl   # prod用のremote backend設定
  terraform.tfvars
staging/
  main.tf
  variables.tf
  backend.hcl   # staging用
  terraform.tfvars

# 例: backend.hcl は init 時に -backend-config=backend.hcl で供給
# moved ブロックの例(リソースをラッパーへ移動する際)
moved {
  from = aws_iam_role.app
  to   = module.security.aws_iam_role.app
}

問題で確認

Pro

問題 1

ルートモジュールでnetwork、iam、eksの3モジュールを合成しています。eksはiamの出力を直接参照していませんが、作成順としてiamが先に適用される必要があります。Terraformのベストプラクティスとして最も適切な方法はどれですか。

  1. module.eksのmoduleブロックにdepends_on = [module.iam]を追加する
  2. variable経由でダミー値を渡し、値参照により暗黙依存を作る
  3. eks内の全resourceにlifecycleのcreate_before_destroyを設定する
  4. terraform refreshを先に実行してからapplyする

正解: A

参照がないが手続き順が必要な場合は、moduleブロックのdepends_onで明示依存を付与します。ダミーの参照で擬似依存を作るのはアンチパターン、lifecycleは破壊順序の制御であり依存解決ではありません。refreshは状態更新で順序制御にはならないため不適切です。

よくある質問

モジュールの複製にはcountとfor_eachのどちらを使うべきですか?

安定した識別子で差分を最小化できるためfor_eachが推奨です。countはインデックスがずれやすく、挿入・削除時に意図せぬ置換を誘発します。試験でもfor_eachのキー安定性が問われやすいです。

子モジュールに別リージョンのプロバイダを使わせるにはどうしますか?

親でエイリアス付きのproviderを定義し、moduleブロックのproviders引数で子が期待するプロバイダ名へマッピングします。例: providers = { aws = aws.usw2 }。子はその名前のプロバイダを利用します。

レジストリのモジュールとプロバイダのバージョン管理はどう分けますか?

モジュールはmoduleブロックのversionで固定し、プロバイダはrequired_providersで範囲指定します。両者を明示することで再現性の高い計画が得られます。

この記事で学んだ内容を問題で確認しましょう

16,000問以上の問題で実力チェック

無料で問題を解いてみる
この記事の著者

NicheeLab編集部

データエンジニアリング・クラウド資格の専門家。Databricks・Snowflake等の認定資格を保有し、実務経験に基づいた問題作成・解説を行っています。NicheeLab運営。


関連記事
Terraform

Terraform HCL 構文の基礎:Block / Attribute / Expression を正しく使い分ける

Terraform Associate で頻出の HCL 構文を、ブロック・属性・式の3視点で整理。実務で迷いがちな書き...

Terraform

Terraform Authoring & Ops Pro: 上位資格の範囲と対策

上位レベルを想定したTerraformの設計・運用ドメインを整理し、実務で通用する対策を提示。モジュール設計、ステート運...

Terraform

Terraform Providers の基本: プラグイン型アーキテクチャを正しく使いこなす

Associate レベルで押さえるべき Provider の基礎、インストール、バージョニング、認証、エイリアス運用を...

Terraform

Terraform Resourceブロック徹底ガイド: 最小単位のリソース定義

Associateレベルで押さえるべきResourceブロックの構造、依存関係、メタ引数、ライフサイクル制御を実務目線で...

Terraform

Terraform Data Source徹底理解:既存リソースの参照で壊さず足す

Terraform Associate向けに、Data Sourceを用いた既存リソース参照の基本、選択基準、評価順序、...

Terraformの記事一覧 (102件)
© 2026 NicheeLab All rights reserved.