1. 概要
この記事では、以下の内容を解説します。
moduleブロックの基本構文とモジュールの呼び出し方- モジュールのディレクトリ構成(variables.tf / main.tf / outputs.tf)
variableでモジュールに値を渡す方法outputでモジュールの値を親から参照する方法(module.<NAME>.<OUTPUT>)for_eachを使ってモジュールを複数インスタンス化する- Terraform Registryの公開モジュールを使う
- よくあるエラーと対処法
moduleは、関連するリソース群をひとまとめにして再利用可能にする仕組みです。EC2・RDS・VPCなど、セットで使うリソースをモジュール化することで、コードの重複を排除し、環境間(dev/stg/prd)での一貫性を保てます。
2. moduleとは
Terraformのコードはモジュールという単位で管理します。ルートモジュール(terraform applyを実行するディレクトリ)から、サブモジュール(./modules/配下のディレクトリ)を呼び出す構成が基本です。
project/
├── main.tf ← ルートモジュール(moduleブロックを書く)
├── variables.tf
├── outputs.tf
└── modules/
├── vpc/ ← サブモジュール
│ ├── main.tf
│ ├── variables.tf
│ └── outputs.tf
└── ec2/ ← サブモジュール
├── main.tf
├── variables.tf
└── outputs.tf
3. 基本構文
module "<モジュール名>" {
source = "<モジュールのパス>"
# サブモジュールのvariableに値を渡す
<variable名> = <値>
}
シンプルな例
# ルートモジュール(main.tf)
terraform {
required_version = ">= 1.9"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
provider "aws" {
region = "ap-northeast-1"
}
module "vpc" {
source = "./modules/vpc" # 相対パスでモジュールを指定
project = var.project
environment = var.environment
cidr_block = "10.0.0.0/16"
}
module "ec2" {
source = "./modules/ec2"
project = var.project
environment = var.environment
vpc_id = module.vpc.vpc_id # VPCモジュールの出力を参照
subnet_ids = module.vpc.private_subnet_ids # VPCモジュールの出力を参照
}
4. モジュールの内部構成
サブモジュールの標準ファイル構成
# modules/ec2/variables.tf
variable "project" {
description = "プロジェクト名(リソース名プレフィックス用)"
type = string
}
variable "environment" {
description = "環境名(dev/stg/prd)"
type = string
}
variable "vpc_id" {
description = "EC2を配置するVPCのID"
type = string
}
variable "subnet_ids" {
description = "EC2を配置するサブネットIDのリスト"
type = list(string)
}
variable "instance_type" {
description = "EC2インスタンスタイプ"
type = string
default = "t3.micro"
}
# modules/ec2/main.tf
locals {
name_prefix = "${var.project}-${var.environment}"
}
data "aws_ami" "amazon_linux_2023" {
most_recent = true
owners = ["amazon"]
filter {
name = "name"
values = ["al2023-ami-*-x86_64"]
}
}
resource "aws_instance" "app" {
ami = data.aws_ami.amazon_linux_2023.id
instance_type = var.instance_type
subnet_id = var.subnet_ids[0]
root_block_device {
volume_size = 30
volume_type = "gp3"
encrypted = true
}
tags = {
Name = "${local.name_prefix}-app"
Environment = var.environment
ManagedBy = "terraform"
}
}
# modules/ec2/outputs.tf
output "instance_id" {
description = "作成したEC2インスタンスのID"
value = aws_instance.app.id
}
output "private_ip" {
description = "EC2インスタンスのプライベートIPアドレス"
value = aws_instance.app.private_ip
}
5. moduleのoutputを参照する
サブモジュールのoutputは module.<モジュール名>.<output名> で参照します。
# ルートモジュールでモジュールのoutputを参照
output "app_instance_id" {
description = "アプリサーバーのインスタンスID"
value = module.ec2.instance_id # module.<NAME>.<OUTPUT_NAME>
}
output "app_private_ip" {
description = "アプリサーバーのプライベートIP"
value = module.ec2.private_ip
}
# 別のリソースでもモジュールのoutputを使える
resource "aws_route53_record" "app" {
zone_id = var.hosted_zone_id
name = "app.example.com"
type = "A"
ttl = 300
records = [module.ec2.private_ip]
}
6. for_eachでモジュールを複数インスタンス化する
for_eachをmoduleブロックに使うと、モジュールを複数環境分まとめて作成できます。
variable "environments" {
description = "デプロイする環境の設定"
type = map(object({
instance_type = string
cidr_block = string
}))
default = {
dev = { instance_type = "t3.micro", cidr_block = "10.0.0.0/16" }
stg = { instance_type = "t3.small", cidr_block = "10.1.0.0/16" }
prd = { instance_type = "t3.medium", cidr_block = "10.2.0.0/16" }
}
}
module "vpc" {
for_each = var.environments # dev/stg/prd の3つのVPCを作成
source = "./modules/vpc"
project = var.project
environment = each.key # "dev" / "stg" / "prd"
cidr_block = each.value.cidr_block
}
# for_eachモジュールのoutputはmapで返ってくる
output "vpc_ids" {
description = "各環境のVPC ID"
value = {
for env, mod in module.vpc : env => mod.vpc_id
}
# → { dev = "vpc-abc", stg = "vpc-def", prd = "vpc-ghi" }
}
7. Terraform Registryの公開モジュールを使う
sourceにレジストリのパスを指定すると、Terraform Registryの公開モジュールを利用できます。
module "s3_bucket" {
source = "terraform-aws-modules/s3-bucket/aws" # レジストリのモジュール
version = "~> 4.0" # バージョン固定(必須)
bucket = "my-project-bucket"
acl = "private"
versioning = {
enabled = true
}
tags = {
Environment = var.environment
ManagedBy = "terraform"
}
}
💡 注意: 公開モジュールを使う場合は必ず
versionを指定してください。指定しないとモジュールの破壊的変更が自動的に取り込まれるリスクがあります。
# 公開モジュールは terraform init でダウンロードされる
terraform init
8. よくあるエラー
Error: Module not installed
│ Error: Module not installed
│
│ This module is not yet installed. Run "terraform init" to install all modules
│ required by this configuration.
原因: moduleブロックを追加・変更したのにterraform initを実行していない。
解決方法:
terraform init
Error: Unsupported argument
│ Error: Unsupported argument
│
│ An argument named "instance_count" is not expected here.
原因: モジュールに存在しないvariable名を渡している。
解決方法: モジュールのvariables.tfを確認し、正しい変数名を使う。
モジュールのoutputを参照できない
│ Error: Unsupported attribute
│
│ This object does not have an attribute named "private_ip".
原因: モジュールのoutputs.tfにprivate_ipが定義されていない。
解決方法: サブモジュールのoutputs.tfに必要なoutputを追加する。
9. 関連記事
- output(出力値)の使い方 — モジュール間のoutputの受け渡し詳細
- variable(入力変数)の使い方 — モジュールへの値の渡し方
- for_each — map/setで動的にリソースを作成するメタ引数 — moduleへのfor_each適用
- backendの設定方法 — モジュールのState管理
- moduleを切る基準 — どこで分割するか
- Terraformのディレクトリ構成 ベストプラクティス
10. まとめ
moduleブロックでsourceにパスを指定してサブモジュールを呼び出す- サブモジュールは
variables.tf(入力) /main.tf(リソース) /outputs.tf(出力)の3ファイルで構成 - 値の受け渡し: 親→子は
variable経由、子→親はmodule.<NAME>.<OUTPUT_NAME>で参照 for_eachをmoduleブロックに使うと複数環境分のリソースをまとめて作成できる- Terraform Registryの公開モジュールは
sourceにレジストリパスを指定し、versionを必ず固定する moduleを追加・変更したら必ずterraform initを実行すること
動作確認バージョン: Terraform >= 1.9 / AWS Provider ~> 5.0 対象リージョン: ap-northeast-1(東京) 公式ドキュメント: https://developer.hashicorp.com/terraform/language/modules