Terraform を使ってインフラをコードで管理しているとします。開発環境、本番環境のように複数の環境を管理する場合の Terraform のコード構成例を紹介します。
3つの方法が考えられ、それぞれ紹介します。
- 環境ごとにディレクトリを分ける方法
tfvarsファイルを使う方法- ワークスペース機能を使う方法
1. 環境ごとにディレクトリを分ける方法 #
このような構成になります。(* をつけているのは Terraform 実行後に自動生成されるファイルです。)
├ envs/
│ ├ dev/
│ │ ├ main.tf
│ │ ├ .terraform.lock.hcl *
│ │ ├ terraform.tfstate *
│ │ └ .terraform/ *
│ └ prod/
│ ├ main.tf
│ ├ .terraform.lock.hcl *
│ ├ terraform.tfstate *
│ └ .terraform/ *
└ modules/
└ hello/
└ main.tf
コード例 #
envs/dev/main.tf
terraform {
backend "local" {}
}
terraform {
required_version = ">= 1.13.0, < 2.0.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 6.0"
}
}
}
provider "aws" {
region = "ap-northeast-1"
}
locals {
my_name = "dev"
}
module "hello" {
my_name = local.my_name
source = "../../modules/hello"
}
envs/prod/main.tf
terraform {
backend "local" {}
}
terraform {
required_version = ">= 1.13.0, < 2.0.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 6.0"
}
}
}
provider "aws" {
region = "ap-northeast-1"
}
locals {
my_name = "prod"
}
module "hello" {
my_name = local.my_name
source = "../../modules/hello"
}
modules/hello/main.tf
variable "my_name" {
type = string
}
resource "terraform_data" "this" {
provisioner "local-exec" {
command = "echo 'Hello, ${var.my_name}!'"
}
}
コマンド実行例 #
対象の環境ディレクトリに移動してから実行する流れとなります。
cd envs/dev
terraform init
terraform apply
cd envs/prod
terraform init
terraform apply
あるいは -chdir オプションを使う方法もあります。
terraform -chdir=envs/dev init
terraform -chdir=envs/dev apply
terraform -chdir=envs/prod init
terraform -chdir=envs/prod apply
解説 #
環境ごとにエントリポイントとなる main.tf を分ける方法です。
- メリット
- 環境によって構成に違いがある場合に有効な方法です。ある環境では A モジュールを使い、別の環境では B モジュールを使うというように、構成を柔軟に変えることができます。
- デメリット
envs/dev/main.tf、envs/prod/main.tfを見ると分かるようにコードの重複記述も多くなる欠点は許容しなくてはいけません。terraform initでのモジュールのダウンロードは環境ごとに実行されるため、同じモジュールを使っていても環境の数だけダウンロードが発生します。
2. tfvars ファイルを使う方法
#
このような構成になります。(* をつけているのは Terraform 実行後に自動生成されるファイルです。)
├ main.tf
├ .terraform.lock.hcl *
├ dev.tfstate *
├ prod.tfstate *
├ .terraform/ *
├ envs/
│ ├ dev/
│ │ ├ backend.config
│ │ └ terraform.tfvars
│ └ prod/
│ ├ backend.config
│ └ terraform.tfvars
└ modules/
└ hello/
└ main.tf
コード例 #
main.tf
terraform {
backend "local" {}
}
terraform {
required_version = ">= 1.13.0, < 2.0.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 6.0"
}
}
}
variable "region" {
type = string
}
variable "my_name" {
type = string
}
provider "aws" {
region = var.region
}
module "hello" {
my_name = var.my_name
source = "./modules/hello"
}
envs/dev/backend.config
path = "dev.tfstate"
envs/dev/terraform.tfvars
region = "ap-northeast-1"
my_name = "dev"
envs/prod/backend.config
path = "prod.tfstate"
envs/prod/terraform.tfvars
region = "ap-northeast-1"
my_name = "prod"
modules/hello/main.tf
「1. 環境ごとにディレクトリを分ける方法」と同じ
コマンド実行例 #
-var-file オプションと -backend-config オプションを使って実行する流れになります。
terraform init -backend-config="envs/dev/backend.config" -reconfigure
terraform apply -var-file="envs/dev/terraform.tfvars"
terraform init -backend-config="envs/prod/backend.config" -reconfigure
terraform apply -var-file="envs/prod/terraform.tfvars"
なお、terraform init の際に -reconfigure オプションをつけないと以下のようなエラーになります。(正確には1回目の -backend-config="envs/dev/backend.config" 時だけは -reconfigure 不要です。)
terraform init -backend-config="envs/prod/backend.config"
Initializing the backend...
Initializing modules...
╷
│ Error: Backend configuration changed
│
│ A change in the backend configuration has been detected, which may require migrating existing state.
│
│ If you wish to attempt automatic migration of the state, use "terraform init -migrate-state".
│ If you wish to store the current configuration with no changes to the state, use "terraform init -reconfigure".
╵
解説 #
エントリポイントとなる main.tf は1つにまとめ、環境ごとに tfvars ファイルとバックエンド設定ファイルを分けて呼び出す方法です。
- メリット
- 環境ごとのコードの重複記述を減らすことができます。
tfvarsによって環境ごとのパラメータを柔軟に変更できます。
- デメリット
- 「1. 環境ごとにディレクトリを分ける方法」では実現できるが、本方式では実現できない環境分けがあります。詳細は後述します。
3. ワークスペース機能を使う方法 #
このような構成になります。(* をつけているのは Terraform 実行後に自動生成されるファイルです。)
├ main.tf
├ .terraform.lock.hcl *
├ terraform.tfstate.d/ *
│ ├ dev/ *
│ │ └ terraform.tfstate *
│ └ prod/ *
│ └ terraform.tfstate *
├ .terraform/ *
└ modules/
└ hello/
└ main.tf
コード例 #
main.tf
terraform {
backend "local" {}
}
terraform {
required_version = ">= 1.13.0, < 2.0.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 6.0"
}
}
}
check "workspace_check" {
assert {
condition = contains(["dev", "prod"], terraform.workspace)
error_message = "Invalid workspace: ${terraform.workspace}."
}
}
provider "aws" {
region = (
terraform.workspace == "dev" ? "ap-northeast-1" :
terraform.workspace == "prod" ? "ap-northeast-1" : ""
)
}
locals {
my_name = (
terraform.workspace == "dev" ? "dev" :
terraform.workspace == "prod" ? "prod" : ""
)
}
module "hello" {
my_name = local.my_name
source = "./modules/hello"
}
modules/hello/main.tf
「1. 環境ごとにディレクトリを分ける方法」と同じ
コマンド実行例 #
terraform workspace コマンドでワークスペースを作成・選択してから実行する流れになります。
terraform workspace new dev
terraform workspace new prod
terraform workspace select dev
terraform workspace list
> default
> * dev
> prod
terraform init
terraform apply
terraform workspace select prod
terraform workspace list
> default
> dev
> * prod
terraform init
terraform apply
なお .terraform/ ディレクトリ直下に environment というファイルが自動生成されています。中身は以下のように現在選択されているワークスペース名が記載されているだけです。terraform workspace コマンドはこのファイルを参照して現在指定されている環境を判断しています。
dev
解説 #
エントリポイントとなる main.tf は1つにまとめ、環境ごとに tfvars ファイルとバックエンド設定ファイルを分けて呼び出す方法です。
- メリット
- 特になし。(「2.
tfvarsファイルを使う方法」と比較してあまり優位性はありません。)
- 特になし。(「2.
- デメリット
- コード中で分岐処理を書く必要があり、コードが煩雑になります。
どの方法を使うべきか? #
結論から言うと、最初に紹介した「1. 環境ごとにディレクトリを分ける方法」を推奨します。
まず「3. ワークスペース機能を使う方法」ですが、コード中に分岐処理を書く必要があり、コードが煩雑になるだけのため避けた方が良いでしょう。この方法を取るよりも「2. tfvars ファイルを使う方法」を使う方が良いです。
そもそもワークスペース機能が想定しているユースケースが今回の目的とは異なっています。ワークスペース機能の説明として以下のコメントがあります。要約すると、「dev, stg, prod」のように環境分けするための機能ではなくて、「dev-1, dev-2, dev-3」のように同一構成の並列コピーを作成するのが想定されたユースケースのようです。
Use Cases
A common use for multiple workspaces is to create a parallel, distinct copy of a set of infrastructure to test a set of changes before modifying production infrastructure.
https://developer.hashicorp.com/terraform/cli/workspaces#use-cases
When Not to Use Multiple Workspaces
When using Terraform to manage larger systems, you should create separate Terraform configurations that correspond to architectural boundaries within the system. This lets teams manage different components separately. Workspaces alone are not a suitable tool for system decomposition because each subsystem should have its own separate configuration and backend.
In particular, organizations commonly want to create a strong separation between multiple deployments of the same infrastructure serving different development stages or different internal teams. In this case, the backend for each deployment often has different credentials and access controls. CLI workspaces within a working directory use the same backend, so they are not a suitable isolation mechanism for this scenario.
https://developer.hashicorp.com/terraform/cli/workspaces#when-not-to-use-multiple-workspaces
次に「1. 環境ごとにディレクトリを分ける方法」と「2. tfvars ファイルを使う方法」の比較ですが、前者には実現できて後者では実現できないことがあります。それがプロバイダーのバージョンの切り替えです。
例えば以下にある aws プロバイダーについて、バージョン 7 系がリリースされたとします。
terraform {
required_version = ">= 1.13.0, < 2.0.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 6.0"
}
}
}
このとき、① まず dev 環境にバージョン 7 系を導入し、 ② 問題なければ prod にも導入、という流れが一般的でしょう。
「1. 環境ごとにディレクトリを分ける方法」では main.tf が環境ごとにわかれているため、dev の main.tf のみバージョンを変更すれば実現可能ですが、「2. tfvars ファイルを使う方法」では main.tf が共通化されているため、dev と prod でバージョンを分けることができません。
後者の方法であっても、実際には terraform apply を実行するまで prod には適用されませんが、コードだけ見るとバージョンが上がったと読み取れますので、他の開発者が混乱する可能性があります。環境ごとに、コードベースでバージョンを書き分けられた方が安全であることは間違いありません。
このように考えると、多少の記述の重複は発生してしまうものの「1. 環境ごとにディレクトリを分ける方法」を採用するのが良いのではないでしょうか?