docker-composeで、Nginx+Flask+MySQLの構成でREST APIを実装していきます。
前回は、とりあえずサンプルAPIがresponseできるところまででした。
今回は、以下のことをやっていきます。
- DBデータ永続化
- マイグレーション
ディレクトリ構成(ファイル構成)
ディレクトリ構成は以下の通りです。
/
|---src/
| |---controller/
| | |---health.py
| |---app.ini
| |---app.py
| |---uwsg.log
| |---wsgi.py
| |---config.py
| |---db.py
| |---models/
| |---user/
| |---migrations/
|---docker/
|---api/
| |---Dockerfile
| |---requirement.txt
|---db/
| |----data/
| |----log/
| |----my.cnf
|---web/
| |----nginx.conf
|---docker-compose.yml
前回からの変更点は以下になります。
- フォルダ名をappからsrcに変更しました。
前回の余談にも記載しましたが、モジュール名とパッケージ名が同名によるエラーが発生したためです。
(リファクタ内容は省略します。) - ./src/models/user 追加
サンプルとしてuserテーブルを作成します。 - ./src/migirations/ 追加
マイグレーションを実行したら自動で生成されます。 - ./src/config.py 追加
DBの設定値などです。 - ./src/db.py 追加
appと紐付けるDBインスタンスを作成します。 - ./docker/db/data 追加
データ永続化に使うディレクトリです。 - ./docker/db/log 追加
データ永続化に使うディレクトリです。
Flask拡張ライブラリを追加
DBを使用するにあたり、必要となるライブラリを追加します。
./docker/api/requirements.txt
flask==2.1.1
uWSGI==2.0.20
+ Flask-SQLAlchemy==2.5.1
+ Flask-Migrate==3.1.0
+ PyMySQL==1.0.2
+ cryptography==37.0.2
DBデータ永続化
DBを導入していくので、まずはデータ部分を永続化しておきます。
docker-compose.ymlに以下を追加します。
./docker/docker-compose.yml
db:
(中略)
volumes:
- ./db/my.cnf:/etc/mysql/conf.d/my.cnf
+ - ./db/data:/var/lib/mysql
+ - ./db/log:/var/log/mysql
DBコンフィグ
DBを動かして行くので、前回、空ファイルで作成していたmy.cnfに最低限の設定だけしておきます。
文字コードとタイムゾーンの設定だけ入れておきます。
./docker/db/my.cnf
[mysqld]
character-set-server = utf8mb4
default-time-zone = 'Asia/Tokyo'
[client]
default-character-set = utf8mb4
Flaskで使用するDBを作成
config作成
DB接続のconfigを定義します。
./src/config.py
class BaseConfig(object):
SQLALCHEMY_DATABASE_URI = 'mysql+pymysql://{user}:{password}@{host}/{db_name}?charset=utf8'.format(**{
'user': "root",
'password': "password",
'host': "db",
'db_name': "api"
})
SQLALCHEMY_TRACK_MODIFICATIONS = False
DEBUG_TB_INTERCEPT_REDIRECTS = False
hostは同じdocker-compose内のDBを使うので、dbになります。
DBインスタンス作成
Flaskのappで使用するDBインスタンスを作成します。
appと同じモジュール内で記述しているサンプルが多いのですが、今回はdb.pyに分けました。db.pyは以下の様に記述します。
./src/db.py
from flask_sqlalchemy import SQLAlchemy
from flask_migrate import Migrate
db = SQLAlchemy()
def init_db(app):
db.init_app(app)
Migrate(app, db)
app.pyモジュールと分けた理由は、このあと作成するmodelファイル内で。dbインスタンスをimportするときに以下のエラーが出たからです。
ImportError: cannot import name 'app' from partially initialized module
app.pyモジュール内で、modelをimportする必要があるのですが、そのmodel内ではまだ作成されていないappモジュールを使おうとして、上記のエラーが出るみたいな感じです。
なのでmodelもapp.pyモジュール内で書いてしまえば、このエラーは避けられるのですが、modelは別モジュールにしたかったので、db.pyを作成して回避しました。
モデル作成
modelを作成します。今回はサンプルとしてidとnameがあるだけのuserテーブルを作ることにします。
./src/models/user.py
from datetime import datetime
from db import db
class User(db.Model):
__tablename__ = 'user'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(100), nullable=False)
created_at = db.Column(db.DateTime, nullable=False, default=datetime.utcnow)
updated_at = db.Column(db.DateTime, nullable=False, default=datetime.utcnow)
appを修正
作成したconfigやdbをFlaskのappに適用するので、app.pyを以下の様にします。
./src/app.py
from flask import Flask
from db import init_db
from models import user
from controller import health
app = Flask(__name__)
# DB
app.config.from_object('config.BaseConfig')
init_db(app)
# Controller
app.register_blueprint(health.bp)
if __name__ == "__main__":
app.run(debug=True, host='0.0.0.0')
from models import user
はソース内では使わないのですが、migration実行時にこのimport文がないとテーブルを認識してくれないので、記述してあります。
PYTHONPATHの設定
ローカルのIDE上では問題ないのですが、後述するDBマイグレーション実行時にNot Found moduleのエラーがでました。
なので、PYTHONPATHを追加しておきます。docker-compose.ymlに以下を追加します。
./docker/docker-compose.yml
services:
api:
(中略)
environment:
+ PYTHONPATH: /usr/src:$PYTHONPATH
ソースの変更は以上で完了です。
docker-compose upを実行して、コンテナが問題なく起動することを確認します。
DBマイグレーション
コンテナが起動したらDBマイグレーションを実行していきます。
最終的にはDockerコマンドなどで自動化したいですが、今回は確認も兼ねて手動で実行していきます。
まずは以下のコマンドでFlaskのコンテナに入ります。
docker exec -it sample_api /bin/bash
コンテナ内で以下のコマンドを実行します。
flask db init
実行後、./docker/db/にmigrationsフォルダが生成されます。
initが終わったら以下のコマンドを実行します。
flask db migrate
このコマンドはマイグレーションされていそうな感じがしますが、実際はされていません。migirations/versions/にrevisionファイルが作成されるだけです。
マイグレーション適用は以下のコマンドになります。
flask db upgrade
以上でマイグレーション完了です。問題なければ、DBにuserテーブルが作成されているはずです。
まとめ
以上、DBマイグレーションまででした。
これで、REST APIを作っていく土台はできたと思います。後はAPI周りを作り込んでいくだけかと思います。
土台で残っている所は、API keyやhttps化、loggerの設定、マイグレーションの自動化などでしょうか。この辺りは、APIを作り込んでいく仮定で必要になったら、随時対応していくことになるかと思います。
というわけで、FlaskのREST APIの構築の備忘録は今回で終わり予定です。
ありがとうございました。
