GitHub ActionsでTerraformの実行を自動化する

公開:2020/09/20
更新:2020/09/24
11 min読了の目安(約7000字TECH技術記事

どんな感じに自動化したいか

  • Pull Request作成時にterraform planを実行し、その結果をPRにコメントしたい。
  • Pull Requestをマージしたときにterraform applyを実行したい。
  • tfstateをいい感じに分けたい。
  • 変更した部分だけterraformを実行したい。

実際のコードはこちら

解説

plan.yml

  • planはPR作成時や変更時にGitHub Actionsを実行しましょう。
name: Plan
on:
  pull_request:
    branches:
      - master

  • strategy.matrix に何か配列を指定すると、それぞれを使って並列にジョブを実行することができます。
  • 1つの tfstate ファイルに対応するディレクトリごとに strategy.matrix.dir に追記していくイメージです。
    • Dev環境、Stg環境などの環境ごとに resources/dev/resources/stg/ などに分割して strategy.matrix.dir に追加していくのがよくありそうです。
    • resources/dev/ 配下でさらに resources/dev/api/ とか resources/dev/worker/ みたいな感じでドリルダウンしていくのも良さそうです。
    • resources/dev/ecs/ とか resources/dev/rds/ みたいにリソースの種類ごとに分けるのもアリかもです。
jobs:
  plan:
    name: Plan
    runs-on: ubuntu-latest
    strategy:
      matrix:
        dir: [
          resources/shd/ecs,
        ]

  • とりあえずCheckoutしてきて、 technote-space/get-diff-action を使ってmasterとの差分を取得しましょう。
    • 自分でgit diffするように実装してもいいと思うんですが、面倒ですよね。
  • ここで取得した差分が存在するかどうかによって、 matrix.dir ごとに terraform plan を実行するかどうかを後続のstepで判断します。
    • id に何か文字列を指定しておくと、後続のstepで実行結果などを参照することができます。
  • 先ほど指定していた strategy.matrix.dir${{ matrix.dir }} みたいな感じで参照することができます。
    steps:
      - name: Checkout
        uses: actions/checkout@v2

      - name: Check diff
        id: diff
        uses: technote-space/get-diff-action@v3.2.0
        with:
          PREFIX_FILTER: |
            modules
            ${{ matrix.dir }}
          SUFFIX_FILTER: .tf

  • 今回はAWSを想定しているのでAWSの認証を行いましょう。
  • ここでは普通にIAM Userのキーペアを使っていますが、会社のセキュリティ事情によってAssumeRoleとかでSessionTokenを取得しないといけなかったりする場合は、頑張ってください。
      - name: Configure aws credentials
        if: steps.diff.outputs.diff
        uses: aws-actions/configure-aws-credentials@v1
        with:
          aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
          aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          aws-region: ap-northeast-1

  • Hashicorp公式が提供している hashicorp/setup-terraform を使って terraform コマンドをインストールしましょう。
  • ifsteps.diff.outputs.diff を指定することで、 Check diff stepで差分が得られたときのみ実行するようにします。
      - name: Setup terraform
        if: steps.diff.outputs.diff
        uses: hashicorp/setup-terraform@v1.2.0
        with:
          terraform_version: 0.13.2

  • terraform fmt でフォーマットをチェックしておきます。
  • フォーマットはPRをマージするまでに直ればいいので、ここでは continue-on-error: true を指定して後続のstepは実行します。
      - name: Check format
        id: fmt
        if: steps.diff.outputs.diff
        run: terraform fmt -check -recursive
        working-directory: ${{ matrix.dir }}
        continue-on-error: true

  • ${{ matrix.dir }}terraform init を実行しましょう。
  • 今回のサンプルではTerraformのBackend設定やProvider設定などを書いた init.tf をディレクトリごとに用意する想定ですが、このファイルを共通化したい場合はリポジトリルートに init/init.tf などを用意して、 ${{ matrix.dir }} にコピーしてから terraform init を実行したりするとよいでしょう。
    • ${{ matrix.dir }} を元に tfstatekey を指定することもお忘れなく。
      - name: Initialize
        id: init
        if: steps.diff.outputs.diff
        run: terraform init
        working-directory: ${{ matrix.dir }}

  • terraform get で依存モジュールのダウンロードなどを行い、 terraform validate で設定を検証しましょう。
      - name: Download modules
        if: steps.diff.outputs.diff
        run: terraform get
        working-directory: ${{ matrix.dir }}

      - name: Validate
        if: steps.diff.outputs.diff
        run: terraform validate
        working-directory: ${{ matrix.dir }}

  • terraform plan を実行しましょう。
  • terraform plan-no-color オプションを指定しているのは、後続のstepでPRにコメントを残す際に表示がおかしくなるのを防ぐためです。
      - name: Plan
        if: steps.diff.outputs.diff
        id: plan
        run: terraform plan -no-color
        working-directory: ${{ matrix.dir }}
        continue-on-error: true

  • actions/github-script を使って terraform fmtterraform plan の結果をPRにコメントしてもらいましょう。
  • いちいちGitHub Actionsの実行結果ページへ行く必要がないので楽ですね。
  • このスクリプトは上述の公式チュートリアルにあるものを拝借しています。
      - name: Comment
        if: steps.diff.outputs.diff
        uses: actions/github-script@v3.0.0
        env:
          PLAN: "terraform\n${{ steps.plan.outputs.stdout }}"
        with:
          github-token: ${{ secrets.GITHUB_TOKEN }}
          script: |
            const output = `## \`${{ matrix.dir }}\`
            #### Terraform Format and Style 🖌\`${{ steps.fmt.outcome }}\`
            #### Terraform Plan 📖\`${{ steps.plan.outcome }}\`

            <details><summary>Show Plan</summary>

            \`\`\`${process.env.PLAN}\`\`\`

            </details>`;

            github.issues.createComment({
              issue_number: context.issue.number,
              owner: context.repo.owner,
              repo: context.repo.repo,
              body: output
            })

apply.yml

  • terraform apply はmasterブランチへのPush時に実行しましょう。
  • strategy まわりはplanと同様です。
name: Apply

on:
  push:
    branches:
      - master

jobs:
  apply:
    name: Apply
    runs-on: ubuntu-latest
    strategy:
      matrix:
        dir: [
          resources/shd/ecs,
        ]

  • applyでは最新のPRの差分を見て実行するかどうかを判断したいので、 reffetch-depth を指定します。
  • actions/checkout@v2 はデフォルトでは最新のコミット、つまり今回の場合はPRのマージコミットしかfetchしてくれないため、 HEAD^HEAD の差分を取得するために fetch-depth: 2 を指定します。
    steps:
      - name: Checkout
        uses: actions/checkout@v2
        with:
          ref: ${{ github.event.pull_request.head.sha }}
          fetch-depth: 2

  • terraform コマンドのインストールとAWSの認証はplanと同様に行います。
      - name: Setup terraform
        uses: hashicorp/setup-terraform@v1.2.0
        with:
          terraform_version: 0.13.2

      - name: Configure aws credentials
        uses: aws-actions/configure-aws-credentials@v1
        with:
          aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
          aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          aws-region: ap-northeast-1

  • 最後に HEAD^ との git diff を取って、 resources/ もしくは modules/ に変更があった場合のみ、 terraform apply を実行します。
  • 小さいですがあまりスクリプトは書きたくないなと思うので、いい感じに差分を取得できるアクションを書いてみたいですね。
  • この結果はGitHub Actionsの実行結果ページに行って確認しましょう。
    • もし失敗していたら新たに修正ブランチをmasterから切ってそのPRをマージする形で対応しましょう。
      - name: Apply
        run: |
          DIFF=$(git diff --name-only HEAD^ modules ${{ matrix.dir }})
          if [[ ${DIFF} = *resources* || ${DIFF} = *modules* ]]; then
            cd ${{ matrix.dir }}
            terraform init
            terraform get
            terraform apply -auto-approve
          fi
        shell: bash

感想

Orb未対応のCircleCIとかで頑張ってスクリプトを書いて実現していたのをある程度キレイに書けていい感じです。