Flask 应用如何部署

15,104 阅读11分钟
原文链接: mp.weixin.qq.com

我原来写过一篇《基于nginx和uWSGI在Ubuntu上部署Django》的文章,很受欢迎。这是个指导开发者一步一步部署的教程,虽然基于此,有心的开发者能悟出一些更加深入的内容,但文章本身并没有点透,可能有不少新手看完文章,只能知其然而不知其所以然。

Flask部署的非常简单,网上搜索如何部署Flask应用,能搜到中英文的很多资料,但基本上也都几句话带过,这让很多稍有经验的“新手”很没底。

所以Gevin前段时间仔细研究了一些相关资料,写了这篇比较深入的介绍Flask部署的文章,既为Flask(并推广到其他Python web框架)部署做了更加深入了阐述,也算是对上面Django部署文章在另一个维度上的补充。希望对大家能有所帮助。

1. Why Flask+Gunicorn+Nginx

Flask+Gunicorn+Nginx是最常用的Flask部署方案,大家深究过为何用这样的搭配么?

1.1 Why?

Flask 是一个web框架,而非web server,直接用Flask拉起的web服务仅限于开发环境使用,生产环境不够稳定,也无法承受大量请求的并发,在生茶环境下需要使用服务器软件来处理各种请求,如Gunicorn、 Nginx或Apache,而Gunicorn+Nginx的搭配,好处多多,一方面基于Nginx转发Gunicorn服务,在生产环境下能补充Gunicorn服务在某些情况下的不足,另一方面,如果做一个Web网站,除了服务外,还有很多静态文件需要被托管,这是Nginx的强项,也是Gunicorn不适合做的事情。所以,基于Flask开发的网站,部署时用Gunicorn和Nginx,是一个很好的选择。

1.2 Anything More?

1、为什么需要Nginx转发Gunicorn服务?

Nginx功能强大,使用Nginx有诸多好处,但用Nginx转发Gunicorn服务,重点是解决“慢客户端行为”给服务器带来的性能降低问题;另外,在互联网上部署HTTP服务时,还要考虑的“快客户端响应”、SSL处理和高并发等问题,而这些问题在Nginx上一并能搞定,所以在Gunicorn服务之上加一层Nginx反向代理,是个一举多得的部署方案。

2、为什么会有“慢客户端行为”带来的服务性能降低问题?

服务器和客户端的通信,我们简略的分为三个部分:request,request handling,和response,即客户端向服务器发起请求,服务器端响应并处理请求,和将请求结果返回客户端,这三个过程。

通常,request handling这部分即服务端的计算,拼的是服务器的性能,处理是比较高效和稳定的,而request和response部分,影响因素比较多,如果这三个过程放到同一个进程中同步处理,如果request和response部分耗时比较多,会使计算资源被占据并无法及时释放,导致计算资源无法有效利用,降低服务器的处理能力。

上述“慢客户端行为”,指的就是request(或response)部分耗时比较多的情况,Gunicorn恰好会把上面三个过程放到同一个进程中,当出现“慢客户端行为”时,效率很低:

Gunicorn 是一个pre-forking的软件,这类软件对低延迟的通信,如负载均衡或服务间的互相通信,是非常有效的。但pre-forking系统的不足是,每个通信都会独占一个进程,当向服务器发出的请求多于服务器可用的进程时,由于服务器端没有更多进程响应新的请求,其响应效率会降低。

对于Web网站或服务而言,由于request和response延时是不可控的,我们需要在考虑处理高延迟客户端请求的情况。这些请求会占据服务器端的进程。当慢客户端直接与服务通信时,由于慢客户端请求会占据进程,可用于处理新请求的进程就会减少,如果有很多慢客户端请求把所有进程都占据后,新的请求只能等待有进程被释放掉后,得到响应。另外,如果应用希望有更高的并发,服务器与客户端的通信要更高效,异步的通信会比同步的通信更有效。

Nginx这类异步的服务器软件擅长用很少的内存和cpu开销来处理大量的请求。由于他们擅长于同时处理大量客户端请求,所以慢客户端请求对他们影响不大。就Nginx而言,现在一般的服务器硬件条件下,同时处理上万个请求都不在话下。

所以把Nginx挡在pre-forking服务前面处理请求是一种很好的选择。Nginx能够异步、高并发的响应客户端request(慢客户端请求对Nginx影响不大),Nginx一旦接收到的请求后立刻转给Gunicorn服务处理,处理结果再由Nginx以response的形式发回给客户端。这样,整个服务端和客户端的通信,就由原来仅通过Gunicorn的同步通信,变成了基于Nginx和Gunicorn的异步通信,通信效率和并发能力得到大大提升。

