repl.info

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を最小限に抑えることができます。今後も機能追加や改善を予定しています。ぜひ使ってみて、フィードバックをいただけると嬉しいです。