0. はじめに
以前投稿した本番稼働を意識したECS Fargateを用いたFastAPIの環境構築(ALB、SSM、BlueGreenデプロイ)という記事では、FastAPIをECS Fargateで動かすための流れについて説明しました。しかしnginxを前段に噛ませる構成ではありませんでした。今回、仕事にてFastAPIを用いかつnginxをかませる形でFargateに構成しましたので、備忘録も兼ねて設定内容を記述したいと思います。
1. 簡単な構成説明
簡単な構成説明ですが、
ALB - nginxコンテナ - FastAPIコンテナ
という形でALBがnginxにリクエストを分散し、nginxとFastAPIはFargate内でそれぞれコンテナとして稼働するという構成になります。
FastAPIをFargateで動かすにあたり、必ずしもnginxはかませる必要はないのですが、uvicornの公式によると、nginxとの連携については、以下の様な記述があります。1
Using Nginx as a proxy in front of your Uvicorn processes may not be neccessary, but is recommended for additional resiliance. Nginx can deal with serving your static media and buffering slow requests, leaving your application servers free from load as much as possible.
お作法的にもALBから直接アプリケーションサーバーにアクセスしに行くよりも、nginxを噛ませた方が丁寧で、アプリケーションサーバーの負荷も下げられるかと思います。2
2. ファイル構成
ファイル構成は以下になります。DockerfileはFargate用にDockerfile-ecs-fargateを作っています。また、今の構成では不要なのですが、後々の拡張性のためにdocker-composeを用いており、そのためのファイルとしてdocker-compose.ecs_fargate.ymlを用意しています。
また、ecs_fargate/nginxディレクトリ内にはECS Fargate向けのnginxのDockerファイルやFastAPI向けのnginxのconfを置いています。
.
├── Dockerfile-ecs-fargate
├── app
│ └── main.py
├── docker-compose.ecs_fargate.yml
├── ecs_fargate
│ └── nginx
│ ├── Dockerfile
│ └── fastapi.conf
├── poetry.lock
└── pyproject.toml
それではそれぞれのファイルの中身について見ていきましょう。
Dockerfile-ecs-fargate
FROM python:3.9-buster
ENV PYTHONUNBUFFERED=1
WORKDIR /src
RUN mkdir -p tmp
RUN pip install poetry
COPY pyproject.toml* poetry.lock* ./
RUN poetry config virtualenvs.in-project true
RUN poetry install
COPY . .
VOLUME /src
VOLUME /src/tmp
CMD ["poetry", "run", "uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000", "--uds", "tmp/uvicorn.sock"]
-
COPY pyproject.toml* poetry.lock* ./
をしたあとにCOPY . .
しているのは、poetry install
でキャッシュを効かせるためになります。 - ポイントとしては、最後の行のCMDで、
"--uds", "tmp/uvicorn.sock"
としているところになります。nginxとの連携をsocketで行うためです。
ecs_fargate/nginx/Dockerfile
FROM nginx:1.21.5
RUN apt-get update && \
apt-get install -y apt-utils curl \
locales && \
echo "ja_JP.UTF-8 UTF-8" > /etc/locale.gen && \
locale-gen ja_JP.UTF-8
ENV LC_ALL ja_JP.UTF-8
ADD ./ecs_fargate/nginx/fastapi.conf /etc/nginx/conf.d
EXPOSE 80
- ポイントは
ADD ./ecs_fargate/nginx/fastapi.conf /etc/nginx/conf.d
でFastAPI用のconfを読み込ませているところです。
ecs_fargate/nginx/fastapi.conf
server {
listen 80 default_server;
location / {
proxy_set_header Host $http_host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_redirect off;
proxy_buffering off;
proxy_pass http://uvicorn;
}
}
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
upstream uvicorn {
server unix:///src/tmp/uvicorn.sock;
}
-
upstream
でuvicorn
を定義しuvicorn.sock
の場所を指定しています。
app/main.py
from fastapi import FastAPI
app = FastAPI()
@app.get("/")
def read_root():
return {"Hello": "World"}
@app.get("/healthcheck")
async def healthcheck():
return {"message": "healthcheck"}
- 基本的なHello Worldおよび、ALBからのヘルスチェック用の
/healthcheck
のみエンドポイントを用意しています。
pyproject.toml
[tool.poetry]
name = "fastapi-sample"
version = "0.1.0"
description = ""
authors = ["someone <someone@example.com>"]
[tool.poetry.dependencies]
python = "^3.9"
uvicorn = "^0.17.6"
pydantic = "^1.9.1"
fastapi = "^0.78.0"
[tool.poetry.dev-dependencies]
[build-system]
requires = ["poetry-core>=1.0.0"]
build-backend = "poetry.core.masonry.api"
2. ビルドおよびECSのタスク定義について
以上の様なファイル構成およびファイル内容であとはお好きな形でイメージをビルドしていけば良いのですが、私はdocker-composeを利用しました。3
docker-compose -f docker-compose.ecs_fargate.yml build
このコマンドを打つとディレクトリ名_app
、ディレクトリ名_nginx
といったイメージが作成されます。docker images
コマンドで確認してみてください。
その後、イメージのタグづけ、ECRへのpushなどを行っていきます。ここでは割愛します。詳細は、ECRへの登録の「docker imageのタグ付け」以降をご覧ください。
タスク定義のコンテナの定義のところですが、今回はapp
とnginx
というコンテナ名で登録しました。
タスク定義の設定に関しては、nginx
コンテナのポートマッピングのコンテナポートに80を、ボリュームソースのソースコンテナにappを設定します。この辺りは例えばdocker-compose.ymlでの設定内容をECSではここで設定してあげるということになるかと思います。
3. 最後に
最終的にはまっていたのはuvicorn.sock
のところでした。今思えばRailsで環境構築した際にはpuma.sock
でnginx
と連携していたので、当然のことでした。また公式1にもserver unix:/tmp/uvicorn.sock;
の記載があり、きちんと公式読めば解決した話でした(アクセス権限の問題でuvicorn.sock
の置き場所は公式とは変更しています)。普段はRailsで開発しているのですが、この度Pythonで開発する必要があり、Railsでの知識が役立った良い例でした。引き続き精進していきます。
-
https://www.uvicorn.org/deployment/#running-behind-nginx ↩ ↩2
-
私が社会人1年目にSIerの研修では「Web3層構造」について学びました。今もこれが一般的なのかなと思っていますが、最近は違ってきているかもしれません。 ↩
-
docker-composeを利用している理由としてはビルド時に環境変数をビルド環境に合わせて外から指定してあげたかったからです。dockerコマンドではコマンドごとに指定する必要があり、prod, stg, demoなどと複数環境ごとにイメージをビルドしたい場合に困ったので、docker-composeを用いています。 ↩