08. モジュール
同じパターンの resource 群を別ディレクトリに切り出して、入力(variable)と出力(output)だけを公開する仕組みが モジュール。プログラミングの「関数」と同じ役割で、再利用とテストの単位になります。
この章の目次
モジュールとは
Terraform で「モジュール」とは 1 つのディレクトリ を指します。.tf が並んでいれば、もうそれは root モジュール。別ディレクトリを module "..." ブロックで呼び出せば、それは 子モジュール です。
my-infra/
├── main.tf # ← root モジュール
├── variables.tf
├── outputs.tf
└── modules/
└── network/ # ← 子モジュール
├── main.tf
├── variables.tf
└── outputs.tf
呼び出し方
module "network" {
source = "./modules/network"
vpc_cidr = "10.0.0.0/16"
public_subnet_cidrs = ["10.0.1.0/24", "10.0.2.0/24"]
tags = {
Project = "hcl-guide"
Env = "dev"
}
}
# 子モジュールの output は module.<NAME>.<OUTPUT> で参照
resource "aws_instance" "web" {
subnet_id = module.network.public_subnet_ids[0]
vpc_security_group_ids = [module.network.web_sg_id]
# ...
}
source の書き方
| 形式 | 例 | 用途 |
|---|---|---|
| ローカルパス | "./modules/network" | 同じリポジトリ内 |
| Terraform Registry | "hashicorp/consul/aws" | 公開モジュール、version 必須 |
| Git (HTTPS) | "git::https://github.com/org/mod.git//path?ref=v1.2.0" | 社内 Git 等 |
| Git (SSH) | "git::ssh://git@github.com/org/mod.git?ref=v1.2.0" | SSH 認証 |
| S3 / GCS | "s3::https://bucket.s3-region.amazonaws.com/mod.zip" | アーカイブ配布 |
Registry 経由のときは version で SemVer の制約を付けます。
module "vpc" {
source = "terraform-aws-modules/vpc/aws"
version = "~> 5.5"
name = "main"
cidr = "10.0.0.0/16"
azs = ["ap-northeast-1a", "ap-northeast-1c"]
# ...
}
本番では必ず固定
version = "~> 5.5" は「5.5.x の最新」。本番運用では具体バージョンに固定するか、.terraform.lock.hcl で完全固定(こちらは terraform init 時に自動生成)。
入力(variable)と出力(output)の設計
子モジュールは 外との接点 を variable と output だけに絞るのが原則。中身(main.tf)は呼び出し側に見せない設計を心がける。
modules/network/variables.tf
variable "vpc_cidr" {
type = string
description = "VPC の CIDR ブロック"
validation {
condition = can(cidrnetmask(var.vpc_cidr))
error_message = "vpc_cidr は有効な CIDR でなければなりません。"
}
}
variable "public_subnet_cidrs" {
type = list(string)
description = "パブリックサブネットの CIDR ブロック一覧"
}
variable "tags" {
type = map(string)
default = {}
description = "全リソースに付ける共通タグ"
}
modules/network/outputs.tf
output "vpc_id" {
description = "作成した VPC の ID"
value = aws_vpc.this.id
}
output "public_subnet_ids" {
description = "パブリックサブネットの ID 一覧"
value = [for s in aws_subnet.public : s.id]
}
output "web_sg_id" {
description = "Web サーバ用セキュリティグループの ID"
value = aws_security_group.web.id
}
count / for_each をモジュールに付ける
module ブロックそのものに count や for_each を付けて、複数の環境を 1 つの root から作れます。
module "service" {
source = "./modules/service"
for_each = {
api = { cpu = 256, memory = 512 }
worker = { cpu = 1024, memory = 2048 }
}
name = each.key
cpu = each.value.cpu
memory = each.value.memory
}
# 参照
output "api_dns" {
value = module.service["api"].dns_name
}
複数プロバイダを子モジュールに渡す
たとえば「ACM 証明書は us-east-1 で作る」「他は ap-northeast-1」のように、リージョンごとに別 provider を使い分ける場合。子モジュール側で provider を宣言してはいけません(公式推奨)。代わりに configuration_aliases で「受け取り口」を作って、親から providers 引数で渡します。
# 親 (root)
provider "aws" {
alias = "tokyo"
region = "ap-northeast-1"
}
provider "aws" {
alias = "virginia"
region = "us-east-1"
}
module "site" {
source = "./modules/static-site"
providers = {
aws = aws.tokyo # デフォルト
aws.acm = aws.virginia # ACM 用
}
domain_name = "hcl-guide.com"
}
# 子 modules/static-site/terraform.tf
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
configuration_aliases = [aws.acm] # ← 受け取り口を宣言
}
}
}
# 中で使う
resource "aws_acm_certificate" "this" {
provider = aws.acm
domain_name = var.domain_name
validation_method = "DNS"
}
良いモジュール/悪いモジュール
| 良い | 悪い |
|---|---|
| 変数が「呼び出し側の関心」だけ(CIDR、サイズ等) | 「内部実装の引数」が外に漏れている(インスタンスの内部タグ命名規則とか) |
| output が「次のモジュールが必要としそうな ID」 | output が無い/全 attribute をそのまま流すだけ |
| README で「何のための/どう使う」が 5 行以内に書ける | 使い方が読まないとわからない |
| 1 つの責務(VPC、IAM、ECS サービス等) | 「全部入りの巨大モジュール」 |
| 環境差はモジュール外(呼び出し側)に出す | モジュール内に if env == "prd" が散在 |