Harada's Diary

TerragruntでIaC構成を作ってみた話

TL;DR

Terraformはよく仕事で触っていて、個人のAWS環境でもIaC管理のツールとして使っていました。

ただ、個人で利用すると以下のような悩みが出てきました。

複数環境の楽な分け方を知りたい

個人で新しいプロジェクトを作成するとき、いちいちtfコードが書くのが面倒、、
ワークスペースを使うほどでもない、でもボイラープレート使うのもイケてない。

モジュール(tfstate)管理がめんどくさい

大きなモジュールは影響範囲がわからないし、差分計算にそれなりに時間がかかってしまう
モジュール間の依存関係がわからない(そもそも覚えてない)
そして、モジュール(tfstate)間での値参照が面倒(terraform_remote_state の呪文が冗長すぎて厳しい)

もっと手軽に、久々に触った私でも直感でわかるような構成にしたい。
というような悩みを持っていたとき、Terragruntというものがあると知りました。

Terragruntってなによ?

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

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

セットアップしてみる

導入方法は色々あるものの、私はTerraformのバージョンマネージャー である tenv を利用しました。
TerraformやTerragrunt以外にOpenTofuTerramateも標準でパッケージ管理してくれます。なんて優秀。

インストール方法などはgitリポジトリのReadME見ながら普通に入れられました。
GUIベースのインタラクティブでも入れられるので、ここはあまり躓きませんでした。

※terragruntインストールする際に、github側でリクエストのAPI Rate Limitにぶち当たってしまったことがあります。
 一定時間経過したら自動解除されますが、私はリトライしまくって弾かれてしまったので、ご注意ください。

Terragruntの構成について

結論から言うと

  • 共通設定は hcl/root.hcl に寄せる
  • envs/<region>/... で環境を素直に分ける
  • modules/* は純Terraformで再利用可能にする
  • dependency でモジュール間の受け渡しを明示する
  • 新規環境は hcl/sample/* からコピーして作る

この形にしてから、「どこを触ればいいか」がかなり分かりやすくなりました。


ディレクトリ構成(公開用サンプル)

terraform/
├── hcl/
│   ├── root.hcl
│   └── sample/
│       ├── region.hcl
│       ├── terragrunt.hcl
│       ├── compute/
│       │   ├── terragrunt.hcl
│       │   └── ec2.hcl
│       └── networking/
│           └── terragrunt.hcl
├── modules/
│   ├── ec2/
│   ├── networking/
│   ├── iam/
│   ├── amplify/
│   └── ...
└── envs/
    └── ap-northeast-1/
        ├── region.hcl
        ├── networking/
        │   └── terragrunt.hcl
        ├── compute/
        │   ├── keypair/
        │   └── ws-example/
        │       ├── terragrunt.hcl
        │       └── ec2.hcl
        └── application/
            └── amplify/
                └── sample-app/
                    ├── terragrunt.hcl
                    ├── amplify.hcl
                    └── iam/

1. 共通設定を root.hcl に集約する

Terragruntのキモはここでした。
backend/provider の共通化と、state key の規則化を root.hcl に置いています。

ポイントは次の2つです。

  1. 全スタックで同じbackend設定を使う
  2. get_path_from_repo_root() / path_relative_to_include() で state key を自動決定する

「新しいディレクトリを切ったらstateの場所も自然に決まる」ので、何も考えなくも再利用性が高まります。

※個人環境とか頻繁に触らないので、久しぶりに触ると忘れてるんですねw
 なのでいちいちコード読み込まなくても直感的にわかる構成を目指しました。


2. envs/<region> でリージョンを設定する

リージョンごとに region.hcl を置き、各スタックはそれを読むだけにしています。

  • networking は VPC / Subnet / Route / Endpoint など土台
  • compute は EC2やKeyPair、必要ならIAM
  • application は Amplify などアプリ寄り

この切り方だと、たとえば compute 側で dependency "networking" を使って
vpc_idsubnet_id を受け取る流れが自然に書けます。


3. サンプルテンプレを先に作っておく

hcl/sample を作っておくと、環境追加がかなりラクです。

新規EC2スタックを作るときは、ざっくりこの流れです。

  1. envs/<region>/region.hcl を作る(または既存を使う)
  2. envs/<region>/compute/<name>/ を作る
  3. hcl/sample/compute/terragrunt.hclec2.hcl をコピー
  4. 名前・インスタンスタイプ・AMI条件などを調整
  5. terragrunt initplanapply

hclファイルはカスタムで記述する箇所が少なくて済むので、事故ったりしないのがよき。


4. 依存関係は dependency で明示

EC2作成時に、networking/keypair/iam などの出力を受ける構成にしています。
merge(local.static_inputs, {...}) の形で、静的値と依存出力を合成するのが定番です。

このやり方の良いところは、入力の出どころが読みやすいこと。
あと、mock_outputs を入れておくと plan 時の扱いも楽になります。


5. 実運用で便利だった小ネタ

  • EC2モジュール側でAMIを動的検索できるようにしておく
  • Security Groupを「既存利用 / 自動作成」の両対応にしておく
  • IAMはEC2用とAmplify用でAssumeRoleを切り替えられるようにする
  • Amplifyは appごとに amplify.hcl を分けて設定を閉じ込める

「全部を汎用化しよう」とすると逆に複雑になるので、再利用したい単位だけmodule化するのがちょうど良かったです。


まとめ

Terragrunt構成は、最初にルールを決めるまでが少し大変ですが、一度整理できると「環境追加」と「見通し」がかなり改善します。

個人的には

  • 共通化は root.hcl
  • 実体は modules
  • 運用単位は envs

この3層で分けるのが、いまのところ一番しっくり来ています。
同じ悩みを持っている人の参考になればうれしいです。