TerragruntでTerraformをいい感じに管理する
はじめに
皆さんはTerraformをどのような管理していますか?最近では、Google Cloudがベストプラクティス[1]を公開していたり、FUTURE社が設計ガイドライン[2]を提供していたりと、Terrafromの設計・開発ガイドラインは成熟して来ているのではないでしょうか。
それでも、何となくもっと良い管理の方法はないかなあ? と思ったことはありませんか。
そんなTerraform Loverに送る、Terragruntというツールを紹介します。
Terraformの課題
基本的なTerraformのディレクトリ構成を以下に示します。AWSリソースを管理することを想定としています。
terraform
├── environments
│ ┝ dev
│ │ ┝ main.tf
│ │ ┝ backend.tf
│ │ ┝ terraform.tf
│ │ └ provider.tf
│ └ prod
└── modules
┝── ec2
┝── vpc
┝── s3
...
仮に、プロダクトが大規模になりmoduleの数が増えた際はどうなるでしょうか。Terraform Plan / Applyの度に、stateファイル内にある大量のResourceの読み込みが発生し開発のスピードが損なわれてしまう可能性があります。
また、stateファイルを複数に分割する方法もあるかと思います。その場合、それぞれのstateファイルを読み込むためにterraform_remote_state
を設定するかと思いますが、複雑になってくるとコードの可読性や保守性が低下する恐れがあります。Dataリソースやべた書きで他stateのリソースを直接定義してもよいですが、依存関係をチーム内で把握する必要があり、これも同様に保守性が低くなってしまいます。
このように、プロダクトが大規模になるとterraformでの管理が辛くなって気がちです。
Terragruntとは
Terragrunt[3]は、Gruntwork社が提供しているTerraformのラッパーツールです。これにより、TerraformでのソースコードをDRYにし、肥大化しがちなtfファイルをモジュール単位で小さく管理することが可能になります。
インストール方法や実行方法は公式ドキュメント[4]を参照してください。
TerragruntでTerraformを実行する際は、上記のディレクトリ構成は以下の形で定義する必要があります。
terraform
├── environments
│ ┝ dev
│ │ ┝ ec2
│ │ │ └ terragrunt.hcl
│ │ ┝ vpc
│ │ │ └ terragrunt.hcl
│ │ ┝ s3
│ │ ... └ terragrunt.hcl
│ └ prod
┝── modules
│ ┝── ec2
│ ┝── vpc
│ ┝── s3
│ ...
└ root.hcl
変更箇所を一つ一つ見ていきます。
ディレクトリの上位層に共通設定のroot.hclを用意する
Terragruntは、Terraformの共通設定をより上位のディレクトリに定義し、下層のhclファイルが読み込むことよって、DRYなコードを実現できます。例として、root.hclを以下のように定義します。
remote_state {
backend = "s3"
generate = {
path = "backend.tf"
if_exists = "overwrite"
}
config = {
bucket = "terragrunt-sample"
key = "${path_relative_to_include()}/terraform.tfstate"
region = "ap-northeast-1"
encrypt = true
}
}
generate "provider" {
path = "provider.tf"
if_exists = "overwrite"
contents = <<EOF
provider "aws" {
region = "ap-northeast-1"
version = "5.98.0"
}
EOF
}
remote_state
ブロックではbackendの設定を行います。
generate "provider"
ではproviderの設定を行います。
path_relative_to_include()
はTerragrunt独自の関数であり、root.hclを読み込むhclのディレクトリパスが代入されます。この場合、
- dev/ec2/terraform.tfstate
- dev/vpc/terraform.tfstate
- dev/s3/terraform.tfstate
というパスでstateファイルが作成されます。モジュール毎にstateファイルを作成することで、リソースの読み込みが早くなるというメリットがあります。また、後述しますがTerragruntであれば異なるstateファイルの読み込みも非常に簡単に設定可能です。
モジュール毎にディレクトリを作成する
繰り返しになりますが、Terragruntはモジュール毎にリソースを分割して管理します。そのため、読み込むモジュール毎にディレクトリを作成する必要があります。
各モジュールを参照するterragrunt.hclを用意する
各モジュールを参照するために、terragrunt.hclを用意します。
vpcの例を以下に示します。
include "root" {
path = find_in_parent_folders("root.hcl")
}
terraform {
source = "../../../modules/vpc"
}
inputs = {
vpc_name = "sample_vpc"
}
include
は、他に読み込むhclファイルを指定します。find_in_parent_folders
により、親ディレクトリからroot.hcl
を探し出し、記載されている内容を自分のディレクトリ内に展開します。
terraformとinputsで、どのモジュールを参照するかを定義します。
vpcのoutputをec2モジュールで使用したい場合はどうすればよいでしょうか?
その場合、dependency
ブロックを使用します。
dependency "vpc" {
config_path = "../vpc"
}
ただし、vpcとec2モジュールを同時にplan、applyしようとするとec2でエラーとなります。理由は、vpcがまだapplyされていないため、ec2で参照できないためです。
この場合、mockを定義します。
dependency "vpc" {
config_path = "../vpc"
mock_outputs = {
vpc_name = "mock-vpc"
}
mock_outputs_allowed_terraform_commands = ["plan"]
}
mockにより、各モジュール毎に独立してplan、apply出来るというメリットが生まれます。
終わりに
Terragruntにより、Terraform管理の新たな選択肢が増えました。大規模なシステムを構成する場合はTerragruntの導入を検討する余地があると思います。
Discussion