
クラウド事業推進部の石倉です。
Terraformで管理しているインフラ環境の状態、把握してますか?
作業途中など把握している差分ならいいですが、作業の戻し忘れによる差分などは時間が経った時に「何の変更だ?」とびっくりすることになります。
そんな環境の状態を把握できるように、今回はTerraformのドリフト検知結果をSlackに通知させる仕組みをGitHub Actionsでやってみたのでご紹介します。 インフラ環境はAWSです。

Terraformのディレクトリ構造
Terraformリソースについて、今回は複数環境の想定で以下のようなディレクトリ構造にしています。
.
└── terraform
├── envs
│ ├── dev
│ │ ├── xxxxx.tf
│ │ └── main.tf
│ ├── prd
│ │ ├── xxxxx.tf
│ │ └── main.tf
│ └── stg
│ ├── xxxxx.tf
│ └── main.tf
└── modules
├── resource-a
│ ├── main.tf
│ └── xxxxx.tf
└── resource-b
├── main.tf
└── xxxxx.tf
参考
一般的なスタイルと構造に関するベスト プラクティス | Terraform | Google Cloud
コード
上記のTerraformのディレクトリ構造における各環境(dev, stg, prd)に対してterraform planを行い、結果をまとめSlackに通知するコードです。
処理の流れとしては以下のような流れです。
- 各環境に対してplanを行い差分があった環境の情報を出力する処理を並列で実行する(drift-detectionジョブ)
- 1の処理が終わったら差分があった場合となかった場合でSlack通知する(notify-drift、notify-no-driftジョブ)
name: Terraform Drift Detection
on:
schedule:
- cron: '0 8 * * *'
workflow_dispatch:
jobs:
drift-detection:
runs-on: ubuntu-latest
outputs:
output_dev: ${{ steps.export.outputs.output_dev }}
output_stg: ${{ steps.export.outputs.output_stg }}
output_prd: ${{ steps.export.outputs.output_prd }}
strategy:
matrix:
environment: ["dev", "stg", "prd"]
fail-fast: false
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup Terraform
uses: hashicorp/setup-terraform@v3
with:
terraform_wrapper: false
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: ap-northeast-1
- name: Terraform Init
id: init
run: |
terraform init
working-directory: ./terraform/envs/${{ matrix.environment }}
- name: Terraform Plan
id: plan
run: |
set +e
terraform plan -detailed-exitcode
echo "exit_code=$?" >> "$GITHUB_OUTPUT"
working-directory: ./terraform/envs/${{ matrix.environment }}
- name: Export environment
if: ${{ steps.plan.outputs.exit_code == 2 }}
id: export
run: |
echo "output_${{ matrix.environment }}=${{ matrix.environment }}" >> "$GITHUB_OUTPUT"
notify-drift:
if: ${{ toJson(needs.drift-detection.outputs) != '{}' }}
runs-on: ubuntu-latest
needs: [drift-detection]
steps:
- name: Collect environment
id: collect
run: |
values=$(echo '${{ toJson(needs.drift-detection.outputs) }}' | jq -r '[.[]] | join(", ")')
echo "collect_env=$values" >> "$GITHUB_OUTPUT"
- name: Post a message in channel
uses: slackapi/slack-github-action@v2.0.0
with:
webhook: ${{ secrets.SLACK_WEBHOOK_URL }}
webhook-type: incoming-webhook
payload: |
{
"blocks": [
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": ":warning: *`${{ steps.collect.outputs.collect_env }}` 環境でドリフトを検知したよ。把握していますか?*"
}
},
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "<https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}|ワークフロー詳細>"
}
}
]
}
notify-no-drift:
if: ${{ toJson(needs.drift-detection.outputs) == '{}' }}
runs-on: ubuntu-latest
needs: [drift-detection]
steps:
- name: Post a message in channel
uses: slackapi/slack-github-action@v2.0.0
with:
webhook: ${{ secrets.SLACK_WEBHOOK_URL }}
webhook-type: incoming-webhook
payload: |
{
"blocks": [
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": ":ok: *ドリフトなし!*"
}
}
]
}
各コードにおけるTips
ドリフト検知を並列実行する
複数環境を想定というところで今回はGitHub Actionsのmatrixを利用して各環境に対してplanを行うdrift-detectionジョブを並列実行し効率化しています。
strategy:
matrix:
environment: ["dev", "stg", "prd"]
fail-fast: false
ワークフローでのジョブのバリエーションの実行 - GitHub Docs
Terraform実行環境の用意
HashiCorpが提供しているGitHubActionを利用してワークフロー上でTerraformコマンドを実行できるようにしています。
- name: Setup Terraform
uses: hashicorp/setup-terraform@v3
with:
terraform_wrapper: false
GitHub - hashicorp/setup-terraform: Sets up Terraform CLI in your GitHub Actions workflow.
ここで一つterraform_wrapper: falseについて、この設定はデフォルトではtrueになっているのですが後ほど説明するterraform plan -detailed-exitcodeの終了コードが正しく効かなくなってしまう事象があったためfalseにしています。
GitHub ActionsからAWS環境への認証方法
今回はAWS環境への認証をアクセスキーの方法でやっていますがプロジェクトで利用する場合はOpenID Connect (OIDC) での方法に変える方がベストかと思います。
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: ap-northeast-1
terraform planで差分があった環境だけを取得する
detailed-exitcodeというplan結果によって終了コードを分けるオプションがあるためこちらを利用して差分があった場合に(終了コードが2)、その環境情報を出力するようにしています。
- name: Terraform Plan
id: plan
run: |
set +e
terraform plan -detailed-exitcode
echo "exit_code=$?" >> "$GITHUB_OUTPUT"
working-directory: ./terraform/envs/${{ matrix.environment }}
- name: Export environment
if: ${{ steps.plan.outputs.exit_code == 2 }}
id: export
run: |
echo "output_${{ matrix.environment }}=${{ matrix.environment }}" >> "$GITHUB_OUTPUT"
https://developer.hashicorp.com/terraform/cli/commands/plan#detailed-exitcode
このときに先ほどのterraform_wrapper: falseがtrueになっていると差分があっても終了コードが0(差分なし)になってしまう事象があります。
https://github.com/hashicorp/setup-terraform/issues/328
またGitHub Actionsはset -eがデフォルトで設定されておりterraform plan -detailed-exitcodeで終了コードが2になるとその時点で処理が終わってしまうのでset +eの設定をしています。
GitHub Actions のワークフロー構文 - GitHub Docs
ステップ間、ジョブ間の出力パラメータの利用
plan結果の情報を次のステップに渡したり、planが失敗した環境の情報を次のジョブに渡して利用しています。
- name: Terraform Plan
id: plan
run: |
...
...
echo "exit_code=$?" >> "$GITHUB_OUTPUT"
working-directory: ./terraform/envs/${{ matrix.environment }}
- name: Export environment
if: ${{ steps.plan.outputs.exit_code == 2 }}
drift-detection:
outputs:
output_dev: ${{ steps.export.outputs.output_dev }}
output_prd: ${{ steps.export.outputs.output_prd }}
output_stg: ${{ steps.export.outputs.output_stg }}
...
...
steps:
...
...
- name: Export environment
...
...
run: |
echo "output_${{ matrix.environment }}=${{ matrix.environment }}" >> "$GITHUB_OUTPUT"
notify-drift:
if: ${{ toJson(needs.drift-detection.outputs) != '{}' }}
それぞれの記述コードについては以下を参考にしました。
GitHub Actions のワークフロー コマンド - GitHub Docs
GitHub Actions のワークフロー構文 - GitHub Docs
jqコマンドによる整形処理
drift-detection:
...
...
steps:
...
...
- name: Export environment
...
...
run: |
echo "output_${{ matrix.environment }}=${{ matrix.environment }}" >> "$GITHUB_OUTPUT"
notify-drift:
...
...
steps:
- name: Collect environment
id: collect
run: |
values=$(echo '${{ toJson(needs.drift-detection.outputs) }}' | jq -r '[.[]] | join(", ")')
echo "collect_env=$values" >> "$GITHUB_OUTPUT"
Export environmentステップで差分があった環境の情報を出力し、notify-driftジョブでecho '${{ toJson(needs.drift-detection.outputs) }}'で取得した情報は以下のような形になっています。
{
"output_stg": "stg",
"output_dev": "dev"
}
記事冒頭でお見せしたイメージのような「stg, dev 環境でドリフトを検知したよ」というようなメッセージで送るためにjqコマンドで値を取得・整形しています。
最終的にはcollect_envに「stg, dev」というような環境名を, で繋げるような形に整形しています。
Slackへの通知
Slackへの通知にはSlackが提供しているGitHubActionを利用しています。
- name: Post a message in channel
uses: slackapi/slack-github-action@v2.0.0
with:
webhook: ${{ secrets.SLACK_WEBHOOK_URL }}
webhook-type: incoming-webhook
GitHub - slackapi/slack-github-action: Send data into Slack using this GitHub Action!
Slackチャンネルにメッセージを送信するためにWebhookURLを利用するのでSlackappを作成してWebhookURLを用意します。
Sending messages using incoming webhooks | Slack Developer Docs
メッセージ作成
Slackへのメッセージの作成はBlock Kitという仕組みを利用しています。 Block Kit Builderという簡単にBlock Kitでのメッセージ作成をできるようにするツールを用意してくれているのでそれを使ってメッセージを作成しました。
- name: Post a message in channel
uses: slackapi/slack-github-action@v2.0.0
with:
webhook: ${{ secrets.SLACK_WEBHOOK_URL }}
webhook-type: incoming-webhook
payload: |
{
"blocks": [
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": ":warning: *`${{ steps.collect.outputs.collect_env }}` 環境でドリフトを検知したよ。把握していますか?*"
}
},
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "<https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}|ワークフロー詳細>"
}
}
]
}
Block Kit | Slack Developer Docs
最後に
いかがでしたでしょうか。 同じようなことをしようと考えている方の参考になれば幸いです。