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以外にOpenTofuやTerramateも標準でパッケージ管理してくれます。なんて優秀。
インストール方法などは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つです。
- 全スタックで同じbackend設定を使う
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、必要ならIAMapplicationは Amplify などアプリ寄り
この切り方だと、たとえば compute 側で dependency "networking" を使ってvpc_id や subnet_id を受け取る流れが自然に書けます。
3. サンプルテンプレを先に作っておく
hcl/sample を作っておくと、環境追加がかなりラクです。
新規EC2スタックを作るときは、ざっくりこの流れです。
envs/<region>/region.hclを作る(または既存を使う)envs/<region>/compute/<name>/を作るhcl/sample/compute/terragrunt.hclとec2.hclをコピー- 名前・インスタンスタイプ・AMI条件などを調整
terragrunt init→plan→apply
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層で分けるのが、いまのところ一番しっくり来ています。
同じ悩みを持っている人の参考になればうれしいです。