はじめに
前回の記事で Terraform の 12 のブロックを復習しました。
module ブロックでやっていることは、resource ブロックだけでも書けるのでは?
実際、module は必須ではありません。では、どういうときに module にした方がよいのか。公式ドキュメントの考え方を確認しつつ、実際に動かしてみましょう。
先に結論です。
resource のまま書く |
module にする |
|
|---|---|---|
| 向いている場面 | 1 回しか作らない/1 つのリソースだけ | 同じまとまりを何度も作る |
| うれしいこと | 読みやすい。ファイルを追わなくてよい | 設定の付け忘れがなくなる。追加が数行で済む |
| 注意点 | コピーが増えると、直し漏れが起きやすい | 使いすぎると、かえって読みにくくなる |
題材は、実務でよくある「社内標準の S3 バケット」です。バケットを作るたびに、次の 4 つのリソースをセットで付けるルールがあるとします。
-
aws_s3_bucket:バケット本体 -
aws_s3_bucket_public_access_block:パブリックアクセスをすべてブロックする -
aws_s3_bucket_versioning:バージョニングを有効にする -
aws_s3_bucket_server_side_encryption_configuration:暗号化を設定する
空のバケットだけを使うので、料金はほぼかかりません。 最後に全部削除します。
最終的に次のファイル構成になります。
tf-module-demo/
├── versions.tf # 手順 1
├── main.tf # 手順 1(手順 2 で上書き)
├── modules/
│ └── secure_bucket/
│ └── main.tf # 手順 2(部品側)
└── data.tf # 手順 3
やってみた
手順 0:準備
terraform version
aws sts get-caller-identity
mkdir tf-module-demo && cd tf-module-demo
以降のファイルは、すべてこの tf-module-demo ディレクトリを基準に作成します。
手順 1:resource だけで書いた場合を確認する(versions.tf, main.tf)
-
resource:実際に作るリソースです
この手順では、モジュールを使わずにログ用のバケットを書き、plan で中身を確認します。社内標準を守るには、バケット 1 つにつき 4 つの resource が必要です。比較のための手順なので、作成はしません。
プロバイダとアカウント ID の取得は versions.tf に書きます。
terraform {
required_version = ">= 1.7"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 6.0"
}
}
}
provider "aws" {
region = "ap-northeast-1"
}
data "aws_caller_identity" "current" {}
バケットと 3 つの設定は main.tf に書きます。
resource "aws_s3_bucket" "logs" {
bucket = "tf-module-demo-logs-${data.aws_caller_identity.current.account_id}"
force_destroy = true
}
resource "aws_s3_bucket_public_access_block" "logs" {
bucket = aws_s3_bucket.logs.id
block_public_acls = true
block_public_policy = true
ignore_public_acls = true
restrict_public_buckets = true
}
resource "aws_s3_bucket_versioning" "logs" {
bucket = aws_s3_bucket.logs.id
versioning_configuration {
status = "Enabled"
}
}
resource "aws_s3_bucket_server_side_encryption_configuration" "logs" {
bucket = aws_s3_bucket.logs.id
rule {
apply_server_side_encryption_by_default {
sse_algorithm = "AES256"
}
}
}
terraform init
terraform plan
Plan: 4 to add, 0 to change, 0 to destroy. と表示されれば OK です。
1 つだけなら resource だけで問題ありません。ただ、バケットがもう 1 つ必要になったら、この約 30 行を名前を変えてコピーすることになります。 標準の設定を 1 つ変えたくなったら、コピーした箇所をすべて直す必要があります。
手順 2:モジュールに切り出す(modules/secure_bucket/main.tf, main.tf)
-
module:ブロックのまとまりを「部品」として呼び出す仕組みです。引数(variable)を受け取り、戻り値(output)を返します
この手順では、4 つのリソースを secure_bucket モジュールにまとめ、main.tf をモジュールの呼び出しに書き換えます。呼び出す側が渡すのは、バケットの用途(name)だけです。
まず部品側を作ります。ファイルは modules/ の直下ではなく、modules/secure_bucket/ の中に置きます。 modules/secure_bucket/ の中が空のままだと、次の terraform init で An argument named "name" is not expected here. エラーになります。
mkdir -p modules/secure_bucket
variable "name" {
type = string
description = "バケットの用途(logs, data など)"
}
data "aws_caller_identity" "current" {}
resource "aws_s3_bucket" "this" {
bucket = "tf-module-demo-${var.name}-${data.aws_caller_identity.current.account_id}"
force_destroy = true
}
resource "aws_s3_bucket_public_access_block" "this" {
bucket = aws_s3_bucket.this.id
block_public_acls = true
block_public_policy = true
ignore_public_acls = true
restrict_public_buckets = true
}
resource "aws_s3_bucket_versioning" "this" {
bucket = aws_s3_bucket.this.id
versioning_configuration {
status = "Enabled"
}
}
resource "aws_s3_bucket_server_side_encryption_configuration" "this" {
bucket = aws_s3_bucket.this.id
rule {
apply_server_side_encryption_by_default {
sse_algorithm = "AES256"
}
}
}
output "bucket_name" {
value = aws_s3_bucket.this.bucket
}
次に、main.tf を次の内容で上書きします。
module "logs" {
source = "./modules/secure_bucket"
name = "logs"
}
モジュールを追加したので terraform init をやり直してから、apply します。
terraform init
terraform apply -auto-approve
Apply complete! Resources: 4 added と表示されれば OK です。 呼び出し側は 5 行ですが、4 つのリソースがすべて作られています。
手順 3:2 つ目のバケットを 5 行で追加する(data.tf)
この手順では、データ用のバケットを追加します。モジュールがあれば、呼び出しを 5 行書くだけです。
module "data" {
source = "./modules/secure_bucket"
name = "data"
}
新しい module ブロックを足したときも terraform init が必要です。 同じモジュールを使っていても、init せずに plan すると Module not installed エラーになります。
terraform init
terraform apply -auto-approve
terraform state list
Apply complete! Resources: 4 added と表示され、state list に module.data. で始まる 4 つのリソースが出れば OK です。 標準の 3 つの設定は、書き忘れようがありません。
手順 4:後片付け
この手順では、作ったバケットをすべて削除します。
terraform destroy -auto-approve
Destroy complete! Resources: 8 destroyed. と表示されれば完了です。
どういうときに module にするべきか
公式ドキュメントの「When to write a module」には、次のように書かれています。
- モジュールを使いすぎると、構成が理解しにくく、保守しにくくなるので、ほどほどにすることを勧めている
- 良いモジュールは、プロバイダのリソースを組み合わせて、新しい概念を表すもの
-
1 つのリソースを包むだけの薄いモジュールは推奨しない。中のリソースタイプと違う名前を付けられないなら、新しい概念を作れていないサインで、
resourceをそのまま使えばよい
今回の secure_bucket は、4 つのリソースで「社内標準のバケット」という概念を表していて、s3_bucket とは違う名前が付けられます。一方、前回の記事で作った ssm_param モジュールは、aws_ssm_parameter を 1 つ包んだだけなので、公式が推奨しない形でした(ブロックの復習用だったので、あえてそうしています)。
まとめ
-
moduleは必須ではありません。1 回しか作らないならresourceのままで十分です -
同じまとまりを何度も作るなら
moduleにします。 追加が数行で済み、標準の設定も付け忘れません - ただし、1 つのリソースを包むだけのモジュールは公式も推奨していません。 「中のリソースタイプと違う名前を付けられるか」が判断の目安になります