Terraformのコードを書いていると、「動くけど良くないコード」が混入しがちです。存在しないインスタンスタイプを指定していたり、非推奨の書き方をしていたり。terraform validateだけでは検出できない問題が結構あります。
本記事では、GitHub ActionsでTerraformのCIを構築する方法を紹介します。特にtflintの良さを伝えたいので、具体的な検出例も交えて解説します。
ワークフローの全体像
name: CI (Terraform)
on:
pull_request:
paths:
- "**.tf"
push:
branches:
- main
workflow_dispatch:
env:
TERRAFORM_VERSION: "1.12.0"
jobs:
terraform-ci:
name: Lint & Format - ${{ matrix.dir }} (Terraform)
runs-on: ubuntu-latest
strategy:
matrix:
dir:
- account-settings
- alb-ecs/infra
- apigw-lambda/terraform
- cloudfront-vpc-origin-internal-alb
- cloudtrail
- subdomain-phz/parent-phz
- subdomain-phz/sub-phz
- vpc
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 1
- name: Install Terraform
uses: hashicorp/setup-terraform@v3
with:
terraform_version: ${{ env.TERRAFORM_VERSION }}
- name: Terraform init
run: terraform init -backend=false
working-directory: ${{ matrix.dir }}
- name: Terraform validate
run: terraform validate
working-directory: ${{ matrix.dir }}
- name: Terraform format
run: terraform fmt -check -recursive
working-directory: ${{ matrix.dir }}
- name: Tflint install
run: curl -s https://raw.githubusercontent.com/terraform-linters/tflint/master/install_linux.sh | bash
- name: Tflint init
run: tflint --init
working-directory: ${{ matrix.dir }}
- name: Run tflint
run: tflint --recursive
working-directory: ${{ matrix.dir }}
このワークフローのポイント
| ポイント | 説明 |
|---|---|
| matrix による並列実行 | 複数ディレクトリを並列でチェック |
| paths フィルター |
.tfファイルの変更時のみ実行 |
| backend=false | CI環境ではバックエンドに接続しない |
| 4段階のチェック | init → validate → fmt → tflint |
それぞれ見ていきます。
トリガー設定
on:
pull_request:
paths:
- "**.tf"
push:
branches:
- main
workflow_dispatch:
.tfファイルが変更されたPRと、mainブランチへのpushで実行されます。workflow_dispatchを入れておくと、手動実行もできるので便利です。
matrix による複数ディレクトリの並列チェック
strategy:
matrix:
dir:
- account-settings
- alb-ecs/infra
- apigw-lambda/terraform
# ...
私は、このGitHub Actionsを自分が作成したコード例のレポジトリに作っています。なので種類の異なるようなTerraformコードが一つのレポジトリに入っています。
そこで役に立ったのがmatrix指定です。これによって、簡単に全てのレポジトリに対して並列にチェックが回せます。
この部分のdir指定は静的なものでなければなりません。本当は必要な箇所を検出して欲しいんですが、ディレクトリの深さが統一されていない点や、その時の流行りによってinfra/terraformのどちらのフォルダ名を使うかがまちまち、という問題があり難しそうでした。
AWS re;Invent参加前はinfra派閥だったのですが、terraformを使っているサンプルコードをre;Inventで見つけてしまい迷い迷いです。
4段階のチェック
1. terraform init
- name: Terraform init
run: terraform init -backend=false
working-directory: ${{ matrix.dir }}
-backend=falseがポイントです。CI環境ではS3などのバックエンドに接続する必要はありません。プロバイダーのダウンロードだけ行います。
2. terraform validate
- name: Terraform validate
run: terraform validate
working-directory: ${{ matrix.dir }}
構文エラーや参照エラーをチェックします。ただし、validateで検出できる問題は限定的です。
3. terraform fmt
- name: Terraform format
run: terraform fmt -check -recursive
working-directory: ${{ matrix.dir }}
フォーマットが統一されているかをチェックします。-checkオプションで、フォーマットが崩れていたら失敗します。
4. tflint
- name: Tflint install
run: curl -s https://raw.githubusercontent.com/terraform-linters/tflint/master/install_linux.sh | bash
- name: Tflint init
run: tflint --init
working-directory: ${{ matrix.dir }}
- name: Run tflint
run: tflint --recursive
working-directory: ${{ matrix.dir }}
ここが本記事のメインです。tflintはterraform validateでは検出できない問題を見つけてくれます。
tflintはいいぞ
tflintは、Terraformのリンターです。構文的には正しいけど「良くないコード」を検出してくれます。
tflintが検出してくれる例
1. 存在しないインスタンスタイプ
# NG: tflintで検出される
resource "aws_instance" "example" {
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t2.micro_typo" # 存在しないタイプ
}
Error: "t2.micro_typo" is an invalid value as instance_type (aws_instance_invalid_type)
terraform validateはこれを通してしまいます。実際にapplyするまでエラーに気づけません。tflintなら即座に検出できます。
2. 非推奨の属性
# NG: tflintで検出される
resource "aws_instance" "example" {
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t3.micro"
vpc_security_group_ids = ["sg-12345678"]
# 非推奨: security_groups は EC2-Classic 用
security_groups = ["default"]
}
Warning: "security_groups" is deprecated (aws_instance_deprecated_attribute)
EC2-Classicは廃止されたので、security_groupsではなくvpc_security_group_idsを使うべきです。
3. 無効なAMI ID形式
# NG: tflintで検出される
resource "aws_instance" "example" {
ami = "invalid-ami-id" # AMI IDの形式が不正
instance_type = "t3.micro"
}
Error: "invalid-ami-id" is an invalid value as ami (aws_instance_invalid_ami)
AMI IDはami-で始まる必要があります。タイポを事前に検出できます。
4. デフォルトのセキュリティグループ
# NG: tflintで検出される(ルールによる)
resource "aws_security_group_rule" "example" {
type = "ingress"
from_port = 22
to_port = 22
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"] # 全世界に公開
security_group_id = aws_security_group.example.id
}
Warning: cidr_blocks contains "0.0.0.0/0" (aws_security_group_rule_invalid_cidr_block)
SSHを全世界に公開するのは危険です。tflintはセキュリティ上の問題も指摘してくれます。
terraform validateでは検出できない理由
terraform validateは構文チェックと参照の整合性チェックを行います。つまり、「HCLとして正しいか」「変数やリソースの参照が正しいか」を見ています。
一方、「t2.micro_typoというインスタンスタイプがAWSに存在するか」は、AWSのAPIを叩かないと分かりません。validateはAPIを叩かないので、この種のエラーは検出できません。
tflintはAWSリソースの有効な値をルールとして持っているので、APIを叩かなくても検出できます。
検出されない例(正しいコード)
# OK: 問題なし
resource "aws_instance" "example" {
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t3.micro"
vpc_security_group_ids = [aws_security_group.example.id]
tags = {
Name = "example"
}
}
resource "aws_security_group" "example" {
name = "example"
description = "Example security group"
vpc_id = aws_vpc.main.id
ingress {
from_port = 22
to_port = 22
protocol = "tcp"
security_groups = [aws_security_group.bastion.id] # 特定のSGからのみ許可
}
}
このコードは以下の点で問題ありません。
-
t3.microは有効なインスタンスタイプ - AMI IDの形式が正しい
-
vpc_security_group_idsを使っている(非推奨のsecurity_groupsではない) - SSHは特定のセキュリティグループからのみ許可
.tflint.hcl の設定
tflintの挙動は.tflint.hclファイルでカスタマイズできます。
plugin "aws" {
enabled = true
version = "0.36.0"
source = "github.com/terraform-linters/tflint-ruleset-aws"
}
rule "terraform_naming_convention" {
enabled = true
}
rule "terraform_documented_variables" {
enabled = true
}
AWSプラグインを有効にすると、AWS固有のルールが使えるようになります。tflint --initでプラグインがダウンロードされます。
CIで落ちたときの対応
tflintで引っかかった場合の対応は簡単です。
- エラーメッセージを確認
- 該当箇所を修正
- ローカルで
tflintを実行して確認 - 再push
ローカルでも同じチェックができるので、pushする前に確認する習慣をつけると良いです。
# ローカルでの実行
cd your-terraform-dir
tflint --init
tflint
まとめ
本記事では、GitHub ActionsでTerraformのCIを構築する方法を紹介しました。
- matrix で並列チェック: 複数ディレクトリを効率的にチェック
- backend=false: CI環境ではバックエンド接続不要
- 4段階のチェック: init → validate → fmt → tflint
特にtflintは、terraform validateでは検出できない問題を見つけてくれます。
- 存在しないインスタンスタイプ
- 非推奨の属性
- 無効なリソース値
- セキュリティ上の問題
applyする前に問題を検出できるので、本番環境での事故を防げます。tflintはいいぞ。