Terraformをいい感じで管理するTerragruntのメリットを紹介する
記事の内容
Terraformをいい感じに管理できるOSSツールTerragruntのメリットを紹介します。
対象読者
- Terraformユーザー
- Terraformの管理をいい感じにしたい人
記事の長さ
5分で読めます
Terragruntとは
Terragruntとは、Gruntwork.io (https://gruntwork.io/)が出しているOSSプロジェクトです。
Terragruntを使うことにより、Terraformを便利に使いこなすことができるようになります。
Terragruntを使い始めたきっかけ
Terraformでさまざまなリソースを管理しているとstateファイルが肥大化します。
すると、少しの変更でもplanに時間がかかったり、意図しない差分が発生しリリースに時間がかかるようになりました。
そういった課題を解決するために、Terragruntに注目しました。
Terragruntを利用すると、Backendを細かく分けStateファイルを小さく保つと同時に、ソースコード自体をDRYに記述することができます。
また、各module間のdependencyも管理することができるので、stateファイルが分かれていたとしても他のmoduleのoutputの値を利用できるようになります。
Terragruntを使うメリット
Terragruntを導入するメリットを紹介します。
英語にはなるのですが、詳しくは公式ドキュメントに記載があります。
本記事では、簡潔に紹介いたします。
Terraform Stateを管理するバケットもIaCで管理できる
Terraformでプロジェクトを作成するときに発生する問題の一つが、TerraformのStateファイルを管理するバケット自体をTerraformで管理できないということです。
というのも、Terraformの環境構築をする際に、Terraform Stateを配置するバケット(S3やGCS)を指定しないといけないのですが、Terraformの環境構築が完了していないとバケット自体が作れないので、バケットは別途先に作っておく必要があります。(ニワタマ問題)
Terragruntをつかうとその問題を解決できます。
Terragruntを使って、Bucketを自動作成する
Terragruntでは、terragrunt.hcl
というファイルを設定ファイルとして利用します。
Terragruntで管理したいディレクトリのルートに以下の設定ファイルを配置し、terragrunt plan
コマンドを実行します。
terragrunt.hcl
remote_state {
backend = "gcs"
generate = {
path = "backend.tf"
if_exists = "overwrite_terragrunt"
}
config = {
project = "terragrunt-experiment"
bucket = "terragrunt-state-auto-generate"
prefix = "${path_relative_to_include()}/terraform.tfstate"
location = "asia-northeast1"
}
}
$ terragrunt plan
Remote state GCS bucket terragrunt-state-auto-generate does not exist or you don't have permissions to access it. Would you like Terragrunt to create it? (y/n) Y
Initializing the backend...
Successfully configured the backend "gcs"! Terraform will automatically
use this backend unless the backend configuration changes.
Initializing provider plugins...
Terraform has been successfully initialized!
You may now begin working with Terraform. Try running "terraform plan" to see
any changes that are required for your infrastructure. All Terraform commands
should now work.
If you ever set or change modules or backend configuration for Terraform,
rerun this command to reinitialize your working directory. If you forget, other
commands will detect it and remind you to do so if necessary.
No changes. Your infrastructure matches the configuration.
Terraform has compared your real infrastructure against your configuration
and found no differences, so no changes are needed.
すると、terragrunt-state-auto-generate
というバケットがないが、そのバケットをTerraform Stateを配置するために利用しているため、作成してもいいですか?という文言が表示されて、Y
を入力すると自動でそのバケットが作成されます。
このTerragruntのプロジェクトではそのバケットがStateファイルを格納するバケットとして登録されます。
Terragruntの挙動
$ tree
.
├── backend.tf
└── terragrunt.hcl
先ほどの、terragrunt plan
を実行したディレクトリを見ると、backend.tf
ファイルが自動生成されています。これは、terragruntが自動で生成したファイルでTerraformのbackend設定が書き込まれています。
backend.tf
# Generated by Terragrunt. Sig: nIlQXj57tbuaRZEa
terraform {
backend "gcs" {
bucket = "terragrunt-state-auto-generate"
prefix = "./terraform.tfstate"
}
}
つまり、Terragruntが行ったことは、terragruntの設定ファイルに基づき、GCSバケットを作成し、backend.tfを自動生成したことになります。
TerragruntはTerraformの薄いラッパーなので、人間にとってわかりやすくTerraformを管理することを可能にしてくれます。
Stateファイルを小さく保つ
Stateファイルを配置するバケットを自動生成してくれるメリットを説明しました。
それだけだと、Terragruntを導入するほどのメリットにはなりません。
Terragruntを導入する最大のメリットはTerraformのStateファイルを小さく保てるということです。
例えば、以下のような構成を作成します。(network/main.tf
を追加)
.
├── backend.tf
├── network
│ └── main.tf
└── terragrunt.hcl
この構成で、network/main.tf
にVPC Networkを記述するResourceを追加します。
network/main.tf
resource "google_compute_network" "vpc_network" {
name = "vpc-network"
auto_create_subnetworks = false
}
このnetworkディレクトリに以下のterragruntの設定ファイルを追加します。
network/terragrunt.hcl
include "root" {
path = find_in_parent_folders()
}
これは、親ディレクトリのTerragruntの設定ファイルを利用するための記述です。この状態で、terragrunt plan
を実行します。
$ cd network
$ terragrunt plan
Terraform used the selected providers to generate the following execution
plan. Resource actions are indicated with the following symbols:
+ create
Terraform will perform the following actions:
# google_compute_network.vpc_network will be created
+ resource "google_compute_network" "vpc_network" {
+ auto_create_subnetworks = false
+ delete_default_routes_on_create = false
+ gateway_ipv4 = (known after apply)
+ id = (known after apply)
+ internal_ipv6_range = (known after apply)
+ mtu = (known after apply)
+ name = "vpc-network"
+ network_firewall_policy_enforcement_order = "AFTER_CLASSIC_FIREWALL"
+ numeric_id = (known after apply)
+ project = "terragrunt-experiment"
+ routing_mode = (known after apply)
+ self_link = (known after apply)
}
Plan: 1 to add, 0 to change, 0 to destroy.
上記のようにTerraform Planの結果が表示されます。また、Terragruntを使って、planを実行したことにより、backend.tf
ファイルがnetwork
ディレクトリの中に自動生成されます。
network/backend.tf
# Generated by Terragrunt. Sig: nIlQXj57tbuaRZEa
terraform {
backend "gcs" {
bucket = "terragrunt-state-auto-generate"
prefix = "network/terraform.tfstate"
}
}
先ほどルートディレクトリで設定した、terragrunt.hcl
の設定を踏襲し、同じバケットにnetwork
というprefixがついたstateファイルを作成するbackend.tf
ファイルです。
この機能により、ディレクトリ毎にStateファイルが作成されます。ディレクトリ毎にStateファイルが作成されることにより、モノリスで巨大なStateファイルが生み出す不都合を解決することができます。
Providerを一括管理できる
様々なディレクトリにResourcesが分かれてしまうと、Providerの管理が煩雑になりがちです。
全てのディレクトリでprovider.tf
ファイルを作成して、コピペを繰り返していくのはあまりにも冗長なので、TerragruntはProviderを一括で管理する機能を提供しています。
terragrunt.hcl
remote_state {
backend = "gcs"
generate = {
path = "backend.tf"
if_exists = "overwrite_terragrunt"
}
config = {
project = "terragrunt-experiment"
bucket = "terragrunt-state-auto-generate"
prefix = "${path_relative_to_include()}/terraform.tfstate"
location = "asia-northeast1"
}
}
generate "provider" {
path = "provider.tf"
if_exists = "overwrite_terragrunt"
contents = <<EOF
provider "google" {
project = "terragrunt-experiment"
region = "asia-northeast1"
}
provider "google" {
alias = "us-central1"
project = "terragrunt-experiment"
region = "us-central1"
}
EOF
}
ルートのterragrunt.hcl
に、generate "provider" ブロックを追加します。
この状態で、terragrunt planを実行すると、各ディレクトリに、provider.tf
ファイルが自動生成されます。
$ tree
.
├── backend.tf
├── network
│ ├── backend.tf
│ ├── main.tf
│ ├── provider.tf
│ └── terragrunt.hcl
└── terragrunt.hcl
CLIにvariablesを自動で取り込む
Terraformでvariablesの値をファイルで管理して、CLI実行時に取り込む場合、
$ terraform apply \
-var-file=../../common.tfvars \
-var-file=../region.tfvars
のように、引数を指定する必要があります。しかし、都度オプションを指定するのはめんどくさいですし、指定忘れのリスクもあります。もちろん、チーム開発する際には余計なコミュニケーションコストも発生します。
Terragruntを使うと、ここもソースコードで管理できるようになります。
common.tfvars
service_name = "terragrunt"
network/variable.tf
variable "service_name" {
type = string
}
network/terragrunt.hcl
include "root" {
path = find_in_parent_folders()
}
terraform {
extra_arguments "common_vars" {
commands = get_terraform_commands_that_need_vars()
arguments = [
"-var-file=../common.tfvars"
]
}
}
上記のようにファイルを作成し、networkディレクトリ配下でterragrunt plan
を実行します。
$ cd network
$ terragrunt plan
Terraform used the selected providers to generate the following execution
plan. Resource actions are indicated with the following symbols:
+ create
Terraform will perform the following actions:
# google_compute_network.vpc_network will be created
+ resource "google_compute_network" "vpc_network" {
+ auto_create_subnetworks = false
+ delete_default_routes_on_create = false
+ gateway_ipv4 = (known after apply)
+ id = (known after apply)
+ internal_ipv6_range = (known after apply)
+ mtu = (known after apply)
+ name = "terragrunt"
+ network_firewall_policy_enforcement_order = "AFTER_CLASSIC_FIREWALL"
+ numeric_id = (known after apply)
+ project = "terragrunt-experiment"
+ routing_mode = (known after apply)
+ self_link = (known after apply)
}
Plan: 1 to add, 0 to change, 0 to destroy.
─────────────────────────────────────────────────────────────────────────────
Note: You didn't use the -out option to save this plan, so Terraform can't
guarantee to take exactly these actions if you run "terraform apply" now.
すると、common.tfvars
で指定したvariableの値を取り込んで、Planが実行されます。
簡易的なmodule管理
├── prod
│ ├── app
│ │ ├── main.tf
│ │ └── outputs.tf
│ ├── mysql
│ │ ├── main.tf
│ │ └── outputs.tf
│ └── vpc
│ ├── main.tf
│ └── outputs.tf
├── qa
│ ├── app
│ │ ├── main.tf
│ │ └── outputs.tf
│ ├── mysql
│ │ ├── main.tf
│ │ └── outputs.tf
│ └── vpc
│ ├── main.tf
│ └── outputs.tf
└── stage
├── app
│ ├── main.tf
│ └── outputs.tf
├── mysql
│ ├── main.tf
│ └── outputs.tf
└── vpc
├── main.tf
└── outputs.tf
Terragruntを使って、小さなStateファイルを管理できるメリットは説明しましたが、それのデメリットとして、上記のように環境✖️機能ごとにディレクトリができてしまうというものがあります。
Terragruntではmoduleを利用して、この問題にアプローチします。
各環境からimportするTerraform Resourceを定義するmoduleディレクトリを作成します。
また、そのmoduleディレクトリの中に、networkに関するmoduleを作成するnetworkディレクトリと必要なterraformファイル一式を用意します。
$ tree
└── modules
└── network
├── main.tf
├── outputs.tf
├── provider.tf
└── variables.tf
それぞれのファイルを以下のように編集します。
main.tf
resource "google_compute_network" "this" {
project = var.project
name = var.name
auto_create_subnetworks = false
}
outputs.tf
output "network_id" {
value = google_compute_network.this.id
}
provider.tf
provider "google" {
region = var.region
project = var.project
}
variables.tf
variable "region" {
type = string
default = "asia-northeast1"
}
variable "name" {
type = string
}
variable "project" {
type = string
}
そして、このmoduleを引き込む形で、terragruntの設定ファイルを作成します。
network/terragrunt.hcl
include "root" {
path = find_in_parent_folders()
}
terraform {
source = "../../modules/network"
}
inputs = merge(
{
name = "my-service"
region = "asia-northeast1"
project = "terragrunt-experiment"
}
)
そして、terragrunt plan
コマンドを実行します。
$ terragrunt plan
Terraform used the selected providers to generate the following execution
plan. Resource actions are indicated with the following symbols:
+ create
Terraform will perform the following actions:
# google_compute_network.this will be created
+ resource "google_compute_network" "this" {
+ auto_create_subnetworks = false
+ delete_default_routes_on_create = false
+ gateway_ipv4 = (known after apply)
+ id = (known after apply)
+ internal_ipv6_range = (known after apply)
+ mtu = (known after apply)
+ name = "my-service"
+ network_firewall_policy_enforcement_order = "AFTER_CLASSIC_FIREWALL"
+ numeric_id = (known after apply)
+ project = "terragrunt-experiment"
+ routing_mode = (known after apply)
+ self_link = (known after apply)
}
Plan: 1 to add, 0 to change, 0 to destroy.
Changes to Outputs:
+ network_id = (known after apply)
─────────────────────────────────────────────────────────────────────────────
Note: You didn't use the -out option to save this plan, so Terraform can't
guarantee to take exactly these actions if you run "terraform apply" now.
すると、与えた引数の通りにmoduleのTerraformResourceを実行します。
こうすることによって、inputの値だけを変更したterragrunt.hcl
ファイルを環境分用意すれば、DRYなディレクトリ構成になります。
このやり方で、再構築したTerragruntプロジェクトのディレクトリ構成は以下のようになります。
├── prod
│ ├── app
│ │ ├── terragrunt.hcl
│ ├── mysql
│ │ ├── terragrunt.hcl
│ └── vpc
│ ├── terragrunt.hcl
├── qa
│ ├── app
│ │ ├── terragrunt.hcl
│ ├── mysql
│ │ ├── terragrunt.hcl
│ └── vpc
│ ├── terragrunt.hcl
└── stage
├── app
│ ├── terragrunt.hcl
├── mysql
│ ├── terragrunt.hcl
└── vpc
├── terragrunt.hcl
共通の値を利用する
DRYなファイル構成を実現することができました。
さらに便利にTerragruntを利用するための機能を紹介します。
Terragruntでは、GCP Project IDやServiceの名前・メインで利用しているリージョンなど、共通で使える値をlocalsに指定して、全てのterragrunt.hclファイルで再利用することができます。
.
├── backend.tf
├── common.hcl
├── network
│ ├── backend.tf
│ ├── provider.tf
│ └── terragrunt.hcl
├── provider.tf
├── region.hcl
└── terragrunt.hcl
共通で使用した値をcommon.hcl
とregion.hcl
というファイルに書き込みます。
common.hcl
locals {
service_name = "my-service"
project_id = "terragrunt-experiment"
}
region.hcl
locals {
main_region = "asia-northeast1"
}
そして、この二つのhclファイルをterragrunt.hclファイルから呼び出します。
network/terragrunt.hcl
locals {
common = read_terragrunt_config(find_in_parent_folders("common.hcl"))
region = read_terragrunt_config(find_in_parent_folders("region.hcl"))
}
include "root" {
path = find_in_parent_folders()
}
terraform {
source = "../../modules/network"
}
inputs = merge(
{
name = local.common.locals.service_name
region = local.region.locals.main_region
project = local.common.locals.project_id
}
)
こうすることによって、様々なディレクトリから共通の設定値を呼び出すことができるようになり、ソースをさらに最適化できます。
また、find_in_parent_folders
という関数名の如く、terragrunt.hcl
ファイルの親ディレクトリを自動でサーチして、ファイル名が一致するファイルを見つけて取り込んでくれるため、以下のような構成も取れるようになります。
├── common.hcl
├── prod
│ ├── env.hcl
│ ├── app
│ │ ├── terragrunt.hcl
│ ├── mysql
│ │ ├── terragrunt.hcl
│ └── vpc
│ ├── terragrunt.hcl
├── qa
│ ├── env.hcl
│ ├── app
│ │ ├── terragrunt.hcl
│ ├── mysql
│ │ ├── terragrunt.hcl
│ └── vpc
│ ├── terragrunt.hcl
└── stage
├── env.hcl
├── app
│ ├── terragrunt.hcl
├── mysql
│ ├── terragrunt.hcl
└── vpc
├── terragrunt.hcl
これで環境毎に異なる設定値を指定することができます。
まとめ
Terraformをさらに使いやすくするTerragruntの解説をしました。
Terraformはそれ単体でも非常に有用なツールで、IaCのデファクトスタンダードになっています。しかし、Terraformのみで使用すると、巨大なStateファイルに悩まされます。
そういったTerraform特有の悩みを解決してくれるのが、Terragruntなので、Terraformを大型プロジェクトで利用する予定がある人は、導入を検討してみてください。
ソース
今回作成したサンプルソースは以下に配置しておきます。
note
勉強法やキャリア構築法など、エンジニアに役立つ記事をnoteで配信しています。
Discussion