インフラ

GitHub ActionsでTerraformのCI/CDをやってみた

kurochan

こんにちは!黒田ことクロちゃんです。
(これからは社内で呼ばれているニックネーム「クロちゃん」で執筆していこうと思います。)

今回は、GitHub ActionsでTerraformのCI/CDを構築した際の話を書いていこうと思います。
似た内容の記事は、既に色々と存在すると思いますので、
特にブランチ運用とCI/CDの流れ、作業中感じた留意点を厚めに書いていこうと思います!

構築経緯

「pushしたTerraformのコードに対してplanコマンドを実行し、その内容を簡単にSlackへ通知したい」
そんなきっかけから始まって、GitHub Actionsを試しに使ってみたいという欲求が重なり、
今回のGitHub Actionsを使ったCI/CDの構築に至りました。

GitHub Actionsは、公開されている他の方が実装したアクションを使用できます。
今回は、OIDCでAWSの認証情報を取得するアクション、Terraformが使えるようになるアクション、Slack通知するアクションを使用させて頂きました。
他にも便利なアクションは、GitHubのMarketplaceから検索することができます。

ブランチ運用とCI/CDの流れ

ブランチ運用とCI/CDの流れですが、簡単に図にすると以下ような感じです。
Mainブランチは、AWSへデプロイされているTerraformのコードが管理されています。

実装作業の流れを細かく説明すると以下の通りです。

  1. Mainブランチから作業ブラントを切って、そこで作業をしてもらいます
  2. 変更内容は適宜プッシュします
  3. プッシュされたらGitHub Actionsでplanを実行し、結果はSlackへ通知します
  4. 作業が終了したらプルリクエストを出します
  5. プルリクエストが承認されたら、作業ブランチをMainブランチへマージします
  6. マージされたらGitHub Actionsでapplyを実行して、AWSへ変更内容をデプロイします

複数人で同時に作業する場合、tfstateファイルは外部ストレージに切り出していないと
差分管理が面倒になります。
ですので、tfstateファイルはS3で管理することにしました。

GitHub Actionsの中身

前項で説明したフローを実現するべく、実装したGitHub Actionsは以下の節の通りです。
実際は、共通部分を別ファイルに切り出す等しているのですが、
諸事情によりブログに掲載しやすい形へ編集しました。

「どの部分で何をしているか」はコメントを細かく書いているので、確認いただければ幸いです。

なお、以下の情報はGitHubのSecretsに登録しておく必要があります。

  • AWS_ROLE_ARN
    • GitHubとAWS間でOIDCを使用するためのIAMロールのARN
  • SLACK_CHANNEL
    • plan、applyの結果通知を投稿するSlackのチャンネル名
  • SLACK_INCOMING_WEBHOOK_URL
    • Slackで通知を許可する権限を持ったWebhookURL

作業ブランチでplanを実行するアクション

name: terraform plan

# planを実行しないブランチを書いてください(今回は「Main」)
on:
  push:
    branches-ignore:
      - Main
  pull_request:
    branches-ignore:
      - Main

# 動作に必要な権限
permissions:
  id-token: write
  contents: read

jobs:
  terraform_plan:
    runs-on: ubuntu-latest
    steps:

      # ファイルをチェックアウト
      - name: Checkout
        uses: actions/checkout@v3

      # AWS認証(OIDC)
      - name: Configure AWS credentials
        uses: aws-actions/configure-aws-credentials@v1
        with:
          role-to-assume: ${{ secrets.AWS_ROLE_ARN }}
          aws-region: ap-northeast-1

      # Terraform準備
      - name: Setup Terraform
        uses: hashicorp/setup-terraform@v2
        with:
          terraform_version: 1.2.5

      # 以下、Terraformのコマンド実行
      ### フォーマット修正箇所をチェック
      - name: Terraform fmt
        id: fmt
        run: terraform fmt -check

      ### 作業ディレクトリの初期構築
      - name: Terraform Init
        id: init
        run: terraform init

      ### 構文チェック
      - name: Terraform Validate
        id: validate
        run: terraform validate -no-color

      ### コードの内容とデプロイ先環境の差分確認
      - name: Terraform Plan
        id: plan
        run: terraform plan -no-color

      # planの結果を環境変数に格納
      - name: truncate terraform plan result
        run: |
          echo 'PLAN_RESULT<> $GITHUB_ENV
          echo '${{ steps.plan.outputs.stdout }}' | grep -e "No changes." -e "Plan:" >> $GITHUB_ENV
          echo 'EOF' >> $GITHUB_ENV

      # Slackに結果通知
      - if: always()
        name: Slack Notification
        uses: rtCamp/action-slack-notify@v2.2.0
        env:
          SLACK_CHANNEL: ${{ secrets.SLACK_CHANNEL_NAME }}
          SLACK_COLOR: ${{ job.status }}
          SLACK_MESSAGE: '${{ job.status }}: ${{ env.PLAN_RESULT }}'
          SLACK_TITLE: Message
          SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK_URL }}

