freeeの開発情報ポータルサイト

Terragruntで快適なTerraformライフをめざす

1. はじめに

はじめまして、サインSREのfujiyaです。現在はサイン全体のインフラを担当しており、基盤の構築や監視などをメインに働いています。今回はサインのインフラ管理で利用しているTerragruntというツールについて、紹介したいと思います。

対象読者

  • Terragruntってなんだ?という人
  • Terragruntについて聞いたことがあるが、詳細はわからない人
  • Terraformの知識があり、Terragruntの基本的な利用方法を学びたい人

ゴール

  • Terragruntの基礎を学ぶ:Terragruntの基本的な概念、主要機能と使用方法について理解する。
  • インフラ管理の効率化を学ぶ:Terragruntを用いてインフラコードをDRYに書く方法を紹介。

2. Terragruntとは?

概要

Terragrunt は Gruntwork 社が公開している Terraform のラッパーツールです。Terraformが提供する機能を拡張し、重複を減らしたり、設定管理を簡素化したりすることで、DRYに保ち、インフラコードをより効率的に管理できるようになります。

また、TerragruntはTerraformの構文をそのまま利用できるため、Terraformに慣れている開発者にとっては、非常に使いやすいツールなのも特徴です。

インストール

インストールは公式サイトのインストールページを参考にインストールしましょう。

使い方

CLIでの使い方は簡単で、コマンドを terraform のかわりに terragrunt に置き換えるだけです。Terragrunt特有のコマンド以外は直接Terraformに転送するため、Terraformのコマンドをそのまま利用することができます。Terragrunt特有のコマンドについては後の章で説明します。

terraform plan
↓
terragrunt plan

参考

3. プロジェクトのフォルダ構造と実装の基本

ここでは、Terragruntプロジェクトの基本について説明します。

基本のフォルダ構造

一般的なTerragruntプロジェクトのフォルダ構造は次のようになります。

my-terraform-project/
├── terragrunt.hcl             # ルートレベルの設定
├── environments/
│   ├── production/
│   │   ├── app/
│   │   │   └── terragrunt.hcl # 本番環境のアプリ用設定ファイル
│   │   ├── database/
│   │   │   └── terragrunt.hcl # 本番環境のデータベース用設定ファイル
│   │   └── network/
│   │       └── terragrunt.hcl # 本番環境のネットワーク用設定ファイル
│   └── staging/
│       ├── app/
│       │   └── terragrunt.hcl # ステージング環境のアプリ用設定ファイル
│       ├── database/
│       │   └── terragrunt.hcl # ステージング環境のデータベース用設定ファイル
│       └── network/
│           └── terragrunt.hcl # ステージング環境のネットワーク用設定ファイル
└── modules/
    ├── app/
    ├── database/
    └── network/

この構造では、modules ディレクトリが実際のTerraformコードを含み、environments ディレクトリが特定の環境(例:productionstaging)に対するTerragruntの設定を持っています。

terragrunt.hcl ファイル

terragrunt.hcl ファイルは、TerragruntとTerraformの設定を定義します。基本的な設定では、以下の要素が含まれます。

  • リモートステートの設定:Terraformのステートファイルを保存する場所(例:S3バケット)を定義
  • 入力変数:Terraformモジュールに必要な入力変数を記述
  • 依存関係:他のTerraformモジュールへの依存関係を定義

以下はterragrunt.hclファイルの記述例です。

# terragrunt.hcl (例:production/app/terragrunt.hcl)

terraform {
  source = "../../modules/app" # Terraformモジュールへのパス

  # リモートステート設定
  backend "s3" {
    bucket         = "my-terraform-state"
    key            = "production/app/terraform.tfstate"
    region         = "us-east-1"
    encrypt        = true
  }
}

# 入力変数
inputs = {
  region = "us-east-1"
  name = "production-app"
}

# 依存関係
dependencies {
  paths = ["../database"]
}

その他の設定項目については公式のConfigurationのページを参考にしてください。

4. 設定をDRYに保つ

