Terraform で本番やステージングなどの複数の環境を管理するとき、環境ごとに異なる変数を管理する方法について。
Terraform のバージョンは 0.14.8 で確認しています。
案 1. -var-file で tfvars ファイルを指定
Workspaces は tfstate を分けるためだけに使い、-var-file
で環境ごとの変数定義ファイルを指定します。
terraform workspace select prod terraform plan -var-file=envs/prod.tfvars
都度都度 -var-file
を指定するのが煩雑なのと、間違った組み合わせを(workspace が本番で tfvars がステージングとか)指定してしまうと悲惨な障害になりかねません。
特に terraform workspace select prod
が必要なために二手になるので事故りやすそうです。
次のように workspace と tfvars が一致していることをチェックするアイデアもあるようです。
あるいは、いまは workspace は環境変数でも指定できるので次のように一手にするとか。
env TF_WORKSPACE=prod terraform plan -var-file=envs/prod.tfvars
案 2. locals から環境名で変数定義をルックアップ
locals
で環境ごとのキーの下に変数を定義し、terraform.workspace
でルックアップします。
locals { envs = { prod = { setting = "this is production" } stg = { setting = "this is staging" } } env = local.envs[terraform.workspace] } output "setting" { value = local.env.setting }
変数定義ファイルを分けたいなら次のようにもできます。
// main.tf locals { env = merge( { prod = local.prod }, { stg = local.stg }, )[terraform.workspace] } output "setting" { value = local.env.setting } // prod.tf locals { prod = { setting = "this is production" } } // stg.tf locals { stg = { setting = "this is staging" } }
案 3. YAML ファイルを読み込み
workspace 名の YAML ファイルで変数を定義し、file
と yamldecode
で locals
に読み込みます。
locals { env = yamldecode(file("envs/${terraform.workspace}.yml")) } output "setting" { value = local.env.setting }
シンプルで良いですね。
案 4. 環境名のモジュール
環境名でモジュールを作成して、そのアウトプットを変数定義として使います。
例えば次のようなディレクトリ構造。
main.tf prod/ output.tf stg/ output.tf
main.tf
で次のようにモジュールを読みます。
module "prod" { count = terraform.workspace == "prod" ? 1 : 0 source = "./prod" } module "stg" { count = terraform.workspace == "stg" ? 1 : 0 source = "./stg" } locals { env = concat(module.prod, module.stg)[0] } output "setting" { value = local.env.setting }
この方法は、共通化しにくいリソース定義でも環境名のモジュールに入れれば count
や for_each
を用いたハックが必要ない、というメリットもあります。
前述の locals
からルックアップする方法と併用して、変数定義は基本的に locals
で、共通化しにくいリソースが含まれるならそれだけ環境名のモジュールに入れる、などの使い方ができそうです。
案.5 Workspaces を使わずにディレクトリで分ける
例えば次のようなディレクトリ構造。
common/ aaa/ aaa.tf bbb/ aaa.tf prod/ main.tf vars.tf ccc/ ccc.tf stg/ main.tf vars.tf ccc/ ccc.tf
共通で使いたいモジュールは common/
に入れて、共通化が難しいリソース定義は環境ごとのディレクトリの中のモジュールに入れます。環境ごとの main.tf
で各モジュールを読み込みます。
locals { env_name = "prod" } module "aaa" { source = "../common/aaa" env_name = local.env_name } module "bbb" { source = "../common/bbb" env_name = local.env_name } module "ccc" { source = "./ccc" env_name = local.env_name }
さいごに
環境によってあったりなかったりするリソースが含まれると、Workspaces を使う方法だと count
やら for_each
やらでめんどくさいことをする必要があるため、なんだかんだ汎用性のある Workspaces を使わずにディレクトリで分ける方法を使う方法が無難な気がします。
また、Workspaces はいちいち terraform workspace select
する必要がひと手間あるのが煩雑なような・・環境変数で TF_WORKSPACE=prod terraform plan
とかできますけど、コマンドラインで --workspace=prod
みたいに指定できるのが一番良いと思うんですけどどうなの。
Terraform の Workspaces は git ブランチをモデルにしているとのことです、なるほど。ただ git みたいに常時使うようなものでもないので、そのためにプロンプトに workspace 名を表示するとかまではやりたくないですね。。。