Mainブランチでapplyを実行するアクション

name: terraform apply

# applyを実行したいブランチを書いてください(今回は「Main」)
on:
  pull_request:
    branches:
      - Main
    types:
      - closed

# 動作に必要な権限
permissions:
  id-token: write
  contents: read

jobs:
  terraform_apply:
    runs-on: ubuntu-latest
    steps:

      # ファイルをチェックアウト
      - name: Checkout
        uses: actions/checkout@v3

      # AWS認証(OIDC)
      - name: Configure AWS credentials
        uses: aws-actions/configure-aws-credentials@v1
        with:
          role-to-assume: ${{ secrets.AWS_ROLE_ARN }}
          aws-region: ap-northeast-1

      # Terraform準備
      - name: Setup Terraform
        uses: hashicorp/setup-terraform@v2
        with:
          terraform_version: 1.2.5

      # 以下、Terraformのコマンド実行
      ### フォーマット修正箇所をチェック
      - name: Terraform fmt
        id: fmt
        run: terraform fmt -check

      ### 作業ディレクトリの初期構築
      - name: Terraform Init
        id: init
        run: terraform init

      ### 構文チェック
      - name: Terraform Validate
        id: validate
        run: terraform validate -no-color

      ### AWSへデプロイ
      - name: Terraform Apply
        id: apply
        run: terraform apply -auto-approve

      # applyの結果を環境変数に格納
      - name: truncate terraform apply result
        run: |
          echo 'APPLY_RESULT<<EOF' >> $GITHUB_ENV
          echo '${{ steps.apply.outputs.stdout }}' | grep "Apply complete!" >> $GITHUB_ENV
          echo 'EOF' >> $GITHUB_ENV

      # Slackに結果通知
      - if: always()
        name: Slack Notification
        uses: rtCamp/action-slack-notify@v2.2.0
        env:
          SLACK_CHANNEL: ${{ secrets.SLACK_CHANNEL_NAME }}
          SLACK_COLOR: ${{ job.status }}
          SLACK_MESSAGE: '${{ job.status }}: ${{ env.APPLY_RESULT }}'
          SLACK_TITLE: Message
          SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK_URL }}

作業中感じた留意点

引用するアクションは利用前に確認

GitHubのMarketplaceから様々なアクションを検索して利用することができます。

ここで注意点ですが、個人が作ったアクションとパートナー組織として確認されたアカウント(Verified creator)が作ったアクションがあります。
個人が作ったアクションにも良いアクションは沢山ありますが、プロジェクトによっては何かしらの理由で使用できない場合もあるかもしれません。
Verified creatorは、認証された証としてチェックアイコンが付いているので確認してみてください。

また、ソースコードが公開されている場合は軽く目を通したり、他に動作実績のある事例を確認する等した方が良いと思います。

以上の話は、仕事で使うアクションであれば、特に気にする必要が出てくるでしょう。

Terraformの設定は予め固めておく

これはTerraform側の話です。

TerraformのバージョンとAWS Providerのバージョンは、一番最初にtfファイル内で指定しておくと良いと思います。

Terraformのバージョンは、GitHub Actionsで同様のバージョンを指定することになります。

AWS Providerは、更新が頻繁にあります。
今回の環境構築中にもアップデートがありました。
バージョンを指定していないと、initコマンドを実行した際に最新のバージョンが導入されます。
その結果、ローカルとGitHub Actions上のAWS Providerに差異が生まれ、ワークフローが失敗する事態となってしまいました。

Terraformは今回初めて触ったのですが、マイナーアップデートでもエラーになるとは思いませんでした。
Terraform開発の特性に触れる良い機会になり、非常に勉強になりました。

最後に

GitHub ActionsとTerraformは今回初めて触れましたが、非常に楽しく、勉強になりました。
GitHub Actionsは、調べていると本当に沢山の便利なアクションが公開されていました。
まだまだ便利にカスタマイズしていく余地はあると思いますので、引き続き改良していきたいと思いました。

AUTHOR
kurochan
kurochan
記事URLをコピーしました