Terraform

Terraform の root モジュールと child モジュールを正しく階層設計する

2026-04-19
NicheeLab編集部

Terraform の設計は「どこを root にして、何を child に切り出すか」で決まります。ここを外すと、状態管理や再利用、チーム開発で苦労します。

本稿は Terraform Associate(初級)の出題観点を押さえつつ、現場で破綻しにくい階層設計の原則を、最小限のパターンとコードで示します。

root と child の定義・役割を一度で腹落ちさせる

root モジュールは terraform init/plan/apply を実行するディレクトリ直下の .tf 群です。child モジュールは root から module ブロックで呼び出される側で、さらに下位の child を呼ぶこともできます。

実務と試験の要点は、プロバイダ設定・状態ファイル・入出力の境界を root に集約し、child は疎結合な再利用単位として小さく保つこと。プロバイダ設定は原則 root に置き、child には providers 引数で渡します。

  • root は単一の状態ファイル(backend)に結びつく運用単位
  • child は機能ごとの再利用単位。I/O(variables/outputs)を明確に
  • provider の設定は root に集約。child は provider 依存を外部化
  • module 間の依存は outputs を介して明示。暗黙依存は避ける
観点root モジュールchild モジュール備考
実行場所terraform init/plan/apply を実行呼び出されて実行されるCLI は常に root で動く
状態管理backend 設定の所在。1つの state に紐づくstate は root に統合されるchild 単体の state は通常持たない
プロバイダprovider 設定を定義providers 引数で受け取って使用意図的に差し替える場合のみ別名供給
I/Ovariables.tf, tfvars, outputs の集約点最小限の variables/outputs を公開locals で内部表現を隠蔽
再利用低い(環境固有が多い)高い(機能単位・汎用)semantic versioning を推奨

最小の root から child を呼ぶ例

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

provider "aws" {
  region = var.region
}

module "network" {
  source    = "./modules/network"
  providers = { aws = aws }
  cidr      = var.vpc_cidr
}

壊れにくいディレクトリ設計パターン

単一レポジトリで modules を共通化し、環境別に root を分けるのが、学習コストと分離性のバランスが良い定番です。複数リポジトリに分けるのは、変更フローやリリース管理が固まってからで十分です。

命名は“動詞より名詞”。modules は network, compute, security のようにリソース境界で分け、root では環境(dev/stg/prod)ごとの差分だけを与えます。

  • modules/ 以下は小さく疎結合に。1 モジュール=1 責務
  • environments/ 以下は root。tfvars と backend を環境ごとに分離
  • 状態ファイルのキー(prefix)は環境名を含め衝突を防止

推奨レイアウト(単一リポジトリ)

repo-root/
  modules/
    network/
      main.tf
      variables.tf
      outputs.tf
    compute/
      main.tf
      variables.tf
      outputs.tf
  environments/
    dev/
      main.tf
      variables.tf
      terraform.tfvars
      backend.hcl
    stg/
      main.tf
      terraform.tfvars
      backend.hcl
    prod/
      main.tf
      terraform.tfvars
      backend.hcl

backend はファイル分離し CLI で渡す(変数は使えない)

# environments/dev/backend.hcl
bucket         = "my-tfstate-bucket"
key            = "env/dev/terraform.tfstate"
region         = "ap-northeast-1"
dynamodb_table = "my-tf-lock"

# 初期化
# terraform -chdir=environments/dev init -backend-config=backend.hcl

variables/outputs/locals の流れを固定化する

child は入力境界を variables.tf、出力境界を outputs.tf で明確化します。内部表現は locals に閉じ込め、root からは見えないようにします。

root は tfvars で環境差分だけを与え、module 出力を別 module の入力に渡すことで依存を明示します。

  • child の variables は必須とデフォルトを厳密に管理
  • outputs は下流に本当に必要なものだけを公開
  • locals は命名規約で外部公開しないことを示す

入出力の最小例

# modules/network/variables.tf
variable "cidr" {
  type        = string
  description = "VPC CIDR"
}

# modules/network/main.tf
resource "aws_vpc" "this" {
  cidr_block = var.cidr
  tags = { Name = "nlab-vpc" }
}

# modules/network/outputs.tf
output "vpc_id" {
  value = aws_vpc.this.id
}

# environments/dev/main.tf
module "network" {
  source = "../../modules/network"
  cidr   = var.vpc_cidr
}

module "compute" {
  source = "../../modules/compute"
  vpc_id = module.network.vpc_id
}

環境分離: ディレクトリ方式とワークスペースの使い分け

強い分離(権限・バックエンド・変更窓口まで分けたい)が必要な本番系は、環境ごとに root ディレクトリを分けるのが基本です。state も backend 設定も物理的に分かれます。

