こんにちは、コドモン SRE チームの渡辺です。
コドモンでは、AWSなどのインフラリソースをTerraformで管理しています。先日、私たちのチームでは、各種Providerのバージョンアップ作業を自動化しました。
本記事では、自動化の仕組みについて紹介します。Terraformを運用されている方の役に立てれば幸いです。
自動化する以前の課題
私たちのチームでは、dependbotを利用してProviderのバージョンアップPR作成を自動で行っていました。
作成されたPRをSREチームのメンバーが確認し、以下の作業を手動で行っていました。
terraform plan
の結果に差分があるか確認する(全環境分)- 差分がある場合、内容を確認し対応を検討する
- 差分がない場合、PRをApproveしてマージする
しかし、各Providerのバージョンアップは頻繁に行われるため、毎日のようにPRが作成されます。このため、チーム内の負荷が高まるにつれ、ProviderバージョンアップPRの対応が遅れてしまうことが多々ありました。
このような状況で、週次のふりかえり会において、この問題についてチーム内で議論しました。議論の結果、週次担当を決めて対応するなどの案もありましたが、手動で行っている作業は機械的に判断しているため自動化を進める方針になりました。
自動化の仕組み
今回は、GitHub Actions(以下 GHA)を利用して自動化を進めていきました。流れは以下のとおりになります。
- 全環境の
terraform plan
の結果をGitHub Storageにアップロード - artifactをダウンロードし、すべてのPlan結果で差分がないか検証する
- 差分がない場合、PRをマージする
私たちのチームでは、Terraformを管理しているリポジトリでPRを作成すると、変更のあったコードに関係する環境のterraform plan
が並列実行されるようにしています。Providerバージョンアップの場合は、すべての環境でterraform plan
が走ります。
まずは、並列実行した全環境のterraform plan
の結果をGitHub Storageにアップロードします。
その後のJobで全環境分のファイルをダウンロードし、差分の有無を検証します。一つでも差分がある環境があれば、Job3は実行しないようにしています。
GHAは、以下のようになります。
plan-workdir: runs-on: ubuntu-latest permissions: contents: read pull-requests: read outputs: workdirs: ${{ steps.filter.outputs.workdirs }} steps: - uses: actions/checkout@v3 - uses: dorny/paths-filter@v2 id: changes with: filters: .github/filters.yml # フィルターファイルを設定する - name: filter id: filter run: | ここでterraform planを実行するディレクトリを$GITHUB_OUTPUTに設定する terraform_plan: needs: plan-workdir if: needs.plan-workdir.outputs.workdirs != '[]' runs-on: ubuntu-latest permissions: contents: write id-token: write pull-requests: write strategy: matrix: workdir: ${{ fromJSON(needs.plan-workdir.outputs.workdirs) }} - uses: actions/checkout@v3 省略(terraformのセットアップなど) - name: Terraform plan dependbot if: github.event.pull_request.user.login == 'dependabot[bot]' id: dependbot_tfplan run: | # 標準出力をファイルにリダイレクトするとGitHub Actionsで実行ログが確認できないため、outオプションでバイナリをファイルに書き込む tfcmt -var target:$WORK_DIR plan -patch -- terraform plan -lock=false -out=tfplan_binary.txt FILENAME=$(echo "plan-result-$WORK_DIR.txt" | tr "/" "-") terraform show tfplan_binary.txt > $FILENAME echo "FILENAME=$FILENAME" >> $GITHUB_OUTPUT env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} WORK_DIR: ${{ matrix.workdir }} - name: Save result if: github.event.pull_request.user.login == 'dependabot[bot]' uses: actions/upload-artifact@v3 with: name: tfplan-result-${{ github.sha }} path: ${{ matrix.workdir }}/${{ steps.dependbot_tfplan.outputs.FILENAME }} if-no-files-found: error terraform_plan_results: needs: terraform_plan if: failure() runs-on: ubuntu-latest steps: - run: exit 1 check_terraform_plan_results: if: github.event.pull_request.user.login == 'dependabot[bot]' needs: terraform_plan runs-on: ubuntu-latest outputs: has_changes: ${{ steps.check.outputs.has_changes }} steps: - name: Download all results uses: actions/download-artifact@v3 with: name: tfplan-result-${{ github.sha }} - name: Check results id: check run: | for file in ./*.txt; do if ! grep -q "No changes." "$file" | grep -q "Your infrastructure matches the configuration." "$file"; then echo "has_changes=true" >> $GITHUB_OUTPUT echo "Changes detected in $file" break fi done auto_merge: needs: check_terraform_plan_results if: needs.check_terraform_plan_results.outputs.has_changes != 'true' runs-on: ubuntu-latest permissions: contents: write pull-requests: write steps: - uses: actions/checkout@v3 - name: Merge PR run: | gh pr comment $PR_NUMBER --body "All Terraform plan results showed No Changes." gh pr review $PR_NUMBER --approve gh pr merge $PR_NUMBER --merge env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} PR_NUMBER: ${{ github.event.pull_request.number }} remove_artifacts: needs: check_terraform_plan_results runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Remove artifacts run: | echo "remove artifact_name: $ARTIFACT_NAME" artifact_ids=$(gh api /repos/$GITHUB_REPO/actions/artifacts --jq ".artifacts[] | select(.name == \"$ARTIFACT_NAME\").id") for id in $artifact_ids; do echo "delete artifacts id: $id" gh api -X DELETE /repos/$GITHUB_REPO/actions/artifacts/$id done env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} ARTIFACT_NAME: tfplan-result-${{ github.sha }} GITHUB_REPO: ${{ github.repository }}
upload-artifactでアップロードしたファイルは、GitHub Storageに保管されます。GitHub Storageには容量制限があるため、remove_artifacts
で検証後のファイル群を削除しています。
このアクションが実行され、terraform plan
に差分がない場合は、以下のようにPRが自動でマージされます。
なお、baseブランチのRequire status checks to pass before merging
にterraform_plan_results
を設定しています。このため、どこか一つの環境でもterraform_plan
が失敗した場合は、PRをマージできないようになっています。
まとめ
今回、Terraformの各種Providerのバージョンアップ作業を自動化する取り組みを行った結果、トイルを削減できました。
今後も人が機械的に行っている作業を抽出し自動化するというプロセスを実施し、チームが抱えている手動作業については、積極的に自動化を進めていこうと思います。
最後に
コドモンでは、今後も新しいサービスの開発や既存サービスのリプレイスなどを予定しており、一緒に盛り上げてくださる方を募集しています。
一緒に働くことに興味を持ちましたら、ぜひ求人情報もご覧ください!