CodeBuild で Alembic を実行して DB マイグレーションを行う

記事タイトルとURLをコピーする

こんにちは。AS部DS1課の兼安です。
先週末、夏の思い出に山陰の皆生温泉のマリンアスレチックに行きました。45 分の予約制で、着実に楽しく遊べる良いアトラクションでした。・・・嘘です楽しい通り越してやられました。ペース配分を間違えて、30 分ぐらいでバテました。残り 15 分が長かったです。マリンアスレチックはいろいろなところでやっているので、興味持たれた方はペース配分を考慮して行かれるとよいと思います。

イントロダクション

本記事では、GitHub リポジトリへのプッシュをトリガーに、CodeBuild で Alembic を用いた DB マイグレーションを行います。これ自体は CodeCommit や、GitHubActions を組み合わせても実現可能と思いますが、今回は GitHub + CodePipeline + CodeBuild で構成しています。

本記事のソースはこちらの記事をベースにしています。
なお、Python のバージョンは、3.11 に変えています。 blog.serverworks.co.jp

本記事の検証ソース一式はこちらのリポジトリにあります。 github.com

CodeBuild と Alembic によるマイグレーションの流れ

CodeBuild と Alembic によるマイグレーションの流れ

今回は CodeBuild までで、CodeDeploy の方は使用していません。
CodeBuild は RDS に対して操作を行うので、VPC の中に入れています。

CodePipeline と CodeBuild の設定

CodePipelineの全体像はこちら。

CodePipelineの設定

ソースの設定はこちらです。プロバイダーに GitHub を選択しています。GitHub は、今はGitHub (バージョン 2)の方を使うのが推奨のようですが、こちらについては別の機会で触れようと思います。

ソースの設定

CodeBuild は、VPC の設定をしています。VPC の設定は、CodeBuild の設定画面の「追加の設定」から行います。RDS 側のセキュリティグループは、CodeBuild のセキュリティグループから通信可能にするのを忘れないようにしてください。

なお、今回の構成では、VPC に NAT ゲートウェイと Elastic IP を用意しています。これは、VPC の中に置いた CodeBuild が S3 に置いたファイルを取得するのに、固定 IP が必要だったからです。ここは、NAT ゲートウェイではなく、エンドポイントでも良いと思うので、ご使用の状況に合わせてください。

CodeBuildのVPC設定

CodeBuild に与えているロールのポリシーは以下の通りです。DB 接続情報のやり取りにパラメータストアを用いているので(後述)、そのポリシーを付与しています。

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Action": ["s3:*", "kms:Decrypt", "kms:DescribeKey", "ssm:GetParameter"],
      "Resource": "*",
      "Effect": "Allow"
    }
  ]
}

その他の設定はデフォルトです。CodeBuildのパラメータそのものは、こちらの記事で詳しく触れています。 blog.serverworks.co.jp

検証ソースの構成

本ソースでは、モデルファイルを app フォルダの下に置いています。

python-alembic/
├── alembic/
│   ├── env.py
│   └── (Alembic に関連する他のファイルやディレクトリ)
├── alembic.ini
├── buildspec.yml
├── poetry.lock
├── pyproject.toml
├── app/
│   ├── __init__.py
│   ├── models/
│   │   ├── __init__.py
│   │   ├── base.py
│   │   ├── address.py
│   │   └── user.py
└── __pycache__

pyproject.toml の packages 設定もそれに合わせています。

[tool.poetry]
name = "python-alembic"
version = "0.1.0"
description = ""
authors = ["Satoshi Kaneaysu <satoshi.kaneaysu@serverworks.co.jp>"]
readme = "README.md"
packages = [
  {include = "app/**/*"}, # app フォルダの下にあるファイルを認識させます
]

alembic.ini と alembic フォルダ

alembic.ini は、Alembic でマイグレーションを実行する際の設定ファイルです。そして、alembic フォルダには Alemic の動作を決める env.py などがあります。

どちらも、alembic initコマンドで自動性生成されるファイルです。なお、alembic フォルダはこのフォルダ名でないとダメではありません。ここのフォルダ名はalembic initで初期設定する時に指定可能です。

DB接続情報の指定

alembic.ini には、DB 接続情報があります。ここを書き換えることで Alembic は DB 操作をできるようになります。

sqlalchemy.url = driver://user:pass@localhost/dbname

ですが、ここに DB 接続情報をベタで書きすることはセキュリティ上よろしくありません。一方、alembic フォルダの env.py は、alembic.ini の sqlalchemy.urlパラメータを書き換えることができます。これを利用して、env.py の先頭に以下のように書きます。

import boto3
import json