Terraform のワークスペースは同一コード・同一 backend で state ファイルを切り替える機能です。軽量なサンドボックスや短命なレビュー環境には有用ですが、環境ごとの差分が大きい場合はディレクトリ分離が安全です。

  • 本番は別 root + 別 backend + 別 IAM を基本線に
  • ワークスペースは短命・同質な環境向け。設定差分は最小に
  • backend は変数を取れないため、環境ごとにファイル分離し init で渡す

環境ごと root で差分を与える(tfvars 例)

# environments/dev/terraform.tfvars
region    = "ap-northeast-1"
vpc_cidr  = "10.10.0.0/16"

# environments/prod/terraform.tfvars
region    = "ap-northeast-1"
vpc_cidr  = "10.20.0.0/16"

# 実行例
# terraform -chdir=environments/dev plan -var-file=terraform.tfvars

モジュールのバージョニングと再利用のクセを掴む

レジストリ配布のモジュールは version 引数でセマンティックに固定します。Git 参照のモジュールは source に ref タグやハッシュを明示します。ローカル相対パスのモジュールに version は使えません。

更新時は terraform init -upgrade を使い、変更は plan で可視化したうえでレビューに掛けます。

  • public/private レジストリは version で固定
  • Git 参照は ref=タグ/コミットで固定。ブランチ固定は避ける
  • ローカル modules は CI で一緒にテストし、外部化の準備が整ったらレジストリ化

registry と Git の指定例

# Registry から取得
module "vpc" {
  source  = "terraform-aws-modules/vpc/aws"
  version = "~> 5.5"
  name    = "nlab"
  cidr    = "10.30.0.0/16"
}

# Git から取得(タグ固定)
module "network" {
  source = "git::https://github.com/example/network-module.git//modules/vpc?ref=v1.4.2"
  cidr   = "10.40.0.0/16"
}

# 更新時
# terraform init -upgrade

検証と CI/CD への組み込み手順

lint → validate → plan の順に自動化し、plan の差分を PR コメントに出すだけでも品質が上がります。apply は環境ごとに手動承認を挟むのが安全です。

モジュール単体は terraform validate と最小モックでの plan を行い、root はバックエンドを指した上で差分の可視化に徹します。

  • fmt/validate/tflint をプリチェックに
  • plan -out で成果物を固定し、apply は別ジョブで
  • destroy は保護ブランチ・承認必須に

最小の CI ステップ例(GitHub Actions)

name: terraform
on: [pull_request]
jobs:
  plan:
    runs-on: ubuntu-latest
    defaults:
      run:
        working-directory: environments/dev
    steps:
      - uses: actions/checkout@v4
      - uses: hashicorp/setup-terraform@v3
        with:
          terraform_version: 1.7.5
      - run: terraform fmt -check
      - run: terraform init -backend-config=backend.hcl
      - run: terraform validate
      - run: terraform plan -var-file=terraform.tfvars -out=tfplan

問題で確認

Associate

問題 1

Terraform で環境ごとに異なる設定を安全に適用したい。root/child の役割分担として最も適切なのはどれか。

  1. プロバイダ設定は root に置き、child には providers 引数で渡す。環境差分は各 root(ディレクトリ)で tfvars と backend を分離する。
  2. child モジュール内にプロバイダ設定を持たせ、root からは variables だけを渡す。環境差分はワークスペースだけで切り替える。
  3. root と child の両方に同じプロバイダ設定を書く。環境差分は variables のデフォルト値で吸収する。
  4. 全ての定義を 1 つの root に集約し、child は使わない。環境差分は count と for_each で切り替える。

正解: A

プロバイダ設定は原則 root に集約し、child には providers で渡すのが公式の推奨パターン。強い分離が必要な環境は root を分け、tfvars と backend を環境単位で管理するのが安全。

よくある質問

root は環境ごとに必ず分けるべき?

本番系は分けるのが基本です。dev/stg のような同質な環境はワークスペース併用も可能ですが、権限やバックエンドまで分けたい場合は root ディレクトリを環境ごとに用意します。

child モジュールで provider ブロックを書いてもよい?

技術的には可能ですが、意図しない競合や差し替え困難さを招きやすいため、root で定義し providers 引数で渡すのが安全です。代替プロバイダ(別名)を使う場合も root で用意して map で渡します.

モジュールの更新はどう管理する?

レジストリ配布は version で固定し、更新時に terraform init -upgrade → plan で差分をレビューします。Git 参照は ref をタグ/コミットで固定し、ブランチ参照は避けます。

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

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の記事一覧 (101件)
© 2026 NicheeLab All rights reserved.