Terraform × GitHub Actionsでドリフト検出

Tech

2024.12.19

Topics

この記事はNHN テコラス Advent Calendar 2024の19日目の記事です。

はじめに

インフラ管理において、コードと実環境の同期は非常に重要です。
たとえば、手動でリソースを変更した場合や、コードの適用漏れがあった場合、実環境が意図しない状態になるリスクがあります。
本記事では、GitHub Actionsを用いてTerraformコードと実環境の差分を定期的に検出し、Slackで通知する仕組みを構築する方法をご紹介します。
今回の仕組みでは以下を実現することを目的としています。

  • コードと実環境の差分を定期的に検出
  • 差分がある場合はSlackに通知
  • 運用負担を軽減するため自動化を実現

コード内容

Github Actionsのワークフローファイルは以下になります。

name: Terraform Plan定期実行

on:
  workflow_dispatch:
   schedule:
     - cron: '0 1 * * *'

permissions:
  id-token: write
  contents: read

env:
  SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }}
  SLACK_CHANNEL: ${{ secrets.SLACK_CHANNEL }}
  IAM_ROLE: ${{ secrets.IAM_ROLE }}

jobs:
  initialization:
    runs-on: ubuntu-latest
    outputs: 
      dir: ${{ steps.find-files.outputs.file_dirs }} 
    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Find main.tf files
        id: find-files
        run: |
          echo "file_dirs=`find . -type f -name 'main.tf' | grep -v '/_.*' | xargs -I {} dirname {} | jq -R -s -c 'split("\n")[:-1]' | jq -c .`" >> $GITHUB_OUTPUT
  tf_plan:
    needs: initialization
    runs-on: ubuntu-latest
    outputs: 
      folder: ${{ steps.update.outputs.folder }} 
    strategy:
      fail-fast: false
      matrix:
        dir: ${{ fromJson(needs.initialization.outputs.dir) }}
    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Configure AWS credentials
        uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: ${{ secrets.IAM_ROLE }}
          role-session-name: github-actions-terraform-drift-session
          aws-region: ap-northeast-1

      - name: Setup Terraform
        uses: hashicorp/setup-terraform@v3
        with:
          terraform_version: 1.3.9
          terraform_wrapper: false

      - name: Terraform Init
        working-directory: ${{ matrix.dir }}
        run: terraform init

      - name: Terraform Plan
        id: plan
        working-directory: ${{ matrix.dir }}
        run: terraform plan -detailed-exitcode -var-file terraform.tfvars.actions

      - name: Slack Notification
        uses: slackapi/slack-github-action@v1.25.0
        if: ${{ failure() }}
        with:
          channel-id: '${{ secrets.SLACK_CHANNEL }}'
          payload: |
            {
              "text": ":rotating_light: *terraformフォルダに差分があります (${{ matrix.dir }})*\n\n",
              "blocks": [
                {
                  "type": "section",
                  "text": {
                    "type": "mrkdwn",
                    "text": ":rotating_light: *terraformフォルダに差分があります (${{ matrix.dir }})*\n\n"
                  }
                },
                {
                  "type": "context",
                  "elements": [
                    {
                        "type": "mrkdwn",
                        "text": "GitHub Actions URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"
                    }
                  ]
                },
                {
                  "type": "divider"
                }
              ]
            }
        env:
          SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }}

差分が検出されると、以下のようにSlackで通知されます。
通知内容には、どのフォルダで差分が発生したか、詳細ログへのリンクが含まれています。

実装概要

ワークフロー上の処理内容は以下になります。

  1. Terraform Planの実行
    • 各フォルダ内でprovider情報が記載されたmain.tfファイルを検索し、フォルダパスを検出
    • 検出したフォルダに対し、matrix関数を用いて並列でTerraform Planを実行
    • 差分検知には-detailed-exitcodeオプションを使用
    • Terraform Planの実行時に必要な値はterraform.tfvars.actionsファイルで仮の値を指定
  2. Slack通知
    • 差分が検出されたフォルダのみSlackに通知
    • 通知にはSlack AppのBotを使用

実装ポイント

フォルダ構成

GitHub上では以下のようにAWSアカウントごとにディレクトリを分割してコードを管理しています。
provider情報があるファイル(今回の場合は、main.tf)を検索し、見つかったファイルのフォルダパスを変数に格納する形にしています。

terraform
├── dev_aws_account
│   ├── main.tf
│   ├── aws_vpc.tf
│   ├── aws_ec2.tf
│   ├── ...
│   ├── terraform.tfvars.actions
├── stag_aws_account
│   ├── main.tf
│   ├── aws_vpc.tf
│   ├── aws_ec2.tf
│   ├── ...
│   ├── terraform.tfvars.actions
├── prod_aws_account
│   ├── main.tf
│   ├── aws_vpc.tf
│   ├── aws_ec2.tf
│   ├── ...
│   ├── terraform.tfvars.actions
└── README.md

Terraform Plan実行時の入力値について

以下のようにvariable変数にデフォルト値を格納せず、Terraform実行時にコマンドプロンプトから一度きり入力するという運用にしている場合、ひと手間必要になります。

variable "database_name" {
  sensitive = true
}

variable "master_username" {
  sensitive = true
}

以下のように空の値を格納したtfvarsファイルを用意してTerraform Plan実行時に指定することで実行エラーを回避しています。

database_name = ""
master_username = ""

差分検知方法について

Terraform Planの-detailed-exitcodeオプションを利用して差分有無を終了コードで判定しています。
終了コードが異常終了の場合、Actionsのjobsも失敗判定になるため、失敗判定になったものだけSlackに通知するように処理しています。
通知では差分があることだけ通知して、実際の差分はjobsの詳細画面から確認することが可能です。

まとめ

GitHub ActionsとTerraformの組み合わせで、より効率的なインフラ運用を目指してみてください!

テックブログ新着情報のほか、AWSやGoogle Cloudに関するお役立ち情報を配信中!

NT1

インフラエンジニアです。刹那的に生きてます。

Recommends

こちらもおすすめ

Special Topics

注目記事はこちら