対象読者
本記事は、以下の知識や経験がある方を対象読者としています。
- terraformを使用して、AWS、Azure、Google Cloudを構築したことがある。
- terraformとGitHub ActionsでCIを実施したことがある。または、導入を予定しているため検証している。
ローカルとCIのバージョンを1ファイルで揃える
Terraformの運用を進めていくと困ってくる事が「ツールのバージョン管理」です。Terraform本体だけならtfenvで済みますが、実際のCI/CDパイプラインではtflint、tfcmtなど周辺ツールが増えていくため、「ローカルとCIでバージョンが違う」「新メンバー向けの環境構築手順が長い」といった問題が出てきます。
宣言的CLIバージョン管理ツール aqua を使って、Terraformとその周辺ツールをローカル・CI共通で1ファイルによって管理する方法や、tflintとtfcmtを導入して実際にサンドボックスリポジトリで検証した結果について紹介します。
使用するツールの説明
- aqua
- https://aquaproj.github.io/
- aqua は suzuki-shunsuke 氏が開発している宣言的なCLIバージョン管理ツールです。tfcmtやtfactionと同じ作者で、Terraform CI/CDエコシステムとの親和性が高いのが特徴です。
- tflint
- https://github.com/terraform-linters/tflint
- AWS、Google CloudやAzureにおいて無効なインスタンスタイプを検出してくれたり、推奨されていない構文や未使用の宣言について検出してくれます。
- tfcmt
- https://suzuki-shunsuke.github.io/tfcmt/
- GitHubのプルリクエストにterraform planやapplyの結果を表示してくれます。
使い方
使い方は、リポジトリ直下に `aqua.yaml` を置き、使いたいツールとバージョンを宣言します。詳細は、この後に記載しています。
# yaml-language-server: $schema=https://raw.githubusercontent.com/aquaproj/aqua/main/json-schema/aqua-yaml.json
registries:
- type: standard
ref: v4.295.0 # 記事執筆時点の例。最新版を確認して指定
packages:
- name: hashicorp/terraform@v1.9.5
- name: terraform-linters/tflint@v0.53.0
- name: suzuki-shunsuke/tfcmt@v4.10.0
あとは、aqua install を実行するだけで、宣言したバージョンのバイナリが揃います。実体は共有ディレクトリにバージョンごとに保存され、PATH上のシンボリックリンク(aqua-proxy)経由で「そのリポジトリの aqua.yaml に書かれたバージョン」が呼び出されます。
tfenv と aqua の比較
バージョンを管理する場合において、tfenvを使用する場面が多いと思います。aquaとの機能を比較し、できることの範囲を確認します。
| tfenv | aqua | |
| 管理対象 | Terraform本体のみ | Terraform + 任意のCLIツール |
| ローカルとCIの共通化 | 別々に設定が必要 | aqua.yamlファイルで共通化 |
| バージョン切り替え | ディレクトリ単位(.terraform-version) | リポジトリ単位(aqua.yaml) |
| tflint / tfcmt 等 | 管理外 | 同じファイルで管理 |
aquaの良いところは、「ローカルで動いたバージョン構成が、そのままCIでも再現される」ことや、バージョンの指定をするためのワークフローもaqua.yamlで管理できることです。バージョン統制をする意味でも管理が楽になります。
既存リポジトリでtfenvを使っている場合、無理に移行すると業務に支障がでるかと思いますので、新しいリポジトリから始めたり検証してから使用するなど、段階的に移行することをお勧めします。
Windows で Git Bash を使用した環境におけるローカルセットアップ
Windows PCを業務で利用している方が多いと思いますので、Windows+Git Bash環境におけるセットアップを参考に記載します。macOS/Linuxを使用している場合は、公式ドキュメントの通りで問題ありません。
インストール
Git Bashは導入済みとします。コマンドはGit Bash上で動作前提となります。
curl -sSfL https://raw.githubusercontent.com/aquaproj/aqua-installer/v3.1.2/aqua-installer | bash
変数`$LOCALAPPDATA` のPATHに注意する
公式手順 ではPATH変数に”${AQUA_ROOT_DIR:-${XDG_DATA_HOME:-$HOME/.local/share}/aquaproj-aqua}/bin “を追加しますが、Git Bash環境で `$LOCALAPPDATA` ベースのPATH変数を使うと、`C:\Users\…` というWindows形式のPATH(コロン入り)がPATH変数に混入しますので、少し対策をいれます。
# NG: export PATH="$LOCALAPPDATA/aquaproj-aqua/bin:$PATH" ← コロンでPATHが壊れる
# OK:
export PATH="$HOME/AppData/Local/aquaproj-aqua/bin:$PATH"
動作確認
以下のコマンドで確認します
cd your-repo
aqua install
terraform version # aqua.yaml で宣言したバージョンが表示されればOK
tflint --version
tfcmt --version
GitHub ActionsでのCI統合
quaproj/aqua-installer` Actionでaqua本体を入れ、`aqua install` を実行すれば、Terraform / tflint / tfcmt がまとめて使える状態になり、今回の構成では「hashicorp/setup-terraform」 を設定しなくてもよくなります。
「hashicorp/setup-terraform」を使用しなくてもよい理由は、以下の点が挙げられます。
- hashicorp/setup-terraform は「Terraform本体をインストールする」の専用アクションです。一方 aqua install は、aqua.yaml に書かれたTerraform / tflint / tfcmt など複数のツールを、1回のコマンドでまとめてインストールします。つまりTerraformも含めて、既に aqua install の中でインストール済みなとなるため、setup-terraform を追加で呼ぶと同じツールを二重にインストールすることになります。
ディレクトリ構成(検証時の構成)
repo/
├── aqua.yaml # ツールバージョンの単一情報源
├── tfcmt.yaml # tfcmtテンプレート(ルート必須)
├── .tflint.hcl # tflint設定ファイル
├── env/
│ └── dev/
│ └── 01_s3/ # terraform S3バケット作成
├── modules/
│ └── s3_bucket # terraform s3バケット作成用モジュール
│ ├── main.tf
│ ├── variables.tf
│ └── outputs.tf
│
└── .github/
└── workflows/
├── terraform_plan.yml # reusable(tflint + plan + tfcmt)
├── terraform_apply.yml # reusable(apply)
├── dev_plan.yml # orchestrator(PR時)
└── dev_apply.yml # orchestrator(mainマージ時)
再利用可能ワークフロー(plan側)
aquaのマニュアルでも推奨されていますが、使用するActionをhash指定にします。
GitHubのSecretsにはAWSとの連携に使用するIAMロールのarnや、github access tokenを設定しています。GitHubの設定については、GitHubのマニュアルやインターネット上に公開されている記事を参考にしてください。working_directoryについては、後述のdev_plan.ymlに記載しています。
検証したコード(.github/workflows/terraform_plan.yml)
name: Terraform Plan (Reusable)
on:
workflow_call:
inputs:
working_directory:
required: true
type: string
jobs:
plan:
runs-on: ubuntu-latest
permissions:
id-token: write # OIDC
contents: read
pull-requests: write # tfcmt
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
# aqua_version の明示が必須
- uses: aquaproj/aqua-installer@96a9bc20066c5bf5e275b41019cfc165b25f4e2e # v4.0.5
with:
aqua_version: v2.59.1
- name: aqua install
run: aqua install
- uses: aws-actions/configure-aws-credentials@517a711dbcd0e402f90c77e7e2f81e849156e31d # v6
with:
aws-region: ap-northeast-1
role-to-assume: ${{ secrets.AWS_ROLE_ARN }}
- name: tflint init
run: tflint --init
working-directory: ${{ inputs.working_directory }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: tflint run
run: tflint --format compact
working-directory: ${{ inputs.working_directory }}
- name: terraform init
run: terraform init
working-directory: ${{ inputs.working_directory }}
# --title は無い。--var "target:..." + tfcmt.yaml テンプレート
- name: terraform plan
run: tfcmt plan -patch --var "target:${{ inputs.working_directory }}" -- terraform plan -no-color
working-directory: ${{ inputs.working_directory }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
“aqua_version” の明示が必須
aquaproj/aqua-installerは、aqua_version` パラメータを省略するとエラーになります。「Actionのバージョンを指定すればaqua本体のバージョンも決まる」というわではありませんので、ご注意ください。
呼び出し側で permissions を明示しないと権限がすべて none になる
再利用可能ワークフロー(`workflow_call`)を呼び出す側のワークフローでも、`permissions` を明示的に宣言する必要があります。宣言を忘れるとデフォルトで権限がなくなり、OIDC認証(`id-token: write`)やtfcmtのPRコメント投稿(`pull-requests: write`)が失敗します。
検証したコード(.github/workflows/dev_plan.yml)
name: Dev Plan
on:
pull_request:
branches: [main]
paths:
- 'env/dev/**'
- 'modules/**'
- 'aqua.yaml'
- '.tflint.hcl'
- '.github/workflows/**'
jobs:
plan-01-s3:
uses: ./.github/workflows/terraform_plan.yml
with:
working_directory: env/dev/01_s3
secrets: inherit
# 呼び出し側でも permissions 明示必須(忘れると全権限none)
permissions:
id-token: write
contents: read
pull-requests: write
tfcmtとの組み合わせ: plan結果をPRコメントに
“tfcmt.yaml” はリポジトリルートに置く
tfcmtは terraform plan の結果を見やすく整形してPRにコメント投稿するツールです。aquaでバージョン管理できます。
設定ファイルは 「.github/workflows/」 配下に置いても読み込まれません。リポジトリルートに配置する必要があります。`-patch` オプションを付けると、同じtargetのコメントは新規投稿ではなく既存コメントの更新になるため、pushのたびにPRがコメントで埋まるのを防げます。
検証したコード(tfcmt.yaml)
plan:
template: |
## Plan Result ({{ .Vars.target }})
{{ template "plan_title" . }}
{{ if .Link }}[CI link]({{ .Link }}){{ end }}
{{ template "deletion_warning" . }}
{{ template "result" . }}
{{ template "updated_resources" . }}
<details><summary>Details (Click me)</summary>
{{ wrapCode .CombinedOutput }}
</details>
apply:
template: |
## Apply Result ({{ .Vars.target }})
{{ template "apply_title" . }}
{{ if .Link }}[CI link]({{ .Link }}){{ end }}
<details><summary>Details (Click me)</summary>
{{ wrapCode .CombinedOutput }}
</details>
検証したコード(ftlint.hcl)
plugin "aws" {
enabled = true
version = "0.48.0"
source = "github.com/terraform-linters/tflint-ruleset-aws"
}
rule "terraform_naming_convention" {
enabled = true
}
rule "terraform_documented_variables" {
enabled = true
}
rule "terraform_unused_declarations" {
enabled = true
}
検証したコード(aqua.yaml)
このファイルは、aqua init( aqua manual Create a configuration file )コマンドで作成されます。作成したファイルをご使用ください。
---
# yaml-language-server: $schema=https://raw.githubusercontent.com/aquaproj/aqua/main/json-schema/aqua-yaml.json
# aqua - Declarative CLI Version Manager
# https://aquaproj.github.io/
# checksum:
# enabled: true
# require_checksum: true
# supported_envs:
# - all
registries:
- type: standard
ref: v4.533.1 # renovate: depName=aquaproj/aqua-registry
packages:
- name: hashicorp/terraform@v1.15.7
- name: terraform-linters/tflint@v0.63.1
- name: suzuki-shunsuke/tfcmt@v4.14.15
GitHubでPRにterraform plan結果が反映されたところ

まとめ
- aquaを使うと、Terraform本体と周辺ツール(tflint、tfcmt等)のバージョンを `aqua.yaml` 1ファイルでローカル・CI共通管理できる
- CIでは `aquaproj/aqua-installer` + `aqua install` の2ステップで全ツールが揃い、`hashicorp/setup-terraform` は不要になる
- aqua + tflint + tfcmt の3点セットなら、追加のGitHub Appなしに標準の `GITHUB_TOKEN` とOIDCだけで完結できる。CI/CDのチームへの初期導入としては、この構成のほうが説明も運用もシンプルになる。(モノレポの変更検出やドリフト検出が必要な場合は、tfactionが選択肢になります。)
- tfactionのようなフル機能フレームワークの前段として、aqua + tflint + tfcmt のシンプル構成から始めることでチーム導入の観点でも有効な選択肢になる。
バージョン管理の一元化は地味ですが、「新メンバーが `aqua install` コマンドで環境構築できる」「CIとローカルで挙動が違う問題が消える」という効果があります。
—-
本記事に記載のバージョンは検証時点のものです。導入時は各ツールの最新バージョンを確認してください。また、動作の確認を十分に行い、検証の上ご使用ください。