gitのブランチ間差分からterraform plan/apply対象のルートモジュールを抽出するtftargetsを作った
TerraformのCI/CDにおいて、すべてのモジュールに対してplan/applyを実行するのは時間とリソースの無駄になります。例えば、数十個のモジュールがある場合、変更がなかったモジュールにも毎回plan/applyが走るのは非効率です。そこで、変更があったモジュールのみを対象にしたいと考え、そのためのツールとしてtftargetsを作成しました。tftargetsは、gitの指定したブランチとの差分をもとに、plan/applyが必要なモジュールの一覧を出力します。これにより、以下のことが実現できます。
- productionやstagingのように環境ごとにルートモジュールを分けている場合、片方だけplanする
- productionやstagingのルートモジュールが別のモジュールに依存しており、そのモジュールに変更があった際に両方のルートモジュールでplanする
tftargets
https://github.com/takaishi/tftargets はgitのブランチ間差分を分析し、モジュール単位で変更があったかどうかを判断するツールです。内部で https://github.com/hashicorp/terraform-config-inspect を利用しており、モジュール間の依存関係を理解した上で、ファイルの変更に基づいて変更があったモジュールについてディレクトリパスをJSON配列として出力します。
dorny/paths-filterのようなツールでもplan/apply対象を定義できますが、依存関係が複雑になると管理が煩雑です。tftargetsはモジュール間の依存関係も自動で解析し、必要なルートモジュールをリストアップします。
インストール
GitHub Actionsでの利用を前提に開発しています。以下のようにAction内で利用できます。
- uses: takaishi/tftargets@main
with:
version: "v0.0.3"
使い方
例えば、以下のようなディレクトリ構成で管理しているとします。staging/productionでディレクトリを分け、その下に用途ごとのルートモジュールがあります。ルートモジュールは必要に応じてmodules内のモジュールを呼び出しています。app1とapp2がecsモジュールを呼び出している場合、ecsモジュールに変更があった際はapp1とapp2のモジュールについてplan/applyを行いたい、ということになります。
terraform/
├── env/
│ ├── staging/
│ │ ├── network/
│ │ ├── storage/
│ │ ├── app1/
│ │ └── app2/
│ └── production/
│ ├── network/
│ ├── storage/
│ ├── app1/
│ └── app2/
└── modules/
├── alb/
├── ecs/
├── network/
└── storage/
ここでtftargetsを利用できます。GitHub Actionsを前提としていますが、以下のようなコマンドを実行することでgithub.base_refとの差分をもとにplan/apply対象のモジュールをリストアップしてくれます。このコマンドを実行すると、差分のあったルートモジュールのパスがJSON配列で出力されます。
tftargets \
--base-branch='${{ github.base_ref }}' \
--base-dir='${{ github.workspace }}' \
--search-path='terraform/env'
実行結果は以下のような文字列配列形式です。
["/path/to/github/workspace/terraform/env/staging/app1", "/path/to/github/workspace/terraform/env/staging/app2", "/path/to/github/workspace/terraform/env/production/app1", "/path/to/github/workspace/terraform/env/production/app2"]
plan/apply対象の環境名だけを抽出したい場合は、以下のようにjqで加工します。
% echo $TFTARGETS | jq -c 'map(split("/")[-2]) | unique | sort'
["production","staging"]
後続ジョブでマトリクスを使い、環境ごとに処理を行うことができます。
plan:
needs: tftargets
strategy:
matrix:
env: ${{ fromJSON(needs.tftargets.outputs.tftargets) }}
runs-on: arm-ubuntu-latest
後続ジョブ内で再度tftargetsを実行し、plan/apply対象のモジュールをリストアップします。
% tftargets \
--base-branch='${{ github.base_ref }}' \
--base-dir='${{ github.workspace }}' \
--search-path='terraform/env/${{ matrix.env }}'
["/path/to/github/workspace/terraform/env/staging/app1", "/path/to/github/workspace/terraform/env/staging/app2"]
リストアップ結果を整形して、terragruntの --queue-include-dir
引数を生成します。これを使うことで、terragruntでplanする際に必要最低限のモジュールのみを対象にできます。
% echo $TFTARGETS | jq -rc 'map(split("/")[-1]) | map("--queue-include-dir=" + .) | join(" ")'
--queue-include-dir=app1 --queue-include-dir=app2
※ terragruntはterraformのラッパー的なソフトウェアで、多数のモジュールを扱う際の手間を軽減するさまざまな機能を持っています。hclファイルでモジュール間の依存関係を定義しておくと、plan/apply時に依存関係を考慮した順番で実行してくれます。
まとめ
tftargetsはGitのブランチ差分とterraformモジュールの依存関係から、効率的にplan/apply対象のルートモジュールをリストアップするツールです。これを使うことで、CI/CDにおける不要なplan/applyを最小限に抑えることができます。今後も機能追加や改善を予定しています。ぜひ使ってみて、フィードバックをいただけると嬉しいです。