Terraformモジュールの概要
Terraform を小規模に使っているうちは1つの main.tf にコードを書くだけで十分ですが、
環境が増えたり、チーム開発になったりすると必ず モジュール化 が必要になってきます。
◇ terraformのモジュールとは?
Terraformのモジュールは、"フォルダ単位でまとめたインフラ構成の部品" のことです。
AWSなら、VPC・EC2・S3といったリソースごとに部品化して使い回せます。
◇ terraformをモジュール化するには?
Terraform でモジュール設計を検討する場合は、
「root モジュール」と「child モジュール」の二層構造 を意識すれば理解できると思います。
例えば s3 と iam をtest(テスト環境)とprod(本番環境)でデプロイする場合、
以下のようなファイルレイアウトにすると、これが "モジュール化された構成" になります。
terraform/
├── modules/
│ ├── s3/ ← 部品ごとの "child モジュール"
│ │ ├── main.tf
│ │ └── ...
│ │
│ └── iam/ ← 部品ごとの "child モジュール"
│ ├── main.tf
│ └── ...
│
├── test/ ← 環境ごとの "root モジュール"
│ ├── main.tf
│ └── ...
│
└── prod/ ← 環境ごとの "root モジュール"
└── ...
◇ モジュール化したterraformをデプロイするには?
重要な点として、
terraform applyは 必ず "root モジュール" で実行します。
modules/の階層、つまり"child モジュール" でapplyを実行すると、意図しないリソースが作成される場合があるので注意してください。
terraform/
├── modules/ ← ✕ ここでは apply しない
├── test/ ← 〇 apply を実行する場所
└── prod/ ← 〇 apply を実行する場所
◇ この記事で扱う内容
この記事では、terraformモジュールを扱う上で重要な次の項目についてまとめています。
【目次】
- ファイルレイアウトとモジュールの関係
- root から child を呼び出す文法
- モジュールで扱う変数
3.1. モジュールの入力変数(variable)
3.2. モジュール内で定義する locals
3.3. モジュールの出力変数(output)
1.ファイルレイアウトとモジュールの関係
◇ モジュールの単位は "ファイルレイアウト" で決まる
モジュールの単位を一言で定義するなら "一意のフォルダ" になります。
そのフォルダに存在する .tf ファイル群が "ひとつのモジュール" を構成します。
modules/s3/ ← このフォルダ全体が "1つのモジュール"
├── main.tf
├── variables.tf
└── outputs.tf
逆にいうと、フォルダが違えばそれは 全く別のモジュール になります。
terraform/
├── modules/
│ ├── s3/ ← "モジュール A"
│ │ ├── main.tf
│ │ └── ...
│ │
│ └── iam/ ← "モジュール B"
│ ├── main.tf
│ └── ...
2.root から child を呼び出す文法
◇ モジュールの基本構造は、
"root モジュール" が "child モジュール" を呼び出す
Terraform のモジュールは大きく二つに分類されます。
- root モジュール :"child モジュール" を呼び出し、apply を実行する場所
- child モジュール :"root モジュール" に呼び出されるモジュール
最初のファイルレイアウトでみると、次のようなイメージになります。
terraform/
├── modules/
│ ├── s3/ ← "child モジュール"
│ │ ├── main.tf
│ │ ├── variables.tf
│ │ └── outputs.tf
│ │
│ └── iam/ ← "child モジュール"
│ ├── main.tf
│ ├── variables.tf
│ └── outputs.tf
│
├── test/ ← "root モジュール"
│ ├── main.tf
│ └── backend.tf (任意)
│
└── prod/ ← "root モジュール"
├── main.tf
└── backend.tf (任意)
このファイルレイアウトの場合、
test / prod はそれぞれ独立した root モジュールになります。
それぞれが modules/s3 や modules/iam といった child モジュールを呼び出し、
環境ごとに別の state(test 用 / prod 用)を持つことになります。
◇ モジュールを呼び出す方法
test/prod の root モジュールで child モジュールを呼び出す際は、
root モジュール側の "main.tf" に次の文法を記述します。
module "<NAME>" {
source = "<SOURCE>"
[CONFIG]
}
例えば、"s3 バケット"を呼び出す場合は、main.tfに次のようなコードを記述します。
module "logs_bucket" {
source = "../modules/s3"
[CONFIG]
}
各コードの意味は次のとおりです。
-
"logs_bucket":
root モジュールでchild モジュールを識別するための名前(自由に決められる) -
source = "../modules/s3":
child モジュールのコードが置かれているパス(相対パス or Git / Registry も可)
これで、root モジュール から child モジュール を呼び出すための基本構造が整います。
ただし、モジュールは呼び出すだけでは真価を発揮しません。
Terraform のモジュールでは、
環境ごとに異なる値を渡したり、モジュール内部で共通の計算値を扱ったり、外へ値を返したりするための仕組み として、いくつかの変数が用意されています。
これらの仕組みについては、次章「3. モジュールで扱う変数」で解説します。
3. モジュールで扱う変数
Terraform では、モジュール内部で扱うためのいくつかの変数が用意されています。
仕組みを知らないうちは「変数が多くてややこしい、、」と感じますが、
「どの変数がどの方向に流れるのか」
というイメージを掴めば、理解がシンプルになります。
モジュールで扱う変数の関係をイメージ図で表現してみました。
Terraform のモジュールで使う変数は大きく次の3種類に分かれます。
- 1. 入力変数(variable)
- root モジュール → child モジュール に値を渡すための変数。
- 外からモジュールへ届けられるパラメータです。
- 2. ローカル変数(locals)
- モジュール内部でのみ利用される "計算用・共通化用の値"。
- ※root と child の間を行き来することはありません。
- 3. 出力変数(output)
- child モジュール → root モジュール に値を返すための変数。
- モジュールから外へ返す結果を出力します。
この3種類の変数について、もう少し詳しく解説します。
3.1. モジュールの入力変数(variable)
◇ 入力変数について
入力変数(variable)は、
root モジュール → child モジュールへ値を渡すための仕組み です。
Terraform のモジュールは、
「環境ごとに値を変えたい部分」や「再利用したい設定」を部品化して使いますが、
その際に child モジュールへ柔軟に値を渡すために variable を使用します。
↑
root 側から渡したパラメータがchild モジュール内の var. として参照され、
リソースの設定値として利用される流れを視覚化してみました。
次のような利用シーンがあります。
- test / prod で値を変えたい
例:S3バケット名、タグ、環境名などをchildモジュールに渡す - モジュールの再利用性を高めたい
例:同じ IAM ロール定義を、「環境依存の値」だけ変えて複数環境で使い回す - child モジュール側で固定値を書かずに済ませたい
→ ハードコーディングを避け、保守性を向上させる
要するに、
「環境の違いで変わる値はすべて root モジュール側で制御し、child モジュールは variable 経由でその値を受け取るだけにする」
というのが、入力変数の本質だと思います。
こうしておくことで、環境差異による変更は root 側の variable を変えるだけで済み、child モジュールのコードは一切触らずに済みます。
◇ 入力変数を設定するポイント
■ child モジュール側
-
variable "<入力変数名>" {}で受け取る入力変数を定義しておく - "main.tf"内の
var.<入力変数名>で入力変数を受け取ってリソースに反映する
■ root モジュール側
- "main.tf"内の
module "<モジュール名>" { ... }でモジュールを呼び出す - "main.tf"内の
<入力変数名> = "代入したい値"で child 側に値を渡す
このステップさえ押さえれば、
入力変数(variable) によって、環境差分を root 側で制御した扱いやすいモジュール構造が実現します。
3.2. モジュール内で定義する locals
◇ ローカル変数について
ローカル変数(locals)は、
モジュール内部だけで使う "計算用・共通化用の値" をまとめる仕組み です。
入力変数(variable)が「外から渡される値」を扱うのに対し、
locals は 「モジュールの内側で完結する値」 を整理するための変数になります。
↑
root モジュールから child モジュールへ渡した var を材料として、
child 内部の locals が値を計算し、その結果を main.tf のリソース設定に反映する流れを視覚化しています。
locals はあくまでモジュール内部だけで完結する "計算・共通化用の領域" であり、
外部へ直接値を渡すことはありません。
次のような利用シーンがあります。
- root から渡された値をもとに、モジュール内部で新しい値を生成したい
例:"${var.env}-${local.project}"のような命名規則の統一 - 複雑な式をmain.tfで可読性の高い "名前付きの値" で参照したい
例:performance_tier = var.env == "prod" ? "HIGH" : "LOW"を locals.tf 内で定義しておき、local.performance_tierとしてリソース側から参照する
まとめると、
locals は child モジュール内の整形・共通化 が主な役割です。
◇ ローカル変数を設定するポイント
■ child モジュール側
- "locals.tf"などに、
locals {<ローカル変数> = "代入したい値"}でローカル変数を定義する - "main.tf"内の
local.<ローカル変数名>"でローカル変数を受け取ってリソースに反映する
このステップを押さえておけば、
ローカル変数(locals) を活用して、child モジュール内部の main.tf を整理された、読みやすい構造に保つことができます。
3.3. モジュールの出力変数(output)
◇ 出力変数について
出力変数(output)は、
child の内部で計算された値を root 側へ返却する仕組み です。
child モジュール内部では、locals やリソースによって「内部でしか見えない値」が生成されますが、
そのままでは root モジュールから参照することはできません。
次のような利用シーンがあります。
- child モジュール内で生成した値を root モジュール側で参照したい
例:S3 バケット名、IAM ロール ARN、作成したリソースの ID など - 別の child モジュールへ渡したい値を、一度 root 側で受け取る必要がある
例:VPC モジュールが返す subnet ID を EC2 モジュールへ渡す - child モジュールをブラックボックス化し、必要な情報だけ外に公開したい
→ 内部構造を隠蔽し、インターフェースだけ提供するための設計意図
つまり、
出力変数は(output)とは、
child モジュールで生成したものを、root モジュールが受け取るための "出口" です。
◇ 出力変数を設定するポイント
■ child モジュール側
- "outputs.tf" に
output "<output名>" {value = <返したい値>}の形式で出力変数を定義する -
<返したい値>はモジュール内部で生成したaws_xxx.yyy.idなどの値を指定する
■ root モジュール側
- "main.tf"内の
module "<モジュール名>" { ... }でモジュールを呼び出す -
module.<モジュール名>.<output名>の形式で参照する
※出力がオブジェクトの場合はmodule.<モジュール名>.<output名>.<キー名>
このステップを押さえれば、
child モジュール内部で生成した値をroot モジュール側のリソース設定に反映することができます。



