技術

Nginx+Flask+MySQLでREST APIを構築【2】

docker-composeで、Nginx+Flask+MySQLの構成でREST APIを実装していきます。

今回のゴールはヘルスチェック用のサンプルAPIがresponseを返すまでです。DB接続やマイグレーションについては、触れていません。

なぜFlaskなのかなどの技術選定についてはこちらです。

ディレクトリ構成(ファイル構成)

サンプルAPIが動くまでなので、以下の様なシンプル構成にします。(作り込んでいくときにアーキテクチャーは考えることにします。)

/
    |---app/
    |   |---controller/
    |   |       |---health.py
    |   |---app.ini
    |   |---app.py
    |   |---uwsg.log
    |   |---wsgi.py
    |---docker/
        |---api/
        |       |---Dockerfile
        |       |---requirement.txt
        |---db/
        |       |----my.cnf
        |---web/
        |       |----nginx.conf
        |---docker-compose.yml

Docker周りの基本設定

まずは、Flask, Nginx, MySQLが動くコンテナを用意するために、docker-compose.ymlを作成します。

docker-compose

version: '3.8'
services:
  api:
    build:
      context: .
      dockerfile: ./api/Dockerfile
    container_name: sample_api
    ports:
      - "5000:5000"
    volumes:
      - ../app:/usr/src/app
      - socket:/tmp
    networks:
      - web-net
      - db-net
#    command: flask run --host=0.0.0.0
    command: uwsgi --ini /usr/src/app/app.ini

  db:
    restart: always
    image: mysql:8.0.28
    container_name: sample_db
    environment:
      MYSQL_DATABASE: api
      MYSQL_USER: api
      MYSQL_PASSWORD: password
      MYSQL_ROOT_PASSWORD: password
    cap_add:
      - SYS_NICE
    ports:
      - '3306:3306'
    volumes:
      - ./db/my.cnf:/etc/mysql/conf.d/my.cnf
    networks:
      - db-net

  web:
    image: nginx:latest
    container_name: sample_web
    ports:
      - "80:80"
    volumes:
      - ./web/nginx.conf:/etc/nginx/conf.d/default.conf
      - socket:/tmp
    networks:
      - web-net

volumes:
  socket:

networks:
  db-net:
    name: sample-db-net
  web-net:
    name: sample-web-net

docker-compseのサンプルより少し盛りすぎに思われるかもしれませんが、これぐらいは最低限かと思います。

Flask実行コマンドについて

Flask内蔵の簡易Webサーバを起動する場合は、

flask run --host=0.0.0.0

のコマンドになります。

ただ今回はNginxを使うので、このコマンドはコメントアウトしuWSGIを起動する様に以下のコマンドにしています。

uwsgi --ini /usr/src/app/app.ini

netの設定

netは設定しなければ、デフォルトで全てのコンテナがbridgeというネットワーク名に入ります。今回はwebサーバとdbサーバ間のアクセスは分離したかったので、webサーバ用のネットワークとdb用のネットワークを作成して、所属を分けました。

DBの設定

DBはまだ使わないので、my.cnfは空ファイルですが、後々タイムゾーンの設定などで使うことになるので、ファイルは作成しておいてマッピングだけ済ませておきます。

DBデータのマッピングは、まだしていません。この辺りはDBの設定を詰めるときに、やることにします。

Nginxの設定

Nginxの設定は、ネットから適当に拾ってきました。uwsgiと接続するだけで、特殊なことはしません。

nginx.conf

server {
    listen       80;
    listen  [::]:80;
    server_name  localhost;

    location / {
        include uwsgi_params;
        uwsgi_pass unix:/tmp/uwsgi.sock;
    }

    error_page   500 502 503 504  /50x.html;
    location = /50x.html {
        root   /usr/share/nginx/html;
    }
}

Flaskの設定

メインのFlask部分は、Pythonのベースイメージに必要ライブラリをインストールしていくことにします。

Dockerfileを作成して、これをdocker-composeで指定しておきます。

Dockerfile

FROM python:3.10.4-slim-buster

WORKDIR /usr/src/app
ENV FLASK_APP=app

COPY ./api/requirements.txt ./

RUN apt update
RUN apt-get install -y build-essential python

RUN pip install --upgrade pip
RUN pip install --upgrade wheel
RUN pip install --upgrade setuptools
RUN pip install -r requirements.txt

後々でDB周りの拡張ライブラリなどが必要になりますが、現時点で必要なライブラリはflaskとuwagiだけなので、requirements.txtは以下の通りです。現時点(2022年5月現在)での最新のバージョンを指定しておきます。

requirements.txt

flask==2.1.1
uWSGI==2.0.20

アプリ側の実装

uwsgiの設定

まずはuwagiの起動時に読まれる設定ファイルを作成します。

app.ini

[uwsgi]
module = wsgi:app
master = true
socket = /tmp/uwsgi.sock
chmod-socket = 666
wsgi-file = /usr/src/app/wsgi.py
logto = /usr/src/app/uwsgi.log

ログは、とりあえずapp直下に出力するようにします。これはみっともないので、DB周りの設定が済んだ後、ログ周りはまとめて設定しなおすことにします。

Controller

これで動作環境周りが一応はできたので、いよいよアプリの実装に入っていきます。

まずはControllerから。Flaskのサンプルは、main(app.py)に直接書いてあることが多いのですが、これは個人的にどうかと思うのでControllerフォルダを作成して、その中に書いていくことにします。

とりあえずヘルスチェック用のGETとPOSTを用意します。

controller/health.py

from flask import Blueprint

bp = Blueprint("health", __name__, url_prefix="/health")


@bp.route("/", methods=["GET"])
def get():
    return "OK"


@bp.route("/hello/<string:name>", methods=["POST"])
def hello(name):
    return f"hello {name}"

この辺はFlask固有の書き方になります。といっても、Webフレームワークによくある書き方だと思います。

Blueprintで起点となるエンドポイントを定義し、デコレータで後続のエンドポイントとメソッドをを定義しています。

app.py

app.pyは、Flask起動を扱うファイルです。いわゆるmainです。

appの起動を設定しつつ、作成したControllerをappに紐付けます。

app.py

from flask import Flask
from controller import health

app = Flask(__name__)
app.register_blueprint(health.bp)


if __name__ == "__main__":
    app.run(debug=True, host='0.0.0.0')

uwagi起動ファイル

最後にuwagiからappをコールするファイルを作成します。このファイルは、app.iniで設定しているwsgi-fileです。

wsgi.py

from app import app

if __name__ == '__main__':
    app.run()

起動

以上で設定は完了です。

問題なければ、

docker-compose up

で、web, db, apiの3つのコンテナが起動します。

http://localhost/health/ にGETでアクセスしてステータス200が返ってくれば動作OKです。

余談

余談1

今回はappのディレクトリ名をappとしましたが、これは配下にapp.pyと名前がかぶるのでよくありません。

他からappを呼ぶとき、モジュールを呼びたいのにパッケージ呼んでしまうといったことが起こります。別名にした方が良いです。

余談2

今回はヘルスチェックで”OK”の文字だけで返していますが、ちゃんと作る場合は、ヘルスチェック用のAPIは、APIのリリースバージョンやコミットハッシュなどのメタ情報を返した方が良いです。

Software Designの2020年12月号の特集に「Dockerアプリケーション開発実践ガイド」が掲載されており、そこにこのようなことが書いていました。