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

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

■  uwsgi的安装和配置  

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

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

  一个典型的配置文件如下:

[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请求成功访问的,这一点后面还会提到,是遇到的一个坑。

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

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

  此外上面也说到这次碰到的一个坑,就是关于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/"则不行。

【Flask】 利用uWSGI和Nginx发布Flask应用的更多相关文章

  1. python服务器环境搭建Flask,uwsgi和nginx

    python服务器环境搭建Flask,uwsgi和nginx 环境配置 服务器配置  [部署系统环境Ubuntu] 使用python的Flask框架搭建好网页后台后,便要开始将网站部署到服务器平台了. ...

  2. 阿里云服务器部署笔记二(python3、Flask、uWSGI、Nginx)

    从git上把项目拉到服务器,项目可以在服务器上运行后,就只需要配置uwsgi和nginx了.它们的逻辑关系是:外部请求->nginx->uwsgi->项目实例. 一.配置uwsgi ...

  3. 阿里云服务器部署笔记一(python3、Flask、uWSGI、Nginx)

    一.重置密码,并重启服务器 二.安全组配置>配置规则>添加安全组规则(为了能在本地ssh到实例) 配置如下: 此配置为允许任意公网IP登陆实例,注意windows与Linux系统端口范围不 ...

  4. CentOS+Uwsgi+Nginx发布Flask开发的WebAPI

    1.WebAPI 开发工具VS 于Windows环境中开发完成后使用SFTP进行同步文件到Centos中使用 2.重点:WebAPI触发的方法是为了发送Celery异步调度任务 Celery框架使用涉 ...

  5. Python 部署 flask 用uwsgi和nginx

    安装uwsgi nginx 具体方法请百度 1.在项目目录下建立.ini文件 xad_uwsgi.ini [uwsgi] master=true #项目目录 chdir=/root/zhaoyingj ...

  6. 利用uWSGI和nginx进行服务器部署

    搭建服务器虚拟环境 1)在本机进入虚拟环境,执行命令导出当前需要的所有包. pip freeze > plist.txt 2)通过ftp软件将项目代码和plist.txt文件上传到服务器. 3) ...

  7. nginx+flask+gevent+uwsgi实现websocket

    Websocket简介 WebSocket是HTML5开始提供的一种在单个 TCP 连接上进行全双工通讯的协议.在WebSocket API中,浏览器和服务器只需要做一个握手的动作,然后,浏览器和服务 ...

  8. CentOS下实现Flask + Virtualenv + uWSGI + Nginx部署

    一.项目简介 在本文中,将一步一步搭建一个简单的Flask + Virtualenv + uWSGI + Nginx 架构的Web服务,可以作为新手的学习也可作为记录备忘. 如果你安装好了环境并有一定 ...

  9. 通过uwsgi+nginx启动flask的python web程序

    通过uwsgi+nginx启动flask的python web程序 一般我们启动python web程序的时候都是通过python直接启动主文件,测试的时候是可以的,当访问量大的时候就会出问题pyth ...

随机推荐

  1. dojo之dojox/data/CsvStore初始化

    dojo之dojox/data/CsvStore初始化 1.var csvStore = new dojox.data.CsvStore({url:"student.csv"}); ...

  2. FusionCharts饼图中label值太长怎么解决

    FusionCharts饼图中label值太长怎么解决 1.使用hoverText属性 <?xml version="1.0" encoding="UTF-8&qu ...

  3. 三级级联查询省份名称和编码(保证名称不重复)的SQL语句

    三级级联查询省份名称和编码(保证名称不重复)的SQL语句 1.省份.地市和县级数据库表 2.SQL语句 SELECT DISTINCT t.`province_name`,t.`province_co ...

  4. 关于tween.js测试介绍

    <!DOCTYPE html><html> <head> <meta charset="UTF-8"> <title>t ...

  5. ssm框架之将数据库的数据导入导出为excel文件

    在这里首先我要将自己遇到的各种问题,以及需求记录下来,做一个备忘,便于以后查看: 需求:主要实现两个功能,将oracle数据库里的数据导出为excel,同时需要将excel表格的数据导入到数据库 环境 ...

  6. 【Luogu3804】【模板】后缀自动机(后缀自动机)

    [Luogu3804][模板]后缀自动机(后缀自动机) 题面 洛谷 题解 一个串的出现次数等于\(right/endpos\)集合的大小 而这个集合的大小等于所有\(parent\)树上儿子的大小 这 ...

  7. WC2001 高性能计算机

    cogs网址 这道题DP 设g[0/1][i][a][b]表示第i个机子做了a个A,b个B,0/1表示当前为A/B的最小代价 N^4转移 设f[i][a][b]表示前i个机子做了a个A,b个B的最小答 ...

  8. github远程仓库初始化配置

    github远程仓库的提交一般是通过shell进行,windows下有Git Bash工具(https://git-for-windows.github.io/) 由于本地Git仓库和GitHub仓库 ...

  9. 17.HTML

    HTML简介 htyper text markup language  即超文本标记语言. 超文本: 就是指页面内可以包含图片.链接,甚至音乐.程序等非文字元素. 标准模板 <!DOCTYPE ...

  10. 如何彻底关闭windows update

    对于我个人来说,我并不喜欢更新windows,打补丁对于我来说是一件没必要的事情,所以每次我装完系统之后的第一件事情就是在联网之前关闭windows更新,我通常是进去服务里面关闭,方法是win+R,然 ...