Docker + FastAPI + nginxで環境構築
背景
業務で今まで触ったことのなかったpythonでapi開発をする必要になったので、同じような人がいた場合参考になればと思い自分が行った環境構築の手順をここに残します。
Pythonのフレームワーク
pythonのどのフレームワークを使って開発をするかからまず決めないといけなかったので、参考までにどういう基準でFastAPIを選んだのか書いておきます。
一応、候補として
- Django
- Flask
- FastAPI
の3種類がありました。
Djangoはフルスタックなフレームワークなので、必要なものが全部揃っているのがメリットですが、今回はサーバーが立てられればいいのでオーバースペックなのと、MVC使いたくないのでナシだなって思いました。
Flaskはマイクロフレームワークで軽量かつカスタマイズが色々できるので迷ったのですが、「Flaskにある機能くらい全部自分で作ればいいなあ」と思い消去法でFastAPIにしました。
FastAPIは、非同期フレームワークなので処理スピードが早いです。業務で重い処理を行う関係上相性がいいなと思いました。また、マイクロサービス化を視野に入れたときに、設計の自由度は自分の中で結構重要な点でした。
メリットとしては、
- デフォルトでSwaggerが使える
- 簡単にサーバーが立てられる
- 非同期通信を簡単に実装できる
デメリットとしては、
- 比較的新しいフレームワークなのでまだまだ情報が少ない
- デフォルトでは、サーバーを立てる機能くらいしかない
という感じです。
前提
- MacOS
- python3.8
- FastAPI 0.103.1
- uvicorn 0.23.2
- requests 2.23.1
- nginx 1.21
ディレクトリ構成
├─ docker/
│ ├─ nginx
│ │ ├─ conf/
│ │ └─ default.conf
│ ├─ log/
│ └─ Dockerfile
├─ python
│ └─ Dockerfile
├─ src/
│ └─ main.py
├─ docker-compose.yaml
├─ poetry.lock
└─ project.toml
packageのinstall
今回は、package管理にpoetry(npmのpythonバージョン)を使います。
pipではなくpoetryを使う理由は、バージョン管理です。pipを使っている人が多い印象ですが、pipでrequirement.txtにパッケージを記述していくのもめんどくさいし、バージョン管理もしてくれないので、プロジェクトという観点から考えたらpoetryを使いたいと思いました。
Python3のインストールがまだの方はpython3のインストールから行なって下さい。
まずはpoetryのinstall
pip install poetry
project.tomlを作成します。
poetry init -n
FastAPIでサーバーを立てるのに必要なものを一式installします。
poetry add fastapi uvicorn requests
サーバーを立てるのに必要なのはこれだけです。
次に、src配下にmain.pyを作成します。main.pyはいわゆるルーティングファイルです。
正直、laravelのrouteファイルのような使い勝手は望めません。
簡単な、仮説検証くらいならここに直接ロジックを書いていく感じです。
必要に応じて、handler層(MVCで言うところのController)を設計してあげて下さい。
データ分析などの簡単な仮説検証だけだとそこまでファットにならなさそうなので、handler層だけでUseCase層やDomain層を書かなくても、そこまで開発効率は落ちないかなと書きながら思いました。なので、本当に早くサーバーを立てて開発したいときに向いてると思います。
from fastapi import FastAPI
app = FastAPI(docs_url="/docs")
@app.get('/')
def index():
return {"message": "Success"}
uvicorn src.main:app --port 8080 --reload
を実行して、localhost:8080にアクセスしてみましょう。
srs.main
の部分は、src/main.py
に対応していて、:app
の部分はapp = FastAPI(docs_url="/docs")
の部分に対応しています。
--port 8080でport番号を指定しているので、もしport番号が被ってサーバーが立てられなかったら、--port 8081など他の番号に変更してあげましょう。
docs_url="/docs"
はSwaggerのルーティングを指定しています。
localhost:8080/docsでSwaggerにアクセスするという意味で、
app = FastAPI()
でも動きます。デフォルトでは、/docsがSwaggerのルーティングです。
staging、localごとにURLを変えたりもできますし、本番環境で隠すこともできます。
Dockerでコンテナを立てる
これだと他の人がgit cloneをしたときに毎回コマンドを打たないといけない上に、バージョンがlocal環境間で異なると言ったような問題が出てきます。なので、このプロジェクトをコンテナで管理するようにしましょう。
pythonのDockerfile
docker/python配下に以下のDockerfileを作成して下さい。
FROM python:3.8
WORKDIR /var/www/app
COPY ./pyproject.toml /var/www/app/pyproject.toml
COPY ./poetry.lock /var/www/app/poetry.lock
RUN pip install --upgrade pip
RUN pip install poetry
RUN poetry install --no-root
COPY ./src /var/www/app/src
CMD ["poetry", "run", "uvicorn", "src.main:app", "--host", "0.0.0.0", "--port", "8080", "--reload"]
docker-compose.yaml
src配下にdocker-compose.yamlを作成して下さい。
version: "3.0"
services:
python38:
build:
context: ./
dockerfile: "./docker/python/Dockerfile"
container_name: "app_python38"
working_dir: /var/www/app
restart: always
volumes:
- ./src:/var/www/app/src
ports:
- "8080:8080"
イメージをbuild
docker compose build
でimageをbuildしましょう。
buildが終わったら、
docker compose up -d
でコンテナを立てます。
先ほどと同様に、localhost:8080にアクセスできればOKです。
サーバーをnginxで立てる
docker/nginx配下にnginxのDockerfileを作成して下さい。
nginxのDockerfile
FROM nginx:1.21-alpine
# ローカルのdefault.confをコンテナにコピー
COPY docker/nginx/conf/default.conf /etc/nginx/conf.d/default.conf
nginxの設定ファイル
docker/nginx/confにdefault.confを作成して下さい。
# FastAPIの8080番ポートとつなぐ
upstream fastapi {
# サーバにFastAPIのコンテナ名を指定。app_python38
# ポートはFastAPIのコンテナの8080番Port
server app_python38:8080;
}
server {
# HTTPの80番Portを指定
# コンテナのnginxのportと合わせる
listen 80;
server_name 0.0.0.0;
# プロキシ設定
# 実際はNginxのコンテナにアクセスしてるのをFastAPIにアクセスしてるかのようにみせる
location / {
proxy_pass http://fastapi;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_redirect off;
}
}
注意点としては、default.confのportとdocker-composeのport番号を合わせることと、pythonのコンテナに正しくつなぐことです。
次に、docker-compose.yamlを書き換えます。
docker-compose.yaml
version: "3.0"
services:
python38:
build:
context: ./
dockerfile: "./docker/python/Dockerfile"
container_name: "app_python38"
working_dir: /var/www/app
restart: always
volumes:
- ./src:/var/www/app/src
expose:
- "8080"
env_file: ./.env
nginx:
image: nginx:1.21-alpine
container_name: "app_nginx"
# NginxのDockerfileをビルドする
build:
# ビルドコンテキストはカレントディレクトリ
context: ./
dockerfile: "./docker/nginx/Dockerfile"
volumes:
- ./docker/nginx/conf:/etc/nginx/conf.d
- ./docker/nginx/log:/var/log/nginx
restart: always
depends_on:
- python38
ports:
- "8080:80"
links:
- python38
書き変わった点としては、python38のportがexposeに変わっています。
pythonでport番号8080でサーバーを立てていたのに対して、nginxでサーバーを立てるためにexpose 8080でnginxにport8080で接続するように変更してます。
その後、同じようにコンテナを立ててlocalhost:8080にアクセスすれば良いです。
docker compose build
docker compose up -d
localhost:8080にアクセスして、 {"message": "Success"}と表示されていればOKです。
補足:別階層のフォルダをimportしたい時のパスの通し方
pythonの言語仕様では、基本的に同ディレクトリのフォルダをimportすることしか想定されていません。
なので、例えばsrc/app_2のファイルのフォルダをsrc/app_1に呼びたいときに普通にimportしてもエラーを吐きます。
なのでそう言った場合、
ENV PYTHONPATH /var/www/app/src/path_1
のように、通したいpathのディレクトリをpythonのDockerfileに記述してあげます。
仮想環境の外でパスを通すときはexposeのコマンドを直接打ちますが、dockerfileではRUNではなくENVを使います。
複数、pathを通したいときは、
ENV PYTHONPATH /var/www/app/src/path_1:/var/www/app/src/path_2
のように書きます。
これで from path_1.xxx import xxx
のように書けます。
Discussion