Terraformで、同じ構成を複数プロビジョニングしたい: ディレクトリ分割編

2022.08.29

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

先日 HashiTalks Japanで「シングルテナント構成のSaaSのIaCにTerraform Workspacesを導入してみた」というビデオ登壇をしました。その中で時間の都合でご紹介できなかった、「Workspacesを使う以外の、同じ構成(リソースセット)を複数個プロビジョニングする方法案」を、複数回に分けてご紹介していきます。

関連エントリ

今回もモジュールを使うパターンのご紹介です。が、複数回applyするのが前回のものとの差分です。

詳細

共通構成部分をモジュール化します。展開先毎にディレクトリを切り、その中で前述のモジュールを呼びます。

dest aとdest bという展開先があるとした場合、以下のようなディレクトリ構成にします。

.
├── dest
│   ├── a
│   │   ├── main.tf
│   │   ├── outputs.tf
│   │   ├── variables.tf
│   │   └── versions.tf
│   └── b
│       ├── main.tf
│       ├── outputs.tf
│       ├── variables.tf
│       └── versions.tf
└── modules
    └── base
        ├── ・ 
        └── ・

dest/a/main.tf

provider "aws" {
  region = "ap-northeast-1"
}

module "base" {
  source = "../../modules/base"
}

dest bのコードも全く同じです。

dest/b/main.tf

provider "aws" {
  region = "ap-northeast-1"
}

module "base" {
  source = "../../modules/base"
}

plan,applyなどの各種Terraformコマンドは dest/a/dest/b/で行ないます。つまり展開先単位でStateファイルを分けます。

お気づきの方も多いと思いますが、この構成はdev/stg/prodといった複数環境(デプロイメントステージって一般的には言うんですかね?)を用意したい際におそらく多くの方が採用されている構成と同じですね。Terraform ベストプラクティスをまとめたエントリでも紹介されています。

以下に記述しますメリット・デメリットは、主に前回の1applyで全展開先にプロビジョニングする構成との対比で記述します。

この構成の良い点

展開先間の差分に対応しやすい

前回のものと同じです。共通部分をモジュール化しているだけなので、展開先間で差分がある場合に対応しやすいです。

使うモジュールを変える

Aではsubモジュールを使うけどBでは使わない例です。

dest/a/main.tf

module "main" {
  source = "../../modules/main"
}

module "sub" {
  source = "../../modules/sub"
}

dest/b/main.tf

module "main" {
  source = "../../modules/main"
}

variableを使ってモジュール内の挙動を変える

dest/a/main.tf

module "base" {
  source = "../../modules/base"

  enable_nat_gateway = true
}

dest/b/main.tf

module "base" {
  source = "../../modules/base"

  enable_nat_gateway = false
}

直接リソース書いてしまっても良い

dest/a/main.tf

module "base" {
  source = "../../modules/base"
}

resource "aws_s3_bucket" "main" {
  bucket_prefix = "dest_a"
}

実行時間が短い

前回の構成は全展開先まとめてapplyする構成でしたので、展開先が増えていくとplanやapplyの実行時間がどんどん伸びていくことになります。今回は展開先毎にコマンドを実行するので、そのような問題は発生しません。

各展開先のプロビジョニングタイミングを任意に決められる

展開先毎にコマンドを実行するので、各展開先のプロビジョニングタイミングは好きに設定可能です。

権限分離がしやすい

各展開先のstateファイルへのアクセス権限を分ければ、展開先毎に操作(=コマンド実行)できるユーザーを分けることが可能です。

provider aliasが不要

前回の構成に比べて今回の構成は provider aliasを書く必要が無いので、コードが少しシンプルになります。

direnvが使える

個人的にはすごく嬉しい部分です。展開先毎にディレクトリが分かれているので、以下のdirenvを使ってクレデンシャルを切り替える仕組みが導入できます。

この構成のイマイチな点

自動化が複雑になる

前回の構成はひとつパイプラインを作ればOKでした。が今回は展開先毎に個別のパイプラインを作成する、もしくは複数の展開先に一括でプロビジョニングできるような少し複雑なパイプラインを用意する必要があります。

展開先の情報がコードに出る、Gitに残る

展開先毎にディレクトリを切ってTerraformの基本設定とmodule呼び出しのコードを書く必要があります。通常それはGitを始めとするVCSに記録されるでしょう。(何らかの方法で動的にコード生成すれば回避できるかもしれませんが…)

要件次第ではこれは好ましいことですが、そうでない場合もあるでしょう。例えばシングルテナント構成のSaaSを作るとなった場合、テナントの情報をコードやGitに残すことはしたくないと思います。

ユースケース

すでにご紹介しましたが、dev/stg/prodといった複数環境を用意するような

  • 作成するリソースに差分がある
  • プロビジョニングするタイミングや頻度が異なる
  • 展開先の重要度が異なる

ようなケースに合う構成かなと思います。