【Flask】 利用uWSGI和Nginx发布Flask应用

  由于Flask比较容易上手,以前也拿flask写过几个小项目,不过当时天真地觉得只要在服务器上nohup跑一个python脚本就算是成功发布了这个flask项目。实际上这还面临不少问题,好比并发性很差,不支持异步(虽然也能够在run里面加上threaded之类的参数来解决,但终究不是正途)等等。真正通用的作法应该是用某些web容器来启动项目。接下来讲明作法,整个过程主要参考了这篇文章(https://segmentfault.com/a/1190000004294634)php

  我测试部署的系统是CentOS7 x86_64,环境搭建部分(包括安装python,安装flask以及flask相关依赖)的工做就跳过了。从安装uWSGI开始讲起。html

 

■  uwsgi的安装和配置  python

  uWSGI是一个由python实现的web容器,能够兼容性比较好地发布Django,Flask等pythonweb框架的应用。由于本质上来讲uwsgi是python的一个模块,因此能够用pip install uwsgi直接来安装它。nginx

  安装完成以后能够在一个合适的目录创建一个uwsgi服务器的配置文件。好比我选择在项目的根目录创建了一个uwsgiconfig.ini的文件。顺便一提,除了ini格式的配置,uwsgi还支持json,xml等多种多样的配置格式。这里以ini格式为例。web

  一个典型的配置文件以下:sql

[uwsgi]
socket = 127.0.0.1:5051
pythonpath = /home/wyz/flask
module = manage
wsgi-file = /home/wyz/flask/manage.py callable
= app processes = 4 threads = 2 daemonize = /home/wyz/flask/server.log

  依次解释一下这些配置项。socket指出了一个套接字,至关于为外界留出一个uwsgi服务器的接口。须要注意的是,socket不等于http。换句话说用这个配置起来的uwsgi服务器是没法直接经过http请求成功访问的,这一点后面还会提到,是遇到的一个坑。json

  pythonpath指出了项目的目录,module指出了项目启动脚本的名字而紧接着的wsgi-file指出了真正的脚本的文件名。callable指出的是具体执行.run方法的那个实体的名字,通常而言都是app=Flask(__name__)的因此这里是app。processes和threads指出了启动uwsgi服务器以后,服务器会打开几个并行的进程,每一个进程会开几条线程来等待处理请求,显然这个数字应该合理,过小会使得处理性能很差而太大则会给服务器自己带来太大负担。daemonize项的出现表示把uwsgi服务器做为后台进程启动,项的值指向一个文件代表后台中的全部输出都重定向到这个日志中去。flask

  以上这些配置项都是一些最为常见的配置项,实际上uwsgi还有不少不少配置。。除了写一个配置文件的启动方式以外,还有命令行的启动方式,这里就很少说了。请须要的本身百度。。【抱歉】segmentfault

  此外上面也说到此次碰到的一个坑,就是关于socket和http的差异。从概念上来讲,socket自己不是协议而是一种具体的TCP/IP实现方式,而HTTP是一种协议且基于TCP/IP。具体到这个配置这里来,若是我只配了socket = 127.0.0.1:5051的话,经过浏览器或者其余HTTP手段是没法成功访问的。而在uwsgi这边的日志里会提示请求包的长度超过了最大固定长度。另外一方面,若是配置的是http = 127.0.0.1:5051的话,那么就能够直接经过通常的http手段来访问到目标。但这会引发nginx没法正常工做。正确的作法应该是,若是有nginx在uwsgi以前做为代理的话应该配socket,而若是想让请求直接甩给uwsgi的话那么就要配http。后端

  配置完成以后就能够键入 uwsgi 配置文件.ini来启动uwsgi,再查看日志(若是配置了daemonize的话)若是最终没有报错,ps也能看到processes指定个数的uwsgi进程在跑的话说明成功启动。若是直接把uwsgi做为留给外部的链接接口发布应用的话固然也能够,可是通常而言咱们确定还要在uwsgi前面再加上一个nginx。nginx的好处在于能够进行安全过滤,防DDOS攻击,多台机器的负载均衡等工做。

  关于uwsgi服务器的中止,官方文档说能够uwsgi -HUP之类的命令操做,可是这须要找到这个uwsgi的pid,目前为止我都仍是很粗暴地killall -9 uwsgi了。。

■  nginx的安装和配置

  最开始用yum install nginx装了好多此仍是报缺乏libpcre.so.0的错,网上搜了一通发现多是由于我用的是CentOS7版本的系统而yum源中仍是适用于CentOS6的包。因此不如去网上找个rpm包或者直接下个源码包来编译安装。。。

   nginx经常使用命令:

  nginx  启动nginx

  nginx -s stop/reload  中止nginx/重载配置文件

  nginx -v  查看版本

  nginx -t  测试配置文件是否有语法上的错误等

  安装完成后默认的nginx的配置文件位于/etc/nginx/conf.d/default.conf,我直接修改了这个文件。在修改以前能够考虑先备个份。若是须要指定配置文件开启nginx能够加入-c参数。其实nginx默认读取的文件是/etc/nginx/nginx.conf,打开这个文件看看能够看到在其http块中有些include /etc/nginx/conf.d/*.conf,因此在那里的default.conf能够直接写server块。

  以前也了解过一点关于nginx的配置问题,其要义大概就是nginx的配置文件格式比较要紧,好比要有大括号,句尾有分号等等。另外以#开头的行都是注释,均可以不用管。在nginx的这个配置中咱们主要修改如下内容:

    server {
        listen       80;         //默认的web访问端口
        server_name  xxxxxx;     //服务器名
        #charset koi8-r;
        access_log  /home/wyz/flask/logs/access.log;    //服务器接收的请求日志,logs目录若不存在须要建立,不然nginx报错
        error_log  /home/wyz/flask/logs/error.log;         //错误日志

        location / {

            include        uwsgi_params;     //这里是导入的uwsgi配置

            uwsgi_pass     127.0.0.1:5051;   //须要和uwsgi的配置文件里socket项的地址
                                             //相同,不然没法让uwsgi接收到请求。

            uwsgi_param UWSGI_CHDIR  /home/wyz/flask;     //项目根目录

            uwsgi_param UWSGI_SCRIPT manage:app;     //启动项目的主程序(在本地上运行
                                                     //这个主程序能够在flask内置的
                                                     //服务器上访问你的项目)
}
}

  这样配置完后,当外部有一个80端口的请求送到本机时,先让nginx开始处理。nginx进行一些处理以后转发给这里配置的uwsgi_pass地址,恰好传送给uwsgi处理。再由uwsgi来调用项目中的代码处理请求返回。再来回味一下上面那个坑,若是当时仅仅配了一个http项而没有配置socket的话,就会致使一切容器启动都顺利,可是当我把请求发送给80端口的时候迟迟不来响应,直到超时。

   * 经网友提醒,这实际上是一个Nginx和uWSGI之间配置协同的一个问题。若是uWSGI直接经过HTTP方式对外提供服务,那么nginx中须要配置proxy_pass,指出HTTP服务具体套接字,从而实现请求的转发(参考zabbix安装时的nginx配置就是这样的)。而若是将uWSGI配置为socket,经过socket对外提供服务(因为socket不涉及具体的协议,外部无法直接经过uWSGI端口访问服务也更加安全一些。好比能够在nginx中配置一些URL的拒接防止sql注入之类的),那么nginx配置就应该得是uwsgi_pass来实现请求的转发。 proxy_pass配置的时候写http://,即表示是走http协议的;uwsgi_pass的时候未指出协议,表示走socket。

  当应用开始运行起来以后,个人这个项目根目录的结构是这样的;

  其中access.log和error.log分别记录了送到nginx处的请求的记录以及nginx部分中发生的错误的记录。项目的入口app.run被写在manage.py中,server.log记录的则是uwsgi服务器的运行情况。

  以上项目仍是一个很是简单的flask项目,不知道随着代码变复杂起来这么作来发布flask应用会不会遇到各类各样的问题。。总以前途仍是险阻呐。

 

■  部署websocket项目时的坑

  不久前作了一个带websocket的小flask项目,然而部署时历经各类问题。。最后都仍是没能彻底解决。

  首先是一个,由于要带websocket因此咱们须要在uwsgi启动的配置文件中写上合适的配置项,好比像下面这个同样:

[uwsgi]
project = /root/ICManage
pythonpath = /root/ICManage
wsgi-file = /root/ICManage/manage.py
chdir = %(project)
module = manage
callable = app

master = true
processes = 1
#threads = 2

socket = 127.0.0.1:5050
chmod-socket = 664

#buffer-size = 32768

http-websockets = 1
gevent = 1000
async = 30

daemonize = /home/hips/ICManage/uwsgi/logs/server.log

 

  project指出了项目目录,%(project)是对已配置项project进行一个取值,设置master是首先开启一个uwsgi的管理进程,而后由它开启若干个worker子进程,当子进程挂掉的时候还会自动重启。这些实际上是对上面通常性配置描述的一个补充,并非决定websocket特性的。

  决定websocket特性的则是http-websockets,gevent,async这些配置项,他们指出了经过这个配置文件启动的uwsgi进程是支持websocket的(uwsgi版本在2.0以后才开始支持websocket)。另外还有一个很重要的改动:processes改为了1,而且注释去掉了threads配置。若是不去掉threads,这会和gevent冲突,致使的现象就是经过nginx访问uwsgi程序时总会返回502 bad gateway。若是processes设置大于1,那么致使的现象就是socket通讯老是迟缓且没有规律。这主要是由于websocket的通讯是要基于一个sessionid的,而每一个进程接受请求时给出的sessionid都不一样。uwsgi在作均衡的时候可能把发向某一个进程的请求发给了另外一个进程,而那个进程显然没有处理这个请求的上下文,致使返回400 bad request,因此在socket通讯时老是会涌现出大量的400和502错误。

  把processes改为1显然不是一个万全之策,如此,性能上就出现了问题,这个要如何解决还有待研究。

  而后贴出改形成兼容websocket以后的nginx配置,至少我是这么配置启动以后能够正常运行:

server {
    listen       80;
    server_name  192.168.1.101;
    #charset koi8-r;
    access_log  /var/log/nginx/access.log;
    error_log  /var/log/nginx/error.log;

    location / {
        include uwsgi_params;
        uwsgi_pass 127.0.0.1:5050;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
    }

}

 

■  在一个nginx下部署多个应用的location配置简单说明

  上述location配置能够保证咱们直接访问这个IP(端口默认是80)就能够看到web应用响应的界面。可是有一个问题,若是这个机器上有好多应用呢?此时应该考虑在nginx的配置中体现出多应用的方法。一个简单的办法就是多加几条location配置来把指向不一样URI的访问路由到不一样的应用上去。

  然而这个过程并无说说的这么简单。好比沿用上面的例子,假如在这个nginx上咱们还要部署一个到zabbix的路由,那么能够把配置文件改为这样:(只写location部分):

location ^~ / {
    include uwsgi_param;
    uwsgi_pass 127.0.0.1:5050;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
}

location ^~ /zabbix/ {
    proxy_pass http://127.0.0.1:8881/zabbix/;
    proxy_redirect default;
    proxy_set_header HOST $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}

 

  把location /中间加上一个^~,是指出了URI从头开始匹配“/”的将所有转发到这个路由,固然/zabbix/开头的URI因为下面还配置了一条^~ /zabbix/,因此会转发到zabbix下面去。这个匹配和转发的详细规则能够学习下nginx的配置明细,就再也不多说。

  上面的location配置中,使用了include uwsgi_param,因此紧跟的配置项是uwsgi_pass,注意这个配置项无需也不能写出http://和后面的URI,这也就意味着,原生请求的URI只能一一对应到uwsgi_pass设置的值的这个根URL上去。考虑的这边下面配置了^~ /zabbix/,因此综合来看,除了http://xxxx:xx/zabbix/以及其余zabbix开头的URI以外都会路由到5050端口的那个web应用中去,而且请求URI不会被nginx作任何加工,好比原生请求指向http://xxxx:xx/a/b/c/ 那么最终路由到的地址就是127.0.0.1:5050/a/b/c/。这看起来彷佛理所固然,可是若是改为location ^~ /fullpack/ 呢,此时若是原生请求是http://xxxx:xx/upload/,那么最终路由到的是127.0.0.1:5050/fullpack/upload仍是127.0.0.1:5050/upload/呢?答案是后者,也就是说nginx未对URI作任何加工。

  相反的,看经过proxy_pass方法配置的location。在下面的配置中若是原生请求是http://xxxx:xx/zabbix/a/b/c/,那么最终请求路由到的是127.0.0.1:8881/zabbix/a/b/c/,能够当作将原生的URI,去掉了开头的/zabbix/,而后再把剩余部分拼接到127.0.0.1:8881/zabbix/后面,虽然这里凑巧两边都是/zabbix/,可是若是把location的换成/zbx/,那么就能够发现,原生的/zbx/a/b/请求将会路由到8881端口的/zabbix/a/b/请求。这证实了nginx对proxy_pass方式的配置收到的URI是有处理的。

■  经过nginx访问时自动加末尾斜杠的问题

  在上面的实验中,其实我遇到了一个小坑。就是配置完nginx以后访问每次都是404,通过缘由排查,发现是这么回事:

  在后端代码中,我写的是@app.route('/info',methods=['GET','POST'])这样的。当不使用uwsgi+nginx部署,而是用flask自带的web服务器进行测试时,我访问xxxx:xx/info,能够访问到界面。可是经过nginx访问时,nginx会把全部末尾不带斜杠的非文件类请求都加上斜杠,而且给出301回应,而后重定向到有斜杠的URL下。这多是由于其余一些比较经典的WEB开发语言中请求每每是一个文件如.php,.aspx,.html等,而python的框架其实是把一个“目录”节点做为一个html文件给出了。这就使得末尾要加上一个斜杠,才能让nginx知道这是一个指向目录的请求。

  解决的办法也很简单,经过浏览器直接发起GET请求的页面(也就是必定要通过nginx访问的),路由设置时记得加上末尾的斜杠就行了。由于不一样过键盘打到浏览器地址栏这种方式的GET请求(好比页面的一个超连接的href值,或者AJAX发起指向的URL)都是不会自动补齐斜杠的,因此其余那些页面也都不会受影响。另外加了斜杠的设置也能够估计没加斜杠的请求,好比我改为@app.route('/info/')以后,浏览器地址栏里打/info会自动补齐成/info/,而点击页面上href="/info"或者经过程序手段如requests.get('xxxx/info')也均可以访问到那个页面的。要是反过来,route('/info')而href="/info/"则不行。

相关文章
相关标签/搜索