先日 HashiTalks Japanで「シングルテナント構成のSaaSのIaCにTerraform Workspacesを導入してみた」というビデオ登壇をしました。その中で時間の都合でご紹介できなかった、「Workspacesを使う以外の、同じ構成(リソースセット)を複数個プロビジョニングする方法案」を、複数回に分けてご紹介していきます。
関連エントリ
- #HashiTalks Japanで「シングルテナント構成のSaaSのIaCにTerraform Workspacesを導入してみた」という発表をしました
- Terraformで、同じ構成を複数プロビジョニングしたい: Module編 👈 イマココ
- Terraformで、同じ構成を複数プロビジョニングしたい: ディレクトリ分割編
- Terraformで、同じ構成を複数プロビジョニングしたい: Terragruntでrun-all編
- Terraformで、同じ構成を複数プロビジョニングしたい: backend-configオプション編
- Terraformで、同じ構成を複数プロビジョニングしたい: Terragruntでbackendを動的設定編
今回はモジュールを使うパターンのご紹介です。
詳細
dest_aとdest_bという展開先があるとします。
ディレクトリ構成
.
├── modules
│ └── base
│ ├──・
│ └──・
├── dest_a.tf
├── dest_b.tf
├── outputs.tf
├── variables.tf
└── versions.tf
dest_a.tf
provider "aws" {
alias = "dest_a"
assume_role {
role_arn = "arn:aws:iam::111111111111:role/terraform"
}
}
module "dest_a_base" {
source = "./modules/base"
providers = {
aws = aws.dest_a
}
}
dest_b.tf
provider "aws" {
alias = "dest_b"
assume_role {
role_arn = "arn:aws:iam::222222222222:role/terraform"
}
}
module "dest_b_base" {
source = "./modules/base"
providers = {
aws = aws.dest_b
}
}
dev/stg/prodのように複数環境が必要な場合は以下のようにし、 env/(dev|stg|prod)
で各種terraform コマンドを実行します。
.
├── env
│ ├── dev
│ │ ├── dest_a.tf
│ │ ├── dest_b.tf
│ │ ├── outputs.tf
│ │ ├── variables.tf
│ │ └── versions.tf
│ ├── prod
│ │ ├── ・
│ │ └── ・
│ └── stg
│ ├── ・
│ └── ・
└── modules
└── base
├── ・
└── ・
プロビジョニングするリソースの数が多い場合はモジュールを複数個に分けることも考えましょう。
.
├── env
│ ├── dev
│ │ ├── dest_a.tf
│ │ ├── dest_b.tf
│ │ ├── outputs.tf
│ │ ├── variables.tf
│ │ └── versions.tf
│ ├── prod
│ │ ├── ・
│ │ └── ・
│ └── stg
│ ├── ・
│ └── ・
└── modules
├── app
│ ├── ・
│ └── ・
├── db
│ ├── ・
│ └── ・
├── network
│ ├── ・
│ └── ・
└── web
├── ・
└── ・
この構成の良い点
シンプル
一度のterraform apply
だけで全展開先にプロビジョニングできるのでシンプルです。CDパイプラインを作る際もシンプルにできるでしょう。
展開先間の差分に対応しやすい
共通部分をモジュール化しているだけなので、展開先間で差分がある場合に対応しやすいです。
使うモジュールを変える
Aではsubモジュールを使うけどBでは使わない例です。
dest_a.tf
module "dest_a_main" {
source = "./modules/main"
providers = {
aws = aws.dest_a
}
}
module "dest_a_sub" {
source = "./modules/sub"
providers = {
aws = aws.dest_a
}
}
dest_b.tf
module "dest_b_main" {
source = "./modules/main"
providers = {
aws = aws.dest_b
}
}
variableを使ってモジュール内の挙動を変える
dest_a.tf
module "dest_a_base" {
source = "./modules/base"
providers = {
aws = aws.dest_a
}
enable_nat_gateway = true
}
dest_b.tf
module "dest_b_base" {
source = "./modules/base"
providers = {
aws = aws.dest_b
}
enable_nat_gateway = false
}
直接リソース書いてしまっても良い
dest_a.tf
module "dest_a_base" {
source = "./modules/base"
providers = {
aws = aws.dest_a
}
}
resource "aws_s3_bucket" "dest_a" {
bucket_prefix = "dest_a"
}
この構成のイマイチな点
実行時間が長くなる
メリットに書いた「一度に全展開先にプロビジョニングできるシンプルさ」と引き換えに、planやapplyなど各terraformコマンドの実行時間は長くなります。そのため展開先が非常にたくさんある場合には適さないでしょう。
この構成を採る場合は、並列実行数を上げてterraformコマンド実行を高速化することを検討するのが良いでしょう。
展開先の情報がコードに出る、Gitに残る
ここまでご紹介したサンプルコードがそうであるように、展開先毎にproviderの設定とmodule呼び出しのコードを書く必要があります。通常それはGitを始めとするVCSに記録されるでしょう。(何らかの方法で動的にコード生成すれば回避できるかもしれませんが…)
要件次第ではこれは好ましいことですが、そうでない場合もあるでしょう。例えばシングルテナント構成のSaaSを作るとなった場合、テナントの情報をコードやGitに残すことはしたくないと思います。
ユースケース
DRとして、メインリージョンとは別にホットスタンバイで別リージョンにも同じリソースセットを用意したい、といった場合にはハマるんじゃないかと思います。あまり展開先が増えない要件が適していると言えるでしょう。