Terragrunt: how to keep your Terraform code DRY and maintainableを見つつ個人的にまとめたものです。
Terragruntは何を解決したいのか
new problems have since cropped up: namely, how to keep your Terraform code DRY and maintainable.
「DRYで保守しやすいTerraformのコードを維持する方法が、問題として出てきた(意訳)」と書かれていることから、現在のTerragruntは、DRYで保守性の高いコードを書けるようにしようという流れの模様。
backendの設定をDRYに保つ
backendでは、変数や式をサポートしていないため、下記のように書くことができません。
そのため、値が少し違うだけでもコピペしないといけません。
1
2
3
4
5
6
7
8
9
10
| terraform {
backend "s3" {
# Using variables does NOT work here!
bucket = var.terraform_state_bucket
key = var.terraform_state_key
region = var.terraform_state_region
encrypt = var.terraform_state_encrypt
dynamodb_table = var.terraform_state_dynamodb_table
}
}
|
terragruntでは設定ファイルを継承できるので、上記の問題を解決することができます。
下記にディレクトリ構成とファイルの内容を示します。
1
2
3
4
5
6
7
8
| stage
├── terragrunt.hcl <- Terragruntを使用するために必要です。
├── frontend-app
│ ├── main.tf
│ └── terragrunt.hcl <- backendの継承で使用します。
└── mysql
├── main.tf
└── terragrunt.hcl <- backendの継承で使用します。
|
下記のように設定することで、terragruntが自動的に親のbackendを子にも設定します。
mysqlのみ載せてありますが、frontend-appも同じになります。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
| # stage/mysql/main.tf
terraform {
# 空にしておくことで、Terragruntによってbackendの内容が自動的に埋め込まれます。必須です。
backend "s3" {}
}
# stage/mysql/terragrunt.hcl
include {
path = find_in_parent_folders() // 親ディレクトリからterragrunt.hclを探してそのパスを返す関数です。今回の場合にはstage/terragrunt.hclへのパス(../terragrunt.hcl)を返します。
}
# stage/terragrunt.hcl
remote_state {
backend = "s3"
config = {
bucket = "my-terraform-state"
key = "${path_relative_to_include()}/terraform.tfstate" // path_relative_to_include関数は、子モジュールへの相対パスを返します。mysqlの場合には、./mysqlが返ります。
region = "us-east-1"
encrypt = true
dynamodb_table = "my-lock-table"
}
}
|
子モジュール(stage/mysql/terragrunt.hcl)が親モジュール(stage/terragrunt.hcl)の設定を参照して自動的にbackend(terraform.backend.s3)を埋めるという挙動になっています。
これにより、backendの設定をDRYに保つことができます。
下記のようにterragrunt.hclに書くことで、コマンド実行時に自動的に引数を設定してくれるようになります。
1
2
3
4
5
6
7
8
9
10
11
| # terragrunt.hcl
terraform {
extra_arguments "common_vars" {
commands = ["plan", "apply"]
arguments = [
"-var-file=../../common.tfvars",
"-var-file=../region.tfvars"
]
}
}
|
1
2
3
| terraform apply \
-var-file=../../common.tfvars \
-var-file=../region.tfvars
|
↕ つまり、上記のterragrunt.hclの設定を行うことで、上のコマンドと下のコマンドは等価になります。
また、下記のようなterragruntが用意している関数を書いて、-var-fileをすべて網羅したコマンドを列挙するということも省くことができます。
1
2
3
4
5
6
7
8
9
10
11
| # terragrunt.hcl
terraform {
extra_arguments "common_vars" {
commands = get_terraform_commands_that_need_vars() // get_terraform_commands_that_need_vars()を使用することで、-var-fileや-varを引数として使用するすべてのコマンドに対して、引数入力の省略を行うことができます。
arguments = [
"-var-file=../../common.tfvars",
"-var-file=../region.tfvars"
]
}
}
|
terragrunt.hclには下記のようにsourceで使用するモジュールとしてgitリポジトリを使用できます。
1
2
3
4
| terraform {
source =
"github.com:foo/infrastructure-modules.git//app?ref=v0.0.1"
}
|
infrastructure-modulesリポジトリのapp/main.tfは下記のような感じです。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
| # infrastructure-modules/app/main.tf
provider "aws" {
region = "us-east-1"
# ... other provider settings ...
}
terraform {
backend "s3" {}
}
module "app" {
source = "../../../app"
instance_type = var.instance_type
instance_count = var.instance_count
# ... other app settings ...
}
# infrastructure-modules/app/outputs.tf
output "url" {
value = module.app.url
}
# infrastructure-modules/app/variables.tf
variable "instance_type" {}
variable "instance_count" {}
|
(Terraformのモジュールと等価ではない)このモジュール(infrastructure-modules/app/main.tf)には、providerやbackendを含めることができるため、呼び出し側は、下記程度のコードで済むようになり、よりDRYに書くことができます。
1
2
3
4
5
6
7
8
9
10
| terraform {
source =
"github.com:foo/infrastructure-modules.git//app?ref=v0.0.2"
}
// inputsに書いたものは、TF_VARのprefixがついた環境変数として渡されます。
inputs = {
instance_count = 3 // TF_VAR_instance_countと同じ
instance_type = "t2.micro" // TF_VAR_instance_countと同じ
}
|
終わりに
(providerとbackendはterraformの文脈だとmoduleに切り出せないので)providerとbackendを共通化する方法どうするんだろうと思っていたけど、terraformのsourceを用いればできることがわかったのでよかった。
シンボリックリンク使ってなんとかしてたので、来週リファクタしようかと思う。
providerとbackendのようにmodule切り出しできないものは、シンボリックリンク使わないとどうにもならないので、自分の場合とりあえず当分はterragruntを使うことになりそうだなーって思いました。
以上