この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
大阪オフィスのかずえです。みなさんTerraform使ってますか?
私はTerraformが好きですが、ちょっと使いづらいなと感じている箇所があるのも事実です。その一つが同じようなコードを何度も書く必要があるところ。
TerragruntというTerraformのラッパーツールを使うことで、TerraformのコードをDRY(Don't Repeat Yourself、つまりコードの重複を減らす、無くす)にすることができることを知りましたので、ご紹介します。
今回扱う環境
以前書いた「Terraform初心者が実戦投入するまでにやったこと」でご紹介したディレクトリ構成でTerraformを使っていると仮定して、その問題点をTerragruntを使って解決したいと思います。
ディレクトリ構成はこんな感じです。複数環境存在し、かつ各環境内でもリソースごとにディレクトリを切ってStateを分けています。
.
├── environment
│ ├── local-pjxxx
│ │ ├── .envrc
│ │ ├── README.md
│ │ ├── alb
│ │ │ ├── main.tf
│ │ │ ├── output.tf
│ │ │ └── variables.tf
│ │ ├── ec2
│ │ │ ├── main.tf
│ │ │ ├── output.tf
│ │ │ └── variables.tf
│ │ ├── security_group
│ │ │ ├── main.tf
│ │ │ ├── output.tf
│ │ │ └── variables.tf
│ │ ├── その他のリソース
│ │ ├── ・
│ │ └── ・
│ ├── production-pjxxx
│ │ ├── ・
│ │ └── ・
│ └── staging-pjxxx
│ ├── ・
│ └── ・
└── modules
├── alb
│ ├── main.tf
│ ├── output.tf
│ └── variables.tf
├── ec2
│ ├── main.tf
│ ├── output.tf
│ ├── userdata.txt
│ └── variables.tf
├── security_group
│ ├── main.tf
│ ├── output.tf
│ └── variables.tf
├── その他のリソース
├── ・
└── ・
terraform apply
などを行なうディレクトリはenvironment/local-pjxxx/alb
などです。- 具体的なコードはmodule以下に書きます。それをenvironment下のファイルから呼び出します。
例えばenvironment/local-pjxxx/alb
のmain.tf
で../../../modules/alb
を呼びます。環境ごとにパラメーターが変わる部分はモジュール内で変数化しておき、呼び出す際に値を指定するようにします。
問題点
色々あるのですが、今回扱うのはbackend設定周りについてです。
environment/local-pjxxx/alb
、 environment/local-pjxxx/ec2
などの各ディレクトリごとにbackendの設定が必要になってきます。これが問題です。ほぼ同じコードが複数個書かれることになります。
例えばenvironment/local-pjxxx/alb
以下のいずれかの.tf
ファイルにこのようなコードが必要です。
terraform {
required_version = "~> 0.12"
backend "s3" {
bucket = "kazue-hogehoge-backend"
key = "alb.tfstate"
region = "ap-northeast-1"
encrypt = true
}
}
さらに、environment/local-pjxxx/ec2
以下にもほぼ同様のコードが必要になります。違うのはkey値だけです。
terraform {
required_version = "~> 0.12"
backend "s3" {
bucket = "kazue-hogehoge-backend"
key = "ec2.tfstate"
region = "ap-northeast-1"
encrypt = true
}
}
以下のように変数を使うことができれば、各ディレクトリでまったく同じコードを使うことができます。がbackendにおいては変数を扱うことができません。(さらにはこのコードブロックをmodule化したくなりますが、これもできません)
// これはエラー
terraform {
required_version = "~> 0.12"
backend "s3" {
bucket = var.backend-bucket
key = var.backend-key
region = var.backend-region
encrypt = var.backend-encrypt
}
}
というわけで、Stateを分けた数だけ、ほぼ同じbackendの設定が書かれることになります。
Terragruntで解決
Terragruntを使うとこのコード重複を解消することができます。具体的にはひとつ上のディレクトリ(environment/local-pjxxx
)で共通設定を定義し、各Stateでそれを参照できるようにします。
方針
以下のようなディレクトリ構成にします。 terragrunt.hcl
というファイルが複数個増えています。
.
├── environment
│ ├── local-pjxxx
│ │ ├── terragrunt.hcl
│ │ ├── .envrc
│ │ ├── README.md
│ │ ├── alb
│ │ │ ├── main.tf
│ │ │ ├── output.tf
│ │ │ ├── variables.tf
│ │ │ └── terragrunt.hcl
│ │ ├── ec2
│ │ │ ├── main.tf
│ │ │ ├── output.tf
│ │ │ ├── variables.tf
│ │ │ └── terragrunt.hcl
terragrunt.hcl
はTerragruntの設定ファイルです。
- 親ディレクトリ(
environment/local-pjxxx/
)に置いたterragrunt.hcl
にbackendの設定を書きます。 - 子ディレクトリ(
environment/local-pjxxx/alb
,environment/local-pjxxx/ec2
など)に置いたterragrunt.hcl
が、親ディレクトリのterragrunt.hcl
の情報を継承するするようにします。
以下、具体的な手順です。
1. インストール
上記に書かれていますが、Macの場合 brew install terragrunt
でインストールできます。
が、私の環境では tfenv と競合したので、以下コマンドを使いました。(情報参照元: Comment on #580 terragrunt conflicts with tfenv)
$ brew install --ignore-dependencies terragrunt
2. Terraformコマンドを実行する各ディレクトリに terragrunt.hcl
を設置
terragrunt.hcl
の書式はTerraformと同じくHCLです。
environment/local-pjxxx/alb/terragrunt.hcl
include {
path = find_in_parent_folders()
}
find_in_parent_folders()
を使うことで親ディレクトリに存在するTerragrunt設定ファイルterragrunt.hcl
を探し、その設定を継承します。
3. ひとつ上のディレクトリに terragrunt.hcl
を設置
共通のbackend設定を記述します。
environment/local-pjxxx/terragrunt.hcl
remote_state {
backend = "s3"
config = {
bucket = "kazue-hogehoge-backend"
key = "${path_relative_to_include()}.tfstate"
region = "ap-northeast-1"
encrypt = true
}
}
path_relative_to_include()
は、Stateの存在するパス(environment/local-pjxxx/alb
)と、先程 path
で定義したディレクトリパス(environment/local-pjxxx
)間の相対パスを返します。つまりこの場合alb
です。
4. buckendについてのTerraformコードを更新
backendの設定はterragruntに委譲されるので、空にします。
environment/local-pjxxx/alb/main.tf
terraform {
required_version = "~> 0.12"
- backend "s3" {
- bucket = "kazue-hogehoge-backend"
- key = "alb.tfstate"
- region = "ap-northeast-1"
- encrypt = true
- }
+ backend "s3" {}
}
5. terragrunt実行
実行するコマンドはterraform
ではなく terragrunt
になります。terragrunt
コマンドは、ここまで設定したTerragruntの設定を反映した上でterraform
コマンドを実行します。(つまりterraformのラッパーコマンドです)
$ cd environment/local-pjxxx/alb/
$ terragrunt apply
このようにすることで、backendに関する設定はenvironment/local-pjxxx/terragrunt.hcl
に一度書かれるだけにすることができ、各Stateディレクトリでは backend "s3" {}
の.tf
コードと、前述のterragrunt.hcl
ファイルを設置するだけでよくなります。
他のStateを参照する部分もDRYにする
別のStateで定義したリソースの情報を違うStateでも参照したい場合があります。例えばVPCとセキュリティグループを分けて作成していた場合、セキュリティグループリソースを作成する際にVPCのIDが必要です。その際Data Recourceのremote_stateが必要になります。コード例は以下です。
environment/local-pjxxx/security-group/variables.tf
data "terraform_remote_state" "vpc" {
backend = "s3"
config = {
bucket = "kazue-hogehoge-backend"
key = "vpc.tfstate"
region = "ap-northeast-1"
}
}
ここにもbackendの具体的な設定がありますね。先程親ディレクトリで定義した設定を参照するようにしたいですね。やっていきましょう。
方針としては、親ディレクトリのTerragrunt設定ファイルterragrunt.hcl
に書かれたbuckend設定情報を一度外部ファイルに外出しして、元のterragrunt.hcl
も、子ディレクトリのTerraformもその情報を参照する構成にします。
(外出しせず親ディレクトリのterragrunt.hcl
にあるbackend設定を小ディレクトリのTerraformに渡すこともできるのですが、コード量が多くなったのでやめました)
1. backendの設定情報を外出しする
設定情報をYAMLファイルにします。JSONファイルでも構いません。
environment/local-pjxxx/backend-config.yaml
bucket: "kazue-hogehoge-backend"
region: "ap-northeast-1"
encrypt: true
2. 親ディレクトリの terragrunt.hcl
が、上記設定情報を参照するようにする
environment/local-pjxxx/terragrunt.hcl
+ locals {
+ backend-config = yamldecode(file("${get_terragrunt_dir()}/${find_in_parent_folders("backend-config.yaml")}"))
+ }
remote_state {
backend = "s3"
config = {
+ bucket = local.backend-config.bucket
- bucket = "kazue-hogehoge-backend"
key = "${path_relative_to_include()}.tfstate"
+ region = local.backend-config.region
- region = "ap-northeast-1"
+ encrypt = local.backend-config.encrypt
- encrypt = true
}
}
3. 小ディレクトリのTerraformが上記設定情報を読み込む
environment/local-pjxxx/security-group/variables.tf
+ locals {
+ backend-config = yamldecode(file("../backend-config.yaml"))
+ }
4. remote_stateの定義で参照
environment/local-pjxxx/security-group/variables.tf
data "terraform_remote_state" "vpc" {
backend = "s3"
config = {
+ bucket = local.backend-config.bucket
- bucket = "kazue-hogehoge-backend"
key = "vpc.tfstate"
+ region = local.backend-config.region
- region = "ap-northeast-1"
}
}
まとめ
TerragruntでTerraformのbackend周りのコードをDRYにする方法をご紹介しました。痒いところに手が届くサービスで非常に気に入りました。Terragruntは、backend周りのコードだけでなく他の箇所のTerraformのコードもDRYに、メンテしやすくすることができますので、今後ご紹介していきます。