Terraformのモジュールを使ってコードを再利用する
はじめに
これまでの学習で、変数を使ってTerraformコードを柔軟にすることはできるようになりました。しかし、VPCやEC2など、複数のリソースを組み合わせたインフラを毎回手動で定義するのは効率的ではありません。今回は、これらのまとまりを「部品」として再利用可能にするための重要な機能、 モジュール(Modules) について解説します。
1. モジュールとは?
モジュールは、関連する複数のリソースを一つの論理的な単位としてカプセル化(まとめる)する機能です。これにより、複雑なインフラをシンプルに、そして再利用可能な形で管理できます。
例えば、VPCと複数のサブネット、ルートテーブル、インターネットゲートウェイといったネットワーク関連のリソース群を一つの「ネットワークモジュール」として作成し、他のプロジェクトで簡単に呼び出して使えるようになります。
2. モジュールの種類
Terraformには、主に2種類のモジュールがあります:
-
ルートモジュール:
terraform plan
やterraform apply
を実行するディレクトリそのものです。これまでのハンズオンで作成してきたコードは、すべてルートモジュールに属します。 - 子モジュール: ルートモジュールから呼び出される、再利用可能なコードブロックです。
3. モジュールの基本的な使い方
モジュールは、module
ブロックを使って呼び出します。実際の例を見てみましょう。
ステップ1:子モジュールの作成
まず、再利用したいコードを別のディレクトリに配置します。例えば、modules/vpc
というディレクトリを作成し、その中に必要なTerraformファイルを配置します。
ディレクトリ構造
project/
├── main.tf
├── variables.tf
├── outputs.tf
└── modules/
└── vpc/
├── main.tf
├── variables.tf
└── outputs.tf
modules/vpc/variables.tf
variable "vpc_cidr" {
description = "CIDR block for VPC"
type = string
default = "10.0.0.0/16"
}
variable "public_subnet_cidr" {
description = "CIDR block for public subnet"
type = string
default = "10.0.1.0/24"
}
variable "availability_zone" {
description = "Availability zone for subnet"
type = string
default = "ap-northeast-1a"
}
variable "project_name" {
description = "Name of the project"
type = string
}
variable "environment" {
description = "Environment name"
type = string
default = "dev"
}
modules/vpc/main.tf
# VPCリソース
resource "aws_vpc" "this" {
cidr_block = var.vpc_cidr
enable_dns_hostnames = true
enable_dns_support = true
tags = {
Name = "${var.project_name}-${var.environment}-vpc"
Environment = var.environment
}
}
# インターネットゲートウェイ
resource "aws_internet_gateway" "this" {
vpc_id = aws_vpc.this.id
tags = {
Name = "${var.project_name}-${var.environment}-igw"
Environment = var.environment
}
}
# パブリックサブネット
resource "aws_subnet" "public_subnet" {
vpc_id = aws_vpc.this.id
cidr_block = var.public_subnet_cidr
availability_zone = var.availability_zone
map_public_ip_on_launch = true
tags = {
Name = "${var.project_name}-${var.environment}-public-subnet"
Environment = var.environment
Type = "Public"
}
}
# ルートテーブル(パブリック)
resource "aws_route_table" "public" {
vpc_id = aws_vpc.this.id
route {
cidr_block = "0.0.0.0/0"
gateway_id = aws_internet_gateway.this.id
}
tags = {
Name = "${var.project_name}-${var.environment}-public-rt"
Environment = var.environment
}
}
# ルートテーブルアソシエーション
resource "aws_route_table_association" "public" {
subnet_id = aws_subnet.public_subnet.id
route_table_id = aws_route_table.public.id
}
modules/vpc/outputs.tf
output "vpc_id" {
description = "ID of the VPC"
value = aws_vpc.this.id
}
output "vpc_cidr_block" {
description = "CIDR block of the VPC"
value = aws_vpc.this.cidr_block
}
output "public_subnet_id" {
description = "ID of the public subnet"
value = aws_subnet.public_subnet.id
}
output "internet_gateway_id" {
description = "ID of the Internet Gateway"
value = aws_internet_gateway.this.id
}
ステップ2:ルートモジュールからの呼び出し
次に、ルートモジュール(main.tf
があるディレクトリ)から、先ほど作成した子モジュールを呼び出します。
main.tf
terraform {
required_version = ">= 1.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
provider "aws" {
region = "ap-northeast-1"
}
# VPCモジュールを呼び出す
module "my_network" {
source = "./modules/vpc" # 子モジュールのパスを指定
vpc_cidr = "10.0.0.0/16"
public_subnet_cidr = "10.0.1.0/24"
availability_zone = "ap-northeast-1a"
project_name = "my-project"
environment = "dev"
}
# 最新のAmazon Linux 2 AMIを取得
data "aws_ami" "amazon_linux" {
most_recent = true
owners = ["amazon"]
filter {
name = "name"
values = ["amzn2-ami-hvm-*-x86_64-gp2"]
}
}
# セキュリティグループ
resource "aws_security_group" "web_sg" {
name_prefix = "web-sg-"
vpc_id = module.my_network.vpc_id
ingress {
from_port = 22
to_port = 22
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
ingress {
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
tags = {
Name = "web-security-group"
}
}
# EC2インスタンス
resource "aws_instance" "web_server" {
ami = data.aws_ami.amazon_linux.id
instance_type = "t3.micro"
subnet_id = module.my_network.public_subnet_id
vpc_security_group_ids = [aws_security_group.web_sg.id]
tags = {
Name = "web-server"
}
}
module
ブロック内のsource
で、呼び出したい子モジュールのパスを指定します。子モジュールのvariable
に値を渡すことで、再利用時にカスタマイズできます。output
は、module.<モジュール名>.<出力名>
の形式で参照できます。
4. モジュールの実行手順
モジュールを含むTerraformコードを実行する際の手順:
# 1. 初期化(モジュールのダウンロード)
terraform init
# 2. プランの確認
terraform plan
# 3. 実行
terraform apply
terraform init
を実行すると、Terraformはモジュールを.terraform/modules/
ディレクトリにダウンロード・コピーします。
5. モジュールのベストプラクティス
ファイル構成
モジュールは以下の3つのファイルに分けることを推奨します:
-
main.tf
: リソース定義 -
variables.tf
: 入力変数の定義 -
outputs.tf
: 出力値の定義
変数の型指定と説明
variable "instance_count" {
description = "Number of EC2 instances to create"
type = number
default = 1
validation {
condition = var.instance_count > 0 && var.instance_count <= 10
error_message = "Instance count must be between 1 and 10."
}
}
バージョン管理
本格的な運用では、モジュールをGitリポジトリで管理し、タグを使ってバージョンを指定します:
module "vpc" {
source = "git::https://github.com/your-org/terraform-aws-vpc.git?ref=v1.2.0"
# 変数の指定...
}
6. モジュールのメリット
メリット | 説明 |
---|---|
再利用性の向上 | 一度作成したモジュールは、複数のプロジェクトで簡単に使い回せる |
コードの抽象化 | 複雑なリソース群を一つのブロックとして扱い、コード全体をシンプルにする |
保守性の向上 | モジュール内で変更が必要な場合、そのモジュールだけを修正すればよい |
標準化の促進 | チーム内でのインフラ構成を統一し、ベストプラクティスを共有できる |
テストしやすさ | 個別のモジュール単位でテストが可能 |
7. 注意点
モジュールの変更時
モジュールを変更した場合は、以下のコマンドでモジュールを更新します:
terraform get -update
循環参照の回避
モジュール間で循環参照が発生しないよう、依存関係を明確に設計しましょう。
セキュリティ
外部のモジュールを使用する際は、コードの内容を十分に確認してからご使用ください。
まとめ
モジュールは、Terraformを大規模なプロジェクトで活用する上で不可欠な機能です。VPCやセキュリティグループ、EC2など、関連するリソースをモジュールとして分割することで、コードの重複をなくし、より保守性の高いインフラ管理を実現できます。
次回は、これまでのTerraformの知識を総動員して、より実践的なWordPress環境の構築に挑戦します。お楽しみに!