前例がない状態から GitHub Actions で AWS RDS に Alembic migration を実行した話
はじめに
ローカルでの実装が一通り終わり、次にAWS環境上でテストする段階になりました。
その中で必要になったのが、AWS上のRDSに対するDB migration です。
ローカルでは、すでに Alembic を使って migration を実行できていました。
alembic upgrade head
ただ、GitHub Actions から AWS RDS に対して Alembic migration を実行する構成は、社内に前例や手順がない状態でした。
自分自身も、最初から GitHub Actions、AWS認証、Secrets Manager、RDS接続、Alembic のCI実行まで理解できていたわけではありません。
既存のローカル実行方法を確認しながら、
- AWS上では何を変える必要があるのか
- GitHub Actions からどうAWSへ認証するのか
- Secrets Manager の値をどう Alembic に渡すのか
- RDSに対して安全に migration を実行するには何を確認すべきか
を一つずつ調べながら進めました。
結果として、GitHub Actions から手動実行し、AWS Secrets Manager からDB接続情報を取得して、AWS RDS の MySQL に対して Alembic migration を実行できる状態まで確認できました。
この記事では、そのときに対応した内容、詰まった点、原因の切り分け方、実際にやってみて学んだことをまとめます。
実際のRepository名、DB名、Secret名、IAM Role名などはすべてダミーに置き換えています。
今回やりたかったこと
今回やりたかったことは、ローカルで動いていた Alembic migration を、GitHub Actions から AWS上のRDS に対して実行できるようにすることです。
最終的な流れは以下です。
GitHub Actions 手動実行
↓
OIDC 経由で AWS の IAM Role を引き受ける
↓
AWS Secrets Manager から DB 接続情報を取得する
↓
取得した値から DATABASE_URL を組み立てる
↓
Alembic migration を実行する
↓
AWS RDS の MySQL に migration が反映される
最初は、ローカルで実行していた alembic upgrade head を GitHub Actions 上で実行できればよいのではないかと考えていました。
ただ、実際にはそれだけでは足りませんでした。
CI上で実行する場合は、以下をすべて成立させる必要があります。
| 観点 | 確認すること |
|---|---|
| AWS認証 | GitHub Actions から IAM Role を引き受けられるか |
| 権限 | AssumeしたRoleで Secrets Manager を読めるか |
| 接続情報 | SecretString から必要な値を取得できるか |
| 環境変数 | Alembic が DATABASE_URL を参照できるか |
| ネットワーク | GitHub Actions から RDS に到達できるか |
| 実行対象 | 意図したDBに対して migration しているか |
つまり、Alembicの問題だけではなく、GitHub Actions、AWS IAM、Secrets Manager、RDS接続、環境変数の受け渡しまで含めて考える必要がありました。
前提
今回の前提は以下です。
| 項目 | 内容 |
|---|---|
| migration | Alembic |
| DB | Amazon RDS for MySQL |
| 実行元 | GitHub Actions |
| 実行方法 |
workflow_dispatch による手動実行 |
| DB接続情報 | AWS Secrets Manager から取得 |
| AWS認証 | OIDC / IAM Role |
| 接続文字列 |
DATABASE_URL として Alembic に渡す |
この記事では、以下のようなダミー名を使います。
Repository: sample-app
AWS Region: ap-northeast-1
IAM Role: sample-github-actions-role
Secret ID: sample/dev/db
Database: sample_database
公開記事では、社内固有のRepository名、Secret名、Role名、DB名は出さない方針にしました。
構成や運用が推測される可能性があるためです。
ローカルでの Alembic migration 構成
ローカルでは、すでに Alembic を使って migration を実行できる状態でした。
alembic upgrade head
ローカル環境では、接続先DBの情報を .env や環境変数から読み込み、Alembic の env.py 側で接続先を解決する構成でした。
ローカル環境
↓
環境変数または .env から接続情報を取得
↓
Alembic が DATABASE_URL を参照
↓
ローカルDBに migration を実行
AWS環境でやりたかったのは、この接続先をRDSに切り替えることです。
ただし、単に接続文字列をRDS向けに変えればよいわけではありません。
GitHub Actions 上では、ローカルの .env をそのまま使うのではなく、AWS Secrets Manager から接続情報を取得し、それを環境変数として Alembic に渡す必要がありました。
確認したポイントは以下です。
-
alembic.iniがどの設定を見ているか -
env.pyがDATABASE_URLを読める実装になっているか - migrationファイルの配置場所はCI上でも変わらないか
- GitHub Actions の実行ディレクトリから
alembic upgrade headが実行できるか - 接続先DBを環境変数で切り替えられるか
今回の作業では、Alembic自体の導入よりも、CI上でRDS向けの接続情報を安全に解決することが主なポイントになりました。
GitHub Actionsで実行する構成
GitHub Actions 側は、手動実行できるように workflow_dispatch を使いました。
自動実行にしなかった理由は、DB migration は影響が大きい処理だからです。
まずは検証段階として、実行タイミングと対象環境を明示的に選べる形にしました。
name: DB Migration
on:
workflow_dispatch:
inputs:
environment:
description: "Target environment"
required: true
type: choice
options:
- dev
- stg
database:
description: "Target database name"
required: true
type: string
permissions:
id-token: write
contents: read
GitHub Actions から AWS に OIDC で認証するため、permissions に id-token: write を指定しています。
次に、AWS認証を行います。
jobs:
migrate:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::123456789012:role/sample-github-actions-role
aws-region: ap-northeast-1
ここで指定している role-to-assume はダミーです。
重要なのは、GitHub Actions 側でRoleを指定するだけでは不十分という点です。
AWS側のIAM Roleの信頼ポリシーで、そのRepositoryからのAssume Roleが許可されている必要があります。
Secrets ManagerからDB接続情報を取得する
DB接続情報は、GitHub Actions のSecretsに直接持たせるのではなく、AWS Secrets Manager から取得する構成にしました。
理由は、DB接続情報をAWS側で管理し、GitHub側にはAWSへ認証するための仕組みだけを持たせたかったためです。
Secrets Manager の値は、例として以下のようなJSONを想定しています。
{
"host": "sample-db.xxxxx.ap-northeast-1.rds.amazonaws.com",
"port": "3306",
"username": "sample_user",
"password": "sample_password"
}
取得した値から、Alembic が参照する DATABASE_URL を組み立てます。
- name: Get DB secrets
run: |
DB_SECRET_JSON=$(aws secretsmanager get-secret-value \
--secret-id sample/${{ inputs.environment }}/db \
--query SecretString \
--output text)
DB_HOST=$(echo "$DB_SECRET_JSON" | jq -r .host)
DB_PORT=$(echo "$DB_SECRET_JSON" | jq -r .port)
DB_USER=$(echo "$DB_SECRET_JSON" | jq -r .username)
DB_PASS=$(echo "$DB_SECRET_JSON" | jq -r .password)
DB_NAME="${{ inputs.database }}"
echo "::add-mask::$DB_HOST"
echo "::add-mask::$DB_USER"
echo "::add-mask::$DB_PASS"
echo "DATABASE_URL=mysql+pymysql://$DB_USER:$DB_PASS@$DB_HOST:$DB_PORT/$DB_NAME" >> "$GITHUB_ENV"
DATABASE_URL を $GITHUB_ENV に書き出すことで、後続stepから参照できるようにしています。
DATABASE_URL にはユーザー名、パスワード、ホスト名、DB名が含まれるため、そのままログに出さないようにしました。
その後、Pythonの依存関係を入れて Alembic を実行します。
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.11"
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
- name: Run Alembic migration
run: |
alembic upgrade head
プロジェクト構成によっては、アプリケーションコードがサブディレクトリ配下にある場合があります。
その場合は、working-directory を明示します。
- name: Run Alembic migration
working-directory: ./backend
run: |
alembic upgrade head
ここを間違えると、alembic.ini や migration ファイルが見つからず失敗します。
苦労した点:RepositoryがIAM Roleを引き受けられなかった
今回一番詰まったのは、別Repositoryで同じようにworkflowを実行したときに、Secrets Manager から値を取得できなかったことです。
最初は、以下のような箇所を疑いました。
- Secret ID が間違っているのではないか
- AWS Region が違うのではないか
-
aws secretsmanager get-secret-valueの書き方が違うのではないか -
jqでJSONを取り出す部分が間違っているのではないか -
DATABASE_URLの組み立て方が間違っているのではないか
ただ、原因はそこではありませんでした。
原因は、対象Repositoryが AWS 側の IAM Role を引き受けられる設定になっていなかったことでした。
GitHub Actions から AWS リソースへアクセスする場合、workflowに以下を書くだけでは完結しません。
permissions:
id-token: write
contents: read
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::123456789012:role/sample-github-actions-role
aws-region: ap-northeast-1
AWS側のIAM Roleの信頼ポリシーで、どのRepositoryからのAssume Roleを許可するかが制御されています。
つまり、あるRepositoryでは動いているworkflowでも、別Repositoryにコピーしただけでは同じように動くとは限りません。
今回のケースでは、対象RepositoryがRoleの信頼条件に含まれていなかったため、GitHub Actions から AWS の IAM Role を引き受けられませんでした。
その結果、AWS認証が成立せず、Secrets Manager にもアクセスできませんでした。
GitHub Actions
↓
OIDC token 発行
↓
IAM Role を Assume
↓
AWS 認証成立
↓
Secrets Manager 参照
↓
DATABASE_URL 作成
↓
Alembic 実行
今回の学びとして大きかったのは、Repositoryが変わると、同じworkflowでもAWS認証できるとは限らないという点です。
原因調査で切り分けた観点
Secrets Manager から値が取得できなかったとき、最初はSecretの設定や取得コマンドを中心に見ていました。
ただ、途中から「これはSecrets Manager単体の問題ではなく、AWS認証から順番に確認した方がよい」と考えました。
切り分けた観点は以下です。
| 順番 | 観点 | 確認内容 |
|---|---|---|
| 1 | OIDC | GitHub Actions の OIDC token は発行されているか |
| 2 | AWS認証 |
configure-aws-credentials は成功しているか |
| 3 | Assume Role | IAM Role を引き受けられているか |
| 4 | 信頼ポリシー | 対象Repositoryが許可されているか |
| 5 | IAM権限 | Secrets Manager の読み取り権限があるか |
| 6 | Secret | Secret ID は正しいか |
| 7 | Region | AWS Region は正しいか |
| 8 | JSON | SecretString の形式は想定通りか |
| 9 | 接続文字列 |
DATABASE_URL の組み立ては正しいか |
| 10 | RDS接続 | GitHub Actions から RDS に到達できるか |
| 11 | 実行パス | Alembic の実行ディレクトリは正しいか |
| 12 | Alembic設定 |
env.py が DATABASE_URL を読めているか |
このように分解すると、どこがAlembicの問題で、どこがAWS認証の問題で、どこがRDS接続の問題なのかが見えやすくなりました。
今回の場合は、Alembic以前の問題でした。
Secrets Manager にアクセスする前段階で、RepositoryがIAM Roleを引き受けられていませんでした。
Secrets Managerから値が取れない という現象でも、実際の原因は Secrets Manager ではなく、AWS認証やIAM Roleの信頼ポリシーにある場合があります。
Alembic実行時に気をつけたこと
Alembic migration を GitHub Actions から実行するうえで、特に気をつけたのは接続先です。
DB migration で一番怖いのは、コマンドが失敗することよりも、意図していないDBに対して成功してしまうことだと思いました。
そのため、以下を確認しました。
-
DATABASE_URLが意図したRDSを向いているか - workflowの入力値とDB名が一致しているか
-
alembic.iniとenv.pyがCI上でも環境変数を読めるか -
alembic upgrade headの実行ディレクトリが正しいか - Secrets Manager の値をログに出していないか
- migration実行タイミングを手動にしているか
env.py 側では、環境変数から接続先を読むようにしておくと、ローカルとCIで接続先を切り替えやすくなります。
import os
from alembic import context
from sqlalchemy import engine_from_config
from sqlalchemy import pool
config = context.config
database_url = os.environ.get("DATABASE_URL")
if database_url is None:
raise RuntimeError("DATABASE_URL is not set")
config.set_main_option("sqlalchemy.url", database_url)
target_metadata = None
def run_migrations_online():
connectable = engine_from_config(
config.get_section(config.config_ini_section),
prefix="sqlalchemy.",
poolclass=pool.NullPool,
)
with connectable.connect() as connection:
context.configure(
connection=connection,
target_metadata=target_metadata,
)
with context.begin_transaction():
context.run_migrations()
ここでは、DATABASE_URL が未設定の場合に明示的にエラーにしています。
未設定のまま意図しないデフォルト値にフォールバックすると、原因が分かりにくくなります。
DB migrationでは、曖昧なフォールバックを避けた方が安全だと感じました。
Before / After
今回の対応前後を整理すると、以下のようになります。
| Before | After |
|---|---|
| ローカルでは Alembic migration を実行できていた | GitHub Actions から RDS に migration できるようになった |
| AWS上のRDSに対する実行手順がなかった | 手動実行できるworkflowを作成した |
| Secrets Manager から値を取得する構成が未整理だった | Secrets Manager から接続情報を取得できるようになった |
| RepositoryごとのIAM Role許可を把握できていなかった | IAM Roleを引き受けられない場合の切り分け観点が整理できた |
| AlembicをRDSに向けるだけだと思っていた | AWS認証からDB接続まで全体を見る必要があると分かった |
最初は「ローカルで動いているAlembicをRDSに向けるだけ」と考えていました。
しかし実際には、GitHub Actions から AWS に入るところ、Secrets Manager から値を取るところ、RDSに接続するところ、Alembicが接続文字列を読むところを分けて確認する必要がありました。
学んだこと
今回の作業で一番学びになったのは、Alembic migration の実行そのものよりも、その前段にある認証と接続情報の受け渡しです。
特に、以下は大きな学びでした。
- Alembicの問題に見えても、実際にはAWS認証やIAM Roleの問題で止まることがある
- GitHub ActionsからAWSへアクセスするには、workflow側とAWS側の両方を見る必要がある
- Repositoryが変わると、同じRoleを引き受けられるとは限らない
- Secrets Managerから値が取れない場合、Secret名だけでなくIAM Roleの信頼ポリシーを見る必要がある
- ローカルで動くmigrationをAWS上で動かすには、接続先、認証、環境変数、ネットワークを分けて確認する必要がある
- DB migrationでは、実行前に「どのDBに対して実行されるか」を明確にする必要がある
前例がない作業では、いきなり完成形を作ろうとするより、処理を層ごとに分けて確認した方が進めやすいと感じました。
AWS認証できるか
↓
Secrets Managerから値を取得できるか
↓
DATABASE_URLを組み立てられるか
↓
RDSへ接続できるか
↓
Alembic migrationを実行できるか
この順番で見ることで、どこが原因かを切り分けやすくなりました。
まとめ
今回は、社内に前例がない状態から、GitHub Actions を使って AWS RDS に Alembic migration を実行できるようにしました。
ローカルでの実装は終わっていたため、最初は alembic upgrade head をRDS向けに実行できればよいと思っていました。
しかし実際には、GitHub Actions、OIDC、IAM Role、Secrets Manager、DATABASE_URL、RDS接続、Alembic実行ディレクトリなど、複数の要素を順番に確認する必要がありました。
特に、別Repositoryで同じworkflowを実行しても、そのRepositoryがIAM Roleを引き受けられる設定になっていなければ Secrets Manager にはアクセスできない、という点は今回大きな学びでした。
GitHub Actions からAWS上のDB migrationを行う場合、migrationコマンドだけでなく、AWS認証からDB接続までの経路全体を見る必要があります。
同じように、前例がない状態で GitHub Actions から AWS RDS への migration を行う方の参考になれば幸いです。
この記事は、個人の経験や調査内容をまとめたものです。
内容の正確性や動作を保証するものではありません。
実際に利用する場合は、環境やセキュリティ要件に合わせて確認してください。