Terraformのmoduleとoutput

2022.09.09

データアナリティクス事業部でGoogle Cloudのデータエンジニアをしています。はんざわです。

Terraformにはmoduleという他のプログラミング言語でいう所の関数のようなものが存在します。今回はmoduleの使い方を改めて学習したのでそのことをまとめておきます。
また、複数のCloudサービスをmoduleで管理する際にmodule間での変数の受け渡しに困ったのでそれについても調べたことをまとめておきます。

環境

Google Cloud Shell

terraform:v1.2.7

構成図

output_test/
└── terraform
    ├── environments
    │   ├── test1
    │   │   ├── main.tf
    │   │   └── terraform.tfvars
    │   ├── test2
    │   │   ├── main.tf
    │   │   └── terraform.tfvars
    │   └── test3
    │       ├── main.tf
    │       └── terraform.tfvars
    └── modules
        ├── cloud_storage
        │   ├── main.tf
        │   ├── output.tf
        │   └── variables.tf
        └── storage_bucket_object
            ├── main.tf
            └── variables.tf

8 directories, 11 files

構成はベストプラクティスを参考にしています。

検証

冒頭でも説明した通り、Terraformにはmoduleという他のプログラミング言語でいう所の関数のようなものが存在します。この関数の中でCloudサービスのリソースを定義し、それを変数と一緒に呼び出すことでその変数通りのCloudサービスを構築することができます。

今回の検証ではmodulesディレクトリでCloudサービスのリソースを定義し、environmentsディレクトリの各testリポジトリでmodulesディレクトリから必要なリソースを呼び出しています。

下記は、modules配下のCloud Storageディレクトリの主要ファイルの中身です。

# modules/cloud_storage/main.tf
resource "google_storage_bucket" "bucket" {
  name     = var.bucket_name
  location = var.bucket_location
}

# modules/cloud_storage/variables.tf
variable "bucket_name" {
  type = string
}

variable "bucket_location" {
  type    = string
  default = "US-CENTRAL1"
}

main.tfで使用するCloudサービスを定義し、variables.tfで使用している変数を宣言しています。defaultが設定されている変数は何も値を与えないとその値で作成されます。

この例では、bucket_namebucket_locationの2つの変数が宣言されています。bucket_locationにはUS-CENTRAL1がdefault値として設定されいるため、何も設定しないとUS-CENTRAL1で作成されます。

1. とりあえずCloud Storageをmoduleで構築する

ここではシンプルにmodulesで定義したリソースを呼び出し、Cloud Storageを構築します。

test1ディレクトリ配下のmain.tfは以下のような構成になっています。

# test1/main.tf
# 変数定義
variable project_id {}

provider "google" {
  project = var.project_id
}

# ロケーションを指定せずにバケットを作成
module "test1_bucket1" {
  source      = "../../modules/cloud_storage/"
  bucket_name = "test1-bucket1-yuya-hanzawa"
}

# ロケーションを指定してバケットを作成
module "test1_bucket2" {
  source          = "../../modules/cloud_storage/"
  bucket_name     = "test1-bucket2-yuya-hanzawa"
  bucket_location = "ASIA-NORTHEAST1"
}

test1_bucket1ではbucket_locationを代入せず、test1_bucket2ではbucket_locationASIA-NORTHEAST1を代入しました。あとはtest1ディレクトリ配下でtf inittf plantf applyと順に実行すればファイルが実行されます。

想定通り作れていました。

2. output.tfファイルを理解する

そもそもoutput.tfとは何者なのか。一言で表すなら関数の戻り値を定義するファイルです。 cloud_storageディレクトリ配下のoutput.tfは以下のようになっています。

# modules/cloud_storage/output.tf
output "bucket_name" {
  value = google_storage_bucket.bucket.name
}

output "location_name" {
  value = google_storage_bucket.bucket.location
}

このファイルで定義されているoutputはこのディレクトリのリソースを呼び出した時に参照することが可能です。 test2ディレクトリのmain.tfは以下のような構成をしています。

# test2/main.tf
# 変数定義
variable project_id {}

provider "google" {
  project = var.project_id
}

# bucketを作成
module "test2_bucket1" {
  source          = "../../modules/cloud_storage/"
  bucket_name     = "test2-bucket1-yuya-hanzawa"
  bucket_location = "ASIA-NORTHEAST1"
}

# 全てのアウトプットを取得
output "bucket_output_all" {
  value = module.test2_bucket1
}

# バケットの名前を取得
output "bucket_output_name" {
  value = module.test2_bucket1.bucket_name
}

# バケットのロケーションを取得
output "bucket_output_location" {
  value = module.test2_bucket1.location_name
}

このファイルのvalue = module.test2_bucket1の部分でmodules配下のoutput.tfで定義したoutput valueを呼び出しています。 ここでoutput.tfを作成していないとこの部分はで返ってきます。 上記ファイルの実行結果は以下の通りです。

Changes to Outputs:
  + bucket_output_all      = {
      + bucket_name   = "test2-bucket1-yuya-hanzawa"
      + location_name = "ASIA-NORTHEAST1"
    }
  + bucket_output_location = "ASIA-NORTHEAST1"
  + bucket_output_name     = "test2-bucket1-yuya-hanzawa"

期待通りの結果になっていました。

3. ファイルをstorage bucketに転送する

最後にoutput.tfからbucketの名前を取得し、そのbucketにファイルを転送してみます。 ファイルを転送するmoduleは以下の通りです。

# modules/storage_bucket_object/main.tf
data "archive_file" "zip_file" {
  type        = "zip"
  source_dir  = var.source_dir
  output_path = var.output_path
}

resource "google_storage_bucket_object" "upload_files" {
  name   = var.file_name
  bucket = var.archive_bucket
  source = "${data.archive_file.zip_file.output_path}"
}

上記のmoduleはtest3ディレクトリのmain.tfで呼び出されています。

# test3/main.tf
# 変数定義
variable project_id {}

provider "google" {
  project = var.project_id
}

# bucket1を作成
module "test3_bucket1" {
    source      = "../../modules/cloud_storage/"
    bucket_name = "test3-bucket1-yuya-hanzawa"
}

# terraformディレクトリ配下のファイルをzip形式でbucket1に転送
module "test3_upload1" {
    source         = "../../modules/storage_bucket_object"
    source_dir    = "../../"
    output_path    = "/tmp/terraform.zip"
    file_name      = "terraform.zip"
    archive_bucket = module.test3_bucket1.bucket_name
}

上記のtest3_upload1moduleのarchive_bucket = module.test3_bucket1.bucket_nametest3_bucket1のバケット名を取得しています。
このようにoutput.tfを適切に設定することで異なるmodule間で変数を呼び出すことが可能になります

まとめ

同一のクラウドサービスを複数個作成する場合、moduleを適切に管理すると可読性が上がり、管理しやすくなると思います。
まだまだDevelopersIO内でGoogle CloudのTerraformに関する記事は少ないのでもっと書いていきたいと思っています。

参照元

Terraform ベストプラクティスを整理してみました。
Google Cloud Terraform を使用するためのベスト プラクティス
google_storage_bucket | Resources | hashicorp/google