【问题标题】:Why running flask app.run with gunicorn and uwsgi is problematic?为什么用 gunicorn 和 uwsgi 运行 flask app.run 会出现问题?
【发布时间】:2022-01-05 07:09:56
【问题描述】:

有一个想法表明不要在生产环境中使用 gunicorn 或 uwsgi 运行烧瓶应用程序。 Tiangolo 在他的一个存储库中提到 app.run 应该仅用于开发,而不是部署或生产。 Link to Tiangolo's comment on this topic 他的代码如下:

from flask import Flask

app = Flask(__name__)

from .core import app_setup


if __name__ == "__main__":
    # Only for debugging while developing
    app.run(host="0.0.0.0", debug=True, port=80)

我的问题是为什么运行烧瓶应用程序可能会出现问题(上述代码的最后一行)并且应该被删除或注释掉。我进行了一系列测试,发现在生产环境中运行或运行烧瓶应用程序生成的进程数量是相同的。这是我用 gunicorn 进行的测试的输出。

这是我的 docker-compose。在第 12 行,您可以查看我是如何运行 gunicorn 的。

version: "3.7"
services:
  face:
    build: ./app
    container_name: face
    restart: always
    expose:
      - 660
    environment:
      - ENDPOINT=/face
      - FACE_DETECTION_MODEL=MTCNNTorchFaceDetector
    command: gunicorn --workers=2 --threads 1 -b 0.0.0.0:660 entry_point:app --worker-class sync

  nginx:
    build: ./nginx
    container_name: nginx
    restart: always
    ports:
      - 8000:80
    depends_on:
      - face

使用 app.run 烧瓶 gunicorn 2 进程:

root@e6c7d9ef03cc:/app# cat entry_point.py 
from endpoints import FaceDetection
from base_app import app, api, ENDPOINT

api.add_resource(FaceDetection, ENDPOINT)

if __name__ == '__main__':
    app.run("127.0.0.1", port=3000)
root@e6c7d9ef03cc:/app# lsof -i
COMMAND  PID USER   FD   TYPE DEVICE SIZE/OFF NODE NAME
gunicorn   1 root    5u  IPv4 471109      0t0  TCP *:660 (LISTEN)
gunicorn   7 root    5u  IPv4 471109      0t0  TCP *:660 (LISTEN)
gunicorn   8 root    5u  IPv4 471109      0t0  TCP *:660 (LISTEN)
root@e6c7d9ef03cc:/app#

正如您在代码中看到的,有 3 个处理器,其中一个是主处理器,另外两个是工作处理器。

flask gunicorn 2 没有 app.run 的进程(已被注释掉):

root@e6c7d9ef03cc:/app# cat entry_point.py 
from endpoints import FaceDetection
from base_app import app, api, ENDPOINT

api.add_resource(FaceDetection, ENDPOINT)

# if __name__ == '__main__':
#     app.run("127.0.0.1", port=3000)
root@e6c7d9ef03cc:/app# lsof -i
COMMAND  PID USER   FD   TYPE DEVICE SIZE/OFF NODE NAME
gunicorn   1 root    5u  IPv4 466580      0t0  TCP *:660 (LISTEN)
gunicorn   8 root    5u  IPv4 466580      0t0  TCP *:660 (LISTEN)
gunicorn   9 root    5u  IPv4 466580      0t0  TCP *:660 (LISTEN)
root@e6c7d9ef03cc:/app#

输出是相同的,并且丢弃主处理器,只有 2 个主要工作器启动并运行。
具有 4 个处理器的 gunicorn 应用程序也是如此。
使用 app.run:

root@f1d9f2d3a5d0:/app# cat entry_point.py 
from endpoints import FaceDetection
from base_app import app, api, ENDPOINT

api.add_resource(FaceDetection, ENDPOINT)

if __name__ == '__main__':
    app.run("127.0.0.1", port=3000)
root@f1d9f2d3a5d0:/app# lsof -i
COMMAND  PID USER   FD   TYPE DEVICE SIZE/OFF NODE NAME
gunicorn   1 root    5u  IPv4 484435      0t0  TCP *:660 (LISTEN)
gunicorn   7 root    5u  IPv4 484435      0t0  TCP *:660 (LISTEN)
gunicorn   8 root    5u  IPv4 484435      0t0  TCP *:660 (LISTEN)
gunicorn   9 root    5u  IPv4 484435      0t0  TCP *:660 (LISTEN)
gunicorn  10 root    5u  IPv4 484435      0t0  TCP *:660 (LISTEN)
root@f1d9f2d3a5d0:/app#

