😯

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の導入を検討する余地があると思います。

脚注
  1. https://cloud.google.com/docs/terraform/best-practices/general-style-structure?hl=ja ↩︎

  2. https://future-architect.github.io/arch-guidelines/documents/forTerraform/terraform_guidelines.html
    ↩︎

  3. https://terragrunt.gruntwork.io/ ↩︎

  4. https://terragrunt.gruntwork.io/docs/getting-started/install/ ↩︎

Discussion