こんにちは、プラットフォームチームのテックリードの星井です。HRBrainのAdvent Calendarの8日目の記事です。
先日、Terraformコマンドを実行するGitHub Actionsのワークフローを作成しました。
作成するにあたって、いくつかインターネットの記事を参考にしましたが、monorepo用でシンプルなワークフローが意外となかったので、ブログにまとめることにしました。
メンテナンスしやすいワークフロー
GitHub Actionsは便利ですが、数が増えてくると、依存関係のアップデートなどが面倒です。なので、今回はメンテナンスコストを最低限にしたワークフローを作ることを目指しました。コマンドスクリプトを使って動的なワークフローにして、便利なtfcmt
を使うことで、メンテナンス性を高めています。
name: terraform-plan on: pull_request: paths: - 'environments/**' env: TF_VAR_project_id: <PROJECT_NAME> jobs: check_changed_dirs: runs-on: ubuntu-20.04 outputs: changes: ${{ steps.pr_dir_changes.outputs.changes }} steps: - uses: actions/checkout@v3 with: fetch-depth: 0 - id: pr_dir_changes # masterと差分のあるディレクトリのみを抽出する # https://stackoverflow.com/questions/50440420/git-diff-only-show-which-directories-changed run: echo "changes="[$(git diff origin/"${GITHUB_BASE_REF}" --dirstat=files,0 | awk '{print $2}' | sed -e 's:/*$::' | grep 'environments' | awk '{ print "\""$0"\""}' | paste -sd, -)]"" >> "$GITHUB_OUTPUT" tf-ci: runs-on: ubuntu-20.04 permissions: contents: 'write' id-token: 'write' pull-requests: 'write' timeout-minutes: 20 needs: check_changed_dirs strategy: matrix: dir: ${{ fromJSON(needs.check_changed_dirs.outputs.changes) }} defaults: run: shell: bash working-directory: ${{ matrix.dir }} steps: - uses: actions/checkout@v3 - run: echo ${{ matrix.dir }} - uses: google-github-actions/auth@v1 with: workload_identity_provider: <WORKFLOW_IDENTITY_PROVIDER_ID> service_account: <SERVICE_ACCOUNT> - uses: google-github-actions/setup-gcloud@v1 # mercari/tfnotifyが頻繁に更新されていないため、mercari/tfnotifyのフォークのtfcmtを使う - run: | sudo curl -fL -o tfcmt.tar.gz https://github.com/suzuki-shunsuke/tfcmt/releases/download/$TFCMT_VERSION/tfcmt_linux_amd64.tar.gz sudo tar -C /usr/bin -xzf ./tfcmt.tar.gz env: TFCMT_VERSION: v4.0.0 - uses: hashicorp/setup-terraform@v2.0.3 with: terraform_version: 1.3.2 - run: terraform fmt continue-on-error: true - run: terraform init # 長くなってしまうので、matrix.dirのenvironmentsは省いてtfcmtに渡す - run: tfcmt -var "target:$(echo ${{ matrix.dir }} | sed 's/environments\///g')" plan -patch -- terraform plan -no-color -lock=false env: TF_VAR_project_id: ${{ env.TF_VAR_project_id }} TF_VAR_org_id: <ORGANIZATION_ID> TF_VAR_billing_account: <BILLING_ACCOUNT> PR_HEAD_SHA: ${{ github.event.pull_request.head.sha }} PR_NUMBER: ${{ github.event.number }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
コマンドスクリプトを使って動的なワークフローにする
HRBrainのTerraformリポジトリは以下のような構成になっています。
├── environments │ ├── dev │ │ ├── app1 │ │ ├── app2 │ ├── prod │ │ ├── app1 │ │ ├── app2 └── modules
各ディレクトリにTerraformの構成ファイルが存在しており、ディレクトリごとにterraform plan
を実行する必要があります。
元々リポジトリ直下のファイルに実行したいディレクトリを指定してterraform plan
を実行していましたが、そのファイルがコンフリクトしたり、手順がそもそも分かりづらく、Terraformリポジトリを触る開発者に混乱を招いていました。
そこで今回は、プルリクエストの差分から実行したいディレクトリを取得し、動的にワークフローを実行するようにしました。できるだけ依存関係を減らしたいので、実行したいディレクトリを取得するのにActionやプログラミング言語を使わず、コマンドスクリプトを使うよう工夫しました。
check_changed_dirs: runs-on: ubuntu-20.04 outputs: changes: ${{ steps.pr_dir_changes.outputs.changes }} steps: - uses: actions/checkout@v3 with: fetch-depth: 0 - id: pr_dir_changes # masterと差分のあるディレクトリのみを抽出する # https://stackoverflow.com/questions/50440420/git-diff-only-show-which-directories-changed run: echo "changes="[$(git diff origin/"${GITHUB_BASE_REF}" --dirstat=files,0 | awk '{print $2}' | sed -e 's:/*$::' | grep 'environments' | awk '{ print "\""$0"\""}' | paste -sd, -)]"" >> "$GITHUB_OUTPUT"
ちなみにcheckout時にfetch-depth: 0
を指定しないと動きません。ここで取得したディレクトリをfromJSONを使って、ジョブのマトリックスに設定します。
マトリックスを使用して、working-directory
に指定すると、ディレクトリごとにジョブを実行することができます。
tf-ci: runs-on: ubuntu-20.04 permissions: contents: 'write' id-token: 'write' pull-requests: 'write' timeout-minutes: 20 needs: check_changed_dirs strategy: matrix: dir: ${{ fromJSON(needs.check_changed_dirs.outputs.changes) }} defaults: run: shell: bash working-directory: ${{ matrix.dir }}
このように並列でterraform plan
が実行できるので便利です。
便利なtfcmt
を使う
以前はmercari/tfnotifyを使っていて、特に問題がなかったのですが、今年の2月くらいからアップデートが止まっているようでした。調べたら、tfcmtというmercari/tfnotify
のフォークがあり、ちゃんとメンテナンスされてそうだったので、こちらを使いました。依存関係は増えますが、terraform plan
の内容をGitHub上にコメントしてくれるため、とても便利です。
- run: tfcmt -var "target:$(echo ${{ matrix.dir }} | sed 's/environments\///g')" plan -patch -- terraform plan -no-color -lock=false env: PR_HEAD_SHA: ${{ github.event.pull_request.head.sha }} PR_NUMBER: ${{ github.event.number }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
tfcmt
はデフォルトの設定のままで、-var
と-patch
オプションを使っています。-var
オプションでターゲットを指定すると、コメントにどのディレクトリで実行したかを表示してくれます。-patch
オプションは、追加コミット時にコメントを追加せず、元のコメントを更新してくれます。これにより、プルリクエスト上のコメントの可読性が上がります。
コメント自体もシンプルなmercari/tfnotify
に比べて読みやすいです。
終わりに
GitHub Actionsを使って、プルリクエストの差分から動的にterraform plan
を実行する方法を説明しました。初めは、動的なワークフローを作ることが目的でしたが、tfcmt
を使うことで、可読性も上げることができました。
今回のワークフローを作ったことにより、依存関係の自動アップデートツールなども導入できるようなります。機会があれば、その内容も記事にしてみたいと思います。