0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

GitHub Actions で AWS RDS に Alembic migration を実行した話

0
Posted at

前例がない状態から 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.pyDATABASE_URL を読める実装になっているか
  • migrationファイルの配置場所はCI上でも変わらないか
  • GitHub Actions の実行ディレクトリから alembic upgrade head が実行できるか
  • 接続先DBを環境変数で切り替えられるか

今回の作業では、Alembic自体の導入よりも、CI上でRDS向けの接続情報を安全に解決することが主なポイントになりました。


GitHub Actionsで実行する構成

GitHub Actions 側は、手動実行できるように workflow_dispatch を使いました。

自動実行にしなかった理由は、DB migration は影響が大きい処理だからです。

まずは検証段階として、実行タイミングと対象環境を明示的に選べる形にしました。

.github/workflows/db-migration.yml
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 で認証するため、permissionsid-token: write を指定しています。

次に、AWS認証を行います。

.github/workflows/db-migration.yml
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 を組み立てます。

.github/workflows/db-migration.yml
      - 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 を実行します。

.github/workflows/db-migration.yml
      - 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 を明示します。

.github/workflows/db-migration.yml
      - 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.pyDATABASE_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.inienv.py がCI上でも環境変数を読めるか
  • alembic upgrade head の実行ディレクトリが正しいか
  • Secrets Manager の値をログに出していないか
  • migration実行タイミングを手動にしているか

env.py 側では、環境変数から接続先を読むようにしておくと、ローカルとCIで接続先を切り替えやすくなります。

env.py
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 を行う方の参考になれば幸いです。


この記事は、個人の経験や調査内容をまとめたものです。
内容の正確性や動作を保証するものではありません。
実際に利用する場合は、環境やセキュリティ要件に合わせて確認してください。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?