Github ActionsでDBマイグレーションを自動化する part3
こんにちは、株式会社tacoms SREの はぶちん(@modokkin) です。
前回は手動トリガーでDry runを実行できるようにする方法について解説しました。よければ前回の記事もあわせてご覧ください。
Part2の投稿から期間が空いてしまったのですが、その後の運用状況なども含めてお伝えできればと思います。
Part3では、PRへのDry run結果投稿とマイグレーション適用する方法について解説します。
- Part1 課題整理とマイグレーション適用までの流れ
- Part2 手動トリガーでDry runを実行できるようにする
- Part3 PRへのDry run結果投稿とマイグレーション適用 👈️この記事
前回までの振り返りと今回の内容
前回までの記事では以下のような内容について触れました。
- 既存のマイグレーションプロセスで抱えていた課題
- GitHub Actionsでどのように承認プロセスを実現するか
- 手動トリガーでGitHub Actionsのワークフローを実行しsql-migrateのDry runを実行できるようにする
今回の記事では次のような対応を行います。
- PRが作成・更新された際に自動でDry runを実行し、実行結果をPRにコメントとして投稿します。
- Dry runの結果を確認した後、承認されたユーザーが安全にマイグレーションを適用できる仕組みを構築します。
PRを作成したら自動的にDry runを実行し、実行結果をコメントする
前回までにDry runのワークフロー自体は作成できたので、次にPR作成時に自動実行し、実行結果をPRにコメントするようにします。
もし参考にしていただく場合は {}
の部分は適宜置き換えてご利用ください。
name: execute-sql-migrate-dryrun
run-name: execute sql-migrate dryrun
on:
workflow_dispatch:
pull_request_target:
types: # トリガー条件
- opened
- synchronize
branches: # 対象とするPRマージ先ブランチを指定
- develop
paths: # 対象パス、ファイル
- scripts/migrations/*.sql
concurrency: # 同じPRをトリガーとして複数のワークフローが実行された場合、後勝ちで1つのみ実行する設定
group: ${{ github.workflow }}-${{ github.event.number }}
cancel-in-progress: true
env:
ENV: {ENVIRONMENT}
ROLE_ARN: 'arn:aws:iam::{AWS_ACCOUNT_ID}:role/{ROLE_NAME}'
CONTAINER_NAME: {CONTAINER_NAME}
ECSPRESSO_CONFIG: {ECSPRESSO_CONFIG}
jobs:
dryrun:
#--------------中略(Part2の記事と同じ)--------------#
# sql-migrateのステータス確認とdryrunを実施し、
# 結果の一部を抽出してGitHub Actionsの出力変数にセットする
- name: Execute sql-migrate status and dryrun
id: dryrun
working-directory: ./deploy/sqlmigrate
run: |
set -eou pipefail
ecspresso run --config=${{ env.ECSPRESSO_CONFIG }} --overrides-file=overrides.json | tee result.txt
{
echo 'RESULT<<EOF'
cut -d ' ' -f 3- result.txt | sed -n '/+------------/,$p' | (head -7 && echo '...' && tail -20)
echo EOF
} >> "${GITHUB_OUTPUT}"
env:
IMAGE_URI: ${{ steps.meta.outputs.tags }}
CONTAINER_NAME: ${{ env.CONTAINER_NAME }}
COMMAND: "./mysql-dryrun.sh"
ENV: ${{ env.ENV }}
ARG: ""
pr_comment:
if: github.event_name == 'pull_request_target' && always()
name: Output dryrun result to PR
runs-on: ubuntu-latest
needs: dryrun
permissions:
pull-requests: write
steps:
# PRに対してdryrunの結果をコメントとして出力する
- name: Post result to PR comment
uses: mshick/add-pr-comment@v2
with:
message: |
## Dry run result of sql-migrate
For the full log, please refer to the GitHub Actions workflow: [${{ env.RUN_ID }}](${{ env.WORKFLOW_URL }})
<details>
<summary>Result (Click me)</summary>
```
${{ env.RESULT }}
```
</details>
message-failure: |
## Dry run result of sql-migrate
Failed to execute sql-migrate. Please check the GitHub Actions workflow: [${{ env.RUN_ID }}](${{ env.WORKFLOW_URL }})
refresh-message-position: true
status: ${{ needs.dryrun.result }}
env:
RUN_ID: ${{ github.run_id }}
WORKFLOW_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
RESULT: ${{ needs.dryrun.outputs.RESULT }}
PRにコメントする処理の補足
Dry runを実行する dryrun
ジョブと、PRにコメントする pr_comment
ジョブ間で複数行の文字列を渡す際に、変数で渡そうとすると文字化けしたり改行コードが消えてしまう問題に遭遇しました。
当初は base64 に変換したりして渡していたのですが、行数が多い文字列を扱う場合はシンプルに一度txtファイルに出力するのが良さそうです。
また、1つのPR内でマイグレーションファイルの修正を繰り返した場合に、複数のDry Run結果コメントが残ってしまいノイズになっていました。
そのため mshick/add-pr-comment@v2 を利用することで一番最後の実行結果だけがPRコメントとして残るようにしました。
PRに投稿されるコメントはこんな感じです。
マイグレーション適用ワークフローについて
マイグレーションを適用するワークフローは、運用プロセスと組み合わせることで安全にマイグレーションを実行できるようにしています。
マイグレーションは次の流れで実行します。
- マイグレーションファイルの修正を含むPRを作成し、DB管理者にレビューを依頼(GithubのCode Ownersを利用することで承認者を限定)
- DB管理者のレビューが完了したら、DBへのマイグレーションタイミングを調整する
- DB管理者がGitHub Actionsのマイグレーション適用ワークフローを手動で実行する
※本来はリリース時に常に最新のマイグレーションが自動実行できることが理想なのですが、現状はリスクを考慮してトリガー自体は手動運用としています。
GitHub Actionsのマイグレーション適用ワークフローは以下の通りです。
name: execute-sql-migrate-up
run-name: execute sql-migrate up
on:
workflow_dispatch: # 手動実行用の設定。自動実行する場合は条件を追加する。
concurrency:
group: ${{ github.workflow }}
cancel-in-progress: false
env:
ENV: {ENVIRONMENT}
ROLE_ARN: 'arn:aws:iam::{AWS_ACCOUNT_ID}:role/{ROLE_NAME}'
CONTAINER_NAME: {CONTAINER_NAME}
ECSPRESSO_CONFIG: {ECSPRESSO_CONFIG}
jobs:
dryrun:
name: Build and dryrun
runs-on: ubuntu-latest
permissions:
id-token: write
contents: read
actions: read
outputs:
IMAGE_URI: ${{ steps.meta.outputs.tags }}
AUTHRIZED_USERS: ${{ steps.get-authrized-users.outputs.AUTHORIZED_USERS }}
REVISION: ${{ steps.dryrun.outputs.REVISION }}
env:
APP_ID: ${{ vars.{GITHUB_APP_ID} }}
APP_KEY: ${{ secrets.{GITHUB_APP_PRIVATE_KEY} }}
steps:
# GitHubアプリの認証トークンを生成する
- name: Generate a token
id: generate-token
uses: actions/create-github-app-token@v1
with:
app-id: ${{ env.APP_ID }}
private-key: ${{ env.APP_KEY }}
# 指定のGitHub組織/チームから許可されたユーザ一覧を取得する
- name: Get authorized users
id: get-authrized-users
run: |
AUTHORIZED_USERS=$(gh api orgs/{YOUR_ORG}/teams/{YOUR_TEAM}/members --jq '.[].login' | tr '\n' ' ' | sed 's/ $//')
echo "AUTHORIZED_USERS=$AUTHORIZED_USERS" >> $GITHUB_OUTPUT
env:
GH_TOKEN: ${{ steps.generate-token.outputs.token }}
# ワークフロー実行者が許可リストに含まれているかをチェックする
- name: Check unauthorized users
run: |
for user in ${{ steps.get-authrized-users.outputs.AUTHORIZED_USERS }}; do
if [ "$user" = "${{ github.triggering_actor }}" ]; then
exit 0
fi
done
exit 1
#--------------dryrunワークフローのdryrunジョブと同様なので省略--------------#
# sql-migrateのステータス確認とdryrun(テスト実行)を行い、最新リビジョン番号を取得する
- name: Execute sql-migrate status and dryrun
id: dryrun
working-directory: ./deploy/sqlmigrate
run: |
set -eou pipefail
ecspresso run --config=${{ env.ECSPRESSO_CONFIG }} --overrides-file=overrides.json
REVISION=`ecspresso revisions --config=${{ env.ECSPRESSO_CONFIG }} --output="tsv" | tail -1 | awk -F':' '{print $2}'`
echo "REVISION=$REVISION" >> $GITHUB_OUTPUT
env:
IMAGE_URI: ${{ steps.meta.outputs.tags }}
CONTAINER_NAME: ${{ env.CONTAINER_NAME }}
COMMAND: "./mysql-dryrun.sh"
ENV: ${{ env.ENV }}
ARG: ""
apply:
name: Apply sql-migrate
runs-on: ubuntu-latest
permissions:
id-token: write
contents: read
actions: read
needs: dryrun
steps:
# ワークフロー実行者が許可リストに含まれているかをチェックする
- name: Check unauthorized users
run: |
for user in ${{ needs.dryrun.outputs.AUTHRIZED_USERS }}; do
if [ "$user" = "${{ github.triggering_actor }}" ]; then
exit 0
fi
done
exit 1
# リポジトリのコードをチェックアウトする
- name: Checkout
uses: actions/checkout@v4
# AWSの認証情報を再設定し、指定ロールを利用する
- name: Configure AWS Credentials
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: ${{ env.ROLE_ARN }}
aws-region: {AWS_REGION}
# ecspressoをインストールする
- name: Install ecspresso
uses: kayac/ecspresso@v2
with:
version-file: ./deploy/sqlmigrate/.ecspresso-version
# dryrunの結果を確認するために一定時間待機する
- name: Dryrun confirmation wait time
run: sleep 120
# sql-migrateを実行し、マイグレーションを適用する
- name: Execute sql-migrate up
working-directory: ./deploy/sqlmigrate
run: |
ecspresso run \
--config={ECSPRESSO_CONFIG} \
--overrides-file=overrides.json \
--skip-task-definition \
--revision=${{ needs.dryrun.outputs.REVISION }}
env:
IMAGE_URI: ${{ needs.dryrun.outputs.IMAGE_URI }}
CONTAINER_NAME: ${{ env.CONTAINER_NAME }}
COMMAND: "./mysql-env.sh"
ENV: ${{ env.ENV }}
ARG: ""
Applyジョブのポイント
Applyジョブの重要なポイントをいくつかご紹介します。
- 実行者チェック
Githubの特定のチームに所属しているメンバーだけがこのワークフローを実行できるようにするため、チームメンバー一覧と実行者が含まれているかをチェックしています。dryrun
ジョブとapply
ジョブでそれぞれ実行しているのは、ワークフローを途中から実行する際にもチェックを行うためです。 - 待機処理
Dry run結果の確認用に一定時間(例:120秒)待機することで、万が一問題が見つかった場合はワークフローをキャンセルします。 - マイグレーション適用
Dry runと同様にecspresso を用いて、マイグレーション適用を実行します。Dry run実行時との違いは処理を一部上書きすることで-dryrun
オプションが外れるようにしているだけです。
まとめ
GitHub Actions と ecspresso を活用して、マイグレーションのレビューフローと適用フローを実現しました。
運用開始にあたっては、この他にもマイグレーション運用フローのドキュメント整備や周知を行い、開発環境から徐々に活用を始め、本番運用へと適用していきました。
最初こそ多少の混乱はありましたが、現在では組織に浸透し安全かつスムーズにマイグレーションを適用できるようになりました。
DBを運用する以上は、マイグレーション運用は切っても切れないプロセスなので、もし同じような課題を抱えている方の参考になれば幸いです。
最後までご覧いただきありがとうございました!
Discussion