巨人の足元でたじlog

そうして言葉を軽んじるから――― 君は私の言葉を聞き逃す

nginxのプロセスとかワーカとかスレッドとかについてよくわからないので整理する備忘録

今nginxとflaskのコンテナを使ったシステムを動かしているのだが、そのあたりでワーカーとかそこらへんがよく整理できていなかったので、この機会に勉強し直してみる。

以前自分が書いた記事を参照してみる。 プロセスとスレッドとかタイムスライスとかスケジューリングがわからない - 巨人の足元でたじlog

こいつわかってねーなー。 この中にある有用な記事をもう一度読む。 イケてるエンジニアになろうシリーズ 〜メモリとプロセスとスレッド編〜 - もろず blog

とりあえずまとめ

プロセスは専用のメモリ領域を利用する
スレッドは共有のメモリ領域を利用する

スレッドのほうがいいじゃん。という結論?だった。

この辺はいいんだ。結局これが実際のアプリケーション、ミドルウェアになったときにどこに効いてくるのかを知りたいのが今日の目的。

nginxとflaskのアプリケーションを作ってみる。

検証したいことは、flaskのアプリケーションで重たい処理があって、1リクエストを処理中に次のリクエストが来たらどうなるかということ。 これをnginxとかがどううまいことやってくれるのかっていうことを確認したい。

Dockerfile

FROM ubuntu:latest
RUN apt-get update -y
RUN apt-get install -y python-pip python-dev build-essential
COPY . /app
WORKDIR /app
RUN pip install -r requirements.txt
ENTRYPOINT ["python"]
CMD ["app.py"]

requirements.txt

Flask==0.10.1
$  docker build . -t flask-thread:latest
$ docker images
REPOSITORY                                                                        TAG                 IMAGE ID            CREATED             SIZE
flask-thread                                                                      latest              5db7f165ba72        2 minutes ago       436MB

できた。

立ち上げる。

$ docker run -it -p 5000:5000 flask-thread

ブラウザからlocalhost:5000を叩く。 特に今は負荷もかけてないので、一瞬でレスポンスが帰ってくる

 * Running on http://0.0.0.0:5000/ (Press CTRL+C to quit)
 * Restarting with stat
 * Debugger is active!
 * Debugger PIN: 926-602-021
172.17.0.1 - - [25/Sep/2019 16:56:22] "GET / HTTP/1.1" 200 -
172.17.0.1 - - [25/Sep/2019 16:56:22] "GET / HTTP/1.1" 200 -
172.17.0.1 - - [25/Sep/2019 16:56:23] "GET /favicon.ico HTTP/1.1" 404 -
172.17.0.1 - - [25/Sep/2019 16:56:35] "GET / HTTP/1.1" 200 -
172.17.0.1 - - [25/Sep/2019 16:57:01] "GET / HTTP/1.1" 200 -

sleep処理を作成

from flask import Flask
import time
from datetime import datetime
app = Flask(__name__)

@app.route('/')
def hello_world():
    return 'Flask Dockerized.Ver.0.1.4'

@app.route('/sleep')
def sleep():
    time.sleep(3)
    dt = datetime.now
    return dt


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

これに連続して6回リクエストを送ってみると、

172.17.0.1 - - [25/Sep/2019 17:06:24] "GET /sleep HTTP/1.1" 200 -
172.17.0.1 - - [25/Sep/2019 17:06:27] "GET /sleep HTTP/1.1" 200 -
172.17.0.1 - - [25/Sep/2019 17:06:30] "GET /sleep HTTP/1.1" 200 -
172.17.0.1 - - [25/Sep/2019 17:06:33] "GET /sleep HTTP/1.1" 200 -
172.17.0.1 - - [25/Sep/2019 17:06:36] "GET /sleep HTTP/1.1" 200 -
172.17.0.1 - - [25/Sep/2019 17:06:39] "GET /sleep HTTP/1.1" 200 -

