Github ActionsでDBマイグレーションを自動化する part2
この記事は、tacoms Advent Calendar 2024の18日目です!
他メンバーのAdvent Calendarはこちらからご覧ください!👇
こんにちは、株式会社tacoms SREの はぶちん(@modokkin) です。
前回は既存のマイグレーションプロセスの課題とGitHub Actionsでどのように承認フローを実現するかについて解説にしました。よければ前回の記事もあわせてご覧ください。
Part2では、手動トリガーでDry runを実行できるようにする方法について解説します。
- Part1 課題整理とマイグレーション適用までの流れ
- Part2 手動トリガーでDry runを実行できるようにする 👈️この記事
- Part3 PRへのDry run結果投稿とマイグレーション適用
開発の流れ
まず、sql-migrateをどこで実行するかですが、経験上 ecspresso + AWS Fargate
で構築する事が手っ取り早いことを知っていたので、この構成を採用しました。
Fargateタスクの実行はもちろんAWS CLIだけで実現することもできるのですが、ecspressoを活用すればFargateタスクを起動するまでのあれこれを省略することが可能です。
構成が決まったので、次に手動トリガーでsql-migrateのstatus確認とDry runができる状態を目指します。
それが出来たらPull Request(以下PR)への組み込み、マイグレーションの適用ワークフローの構築を徐々に進めていきます。
GitHub Actionsにおけるワークフローのトリガー
GitHub Actionsには、さまざまな実行トリガーが用意されているのですが、事前知識として今回扱うトリガーについてピックアップします。ご存知の方は読み飛ばしてください。
-
pull_request_target
特定のベースブランチ(マージ先)向けのPRを作成した際や、ヘッドブランチ(マージ元)へのプッシュでトリガー。マージ前のテストやセキュリティスキャンに利用。 -
pull_request
PRの作成、更新、マージなどでトリガー。コードレビューの自動化やコードフォーマットチェックに利用。 -
workflow_dispatch
手動でトリガー。任意のタイミングでワークフローを実行したい場合に利用。
他にもいろいろあるので、詳細は公式ドキュメントをご覧ください。
sql-migrateのビルドからDry run実行まで
とりあえず手動でDry runが実行できることを目標に準備を進めます。
この記事では割愛しますが、ECRリポジトリやFargateの実行に必要なリソース(ロール、ネットワーク周り、Logs周りとか)は事前にterraformで作成しました。参考にされる場合は、事前に準備してください。
各種ファイルの内容はブログ向けに生成したものなので、一部そのまま使えない可能性がある点を予めご了承ください!
.
├── Docker
│ └── Dockerfile
├── ecspresso
│ ├── ecs-service-def.json
│ ├── ecs-task-def.json
│ ├── ecspresso.yml
│ └── overrides.json
└── scripts
├── dbconfig.yml
├── migrations
│ └── 202412180000-sample.sql
├── mysql-dryrun.sh
└── mysql-up.sh
Dockerfileとsql-migrate実行に必要なファイル
着手する時点で sql-migrate は、ビルドすらまともにできない状態だったので、まずはDockerfileを作成します。
実行コマンドは後ほどGitHub Actionsのワークフロー側で調整しやすいようにシェルスクリプトにしました。
FROM golang:1.22.10
WORKDIR /go/src/github.com/example/sqlmigrate
RUN go install -v github.com/rubenv/sql-migrate/...@v1.7.0
# Copy Directory
COPY ./scripts/ ./
RUN chmod +x ./*.sh
RUN ls -la
# Execute command
// 後々上書きしたいのでCMDを利用します
CMD ["./mysql-dryrun.sh", "-config=dbconfig.yml", "-env=development"]
// シェバンは間違え無い
#!/bin/bash
// エラーがあったら止まってほしいのでsetを利用
set -ouex
// スクリプト実行時に引数があったらそれを使う
OPTIONS=${@:-"-config=./dbconfig.yml -env=development"}
// statusで状態を確認し、up -dryrunでDryrunを実行
sql-migrate status $OPTIONS && \
sql-migrate up -dryrun $OPTIONS
development:
dialect: mysql
datasource: ${DB_USER}:${PASSWORD}@tcp(${HOST}:3306)/${DATABASE}?parseTime=true
dir: ./migrations
-- +migrate Up
-- SQL in section 'Up' is executed when this migration is applied
CREATE TABLE people (id int);
-- +migrate Down
-- SQL section 'Down' is executed when this migration is rolled back
DROP TABLE people;
ecspressoを実行するために必要なファイル
ecspresso自体の使い方については、とても読みやすい公式ドキュメントがあるのでそちらを参考にしてください。
(ecspressoのドキュメントは一部を除いて有料ですが、非常に有益なので今後ecspressoを活用する方は課金することをおすすめします!)
.terraform_versionみたいなやつです。
2.4.2
ecspressoの設定ファイル
region: ap-northeast-1
cluster: example-cluster
service_definition: ecs-service-def.json
task_definition: ecs-task-def.json
timeout: "10m0s"
設定ファイルから参照するサービス定義(ecspressoがネットワーク周りとかを参照する仕様なので必要)
{
"deploymentConfiguration": {
"deploymentCircuitBreaker": {
"enable": true,
"rollback": true
},
"maximumPercent": 200,
"minimumHealthyPercent": 100
},
"deploymentController": {
"type": "ECS"
},
"desiredCount": 1,
"enableECSManagedTags": false,
"enableExecuteCommand": true,
"healthCheckGracePeriodSeconds": 0,
"launchType": "FARGATE",
"loadBalancers": [],
"networkConfiguration": {
"awsvpcConfiguration": {
"assignPublicIp": "DISABLED",
"securityGroups": [
"{securityGroupID}"
],
"subnets": [
"{SubnetID}"
]
}
},
"platformFamily": "Linux",
"platformVersion": "LATEST",
"propagateTags": "NONE",
"schedulingStrategy": "REPLICA",
"tags": []
}
設定ファイルから参照するタスク定義
{
"containerDefinitions": [
{
"command": [],
"cpu": 1024,
"environment": [
{
"name": "DB_HOST",
"value": "example-cluster.cluster-abcdefghijk.ap-northeast-1.rds.amazonaws.com"
},
{
"name": "AWS_REGION",
"value": "ap-northeast-1"
},
{
"name": "DB_USER",
"value": "user"
},
{
"name": "DATABASE",
"value": "example"
}
],
"essential": true,
"image": "{{ must_env `IMAGE_URI` }}",
"logConfiguration": {
"logDriver": "awslogs",
"options": {
"awslogs-group": "/ecs/logs/example",
"awslogs-region": "ap-northeast-1",
"awslogs-stream-prefix": "ecs"
}
},
"memory": 2048,
"name": "sql-migrate",
"portMappings": [],
"secrets": [
{
"name": "DB_PW",
"valueFrom": "arn:aws:ssm:ap-northeast-1:{AWS_ACCOUNT_ID}:parameter/example/DB_PW"
}
],
"ulimits": []
}
],
"cpu": "1024",
"executionRoleArn": "arn:aws:iam::{AWS_ACCOUNT_ID}:role/example-task-excution-role",
"family": "sql-migrate",
"ipcMode": "",
"memory": "2048",
"networkMode": "awsvpc",
"pidMode": "",
"requiresCompatibilities": [
"FARGATE"
],
"tags": [],
"taskRoleArn": "arn:aws:iam::{AWS_ACCOUNT_ID}:role/example-task-role"
}
設定を上書きするoverridesファイルを分けておくことで可変性を持たせています。
{
"containerOverrides": [
{
"name": "{{ must_env `CONTAINER_NAME` }}",
"command": [
"{{ must_env `COMMAND` }}",
"-config=dbconfig.yml",
"-env={{ must_env `ENV` }}",
"{{ env `ARG` ``}}"
]
}
]
}
GitHub ActionsのDry runワークフロー
ここからはGitHub Actionsのワークフローの実装について触れていきます。
多分そのままでも動くと思いますが、環境変数周りはGitHubの機能を活用したほうが良いと思います。
name: execute-sql-migrate-dryrun
run-name: execute sql-migrate dryrun
on:
workflow_dispatch:
concurrency:
group: ${{ github.workflow }}-${{ github.event.number }}
cancel-in-progress: true
env:
ENV: development
ROLE_ARN: 'arn:aws:iam::{AWS_ACCOUNT_ID}:role/{ROLE_NAME}'
CONTAINER_NAME: sql-migrate
ECSPRESSO_CONFIG: ecspresso.yml
jobs:
dryrun:
name: Build and dryrun
runs-on: ubuntu-latest
permissions:
id-token: write
contents: read
steps:
- name: Checkout base branch
uses: actions/checkout@v4
# OIDCでIAM Roleを利用
- name: Configure AWS Credentials
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: ${{ env.ROLE_ARN }}
aws-region: ap-northeast-1
- name: Login to Amazon ECR
id: login-ecr
uses: aws-actions/amazon-ecr-login@v2
# マイグレーションファイルをイメージに含めているので、毎回ユニークになるようにする
- name: Set up meta data # Dockerイメージにタグを付与
id: meta
uses: docker/metadata-action@v5
with:
images: |
${{ env.ECR_REGISTRY }}/${{ env.ECR_REPOSITORY }}
tags: |
value=${{ github.sha }}
env:
ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }} # AWSのECRのレジストリ名を取得
ECR_REPOSITORY: ${{ env.CONTAINER_NAME }}
- name: Build and push images
uses: docker/build-push-action@v6
with:
context: .
platforms: linux/amd64
file: Docker/Dockerfile
tags: ${{ steps.meta.outputs.tags }}
push: true
# 公式のActionsありがたいですね
- name: Install ecspresso
uses: kayac/ecspresso@v2
with:
version-file: ./ecspresso/.ecspresso-version
- name: Execute sql-migrate status and dryrun
working-directory: ./ecspresso
run: |
ecspresso run --config=${{ env.ECSPRESSO_CONFIG }} --overrides-file=overrides.json
env:
IMAGE_URI: ${{ steps.meta.outputs.tags }}
CONTAINER_NAME: ${{ env.CONTAINER_NAME }}
COMMAND: "./mysql-dryrun.sh"
ENV: ${{ env.ENV }}
ARG: ""
前回の記事で紹介したCode Ownersの設定ファイルも作成しておきます。
/.github/CODEOWNERS @example-org/db-admins
/.github/workflows/*sql-migrate* @example-org/db-admins
/scripts/ @example-org/db-admins
工夫したポイント
- マイグレーションファイルは、S3などに置いておいて取ってくることもできると思いますが、今回はdockerイメージに埋め込んでいます。その代わりイメージを判別できるようにする必要があるので、イメージタグにgitのコミットハッシュを付与しています。
- ecspressoのrunオプションを利用することでFargateでタスク実行時ハンドリングをActionsで気にする必要が無いのでとても楽ちんです。
- ecspressoに渡す引数を可変にすることで、トラブルシューティング時にdebugオプションを付けたり、別環境に適用したりと柔軟性を持たせました。ただ、これはリポジトリ権限管理を適切に行なっていないとセキュリティリスクにつながるのでご注意ください。
手動実行手順
ここまでで作成したファイルをリポジトリのデフォルトブランチにマージすると、GitHub Actionsのワークフローリストに作成したワークフローが表れます。その後、ブランチを選んで実行するとActionsの実行画面でecspressoの実行ログが確認出来ます。
+ sql-migrate status -config=dbconfig.yml -env=development
+----------------------------+-------------------------------+
| MIGRATION | APPLIED |
+----------------------------+-------------------------------+
| 202412180000-sample.sql | 2024-12-18 00:00:00 +0000 UTC |
+----------------------- ----+-------------------------------+
+ sql-migrate up -dryrun -config=dbconfig.yml -env=development
==> Would apply migration 202412180000-sample.sql (up)
CREATE TABLE people (id int);
/example-cluster Run task completed!
workflow_dispatchの詳細は公式ドキュメントをご覧ください。
workflow_dispatchで気になった点
ワークフローを手動実行する際は、実行元ブランチを選択することになるのですが、公式の機能としてブランチを制限するような機能は今のところ無さそうでした。制限したい場合はワークフロー内でブランチを判定するような処理が必要になるようです。
つづく
今回の記事はここまでです。次の記事では以下のような内容について触れたいと思いますので、お楽しみに!(年明けになっちゃうかも)
- マイグレーション適用時にユーザーを制限する方法。
- PullRequestにDry runワークフロー結果をコメントする方法。
Discussion