对于网站而言,除了要考虑上面介绍的情况,还要考虑各种静态文件的托管问题。静态文件既包括CSS、JavaScript等前端文件,也包括图片、视频和各类文档等,所以静态文件要么可能会比较大,要么会调用比较频繁,静态文件的托管功能,就是要保证各类静态能正常的加载、预览或下载,这其实就是Response耗时长的“慢客户端行为”。用Gunicorn托管静态文件,也会严重影响Gunicorn的响应效率,而这恰恰又是Nginx擅长的工作,所以静态文件的托管也交给Nginx搞定就好。

2. Flask网站如何部署

结合上一节的解释,Flask网站如何部署也很明确了:

  1. 用一个服务器软件(如Gunicorn)把Flask写好的应用拉起来

  2. 用Nginx给上一步拉起的应用做一个反向代理

  3. 网站涉及到的静态文件,用Nginx做文件托管

常见的服务器软件是Gunicorn和uWSGI,由于Gunicorn配置使用简单,效率也不错,Gunicorn拉起Flask网站的配置极为简单,所以通常用Gunicorn来部署Flask网站是最常见的部署方案。(另,Gevin个人还有一个喜欢Gunicorn的原因是,Gunicorn是纯Python写的,直接pip安装即可,而uwsgi还要系统装额外的依赖,这在与docker配合使用时,Gunicorn的简单性尤为突出)

对于静态文件的托管,由于在开发阶段通常会基于Flask框架做静态文件托管的实现,所以当用Gunicorn拉起Flask网站时,网站已经实现了基于Gunicorn的文件托管功能,所以配置Nginx的静态文件托管URL时,可以直接配置成与基于Gunicorn托管一致的文件路径,这样能简化开发和部署的逻辑,而且由于Nginx比Gunicorn更靠外一层,客户端请求静态文件时,Nginx就直接返回Response了,不用担心会请求到Gunicorn中去影响服务器效率。

2.1 Gunicorn

Gunicorn如何部署Flask网站,直接看Flask或Gunicorn官方文件即可,通常只要执行类似下面的一行命令:

/usr/local/bin/gunicorn -w 2 -b :4000 manage:app

2.2 Nginx

用Nginx做反向代理和托管静态文件,也很简单,我这里提供一个可用Demo,更多玩法大家自行查阅Nginx文档吧

server {
    listen 80;

    server_name localhost;

    access_log  /var/log/nginx/access.log;
    error_log  /var/log/nginx/error.log;

    location / {
        proxy_pass         http://localhost:8000/;
        proxy_redirect     off;

        proxy_set_header   Host             $http_host;
        proxy_set_header   X-Real-IP        $remote_addr;
        proxy_set_header   X-Forwarded-For  $proxy_add_x_forwarded_for;

    }

    location /media  {
        alias /usr/share/nginx/html/media;  
    }

    location /static  {
        alias /usr/share/nginx/html/static;  
    }
}

其中,做反向代理的配置是:

    location / {
        proxy_pass         http://localhost:8000/;
        proxy_redirect     off;

        proxy_set_header   Host             $http_host;
        proxy_set_header   X-Real-IP        $remote_addr;
        proxy_set_header   X-Forwarded-For  $proxy_add_x_forwarded_for;

    }

做静态文件托管的配置是:

    location /media  {
        alias /usr/share/nginx/html/media;  
    }

    location /static  {
        alias /usr/share/nginx/html/static;  
    }

我这里对两个文件夹的文件做了托管。

3. 基于Docker的Flask网站部署

Docker具有一次部署,到处运行的好处,把上述传统部署的方法,封装到docker image中,然后配合Docker Compose编排服务,在实践中更方便。

3.1 构建Flask网站的镜像

通常,镜像中包含Flask网站的运行环境,然后把Gunicorn拉起服务作为image的运行命令即可,比如,我的OctBlog的Dockerfile编写如下:

# MAINTAINER        Gevin <flyhigher139@gmail.com>
# DOCKER-VERSION    18.03.0-ce, build 0520e24FROM python:3.6.5-alpine3.7
LABEL maintainer="flyhigher139@gmail.com"

RUN mkdir -p /usr/src/app  && \
    mkdir -p /var/log/gunicorn

