CentOS下实现Flask + Virtualenv + uWSGI + Nginx部署
一、项目简介
在本文中,将一步一步搭建一个简单的Flask + Virtualenv + uWSGI + Nginx 架构的Web服务,可以作为新手的学习也可作为记录备忘。
如果你安装好了环境并有一定基础可以直接从第五节开始部署。
项目中只是演示了浏览器访问地址,获得文本返回的过程,本人尽量把配置解释的清晰。基于搭建好的架构,后续可以将业务层(Python)进行扩展,本文不做研究 ,比如:
1、扩展业务代码:实现json、静态资源等等的请求响应。
2、基于业务的数据库查询和部署。
3、服务器端的部署完备(优化):域名、缓存、负载均衡、安全、备份、防火墙、异步IO等。
4、项目代码管理。
二、框架介绍
1、Virtualenv
- virtualenv可以创建新的Python环境,独立的虚拟环境之间互不干扰,在有些场景下非常有用,例如:
(1)同时拥有两个python项目,一个是python2.7的,另一个是python3的,可以创建两个虚拟环境。
(2)同时拥有两个python项目,都依赖一个module(模块)的不同版本,可以创建两个不同的虚拟环境,分别安装这个module的不同版本。
(3)同时拥有两个python项目,各自依赖不同的module,可以在两个不同的虚拟环境里安装模块并开发项目。
- virtualenvwrapper工具:在virtualenv的基础上提供了一些更方便的命令,本文只介绍通过此工具管理虚拟环境。
2、Flask
Flask是一个Python的轻量级web框架,是基于Python开发并且依赖jinja2模板(模板语言)和 Werkzeug WSGI(WSGI工具集)服务的一个微型框架,对于Werkzeug本质是Socket服务端,其用于接收http请求并对请求进行预处理,然后触发Flask框架,开发人员基于Flask框架提供的功能对请求进行相应的处理,并返回给用户,如果要返回给用户复杂的内容时,需要借助jinja2模板来实现对模板的处理,即:将模板和数据进行渲染,将渲染后的字符串返回给用户浏览器。
Werkzeug的补充:Werkzeug是WSGI工具包,他可以作为一个Web框架的底层库。它不是一个web服务器,也不是一个web框架,而是一个工具包,官方的介绍说是一个 WSGI 工具包,它可以作为一个 Web 框架的底层库,因为它封装好了很多 Web 框架的东西( python web WSGI 开发相关的功能),例如 :
- 路由处理:如何根据请求 URL 找到对应的视图函数
- request 和 response 封装: 提供更好的方式处理request和生成response对象
- 自带的 WSGI server: 测试环境运行WSGI应用
3、uWSGI
(1)WSGI
全称 Web Server Gateway Interface ,是为 Python 语言定义的 Web 服务器和 Web 应用程序或框架之间的一种简单而通用的接口(协议)。WSGI是Web 服务器(uWSGI)与 Web 应用程序或应用框架(Flask,Django)之间的一种低级别的接口。可以参考阅读 PEP3333。
WSGI 分为两个部分
- Server/Gateway: 即是HTTP Server, 负责从客户端(Nginx、apache、IIS)接收请求,将 request 转发给 application, 并将 application(可能是个Flask应用) 返回的response 返回给客户端。
- Application/Framework: 一个python web 应用或 web 框架接收由 server 转发的request,处理请求,并将处理结果返回给 server。
(2)uWSGI
uWSGI是一个Web服务器,C语言编写,它实现了WSGI协议、uwsgi、http等协议。它要做的就是把HTTP协议转化成语言支持的网络协议。比如把HTTP协议转化成WSGI协议,让Python可以直接使用。
(3)uwsgi
与WSGI一样,是uWSGI服务器的独占通信协议,用于定义传输信息的类型(type of information)。每一个uwsgi packet前4byte为传输信息类型的描述。
4、Nginx
Nginx ("engine x") 是一个高性能的HTTP和反向代理服务器,也是一个IMAP/POP3/SMTP服务器 。可以作为一个HTTP服务器进行网站的发布处理,另外nginx可以作为反向代理进行负载均衡的实现。因它的稳定性、丰富的功能集、示例配置文件和低系统资源的消耗而闻名。
三、访问过程
1、描述一:web客户端->web服务器->业务代码的访问过程 (通过协议通信)
2、描述二:不同用户场景->服务器->不同应用的访问过程(简单架构,不包括复杂的部署,缓存等)
(1) 部署多个APP,采用单个Nginx,多个uwsgi+Flask,每个uWSGI监听不同端口。(标准)
(2) 部署多个APP,采用单个Nginx,单个uWSGI,多个Flask路由,Nginx和uwsg通过一个端口通信。(不好)
注意:这种模式不是标准方式,“转发”任务应该是由Nginx实现,通过多个端口和uWSGI通信,每个端口对应一个应用,如同描述一。这里靠路由区分,是不好的。
四、环境准备
1、基本环境
(1)阿里ECS服务器 CentOS7.4 ,如需用户密钥部署可以参考https://www.cnblogs.com/cleven/p/10899171.html
(2)Python环境:Python3.7,pip ,安装过程省略。
2、虚拟环境:
(1)安装
sudo pip3 install virtualenv sudo pip3 install virtualenvwrapper
(2)创建虚拟环境
mkvirtualenv -p python3 env1
-p 后面的参数指定了python3(也有可能要换成python3.2/python3.4,具体要看你系统里面/use/bin/里面的文件是什么名字),如果去掉这个参数,就会使用系统默认的python。最后一个参数env1是创建的这个环境的名字。
(3)管理虚拟环境
deactivate # 退出当前虚拟环境
workon env1 # 使用虚拟环境env1
rmvirtualenv env1 # 删除某个虚拟环境
lsvirtualenv # 列出所有虚拟环境
cdvirtualenv # 进入虚拟环境存储目录
(4)在服务器上同步本地的虚拟环境包
在开发机器上执行下面这个命令,来列出所有的包并保存到packages.txt,其中-l参数是只列出当前虚拟环境的包:
pip3 freeze -l > packages.txt
然后在部署到生产环境的时候,把packages.txt也复制到每个机器,并在每个机器上执行:
pip3 install -r packages.txt
(5)注意点
安装完虚拟环境,重启shell后,workon等命令就无法使用了,这是因为没有添加环境变量:
export WORKON_HOME=/home/.virtualenv #虚拟环境所放置的目录,可以自行指定 source /usr/local/bin/virtualenvwrapper.sh
配置完上面,使用 source ~/.bashrc 命令就可以了
3、web环境
(1)Flask ,虚拟环境下安装
pip3 install flask
检验一下Flask和虚拟环境的安装情况:
<1> 创建项目目录
mkdir webtest
<2> 进入虚拟环境
workon env1
<3> 进入webtest,创建hello.py
from flask import Flask app = Flask(__name__) @app.route("/app/flask/") def hello_flask(): return "Hello Flask!" if __name__ == "__main__": #Flask 开启监听所有地址的8888端口 app.run(host='0.0.0.0', port=8888)
<4> 启动服务,并测试。 Flask是有内置web server的(一般只是测试时使用,不安全,可控性差,线上还是使用uWSGI部署)
python3 hello.py
在浏览器访问 [服务器的ip地址]:8888/app/flask/ ,浏览器中如果显示“Hello Flask”,则Flask配置正常。
(2)uWSGI,虚拟环境下安装
pip3 install uwsgi
(3)Nginx:不用在虚拟环境下安装
sudo yum install nginx
五、部署步骤
1、创建uWSGI项目目录
在/home/project/mysite目录下创建如下结构
2、编写run.py 启动脚本
from flask import Flask app = Flask(__name__) @app.route("/app/uwsgi/") def hello_flask(): return "Hello uWSGI!"
3、配置uWSGI:
(1)进入项目目录,编辑uwsgi.ini
vim uwsgi.ini
(2)下面给出配置文件的详细说明(有些字段注释掉了,毕竟我们是简单的搭建uWSGI服务)
[uwsgi] chdir=/home/project/mysite/ # 项目目录 home=/home/zhangqi/.virtualenvs/env1 # 虚拟环境的路径 wsgi-file=%(chdir)/run.py # 项目的启动脚本文件路径
#module=run # 项目的启动脚本名字,不能是路径,和wsgi-file功能类似
callable=app # 程序内启用的application变量名,一般而言都是app=Flask(__name__),所以这里是app master=true # 启用主进程 processes=2 # worker进程个数 threads=2 # 每个进程的线程数 procname-prefix-spaced=mysite # uwsgi的进程名称前缀 ,使用 ps -ef | grep mysite查看
#############———————— 注释掉的一些配置 ————————##############
#chmod-socket=666 # socket文件的访问权限(socket字段配置的是文件的情况) #logfile-chmod=644 #log权限 #uid=zhangqi # 启动uwsgi的用户名 #gid=zhangqi # 启动uwsgi的用户组 #py-autoreload=1 # py文件修改,自动加载 #vacuum=true # 退出uwsgi是否清理中间文件,包含pid、sock和status文件 #harakiri=30 # 设置自中断时间 #post-buffering=4096 # 设置缓冲 #touch-reload=%(chdir) # 动态监控文件变化
###########################################################
############———————— 设置uWSGI的socket连接 ————————############ # # 方式一: socket文件,配置nginx时候使用。socket文件需要使用socket函数编写。本文中没有使用此方式 #socket=%(chdir)/uwsgi/uwsgi.sock # # 方式二:绑定地址+端口 socket=:8001 # # 方式三:监听http端口,测试时候使用。如果不使用Nginx,浏览器是http协议,无法使用socket直接通信 #http=0.0.0.0:8001 # ################################################################ ############———————— 设置uWSGI的管理文件 ————————############ # # status文件,可以查看uwsgi的运行状态 # 命令:uwsgi --connect-and-read uwsgi/uwsgi.status stats=%(chdir)/uwsgi/uwsgi.status # # pid文件,通过该文件可以控制uwsgi的重启和停止 # 命令:uwsgi --reload uwsgi/uwsgi.pid # 命令:uwsgi --stop uwsgi/uwsgi.pid pidfile=%(chdir)/uwsgi/uwsgi.pid # # 日志文件,通过该文件查看uwsgi的日志 daemonize=%(chdir)/uwsgi/uwsgi.log # #############################################################
(3)启动uWSGI,注意是虚拟环境。
uwsgi --ini uwsgi.ini
(4)调试uWSGI
- 查看uWSGI进程
ps -ef | grep mysite #或者 netstat -antp |grep 8001
显示如下,uWSGI启动正常
- 查看uWSGI状态。
会以json格式显示出完整内容,包括每个总的状态,每个work是状态,响应时间等,非常全面。
uwsgi --connect-and-read uwsgi/uwsgi.status
也有一些开源的监控工具可以使用:uwsgitop
# pip3 install uwsgitop # uwsgitop uwsgi/uwsgi.status
- 控制uWSGI服务器
uwsgi --ini uwsgi.ini # 启动 uwsgi --reload uwsgi.pid # 重启 uwsgi --stop uwsgi.pid # 关闭
4、配置Nginx:
(1)编辑配置文件 nginx.conf(主):
vim /etc/nginx/nginx.conf
内容如下
########### 每个指令必须有分号结束 ################# user nginx; #配置用户或者组,默认为nobody nobody worker_processes auto; #允许生成的进程数,默认为1 pid /run/nginx.pid; #指定nginx进程运行文件存放地址 include /usr/share/nginx/modules/*.conf; #加载动态模块 #制定日志路径和级别。这个设置可以放入全局块,http块,server块 #级别依次为:debug|info|notice|warn|error|crit|alert|emerg error_log /var/log/nginx/error.log; events { worker_connections 1024; #最大连接数,默认为512 #use epoll; #事件驱动模型,select|poll|kqueue|epoll|resig|/dev/poll|eventport #accept_mutex on; #设置网路连接序列化,防止惊群现象发生,默认为on #multi_accept on; #设置一个进程是否同时接受多个网络连接,默认为off } http { default_type application/octet-stream; #默认文件类型,默认为text/plain keepalive_timeout 65;#连接超时时间,默认为75s,可以在http,server,location块配置 include /etc/nginx/conf.d/*.conf; #虚拟主机配置,引入不同server的配置(uWSGI) include /etc/nginx/mime.types; #引入文件类型 #sendfile_max_chunk 100k; #每个进程每次调用传输数量最大值,默认为0(无上限) ################## 日志 ######################### #自定义日志格式 log_format main '$remote_addr - $remote_user [$time_local] "$request" ' '$status $body_bytes_sent "$http_referer" ' '"$http_user_agent" "$http_x_forwarded_for"'; #接入日志设置 access_log /var/log/nginx/access.log main; #access_log off; #取消服务日志 # #################################################### #################### SSL证书加密 ################### # #ssl_protocols TLSv1 TLSv1.1 TLSv1.2 SSLv3;#启动特定的加密协议 #ssl_prefer_server_ciphers on;#设置协商加密算法时,优先使用服务端的加密套件,而不是客户端浏览器 # #################################################### #################### 缓存性能优化 ################### # # 1、open_file_cache :配置可以存储的缓存 # max设置缓存中的最大元素数; 在缓存溢出时,删除最近最少使用(LRU)的元素; # inactive是指经过多长时间文件没被请求后删除缓存。 # inactive设置20s,等到至少20s不访问这个文件,相应缓存的这个文件的更改信息才会被删除。 # 例: # open_file_cache max=1000 inactive=20s; # # # 2、open_file_cache_valid:多长时间检查一次缓存的有效信息。 # 设置为30s,也就是说即使一直访问这个文件,30s后会检查此文件的更改信息是否变化,发现变化就更新 # 例: # open_file_cache_valid 30s; # # # 3、open_file_cache_min_uses : # 在上面open_file_cache 的 inactive时间内文件的最少使用次数。如果超过这个数字,文件更改信息一直是在缓存中打开的。 # 例: # open_file_cache_min_uses 2; # # 4、文件错误是否也同样缓存 # open_file_cache_errors on; # #################################################### ################# 数据传输优化 ################### # # 允许sendfile( )方式传输文件,提高传输性能,默认为off # 可以在http块,server块,location块配置 sendfile on; # # 设置调用tcp_cork方法,数据包不会马上传送出去,等到数据包最大时,一次性的传输出去,这样有助于解决网络堵塞。默认on, # tcp_nopush on; # # 打开tcp_nodelay ,禁用Nagle的缓冲算法,并在数据可用时立即发送,优化传输效率,默认on # tcp_nodelay on; # # #################################################### ################### 散列表大小 ######################## # Nginx使用散列表来存储MIME type与文件扩展名。 # types_hash_bucket_size 设置了每个散列表占用的内存大小,其影响散列表的冲突率。 # 值越大,就会消耗更多的内存,但散列key的冲突率会降低,检索速度就更快。 # 值越小,消耗的内存就越小,但散列key的冲突率可能上升。 # 默认1024。 types_hash_max_size 2048; #################################################### ################# Nginx负载均衡 (本文中没有使用)################ # 负载均衡算法: # 1、热备 # 2、轮询 # 3、加权轮询 # 4、ip_hash(相同的客户端ip请求相同的服务器) # # 使用服务器列表: # 1、定义服务器列表 mysvr # 2、server的location模块中将请求转向mysvr 定义的服务器列表 # # server { #服务器访问信息的配置 # ..... # location ~*^.+$ { #访问路由的配置 # proxy_pass http://mysvr; #请求转向mysvr 定义的服务器列表 # } # # # 写法举例: # 1、热备 # upstream mysvr { //服务器列表 # server 127.0.0.1:7878; # server 192.168.10.121:3333 backup; #热备 # } # # 2、轮询 # upstream mysvr { # server 127.0.0.1:7878; # server 192.168.10.121:3333; # } # # 3、加权(参数) # upstream mysvr { # server 127.0.0.1:7878 weight=2 max_fails=2 fail_timeout=2; # server 192.168.10.121:3333 weight=1 max_fails=2 fail_timeout=1; # } # # 4、ip_hash # upstream mysvr { # server 127.0.0.1:7878; # server 192.168.10.121:3333; # ip_hash; # } # # nginx负载调优总结 :https://www.jianshu.com/p/4fa08f2a04ed # ################################################# }
(2)Flask站点对应的server配置:
上面的主配置中,有此句“include /etc/nginx/conf.d/*.conf;” ,我们将Flask对应的server模块,单独写在一个文件里(当然也可以直接写在上面的主配置中),一个server中又通过多个location指向不同应用。
理论上,一个Nginx对应多个站点,一个站点对应一个server,一个server又可以对应多个location(应用)。
- 进入conf.d文件夹,并创建flask.conf
$ sodu touch /etc/nginx/conf.d/flask.conf
- 内容如下
server {
listen 81; #监听外部端口(浏览器)
server_name 39.xxx.xxx.xxx; #服务器地址
charset utf-8;
client_max_body_size 5M;
root /usr/share/nginx/html; #默认根目录
include /etc/nginx/default.d/*.conf;
#配置路由
location / { #对根目录的访问都匹配,此处没有做正则匹配讲解
include uwsgi_params; #引入uwsgi
uwsgi_pass localhost:8001; #通过localhost:8001端口和uWSGI通信(这是绑定的IP和端口,对应上文中uWSGI.ini里面的配置)
# uwsgi_pass unix:/home/project/mysite/uwsgi/uwsgi.sock; #本文没有使用socket文件进行演示。
}
location /static { #静态文件,直接访问路径,不用进入uWSGI进行处理
alias /home/project/mysite/static/;
}
error_page 404 /404.html;
location = /40x.html {
#可以更改错误页面路径
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
#可以更改错误页面路径
}
}
(2)控制Nginx:
- 启动
$nginx
或者
$systemctl start nginx
- 重启
nginx -s reload #热启动,配置文件重装载
kill -HUP 主进程号或进程号文件路径 #平滑重启
systemctl restart nginx
- 停止
system stop nginx nginx -s stop #快速关闭 nginx -s quit #正常关闭
或者通过进程控制
ps -ef | grep nginx
kill -QUIT 主进程号 #从容停止
kill -TERM 主进程号 #快速停止
kill -9 主进程号 #强制停止
(3)查看Nginx状态:
- ps -ef | grep nginx
- systemctl status nginx
5、测试服务
(1)方法一:
浏览器输入 39.xxx.xxx.xxx:81/app/uwsgi/ ,如果页面显示 “Hello uWSGI!” ,大功告成!
(2)方法二:
curl http://39.xxx.xxx.xxx:81/app/uwsgi/ ,终端输出 “Hello uWSGI!”,OK。
六、收尾
整个项目下来并不是很复杂,主要是熟悉配置流程,并且根据搭好的基础框架,升级业务功能。
在调试过程中如果出现问题,可以查看各个日志,根据 上文: 三、访问过程 进行逐步排查解决。
最后,后端领域还有很多东西需要学习,如有错误之处还望大家多多指教,本文多以做技术交流和配置备忘之用。