と、1つの処理が終わってから3秒待っている。 つまり、一つの処理のレスポンスを返さない限りは他のリクエストの処理が開始できないということ。

このあたりに関しては昔調べてた気がした。 Flaskのデフォルトでは同時アクセスを処理できない - Qiita これにコメントしといた。

ところでいま適当に動かしてるのはFlask==0.10.1だった。 最新版のFlask==1.1.1に変更してみる。

 * Serving Flask app "app" (lazy loading)
 * Environment: production
   WARNING: This is a development server. Do not use it in a production deployment.
   Use a production WSGI server instead.
 * Debug mode: on
 * Running on http://0.0.0.0:5000/ (Press CTRL+C to quit)
 * Restarting with stat
 * Debugger is active!
 * Debugger PIN: 209-172-304
172.17.0.1 - - [25/Sep/2019 17:14:07] "GET /sleep HTTP/1.1" 200 -
172.17.0.1 - - [25/Sep/2019 17:14:08] "GET /sleep HTTP/1.1" 200 -
172.17.0.1 - - [25/Sep/2019 17:14:10] "GET /sleep HTTP/1.1" 200 -
172.17.0.1 - - [25/Sep/2019 17:14:12] "GET /sleep HTTP/1.1" 200 -
172.17.0.1 - - [25/Sep/2019 17:14:13] "GET /sleep HTTP/1.1" 200 -
172.17.0.1 - - [25/Sep/2019 17:14:15] "GET /sleep HTTP/1.1" 200 -

3秒待たずして、次のリクエストが返ってきている。

これがマルチスレッドになっているということか。

ところで、会社のコードではworkerとかだった気がする。

flaskではなくて、gunicornの設定だった。
そもそも、flaskは裸で使うのはdev環境だけにしなさいって注意されてるしな。
gunicornの設定・挙動ををまた確認しなくては。

flask + gunicorn

そもそも今までなんの気なしに書いていたDockerfileの末尾

ENTRYPOINT ["python"]
CMD ["app.py"]

これの表現気になった。 このあたりは雰囲気でやっている。
[docker] CMD とENTRYPOINT の違いを試してみた - Qiita をみると、

docker image からdocker container を実行するときにCMD やENTRYPOINT の記述内容が実行されます。 1つのDockerfile にCMD、ENTRYPOINT は1度のみ。 ※ 複数記載されている場合、最後の1個が実行される。

とな。
話が違う。 一旦、以下で試してみる。

# ENTRYPOINT ["python"]
# CMD ["app.py"]
ENTRYPOINT ["python", "app.py"]

こちらでも挙動は特に問題なさそう。
このあたりはまたの機会に整理することにする。

docker-composeでgunicorn+nginx+flaskを動かしてみた話 - Qiita

gunicornの使い方なんとなく理解。

CMD ["gunicorn", "flask_sample:app"]

と書いてあるところの、flask_sampleがファイル名、appが最初に実行させたい関数か。

FROM ubuntu:latest
RUN apt-get update -y
RUN apt-get install -y python-pip python-dev build-essential
COPY . /app
WORKDIR /app
RUN pip install -r requirements.txt
ENTRYPOINT ["gunicorn", "app:app", "-b", "0.0.0.0:5000"]

これでbuildしてd run -it -p 5000:5000 flask-thread すると、複数リクエストを投げると、1リクエストずつしか処理されていない。 これは最初にまずgunicornがリクエストを受け付けているが、gunicornはデフォルトでマルチスレッドとかになっていないからなはず。

これをマルチスレッドにしたかったら、gunicornのconfigに設定する。
gunicorn + Flask + nginx + Systemdで動かしてみた - Qiita

guniconf.py

import multiprocessing

workers = 2

Dockerfile

ENTRYPOINT ["gunicorn", "app:app", "-b", "0.0.0.0:5000", "--config", "guniconf.py"]

これで2スレッドで実行できる。