WORKDIR /usr/src/app
COPY requirements.txt /usr/src/app/requirements.txt

RUN pip install --no-cache-dir gunicorn && \
    pip install --no-cache-dir -r /usr/src/app/requirements.txt

COPY . /usr/src/app

ENV PORT 8000
EXPOSE 8000 5000

CMD ["/usr/local/bin/gunicorn", "-w", "2", "-b", ":8000", "manage:app"]

这里,Gevin直接用了最小的Python-alpine镜像作为基础镜像,大大减少了即将构建的Flask应用镜像的体积。对于alpine这种只有几M的极简image而言,不安装其他系统依赖,直接pip install uwsgi就会报错。

3.2 Nginx 相关的配置

Nginx上主要做反向代理和静态文件托管,和上面的配置文件一致,如:

server {
    listen 80;

    server_name localhost;

    access_log  /var/log/nginx/access.log;
    error_log  /var/log/nginx/error.log;

    location / {
        proxy_pass         http://blog:8000/;
        proxy_redirect     off;

        proxy_set_header   Host             $http_host;
        proxy_set_header   X-Real-IP        $remote_addr;
        proxy_set_header   X-Forwarded-For  $proxy_add_x_forwarded_for;

    }

    location /media  {
        alias /usr/share/nginx/html/media;  
    }

    location /static  {
        alias /usr/share/nginx/html/static;  
    }
}

这个配置文件和上一章节的唯一区别是,第10行的proxy_pass         http://blog:8000/; 这里的反向代理的服务为blog,是下面Docker-compose中配置的OctBlog网站服务。

3.3 用Docker-compose编排服务

OctBlog的Docker-compose编排文件如下:

version: '3'
services:
  blog:
    # restart: always
    image: gevin/octblog:0.4.1
    volumes:
      - blog-static:/usr/src/app/static
    env_file: .env
    networks:
      - webnet

  mongo:
    # restart: always
    image: mongo:3.2
    volumes:
      - /Users/gevin/projects/data/mongodb:/data/db
    networks:
      - webnet

  blog-proxy:
    # restart: always
    image: nginx:stable-alpine
    ports:
      - "8080:80"
    volumes:
      - ./default.conf:/etc/nginx/conf.d/default.conf
      - blog-static:/usr/share/nginx/html/static:ro
      - blog-static:/usr/share/nginx/html/media:ro
    networks:
      - webnet

volumes:
  blog-static:
networks:
  webnet:

其中,为了让多个服务能互通,创建了自定义的network webnet,为了让文件能在多个服务之间共享,公用了volume blog-static

4. 其他Python web网站的部署

基于上面的内容,举一反三,我们也可以梳理出Python 各类web框架做网站部署时的一般套路:

  1. 用一个Python WSGI HTTP Server来部署代码,如:

    • Green Unicorn

    • uWSGI

    • mod_wsgi

    • CherryPy

  2. 用Nginx做反向代理

  3. 做好静态文件的托管

5. 注:

  1. 服务器软件除了Nginx外,还有Apache等其他选项,Gevin对其他服务器软件部署Python网站理解不深入,所以本文并未涉及。

  2. OctBlog的源码托管在GitHub上,大家可以搜索flyhigher139/OctBlog这个repo查看

本文撰写时参考了以下内容,大家可以做延伸扩展:

Why flask + nginx + gunicorn:

  • What benefit is added by using Gunicorn + Nginx + Flask?

  • Why is setting Nginx as a reverse proxy a good idea?

  • What are the differences between nginx and gunicorn?

  • Why do I need Nginx and something like Gunicorn?

About Preforking:

  • The Preforking Model

Nginx serve flask static files:

  • How to serve Flask static files using Nginx?

  • nginx + uwsgi - what is serving static files?

最后

前段时间,『极客时间』的专栏《从0开始学架构》,第18课《单服务器高性能模式:PPC与TPC》,对pre-fork 及相关知识有更深的讲解,我把宣传海报放到文末,如果大家感兴趣,扫海报中的二维码订阅,按现在极客时间的营销规则,有8元钱返现。

6. What's More

如无特别说明,文章均为Gevin原创,未经Gevin许可,禁止转载

点击的『阅读原文』,将打开『Gevin的博客』,具有更好的代码阅读体验,也可以查看文章中提到的相关链接。

本帐号同步发表博客中的文章,以及一些与之互补的浅阅读技术类内容,欢迎大家关注。