V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
爱意满满的作品展示区。
itskingname
V2EX  ›  分享创造

使用 uwsgi 部署 Flask,不使用 Unix 套接字连接 Nginx

  •  1
     
  •   itskingname · 2019-07-08 21:36:38 +08:00 · 3838 次点击
    这是一个创建于 1725 天前的主题,其中的信息可能已经有所发展或是发生改变。

    如果你在 Google 或者百度或者某些技术社区上面搜索uwsgi + Flask,你会发现大量的文章,是教你如何使用uwsgi + Flask + Nginx搭建网站。如下图所示:

    怪现状

    而且这些文章,全部都像是约定俗成一样,一定会首先用命令行启动 uwsgi,测试 uwsgi 与 Flask 运行是否正常,然后写 uwsgi 的配置文件。然后使用Unix 套接字沟通 uwsgi 与 Nginx。所以 uwsgi 的配置文件里面一定会写成类似于下面这样:

    socket = /xxx/yyy/zzz.sock
    

    Nginx 的配置一定有类似于下面这一段:

    location / {
            include uwsgi_params;
            uwsgi_pass unix:///xxx/yyy/zzz.sock;
        }
    

    他们为什么要这样写?因为他们看的别的博客上就是这样写的!他们知其然,但是不知其所以然。

    有什么问题?

    这种写法本身没有问题,甚至 Flask 的官方文档里面也是这样写的,如下图所示:

    但是他们这样写,有一个基本前提——就是 Flask 程序、uwsgi、Nginx 三个东西运行在同一个服务器上。如果用 Docker,那么这三个东西甚至需要运行到一个容器里面。

    如果是一个小网站,服务器资源足够,那么这样写没有问题,Unix 套接字安全性高,速度也快。

    那么如果你同一个服务器上有三个 Docker 容器,每一个容器都有一个不同的网站,是不是每个容器里面都需要安装一个 Nginx ?

    对于大一些的网站,Nginx 需要做负载均衡,如果把 Nginx 和网站放在同一台服务器上,无论是 Nginx 拖垮了服务器,还是网站拖垮了服务器,都会导致很严重的问题。

    能不能实现,一个服务器上直接安装 Nginx,然后服务器上的三个网站分别在三个 Docker 容器里面,每个容器里面只有 Flask 和 uwsgi,没有 Nginx ?

    如果你的网站大一些,你在 A 服务器安装 Nginx,在 B、C、D、E、F 服务器上不安装 Nginx,只安装 uwsgi + Flask,又怎么做?

    所以进入我们今天的主题,安装 uwsgi + Flask(或者 Django),但是不安装 Nginx ( Deploy Flask with uwsgi but without Nginx )

    不使用 Unix 套接字的 uwsgi

    Unix 套接字,本质上是一个文件( Unix/Linux 哲学:一切皆文件),Nginx 和 uwsgi 通过这个文件来进行通信。所以需要 Nginx 与 uwsgi 放在同一个机器上。

    但实际上,uwsgi 本身就是一个服务器,A 服务器上的 Nginx 与 B 服务器上的 uwsgi 之间是可以通过 http 进行通信的。

    要让 uwsgi 使用 http 进行通信,我们可以修改 uwsgi 的配置文件 xxx.ini:

    [uwsgi]
    module = wsgi:app
    master = true
    process = 5
    threads = 100
    gevent = 100
    async = 100
    http-socket = 0.0.0.0:5001
    virtualenv = /Users/kingname/.local/share/virtualenvs/ActiveScoreApi-Ax_h-Y5w
    

    其他参数的意义不是本文的重点,我们要关心的是http-socket = 0.0.0.0:5001。它的作用把网站部署在本机的 5001 端口,并允许外网通过 http 访问。

    写了这个配置文件以后,通过以下命令来启动 uwsgi:

    uwsgi --ini xxx.ini
    

    然后你使用IP:5001就可以访问你的网站了。此时,如果你有 Nginx,那么只需要在 Nginx 上设置反向代理,把 80 端口的请求代理到 5001 端口即可。

    同理,把 uwsgi 和网站放在 Docker 镜像里面,容器开放 5001 端口。宿主机或者其他机器上的 Nginx 直接通过 IP:端口 就可以访问容器里面的 uwsgi,不再需要设置 Unix 套接字了。

    另外,如果你阅读过 uwsgi 的官方文档,你还会发现,除了http-socket = 0.0.0.0:5001外,你也可以把它改成http = 0.0.0.0:5001。那么这两种写法是否一样呢?

    在官方文档里面特别区分了它们的使用场景:

    The http and http-socket options are entirely different beasts. The first one spawns an additional process forwarding requests to a series of workers (think about it as a form of shield, at the same level of apache or nginx), while the second one sets workers to natively speak the http protocol. TL/DR: if you plan to expose uWSGI directly to the public, use --http, if you want to proxy it behind a webserver speaking http with backends, use --http-socket.

    简言之,如果你直接把 uwsgi 作为服务器,uwsgi 启动以后,直接就把 IP:端口拿给别人访问,那么你就可以使用http;如果你的 uwsgi 前面还挡了一个 Nginx,那么你就使用http-socket

    本文首发于我的微信公众号 未闻 Code ( ID:itskingname )

    34 条回复    2019-07-12 10:48:07 +08:00
    miniyao
        1
    miniyao  
       2019-07-08 21:47:30 +08:00   ❤️ 2
    flask 不是应该用 gunicorn 吗?
    est
        2
    est  
       2019-07-08 21:50:21 +08:00
    还可以通过 fastcgi。
    itskingname
        3
    itskingname  
    OP
       2019-07-08 21:52:22 +08:00
    @miniyao 是的,gunicorn 确实非常简单。
    itskingname
        4
    itskingname  
    OP
       2019-07-08 21:52:43 +08:00
    @est fastcgi 有点老了。
    NoirStrike
        5
    NoirStrike  
       2019-07-08 22:12:05 +08:00
    收藏下
    ericls
        6
    ericls  
       2019-07-09 01:13:08 +08:00 via iPhone
    还可以 mount static
    dongxiaozhuo
        7
    dongxiaozhuo  
       2019-07-09 01:45:13 +08:00
    总结下来就两个问题:
    1. Nginx 与 uWSGI + Flask App 是否在同一个系统空间下(容器是隔离的系统)?
    同一个系统空间: 既可以是 sock 通信(文件),也可以是 tcp 通信(网络,uWSGI 监听一个端口)
    不同的系统空间:只能使用 tcp 通信

    2. Nginx 与 uWSGI 之间的通信用 uwsgi (注意全是小写)协议,还是用 HTTP 协议?
    uwsgi 协议是二进制的。使用起来的区别是 Nginx 的 pass 写成 proxy_pass 转发的是 HTTP 协议,uwsgi_pass 转发的是 uwsgi 协议。
    ryd994
        8
    ryd994  
       2019-07-09 06:02:09 +08:00
    1. unix socket 可以通过 mount 共享
    2. uwsgi 协议可以直接走 TCP,不需要 http。https://uwsgi-docs.readthedocs.io/en/latest/Nginx.html
    Nginx 本身就需要解析 http 协议,然后反代的时候又要重新组合 http 请求,然后 uwsgi 又要再解析一次,这完全就是浪费。
    uwsgi 协议的目的之一就是避免这样的浪费
    ryd994
        9
    ryd994  
       2019-07-09 06:06:07 +08:00
    @miniyao gunicorn 的优点是可以直接部署,配置简单。
    前面套 nginx 或者反代的话的话没必要用 gunicorn。gevent 的目标场景是大量闲置连接。有反代的情况下,nginx 负责处理 buffer,而且能 pipeline 请求,这时用 gevent 反而浪费。

    @itskingname fastcgi 老,你用 http 不是更老?任何协议,只要能高效的完成目的,都可以用。无论是 fcgi 还是 uwsgi,都比 http 有效率的多。
    cloudyplain
        10
    cloudyplain  
       2019-07-09 06:23:28 +08:00   ❤️ 1
    这篇文章也太水了吧
    cloudyplain
        11
    cloudyplain  
       2019-07-09 06:28:34 +08:00
    @ryd994 gevent 并不浪费而是为了异步非阻塞,gunicorn 和 uswgi 个人更偏向 gunicorn,代码清晰明了,另外 uswgi 是短连接。
    carlclone
        12
    carlclone  
       2019-07-09 07:10:52 +08:00 via Android
    docker 没学好吧
    ryd994
        13
    ryd994  
       2019-07-09 07:46:14 +08:00
    @cloudyplain 正常情况下,nginx 已经缓冲了完整请求。内网带宽假设总是能跑满,这时同步阻塞处理反而更有效率。
    举个例子,CPU 已经满载时再加多线程反而会降低性能。异步非阻塞并不总是更好。Nginx 已经把这部分做好了,前提是正确配置。
    youngce
        14
    youngce  
       2019-07-09 08:44:17 +08:00
    > "有一个基本前提——就是 Flask 程序、uwsgi、Nginx 三个东西运行在同一个服务器上。如果用 Docker,那么这三个东西甚至需要运行到一个容器里面。"
    从这里开始出现偏差。即使是使用 Unix 套接字,nginx 也可用与 flask 用两个容器,挂载同一个文件区,来共享 sock 文件就可以了。所以下面的,由于不能使用多容器部署引出的长篇大论,可以不用看了。
    est
        15
    est  
       2019-07-09 08:48:15 +08:00
    @cloudyplain uwsgi 是短链接的说法是哪里来的?
    @ryd994 gunicorn 的优点,uwsgi 不也是优点么。直接部署配置简单。
    tony9413
        16
    tony9413  
       2019-07-09 08:49:55 +08:00
    网上这些技术文章很多都是复制粘贴的,也是正常的
    STRRL
        17
    STRRL  
       2019-07-09 09:13:39 +08:00 via Android
    11 楼正解 只需把 unix domain socket 文件挂载过去就可以了
    cloudyplain
        18
    cloudyplain  
       2019-07-09 09:14:42 +08:00
    @ryd994 nginx 缓冲了请求不假,但后面业务代码中一个数据库或者 redis 查询就 block 一个线程,那就需要开多个线程或进程来处理,one request per thread 显然不适用,gevent 或者 twisted 就是处理这种问题。
    sagaxu
        19
    sagaxu  
       2019-07-09 09:24:10 +08:00 via Android
    一句话的信息量也能写这么长,文字功底不错
    gaigechunfeng
        20
    gaigechunfeng  
       2019-07-09 09:43:20 +08:00
    [uwsgi]
    socket=127.0.0.1:8086
    plugin=python
    wsgi-file=app.py
    callable= wxapp
    #master=true
    #processes= 4
    #threads= 2
    stats= 127.0.0.1:9192
    daemonize = ./uwsgi.log

    socket 后面也可以直接接 ip 加端口号吧。

    和楼主写的 http-socket 是同样的效果吗?
    shuizhengqi
        21
    shuizhengqi  
       2019-07-09 09:50:38 +08:00
    我 tm 真的是服气的,乌拉乌拉的说一大堆,你自己对 docker 的理解有问题吧,服务器起一个 docker 放 nginx,然后起三个 docker 放应用,映射 nginx 的端口出去,nginx 再做转发到其它容器的端口,这有问题么?
    shuizhengqi
        22
    shuizhengqi  
       2019-07-09 09:51:02 +08:00
    也就是为了在这水文章吧
    itskingname
        23
    itskingname  
    OP
       2019-07-09 09:59:18 +08:00
    @gaigechunfeng 是的,这样走的是 TCP
    itskingname
        24
    itskingname  
    OP
       2019-07-09 09:59:50 +08:00
    @shuizhengqi 麻不麻烦~
    zw1027
        25
    zw1027  
       2019-07-09 11:49:18 +08:00
    你赢了,我一开始还认真看,还以为楼主发现了什么了不得的内容

    结果...
    Imr
        26
    Imr  
       2019-07-09 12:36:16 +08:00 via iPhone
    多节点 k8s 调度确实有这个烦恼,py 写的一些服务还是得依赖 gunicron 来做,http-sock 以前没发现,下午我也去瞅瞅能不能替代容器内封装 nginx 的做法
    shuizhengqi
        27
    shuizhengqi  
       2019-07-09 13:51:10 +08:00
    @itskingname 你直接暴露 ip 跟端口让别人访问?
    itskingname
        28
    itskingname  
    OP
       2019-07-09 14:14:21 +08:00
    @shuizhengqi 我的 ip 和端口,只暴露给专门跑在另外一台服务器上面的 Nginx。
    itskingname
        29
    itskingname  
    OP
       2019-07-09 14:15:16 +08:00
    @Imr 如果帮助到了你,麻烦回来帮我怼一下反驳我的人。感谢。
    vipppppp
        30
    vipppppp  
       2019-07-09 18:32:21 +08:00
    没什么请求的网站别说不使用 nginx,你甚至可以 python app.py ,我相信只要你机器正常服务也不会挂
    itskingname
        31
    itskingname  
    OP
       2019-07-09 19:14:41 +08:00
    @vipppppp emmmm, 你看完了正文?
    baojiweicn2
        32
    baojiweicn2  
       2019-07-10 01:14:41 +08:00 via Android
    核心的本质是要开多进程,绕开 gil。况且也可以把.socket 文件挂到 docker 外面,nginx 直接怼上去就可以了,也可以开一个 docker 的 network 也成啊。况且都 docker 了为啥不 k8s
    baojiweicn2
        33
    baojiweicn2  
       2019-07-10 01:17:06 +08:00 via Android
    @shuizhengqi 直接用云商的 lbs 也可以吧。这波操作太麻烦了吧
    xhinliang
        34
    xhinliang  
       2019-07-12 10:48:07 +08:00
    水文...
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   4392 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 37ms · UTC 10:06 · PVG 18:06 · LAX 03:06 · JFK 06:06
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.