1. 概要
- moduleを切るべき判断基準
- module化のメリットとデメリット
- module化の具体的なサイン(いつ切るか)
- module化すべきでないケース
- ディレクトリ構成の実例
Terraformのmoduleは「再利用性」だけのための機能ではありません。コードの意図を明確にし、チームで管理しやすくするためのものです。しかし、早すぎるmodule化はかえってコードを複雑にします。この記事では「いつmoduleを切るか」の判断基準を具体例とともに解説します。
2. module化の基本的な判断基準
✅ module化すべきサイン
同じリソースセットを2箇所以上で書いているとき
同一のリソース群(例: ALB + ターゲットグループ + リスナー)を複数の環境やサービスで繰り返す場合は、module化の強いサインです。
# ❌ dev/main.tf と prd/main.tf で同じ構成を重複
resource "aws_lb" "web" { ... }
resource "aws_lb_target_group" "web" { ... }
resource "aws_lb_listener" "http" { ... }
# ✅ modules/alb/main.tf として切り出し
module "alb" {
source = "../../modules/alb"
name = "${var.environment}-web"
vpc_id = module.vpc.vpc_id
}
論理的にまとまりのある単位があるとき
「ネットワーク層(VPC/サブネット/ルートテーブル)」「コンピュート層(EC2/ASG)」「データ層(RDS/ElastiCache)」のように論理的な境界が明確な場合は分割候補です。
チームをまたいで共有するとき
インフラチームが提供する共通moduleをアプリチームが使う、という組織的な分離にもmoduleは有効です。
3. module化のメリット
terraform {
required_version = ">= 1.9"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
provider "aws" {
region = "ap-northeast-1"
}
variable "environment" {
description = "環境名(dev/stg/prd)"
type = string
default = "dev"
}
# moduleを呼び出す側(ルートモジュール)
module "network" {
source = "./modules/network"
environment = var.environment
vpc_cidr = "10.0.0.0/16"
}
module "compute" {
source = "./modules/compute"
environment = var.environment
vpc_id = module.network.vpc_id
subnet_ids = module.network.private_subnet_ids
instance_type = var.environment == "prd" ? "t3.medium" : "t3.micro"
}
# modules/network/main.tf
variable "environment" {
type = string
}
variable "vpc_cidr" {
type = string
}
resource "aws_vpc" "main" {
cidr_block = var.vpc_cidr
tags = {
Name = "${var.environment}-vpc"
Environment = var.environment
ManagedBy = "terraform"
}
}
resource "aws_subnet" "private" {
count = 2
vpc_id = aws_vpc.main.id
cidr_block = cidrsubnet(var.vpc_cidr, 8, count.index)
availability_zone = data.aws_availability_zones.available.names[count.index]
tags = {
Name = "${var.environment}-private-${count.index + 1}"
Environment = var.environment
ManagedBy = "terraform"
}
}
data "aws_availability_zones" "available" {
state = "available"
}
output "vpc_id" {
value = aws_vpc.main.id
}
output "private_subnet_ids" {
value = aws_subnet.private[*].id
}
4. module化すべきでないケース
❌ 1箇所でしか使わないとき(まだ)
「将来使いまわすかも」という理由でのmodule化は早すぎます。DRY原則よりも「YAGNI(You Aren’t Gonna Need It)」を優先してください。
# ❌ 1箇所しか使わないのに複雑なmoduleを作る
module "single_ec2" {
source = "./modules/single-ec2-that-no-one-else-uses"
...
}
# ✅ シンプルにそのまま書く
resource "aws_instance" "bastion" {
ami = data.aws_ami.amazon_linux_2023.id
instance_type = "t3.micro"
tags = {
Name = "${var.environment}-bastion"
Environment = var.environment
ManagedBy = "terraform"
}
}
❌ ラッパーが薄すぎるとき
単に1つのリソースをラップするだけのmoduleは、抽象化のコストだけが増えます。
# ❌ S3バケット1つをラップするだけのmodule(不要)
module "s3" {
source = "./modules/s3"
bucket_name = "my-bucket"
}
❌ moduleの境界が不明確なとき
「ALBとRDSをまとめたmodule」のように、論理的なまとまりのない寄せ集めはmodeule化しても管理が難しくなります。
5. 判断フローチャート
同じリソース群を2回以上書いた?
→ YES → module化を検討
→ NO → まだ書かない
論理的なまとまりがある?(ネットワーク層、コンピュート層など)
→ YES → module化OK
→ NO → ルートモジュールに直書きのままで可
チームをまたいで共有する?
→ YES → module化 + バージョン管理を検討
→ NO → ローカルmoduleか直書きで十分
6. ディレクトリ構成の実例
project/
├── environments/
│ ├── dev/
│ │ ├── main.tf # moduleを呼ぶルートモジュール
│ │ ├── variables.tf
│ │ └── terraform.tfvars
│ └── prd/
│ ├── main.tf
│ ├── variables.tf
│ └── terraform.tfvars
└── modules/
├── network/ # VPC・サブネット・ルートテーブル
│ ├── main.tf
│ ├── outputs.tf
│ └── variables.tf
├── compute/ # EC2・ASG・起動テンプレート
│ ├── main.tf
│ ├── outputs.tf
│ └── variables.tf
└── rds/ # RDS・パラメータグループ・サブネットグループ
├── main.tf
├── outputs.tf
└── variables.tf
7. 関連記事
- 環境分離のパターン比較 — workspace vs ディレクトリ vs ブランチ — module化と環境分離の組み合わせ
- module ブロックの使い方 — moduleの基本構文
- Terraform命名規則 — moduleのファイル名・変数名の命名規則
8. まとめ
- 2箇所以上で使う・論理的なまとまり・チーム間共有がmodule化の3つのサイン
- 「将来使うかも」という理由での早すぎるmodule化はコードを複雑にする
- 1リソースをラップするだけの薄いmoduleは不要
- まず直書き → 重複が生まれたら切り出す、という順序が現実的
動作確認バージョン: Terraform >= 1.9 公式ドキュメント: https://developer.hashicorp.com/terraform/language/modules/develop