Github Actions で Terraform の構成ドリフト検出

2022/12/05に公開

今回のサンプルコードを上げているリポジトリです。

https://github.com/jDBTISK/tf-detect-drift-sample

想定読者

  • Github で Terraform のコードを管理している、もしくはしようと思っている人
  • Terraform 自体の経験はすでにあるよという人

ちなみに紹介しているコードが AWS 仕様になっているので、GCP や Azure のリソースを Terraform で管理している場合は若干置き換えて読んで頂く必要があります。

Github Actions や Terraform 自体の書き方については細かく触れていないのでご注意を。

構成ドリフト

コードで管理しているインフラの構成と、実リソースの構成に不一致がある場合「構成ドリフトが発生している」と呼ばれます。

terraform を使用してインフラを管理する場合、実際に作られたインフラの情報は terraform 自体のソースコードではなく、terraform コマンドの実行時に作成/更新される tfstate ファイルに保存されます。
この tfstate ファイルに記録されている内容と実リソースに差分が出ているかどうかで構成ドリフトを判断します。

本記事は Github Actions を用いて1日1回 Terraform で管理しているインフラリソースの構成ドリフトを検出しようという内容です。

なぜ構成ドリフト検出が必要なのか

最近は terraform や CDK 等の IaC ツールを用いてインフラもコードで管理する事が多くなっていますよね。

しかし、いざ terraform を使い始めると「terraform で作ったリソースを手動で変更してしまい terraform のコードと差分が出てしまう」現象に遭遇することがあります。
個人で開発している場合は自分だけ気をつければ回避出来ますが、人数が増えてくると「terraform で作ったリソースを絶対他の手段で変更しない」という統制も難しくなってきます。

「terraform で作ったリソースの設定がいつの間にか変更されてた!!」と気づくのは、terraform planterraform apply コマンドの実行時になります。

このときに出る差分を見て「いつの間にか設定変わっとるやん!!」となってから、誰が変更したのかを CloudTrail 等で追っかけて、元の設定(terraform 上の設定)に戻していいのか確認して... という作業が発生することになるのですが、これに気づくのが遅れるほど当初 terraform のコードを書いた人の想定とは異なる状態で運用されることになりますし、手動で変更してしまった人もどういう理由で操作したのか忘れてしまったりということがあります。

そのため、構成ドリフトが発生している場合はなるべく早い段階で検知できる仕組みがほしいということになります。

どうやって構成ドリフトを自動で検出するか

terraform plan コマンドに対して -detailed-exitcode オプションを付与すると終了コードで差分があるか判定出来るのでこれを利用します。

Github Actions では cron 式を用いたスケジュール実行も可能なので、これにより毎日1回構成ドリフトの有無を Slack に通知するということをやっていきたいと思います。

今回は Github Actions での例を紹介しますが、やることとしては「terraform plan -detailed-exitcode の終了コードによって Slack への通知メッセージを変える」だけなので、GitLab CI/CD 等の他の CI ツールでもできるはずです。

Github Actions

まずはコードを貼ります。

.github/workflows/drift.yml
name: drift

on:
  schedule:
    - cron: "0 20 * * *"

permissions:
  id-token: write
  contents: read
  pull-requests: read

env:
  SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK_URL }}
  SLACK_USERNAME: github
  SLACK_CHANNEL: ${{ secrets.SLACK_CHANNEL }}

jobs:
  drift:
    name: Drift
    runs-on: ubuntu-latest
    defaults:
      run:
        working-directory: src

    steps:
      - name: Checkout
        uses: actions/checkout@v3

      - uses: aws-actions/configure-aws-credentials@v1
        with:
          role-to-assume: ${{ secrets.BACKEND_ASSUME_ROLE_ARN }}
          role-session-name: github-actions-terraform-drift-session
          aws-region: ${{ secrets.AWS_REGION }}

      - name: Setup Terraform
        uses: hashicorp/setup-terraform@v1
        with:
          terraform_version: ${{ secrets.TF_VERSION }}

      - name: Terraform Init
        run: terraform init
          -backend-config "region=${{ secrets.AWS_REGION }}"
          -backend-config "bucket=${{ secrets.BACKEND_S3_BUCKET }}"

      - name: Terraform Plan
        run: terraform plan
          -var "assume_role_arn=${{ secrets.ASSUME_ROLE_ARN }}"
          -detailed-exitcode

      - name: Nothing Drift
        if: ${{ success() }}
        uses: rtCamp/action-slack-notify@v2
        env:
          SLACK_TITLE: Nothing Drift
          SLACK_COLOR: good

      - name: Detect Drift
        if: ${{ failure() }}
        uses: rtCamp/action-slack-notify@v2
        env:
          SLACK_TITLE: Detect Drift
          SLACK_COLOR: danger

ポイントになるのは jobs の最後の 3 step です

      - name: Terraform Plan
        run: terraform plan
          -var "assume_role_arn=${{ secrets.ASSUME_ROLE_ARN }}"
          -detailed-exitcode

      - name: Nothing Drift
        if: ${{ success() }}
        uses: rtCamp/action-slack-notify@v2
        env:
          SLACK_TITLE: Nothing Drift
          SLACK_COLOR: good

      - name: Detect Drift
        if: ${{ failure() }}
        uses: rtCamp/action-slack-notify@v2
        env:
          SLACK_TITLE: Detect Drift
          SLACK_COLOR: danger

Terraform Plan で変更がなかった場合(構成ドリフトが検出されなかった場合)は Nothing Drift 、変更があった場合は Detect Drift が実行されます。

Nothing DriftDetect Drift では Slack Notify という Github Actions Library を使用しています。

今回の例では毎日日本時間の5時(UTC の20時)に実行するようになっています。(実際には Github Actions のスケジュール実行は数分~20分くらい遅れるので5時10分台くらいに通知が来ることが多いです)

  schedule:
    - cron: "0 20 * * *"

実際の通知例

時間が5時からかけ離れてるのはスクショ取るために時間ずらして CI 動かしてたからなので気にしないでください

Nothing Drift

Detect Drift

Discussion