# パラメータストアから値を取得
ssm = boto3.client('ssm')
response = ssm.get_parameter(Name='MyAppDBConnection', WithDecryption=True)
db_connection_dict = json.loads(response['Parameter']['Value'])
print(db_connection_dict)
db_host = db_connection_dict['db_host']
db_port = db_connection_dict['db_port']
db_user = db_connection_dict['db_username']
db_pass = db_connection_dict['db_password']
db_schema = db_connection_dict['db_schema']

db_connection_string = f"postgresql://{db_user}:{db_pass}@{db_host}:{db_port}/{db_schema}"

# `alembic.ini`の`sqlalchemy.url`をパラメータストアから取得した値に置き換える
config = context.config
config.set_main_option('sqlalchemy.url', db_connection_string)

パラメータストアにMyAppDBConnectionという名前で DB 接続情報を格納しておき、env.py でそれを取得する方式です。

モデルの基底クラスの指定

Alembic は、env.py の指定で Base クラスを親に持つクラスをモデルと認識しています。

target_metadata = Base.metadata   # モデルの基底クラスのメタデータを指定します

従って、env.py の Base クラスのインポートを書き換えておきます。

from app.models.base import Base

session.py について

本ソースのベースにした記事ではsession.pyがありましたが、session.pyは DBからモデルを逆算するsqlacodegen の方で使うファイルなので今回は省略しています。

buildspec.yml の設定

buildspec.yml は、CodeBuild で実行するコマンドを定義するファイルです。 CodeBuild はこのファイルに基づいて、ビルドを実行するので、ここにマイグレーションの下準備と実行を記述します。

version: 0.2

phases:
  install:
    runtime-versions:
      python: 3.11
    commands:
      - pip install poetry
  pre_build:
    commands:
      - poetry install
  build:
    commands:
      - poetry run alembic upgrade head

この buildspec.yml は以下のことを行なっています。

  1. poetry をインストール
  2. poetry でライブラリをインストール
  3. alembic でマイグレーションを実行

alembic upgrade headコマンド は、alembic.ini で設定したデータベースに対して、app フォルダの下にあるモデルファイルを元にマイグレーションを実行します。 alembic upgrade headコマンドの実行は、poetry runで行います。 これは、poetry shellが対話形式のコマンドなので、CodeBuild では使えないからです。

マイグレーションの対象となるモデルファイルはここにあるファイルです。address.pyとuser.pyにあるクラスは、base.pyのBaseクラスを継承しているので、先述のenv.pyの指定により、モデルと認識されDBマイグレーションが走ります。

│   ├── __init__.py
│   ├── models/
│   │   ├── __init__.py
│   │   ├── base.py
│   │   ├── address.py # このファイルを元にマイグレーションを実行
│   │   └── user.py # このファイルを元にマイグレーションを実行

buildspec.yml のもっと詳しい使い方はこちらのページをご覧ください。 docs.aws.amazon.com


CodePipeline で CodeBuild を実行し、CodeBuild で Alembic を実行して DB マイグレーションを行う説明は以上です。ここまでの設定をした後、GitHub にソースをプッシュすればモデルファイルに従い、DB マイグレーションが実行されます。

DBにテーブルがない状態で、検証ソースをプッシュすると、2つのテーブルと1つの管理テーブルができるのを確認できます。

alembic_rds=> \dt
             List of relations
 Schema |      Name       | Type  |  Owner  
--------+-----------------+-------+---------
 public | address         | table | alembic
 public | alembic_version | table | alembic
 public | user            | table | alembic

DB マイグレーションを使う理由

今回 CodeBuild で DB マイグレーションを実行する例を書きましたが、そもそも DB マイグレーションを使う理由はなのでしょうか?

仮にテーブル定義を SQL を直接実行して変更するとしても、本番リリースの前にリハーサルで手順を確立しておけば、そうそう問題は起きることはないと言えます。
ですが・・・、個人的に本番リリースの時は大なり小なり焦っていてリハーサルの時よりパフォーマンスが落ちていることが多いです。そういう時に、一部だけでも手順が自動化されていると、気持ちのバッファのような効果を感じます。効果が地味だとは思いますが、小さな自動化の積み重ねていけば、デプロイは一大イベントではなくなり、リードタイムの短縮に繋がっていくのではと思います。

最後に

サーバワークスでは、お客様のシステム内製を支援する活動を行なっています。

www.serverworks.co.jp

兼安 聡(執筆記事の一覧)

アプリケーションサービス部 DS1課所属
AWS12冠。
広島在住です。
最近認定スクラムマスターになりました。今日も明日も修行中です。