TFLintとdiggerでTerraformのCI/CDを構築する
IaC (Infrastructure as Code) を実現するためのツールとしてTerraformがあります。この記事ではTFLint (リンター) とdigger (CI/CD オーケストレータ) をGitHub Actions上で動かすことを目標に、TerraformのCI/CDを構築する手順を紹介します。diggerについては国内の採用事例が少なく情報が見つけにくかったので、何かの参考になれば幸いです。
前提
AWSリソースを扱う複数の環境 (本番とステージング) を1つのリポジトリで管理することを想定して設定しています。ディレクトリ構成は以下の通りです。
.
├── .github
│ └── workflows
│ ├── ci.yml
│ └── digger_workflow.yml
├── .gitignore
├── .terraform-version
├── .tflint.hcl
├── digger.yml
└── environments
├── production
│ ├── .terraform.lock.hcl
│ ├── backend.tf
│ ├── main.tf
│ ├── outputs.tf
│ ├── providers.tf
│ ├── terraform.tf
│ └── variables.tf
└── staging
├── .terraform.lock.hcl
├── backend.tf
├── main.tf
├── outputs.tf
├── providers.tf
├── terraform.tf
└── variables.tf
Terraformのインストール
バージョン管理ツールであるtfenvを使用して、Terraformをインストールします。導入の目的としては開発者のローカルPCにおける環境差異をなくし、diggerでもローカルと同じバージョンのTerraformを使用するためです。
brew install tfenv
touch .terraform-version
tfenv install
1.11.3
TFLintの設定
TFLintとは
TFLintはTerraformのリントツールです。プラグインを読み込ませることで、AWSといったクラウドプロバイダーのレベルで構文をチェックしてくれるので、Terraformの標準コマンドである teffaform validate
よりも厳密なチェックが行えます。
また、公式のスタイルガイドでも紹介されており、非推奨となった構文やベストプラクティスに沿っていない書き方を注意してくれるため、コード品質の向上にも大きく寄与します。
インストール・設定ファイルの作成
TFLintをインストールして、プロジェクトルートに設定ファイル .tflint.hcl
を作成します。
brew install tflint
touch .tflint.hcl
plugin "terraform" {
# すべてのルールを有効にする
preset = "all"
enabled = true
}
# AWSプラグインを設定する
plugin "aws" {
enabled = true
version = "0.38.0"
source = "github.com/terraform-linters/tflint-ruleset-aws"
}
試しに不正なコードを書いてみて、TFLintが検知できることを確認しましょう。
- コメントの書き方がベストプラクティスに従っていない
- 存在しないEC2 インスタンスイメージを指定
- 存在しないEc2 インスタンスタイプを指定
+resource "aws_instance" "web_app" {
+ // invalid comment
+ ami = "ami-no-such-image"
+ instance_type = "t1.no-such-instance"
$ cd environments/staging
$ tflint --config=$(realpath ../../.tflint.hcl)
4 issue(s) found:
Warning: [Fixable] Single line comments should begin with # (terraform_comment_syntax)
on main.tf line 2:
2: // invalid comment
3: ami = "ami-no-such-image"
Reference: https://github.com/terraform-linters/tflint-ruleset-terraform/blob/v0.11.0/docs/rules/terraform_comment_syntax.md
Error: "ami-no-such-image" is invalid AMI ID. (aws_instance_invalid_ami)
on main.tf line 3:
3: ami = "ami-no-such-image"
Error: "t1.no-such-instance" is an invalid value as instance_type (aws_instance_invalid_type)
on main.tf line 4:
4: instance_type = "t1.no-such-instance"
Warning: "t1.no-such-instance" is previous generation instance type. (aws_instance_previous_type)
on main.tf line 4:
4: instance_type = "t1.no-such-instance"
Reference: https://github.com/terraform-linters/tflint-ruleset-aws/blob/v0.38.0/docs/rules/aws_instance_previous_type.md
CIの設定
続いて、GitHub Actionsでフォーマットとリントをの設定をしていきます。フォーマットには標準の terraform fmt
コマンド、リントにはTFLintを利用するのですが、それぞれマーケットプレイスでアクションが提供されています。
基本的にこれらのアクションを利用するだけになります。ワークフローファイル (.github/workflows/ci.yml
) を作成して、2つのジョブを記載します。
name: CI
on:
push:
branches:
- main
pull_request:
branches:
- main
jobs:
format:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: hashicorp/setup-terraform@v3
with:
# `.terraform-version` で指定されたバージョンを使用する
terraform_version: $(head -n 1 .terraform-version)
- name: Run Terraform Format
run: terraform fmt --diff --recursive
lint:
runs-on: ubuntu-latest
needs: format
steps:
- uses: actions/checkout@v4
- uses: terraform-linters/setup-tflint@v4
with:
tflint_version: v0.56.0
- name: Init TFLint
run: tflint --init
env:
GITHUB_TOKEN: ${{ github.token }}
- name: Run TFLint
run: tflint --config "$(pwd)/.tflint.hcl" --recursive --format compact
今回のように環境ごとにディレクトリを分割する構成では、--chdir
オプションを使って各ディレクトリに移動してから tflint
を実行する形になります。また、--format compact
オプションを付与すると、GitHub上でコードの差分とエラーを紐づけて確認できるので便利です。
diggerの設定
diggerとは
diggerはTerraform向けのCI/CD オーケストレーションツールです。類似ツールとして公式が提供するHCP Terraformがありますが、GitHub Actionsへの統合が容易であり、フリープランでリソース数の制限がないことからdiggerを採用しました。
使用イメージとしては、以下のようにPR上で terraform plan
の実行結果が確認できたり、コメントから terraform apply
を実行できたりします。
インストール・設定ファイルの作成
公式チュートリアルに従って進めていきます。GitHub上の設定として以下の2つが必要です。
- diggerを使用するリポジトリに対して、Digger GitHub Appを有効にする
- [1]リポジトリのシークレットにAWSの認証情報を追加する
Digger GitHub Appを有効にする
GitHub Secretsに認証情報を設定する
続いて、プロジェクトルートに 設定ファイル (digger.yml
) を作成します。
projects:
- name: production
dir: ./environments/production
- name: staging
dir: ./environments/staging
# digger applyが成功したらPRを自動マージする
auto_merge: true
auto_merge_strategy: "merge"
最後にワークフローファイル (digger_workflow.yml
) を作成します。
name: Digger
on:
workflow_dispatch:
inputs:
spec:
required: true
run_name:
required: false
run-name: "${{inputs.run_name}}"
jobs:
digger-job:
runs-on: ubuntu-latest
permissions:
contents: write
actions: write
id-token: write
pull-requests: write
issues: read
statuses: write
steps:
- uses: actions/checkout@v4
- name: ${{ fromJSON(github.event.inputs.spec).job_id }}
run: echo "job id ${{ fromJSON(github.event.inputs.spec).job_id }}"
- uses: diggerhq/digger@vLatest
with:
digger-spec: ${{ inputs.spec }}
setup-aws: true
setup-terraform: true
# `.terraform-version` で指定されたバージョンを使用する
terraform-version: $(head -n 1 .terraform-version)
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
env:
GITHUB_CONTEXT: ${{ toJson(github) }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
Terraformを使っていると、terraform plan
はパスしても terraform apply
で失敗することがよくあります。デフォルトの設定では、安全のためPRのコメントから digger apply
を適用するようになっており、PRのマージ後に発火させるよりも安全に適用できます。
ブランチ保護ルールが効かない?
おわりに
TerraformのCI/CD構築について紹介しました。こういった自動化ワークフローは、一度構築することでその後の開発効率が向上します。インフラの変更を容易にするため、今後も力を入れていこうと思います。
-
AWSの認証に環境変数を使用する場合の設定 ↩︎
Discussion