目標
- docker-composeを使い、NginxのリバースプロキシによってFastAPI(uvicornで起動)のアプリをhttpで公開する
- まずは認証無しのhttp(80ポート)で公開
ファイル構成
$ tree
.
├── app
│ ├── Dockerfile
│ ├── app
│ │ ├── __init__.py
│ │ └── main.py
│ ├── poetry.lock
│ └── pyproject.toml
├── docker-compose.yml
└── web
└── conf.d
└── app.conf
- FastAPIアプリは
app/Dockerfile
を使ってイメージを作成- ソースコードは
app/app
内に格納 - Pythonのパッケージ管理は
pyproject.toml
と必要であればpoetry.lock
を使っている(Poetry使用)- (別に深い理由も無いので簡単に
pip + requirements.txt
でやったりPipenv
やminiconda
を利用するなど色々やり方があると思います)
- (別に深い理由も無いので簡単に
- ソースコードは
- Nginxの設定は
web/conf.d/app.conf
で行っている
docker-compose.yml
- システム構成の概要について
version: '3'
services:
web:
container_name: web
image: nginx:alpine
depends_on:
# `app`サービス(コンテナ)が先に起動している必要があるので`depends_on`を指定しておく
- app
ports:
# ポートマッピング: "ホストOSのポート:コンテナ(Nginx)のポート"
- "80:80"
volumes:
# volumeマウント: "ホストOSのパス:コンテナにおけるパス"
- ./web/conf.d:/etc/nginx/conf.d
networks:
- nginx_network
app:
container_name: app
image: test_fastapi_app # ビルドされるDockerイメージ名を指定
build:
context: ./app
dockerfile: Dockerfile
expose:
- 8000
networks:
- nginx_network
# 例えばソースコードをリアルタイムに編集したいときは`volumes`でマウントすると便利
# volumes:
# - ./app/app:/app/app
# appコンテナの`CMD`を上書きするには`command`を使う
# command: "uvicorn app.main:app --reload --host 0.0.0.0"
networks:
nginx_network:
driver: bridge
- Nginxのコンテナ
web
とFastAPIアプリのコンテナapp
を別個に作り、docker-composeのnetworks:nginx_network
で接続している - nginxのイメージは軽量なalpineバージョンを使っている
app
およびweb
の中身の詳細は後述。
app
(FastAPIアプリ本体)
Dockerfile
ARG BASE_IMAGE=python:3.8-slim-buster
FROM $BASE_IMAGE
# system update & package install
RUN apt-get -y update && \
apt-get install -y --no-install-recommends \
build-essential \
openssl libssl-dev \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*
# Add Tini
ENV TINI_VERSION v0.19.0
ADD https://github.com/krallin/tini/releases/download/${TINI_VERSION}/tini /tini
RUN chmod +x /tini
ENTRYPOINT ["/tini", "--"]
# 一般ユーザーアカウントを追加
ARG USER_NAME=app
ARG USER_UID=1000
ARG PASSWD=password
RUN useradd -m -s /bin/bash -u $USER_UID $USER_NAME && \
gpasswd -a $USER_NAME sudo && \
echo "${USER_NAME}:${PASSWD}" | chpasswd && \
echo "${USER_NAME} ALL=(ALL) ALL" >> /etc/sudoers
# FastAPIのソースコードなどをコンテナ内へCOPY
COPY ./app /app/app
COPY ./pyproject.toml /app/pyproject.toml
# local環境で使用しているpoetry.lockファイルがあればそれも追加する
# COPY ./poetry.lock /app/poetry.lock
RUN chown -R ${USER_NAME}:${USER_NAME} /app
USER $USER_NAME
WORKDIR /app
# pip & poetry
ENV PATH $PATH:/home/${USER_NAME}/.local/bin
RUN python3 -m pip install --user --upgrade pip && \
python3 -m pip install poetry --user && \
poetry config virtualenvs.in-project true && \
poetry install && \
rm -rf ~/.cache/pip/* && \
rm -rf ~/.cache/pypoetry/*
# Poetryで作った仮想環境にPATHを通しておく(予めpoetryのconfigを設定して仮想環境のPATHを一意にしておく)
ENV PATH=/app/.venv/bin:$PATH
# Configration
EXPOSE 8000
# Execute
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0"]
- とりあえずPythonのバージョンは3.8を使用(公式イメージの
slim-buster
系(debianベース)のタグを利用) - debianベースなので
apt
で必要なライブラリをインストール - 個人的にrootユーザーで起動したくないので、アプリを起動させる用のユーザーを作成
-
tini
は別に無くても動くが、containerを起動するときに使うとベターらしいのでGitHubでの説明にしたがって入れておく - コンテナの
/app
ディレクトリ上にFastAPIアプリ用のファイル一式を格納し、WORKスペースにしている- ソースコードの本体は
/app/app
- ソースコードの本体は
- ローカルユーザー権限で
Poetry
を使って必要なパッケージをインストールしている-
poetry config virtualenvs.in-project true
によって/app/.venv
に仮想環境が作られるようにしている - ローカル環境における開発などで
poetry.lock
ファイルがある場合、# COPY ./poetry.lock /app/poetry.lock
のコメントアウトを外せばインストールするパッケージを固定出来る
-
pyproject.toml
- インストールするパッケージの情報など
-
tool.poetry.dependencies
に必要最低限なパッケージ情報を記述していて、後は適当です - (必要に応じて公式ドキュメントなどを参照して追記・修正していく)
-
[tool.poetry]
name = "test_fastapi_app"
version = "0.1.0"
description = "just for test"
authors = ["Your Name <you@example.com>"]
[tool.poetry.dependencies]
python = "^3.8"
uvicorn = "*"
fastapi = "*"
[build-system]
requires = ["poetry>=0.12"]
build-backend = "poetry.masonry.api"
FastAPIアプリ
- 今回はごく最低限だけ準備
app/app/main.py
は以下のようにしてある:
"""
app main
"""
from fastapi import FastAPI
app = FastAPI()
@app.get('/')
async def site_root():
"""root"""
return {"message": "Hello, WORLD!"}
- (
app/app/__init__.py
は空ファイル)
app/app/
以下はそのときどきの目的・必要性に応じて今後作り込んでいく
uvicorn
FastAPIアプリの起動には、uvicorn
(公式ドキュメント)を使って、
uvicorn app.main:app --host 0.0.0.0
のようにする。(ディレクトリ名やファイル名が変わるとapp.main:app
の部分も変わるはずなので注意する)
オプション:--host 0.0.0.0
はコンテナ上で起動しているuvicornサーバーを外部から見れるようにするために必要。
uvicornサーバーはデフォルトだと8000ポートで起動するので、EXPOSE 8000
などをDockerfile
やdocker-compose.yml
上でやっておく。(もしかすると要らなかったかもしれない)
なお、--reload
オプションをつけるとファイルが変更されたときにuvicornサーバーが検知して再起動するようになるので、開発時などに便利
web
(NginxによるWebサーバー)
-
app
コンテナで立ち上げているuvicornサーバーのリバースプロキシ -
/etc/nginx/conf.d/***.conf
に設定を書くことで各種設定が出来る-
***.conf
の名前は割となんでも良いらしい(今回はapp.conf
にしている)
-
upstream backend {
server app:8000;
}
server {
listen 80;
# server_name localhost;
location / {
proxy_pass http://backend;
}
# log
# access_log /var/log/nginx/access.log;
# error_log /var/log/nginx/error.log;
}
-
upstream
のところでapp:8000
のリバースプロキシの設定をしている- ここでの
app
はdocker-compose.yml
上で書いたservice名に対応していた気がする(違ってたらすみません) -
location /
のところでapp:8000
の内容をNginxの/
以下で公開している- 結果としてNginxの80ポートにアクセスするとFastAPI(uvicorn)の8000ポートが公開している内容が見れることになる
- ここでの
-
server_name
やログ出力が必要だったら適宜該当箇所をコメントアウトする
実行
- よく使うもの:
# `app`のイメージをビルド
docker-compose build
# サービス全体の起動
docker-compose up -d
# 終了時
docker-compose down
# コンソール出力の確認
docker-compose logs
docker-compose logs app # appサービスのログのみ確認
# appコンテナの中に入る
docker-compose run app bash
うまく設定ができていれば、docker-compose up -d
をして http://localhost および http://localhost/docs などにアクセスすると、
- http://localhost (ローカル環境で実行時)
- http://localhost/docs (ローカル環境で実行時)
のような画面が確認出来る。
AWSのEC2など、リモート環境で実行しているときはlocalhost
を実行しているマシンのアドレスに適宜置き換えればOKのはず。
まとめ
なるべくミニマムな内容でタイトルのような構成を作った。
今回はやっていないがNginxを使ってSSL化(HTTPS通信)やBASIC認証などをかけられる。
→ なお、Jinja2のTemplate機能を使っているときに、SSL化で結構ハマって手こずった(httpsがhttpになったり、デフォルト以外のポートを使うときにポート番号情報が欠落したりしてリンク機能がうまく働かなくなったりした)ので、別途まとめたる予定。(下記↓)