Terragruntでは、共通の設定をルートレベルのterragrunt.hclファイルに定義し、サブディレクトリのterragrunt.hclファイルでこれらの設定を参照することで、設定をDRYに保つことができます。

先に紹介した、一般的なTerragruntプロジェクトのフォルダ構造を例に説明します。

my-terraform-project/
├── terragrunt.hcl             # ルートレベルの設定
├── environments/
│   ├── production/
│   │   ├── app/
│   │   │   └── terragrunt.hcl # 本番環境のアプリ用設定ファイル
│   │   ├── database/
│   │   │   └── terragrunt.hcl # 本番環境のデータベース用設定ファイル
│   │   └── network/
│   │       └── terragrunt.hcl # 本番環境のネットワーク用設定ファイル
│   └── staging/
│       ├── app/
│       │   └── terragrunt.hcl # ステージング環境のアプリ用設定ファイル
│       ├── database/
│       │   └── terragrunt.hcl # ステージング環境のデータベース用設定ファイル
│       └── network/
│           └── terragrunt.hcl # ステージング環境のネットワーク用設定ファイル
└── modules/
    ├── app/
    ├── database/
    └── network/

ルートレベルの terragrunt.hcl

このファイルでは、全ての環境で共通する設定を定義します。

以下では、共通の設定の例としてリージョンとリモートステートの設定を定義しています。

# my-terraform-project/terragrunt.hcl

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

inputs = {
  region = "us-east-1"
}

path_relative_to_include()は、サブディレクトリのパスを動的に生成し、各環境のステートファイルを別々に保持します。これにより、各フォルダで定義しなければならなかったリモートステートを一箇所で定義することができます。

サブディレクトリの terragrunt.hcl

サブディレクトリのterragrunt.hclファイルでは、ルートのterragrunt.hclファイルを参照し、その他の固有の設定を追加します。

# my-terraform-project/environments/production/app/terragrunt.hcl

include {
  path = find_in_parent_folders()
}

terraform {
  source = "../../../modules/app"
}

inputs = {
  name = "my-app-production"
}

この設定では、includeブロックを使用してルートレベルのterragrunt.hclを含めています。これにより、リモートステートの設定が引き継がれます。また、terraformブロックでモジュールのソースを指定し、inputsブロックで固有の入力変数を設定します。

includeブロック内で使用しているfind_in_parent_folders() は Terragrunt のビルトイン関数で、現在のディレクトリから親ディレクトリを遡って、特定のファイルを探します。

find_in_parent_folders() は以下のような特徴があります。

  • 初めて見つかる terragrunt.hcl ファイルのパスを include ブロックで使用
  • 引数を渡すことで他のファイル名を指定することも可能

そのほかの設定をDRYに保つ

ここで紹介した設定項目以外に、プロバイダー構成を DRYに保つ なども存在します。公式ドキュメントを参考にしてみてください。

参考

5. TerraformとTerragruntを比較する

4 設定をDRYに保つで示したように、ルートレベルのterragrunt.hclinputsブロックで共通の入力変数を設定することでDRYに保つことができます。ここでは、素のTerraformで書いた場合と比較しながらDRYに保つメリットを紹介します。

素のTerraformで書いた場合

以下はappモジュールの記述例です。このように共通の設定であるregionやリモートステートは各種モジュールに記述が必要になってしまいます。

# my-terraform-project/environments/production/app/main.tf

module "app" {
  source = "../../../modules/app"

  name    = "production-app"
  region  = "us-east-1"
}

# リモートステートの設定
terraform {
  backend "s3" {
    bucket = "my-terraform-state"
    key    = "production/app/terraform.tfstate"
    region = "us-east-1"
  }
}

Terragruntで書いた場合

ルートレベルのterragrunt.hclregionとリモートステートを記述しておけるため、appモジュールの記述は以下のように簡潔に済ませることができます。

# my-terraform-project/environments/production/app/terragrunt.hcl

include {
  path = find_in_parent_folders()
}

terraform {
  source = "../../../modules/app"
}

inputs = {
  name    = "production-app"
}

