0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Terraform の module ブロックは必要? resource で書けるのに使う理由確かめる

0
Posted at

はじめに

前回の記事で 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:実際に作るリソースです

resource ブロック

この手順では、モジュールを使わずにログ用のバケットを書き、plan で中身を確認します。社内標準を守るには、バケット 1 つにつき 4 つの resource が必要です。比較のための手順なので、作成はしません。

プロバイダとアカウント ID の取得は versions.tf に書きます。

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 に書きます。

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)を返します

module ブロック

この手順では、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
modules/secure_bucket/main.tf
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 を次の内容で上書きします。

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 行書くだけです。

data.tf
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 つのリソースを包むだけのモジュールは公式も推奨していません。 「中のリソースタイプと違う名前を付けられるか」が判断の目安になります
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?