没有 app.run:

root@f1d9f2d3a5d0:/app# cat entry_point.py 
from endpoints import FaceDetection
from base_app import app, api, ENDPOINT

api.add_resource(FaceDetection, ENDPOINT)

# if __name__ == '__main__':
#     app.run("127.0.0.1", port=3000)
root@f1d9f2d3a5d0:/app# lsof -i
COMMAND  PID USER   FD   TYPE DEVICE SIZE/OFF NODE NAME
gunicorn   1 root    5u  IPv4 476011      0t0  TCP *:660 (LISTEN)
gunicorn   8 root    5u  IPv4 476011      0t0  TCP *:660 (LISTEN)
gunicorn   9 root    5u  IPv4 476011      0t0  TCP *:660 (LISTEN)
gunicorn  10 root    5u  IPv4 476011      0t0  TCP *:660 (LISTEN)
gunicorn  11 root    5u  IPv4 476011      0t0  TCP *:660 (LISTEN)
root@f1d9f2d3a5d0:/app#

要重现问题克隆 face-detection-flask-gunicron-docker-compose 并运行以下命令:

# get the project
git clone https://github.com/pooya-mohammadi/face-detection-flask-nginx-gunicorn-docker.git
# cd to project's root 
cd ace-detection-flask-nginx-gunicorn-docker
# build the images and run the project
sudo docker-compose up --build
# open a new terminal
sudo docker ps -a 
# get the container-id for face-detection_face
# open a bash command with that container-id
sudo docker exec -it <container-i> bash # This opens a new command line
# install lsof to view listening services
apt-get install lsof
# view app.run condition
cat entry_point.py | grep app.run # The default is not commented. 
lsof -i  

在 entry_point.py 中注释 app.run 并再次运行该过程。
要更改工作人员的数量,请修改 docker-compose.yml 中的第 12 行。

【问题讨论】:

  • 请编辑问题,让您的问题更清楚。我猜你在 GitHub 存储库中有一个 Flask 应用程序,它会生成一些 PNG 文件?您应该直接在问题中的文本中包含问题的详细信息(不是图像,也不是链接后面),并且您还需要在问题中直接包含minimal reproducible example(不仅仅是指向您的存储库的链接)。
  • @DavidMaze 感谢您的回复。我删除了图像。不过,为了重现问题,需要运行 docker-compose。
  • 如果 docker-compose.yml 是 MRE 的一部分,您还需要在问题中包含它。我有点不清楚为什么您在容器进程旁边的外壳中做了这么多;在普通的 Python 虚拟环境中,在 Docker 之外运行相同的代码,您是否遇到同样的问题?
  • @DavidMaze 实际上,这不是问题。我相信这是一个误解。我怀疑为什么人们一直说您不应该将 app.run 保留在 entrypoint.py 中。我进行了几项测试以表明将 app.run 保留在 entry_point 中不会对生成的处理器数量产生任何影响。在第一个答案中,我说明 app.run 位于 main 下,Gunicorn 不会执行它,将其保留在那里非常安全。
  • 您是指 Docker 入口点(Dockerfile ENTRYPOINT 指令,Compose entrypoint: 覆盖)还是 Python setuptools entry_points 脚本包装器? (我认为这是我困惑的原因之一:我看到 entry_point.py 并假设它是主容器进程。)

标签: python docker flask docker-compose gunicorn


【解决方案1】:

在研究了 gunicorn 库一段时间后,我注意到 gunicorn 使用 import.import_module 导入入口点模块(包含应用程序的模块,在我的例子中是 entry_point.py)和 if __name__ == '__main__': 下的代码 won'不会被处决,放任何东西都很安全。 Link to import_app method in gunicorn library。这个方法是从主要运行器类WSGIApplicationLink to WSGIApplication class内的方法load_wsgiapplink to load_wsgiapp调用的。
我注意到 Tiangolo 意味着直接使用烧瓶应用程序进行生产是不安全的,因为:

flask 应用服务器未针对生产性能或安全性进行开发或测试。

Answer from Justin Triplett(discord)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2015-05-14
    • 2014-09-14
    • 1970-01-01
    • 2020-03-11
    • 1970-01-01
    • 1970-01-01
    • 2016-11-20
    • 1970-01-01
    相关资源
    最近更新 更多