TerragruntでDRYなTerraform Remote State

はじめに

前回に引き続いてTerraformをさらにいい感じに使えるTerragrunt。今回はそのTerragruntでTerraformのRemote Stateをいい感じにDRYに管理する方法を紹介します。

便利なRemote Stateだが。。。

Terraformにはインフラの状態( state )をリモートに保存しておけるように remote state という便利な機能があります。保存先として主流なのはきっと s3 や consul になるのですが、リモートに保存しておくことで、複数人でインフラをTerraformで扱ったり(要ロック)ローカルに保存してたstateが消えたりしてもリモートにあるから大丈夫になります。要はインフラの状態管理がもっといい感じになるのです。が、これには致命的な問題があって、terraformファイルで記述するのは backend というブロックになるのですが、ここでインタポレーション(変数を使うこと)が効かないのです。

 
terraform {
backend "s3" {
bucket     = "my-terraform-state"
key        = "frontend-app/terraform.tfstate"
region     = "us-east-1"
encrypt    = true
lock_table = "my-lock-table"
}
}

上のブロックで記述したものは再利用される項目も多々あるのですが、ここで変数を使えなかったり、共通化できないのは結構な痛手なんですよね。

├── backend-app
│   └── main.tf
├── frontend-app
│   └── main.tf
├── mysql
│   └── main.tf
└── vpc
└── main.tf

例えば上のような構成だった場合。 main.tf の backend ブロックに何回も同じ記述を書かないといけないのです。これはDRYじゃないですしバグを作る原因にもなります。そこで、Terragruntでは remote_state というブロックを使うことができます。

Terragruntの remote_state、そして include

上の問題をTerragruntでどう解決するのかと言うとまずは main.tf にプレースホルダを記載しておきます(これは残念ながら必須)。

terraform {
# ↓この中身はTerragruntが埋める
backend "s3" {}
}

中身は空っぽです。この中身をTerragruntが埋めてくれる感じです。ディレクトリ構造としては以下のようになります。

├── terraform.tfvars
├── backend-app
│   ├── main.tf
│   └── terraform.tfvars
├── frontend-app
│   ├── main.tf
│   └── terraform.tfvars
├── mysql
│   ├── main.tf
│   └── terraform.tfvars
└── vpc
├── main.tf
└── terraform.tfvars

親ディレクトリに共通利用する terraform.tfvars があり、モジュール固有の設定についてはそれぞれサブディレクトリに terraform.tfvars を配置します。そして、親ディレクトリの terraform.tfvars は以下のように記述します。

terragrunt = {
remote_state {
backend = "s3"
config {
bucket     = "my-terraform-state"
key        = "${path_relative_to_include()}/terraform.tfstate"
region     = "us-east-1"
encrypt    = true
lock_table = "my-lock-table"
}
}
}

ポイントとなるのは path_relative_to_include() の部分です。ここがモジュール固有の設定が入る場所となります。モジュール固有の terraform.tfvars がどうなるかと言うと

terragrunt = {
include {
path = "${find_in_parent_folders()}"
}
}

となります。include が重要な役割を担っています。 include ブロックの path で指定された terraform.tfvars の terragrunt ブロックがまるっとここにコピペされると思ってください。そして、もちろん子の設定が優先されるので、同じ設定を記述している場合は子の設定が反映されます。 find_in_parent_folders はTerragrunt特有の関数で、親ディレクトリで最初にみつけた terraform.tfvars ファイルのパスを返却します。こうすることで path に自分でパスを指定する必要がなくなります。そして、上で少し述べた path_relative_to_include() は、親 terraform.tfvars からみた include が含まれる terraform.tfvars への相対パスに変換されます。

何を言っているのかさっぱり想像しづらいと思いますが、例えば backend-app の terraform.tfvars を例に取った場合、親の terraform.tfvars からみて、 backend-app の terraform.tfvars は backend-app/terraform.tfvars になるので、 path_relative_to_include() は backend-app になるんです(なんとなくイメージできましたか?)。こうすることで、 backend-app の tfstate の key は backend-app/terraform.tfstate というパスに保存されることになり、モジュール固有の state をリモートで key を分けて管理できるということです。

ちょっとHackyな雰囲気出てますけど、どのみち現状のTerraformでコピペミスとかを防ごうとするとこういった感じのスクリプトを自分で書くことになるかと思うので、Terragrunt側で用意してくれているのはうれしいことですね。

おわりに

Terragruntで backend ブロックをDRYにする方法を紹介しました(本家のREADMEに記載されている内容です)。 IaC でインフラ自動化するのは良いんですけど、テストが普通のコーディングと違ってしづらい為、極力バグが混入しにくい使い方をすることが大切です。まだTerragruntで紹介できていない機能がありますが、それはまたの機会に。

APN Consulting Partner
スーパーソフトウエアはAWSパートナーネットワーク(APN)のコンサルティングパートナーです。