比較

  • DRY原則の適用
    • 素のTerraform:各環境でTerraformのbackend設定を繰り返し記述する必要がある。これは設定が重複し、変更があった場合に複数のファイルを更新する必要がある。
    • Terragrunt:共通の設定(リモートステートの設定など)をルートのterragrunt.hclに一度だけ記述し、includeを使用して各環境で継承できる。
  • コードの管理と再利用性
    • 素のTerraform:各環境でモジュールを呼び出す際に、モジュールのソースと変数を指定する必要がある。
    • Terragrunt:モジュール(appdatabasenetwork)を一度定義し、異なる環境で再利用。
  • 保守性
    • 素のTerraform:共通設定の変更には、各環境やモジュールのTerraformファイルを個別に編集する必要がある。また、誤ってほかのフォルダのステートファイルを指定してしまった場合、環境を破壊する可能性も出てくる。
    • Terragrunt:共通設定の変更はルートのterragrunt.hclで一度だけ行えば良いため、保守が容易

Terragruntを使用するとDRYに記述ができるため、設定の変更が容易になり、全体的なコードベースの保守性が向上します。

6. Terragruntのコマンド

ここでは、Terragrunt特有のコマンドを紹介します。

これより前の章でも書きましたが、Terragrunt特有のコマンド以外は直接Terraformに転送します。

Terragrunt Command 説明
aws-provider-patch ネストされたAWSプロバイダーの設定を上書きして、Terraformのバグを回避
graph-dependencies terragruntの依存関係グラフを標準出力に出力
hclfmt 再帰的にhclファイルを検索し、それらを正規形式に書き換える
output-module-groups コマンド(applyまたはdestroy)で順序付けられたモジュールのグループをJSONのリストとして出力(CIでに役立つ)。
render-json すべての変数、インクルード、および機能が解決された最終的なterragrunt設定をjsonとしてレンダリング
run-all 指定されたコマンドを各サブフォルダで実行することにより、'stack'に対してterraformコマンドを実行
terragrunt-info 限定的なterragruntの状態を標準出力に出力し、終了
validate-inputs terragruntで設定された入力がterraformで定義された変数と整合しているかを確認
* Terragruntは他のすべてのコマンドを直接Terraformに転送

コマンドやオプションに関しては以下で確認することができます。

terragrunt --help

ドキュメントはこちら

run-allコマンド

run-all コマンドは、複数の Terraform モジュールが含まれるディレクトリ構造において、指定された Terraform コマンドをすべてのサブモジュールに対して一度に実行することができます。

先に示したTerragruntProjectのフォルダ構造の一部を参考に説明します。

environmensts/production/
├── app/
│   └── terragrunt.hcl
├── database/
│   └── terragrunt.hcl
└── network/
    └── terragrunt.hcl

run-all コマンドの使い方

run-all コマンドを使用するには、コマンドラインで environments/production ディレクトリに移動し、以下のようにコマンドを実行することで利用できます。

terragrunt run-all apply

このコマンドは、my-terraform-infra ディレクトリ内のすべてのサブディレクトリ(この場合はappdatabasenetwork)に対して terraform apply を実行します。

依存関係の管理

run-all コマンドは、サブディレクトリ間の依存関係も考慮します。例えば、app モジュールが database モジュールに依存している場合、Terragrunt は databaseapply が完了するまで appapply を開始しません。これは terragrunt.hcl ファイル内で依存関係を設定できます。

7. Terragruntについてより深く学ぶ

Terragruntの知識を深めたい方々に向けて、Terragruntについて学ぶための情報源を紹介します。

  • 公式ドキュメント
  • TerragruntのGitHub Actions(terragrunt-action)も存在し、CI/CDでの利用もできます。
  • その他
    • 書籍はTerragruntに特化した書籍は見当たりませんでした。
    • 動画はいくつかコンテンツを見つけました。

8. さいごに

この記事では、Terragruntについての基礎とこれを用いてインフラコードをDRYに書く方法を紹介しました。Terragruntを用いることでTerraformのコードをDRYに保ち、インフラの設定管理を効率化できます。快適なTerraformライフを!