开发时我们是通过Flask内置得服务器进行调试,但在生成环境我们不应再使用Flask内置服务器而应该用更稳定的服务器接受客户端的请求,Flask应用处理业务。

Python应用的线上部署离不开WSGI

Web服务器网关接口(Python Web Server Gateway Interface,缩写为WSGI)是为Python语言定义的Web服务器和Web应用程序或框架之间的一种简单而通用的接口。

简单说它就是Web服务器和Web应用的中间件。

它的作用是方便Web应用的移植。因为Web应用的框架选择将限制Web服务器的选择。有了WSGI只要Web应用和Web服务器准守WSGI接口规范,就可以随意组合Web应用和Web服务器了。

现有已经有很多成熟的WSGI容器(Gunicorn,Tornado,Gevent),它提供了Web服务器并实现了WSGI接口,它们可以很方便的于Flask应用对接上。

uWSGI

uWSGI是一个Web应用服务器,它支持WSGI协议同时也支持自有的uWSGI协议。
它的目标为开发服务提供全栈的构建工具。它能作为应用服务器(支持各种编程语言:Python,perl,ruby,PHP等),代理服务器,进程管理器和监视器。这些都使用统一的API和配置风格。
在Python应用中一般将它作为应用服务器配合Nginx使用。

安装

构建uWSGI,你需要Python和一个C编译器。

1
2
3
4
$ yum update
$ yum groupinstall "Development Tools"
$ yum install python
$ yum install python-devel

依赖的环境安装好后,就可以通过pip安装uwsgi了。

1
$ pip install uwsgi

第一个WSGI应用

1
2
3
def application(env, start_response):
start_response('200 ok', [('Content-type','text/html')])
return [b"Hellow World"]

将其保存为 foobar.py。

foorbar.py是个符合WSGI接口规范的应用(入口函数默认是 application)。当uWSGI加载foobar.py文件时会自动去查找application函数并在必要时调用它。

1
$ uwsgi --http :9090 --wsgi-file foorbar.py

注意: 当你有一个前端web服务器(Nginx)时,不要使用--http,使用--http-socket

--http参数指定HTTP监听地址和端口,--wsgi-file参数指定WSGI应用的程序入口。

默认情况下uWSGI启动一个单一的进程和一个单一的线程。你可以通过--processes--threads开启多个进程和线程。

1
$ uwsgi --http :9090 --wsgi-file foobar.py --master --processes 4 --threads 2 --stats 127.0.0.1:9191

上面例子uWSGI启动了4个应用进程,每个进程有2个线程。并启动了一个 --master主进程,用于监控其他进程状态,如果进程死了将它重启。通过--stats 127.0.0.1:9191你听过浏览器访问127.0.0.1:9191获取JSON格式的应用运行信息。
uWSGI提供了工具uwsgitop 用于监控应用运行状态,可以通过pip安装它们。

配置文件

uWSGI支持通过配置启动服务器,这样就不需要在命令行输入一串指令。 (uWSGI配置上有上百种
uWSGI支持多中配置文件格式:ini,xml,yaml,json

1
2
3
4
5
6
7
[uwsgi]
http=:9090
wsgi-file=server.py
master=true
processes=4
threads=2
stats=127.0.0.1:9191

启动服务:

1
$ uwsgi myapp.ini

uWSGI中常用的配置项有:

  • chdir =/opt/myapp #指定项目目录
  • socket =xxx #指定uwsgi的客户端将要连接的socket的路径(使用UNIX socket的情况)或者地址(使用网络地址的情况)
  • callable = xxx #指定WSGI 加载应用那个变量调用。默认是:application
  • virtualenv = /xxx/xxx #指定虚拟环境变量。其他指定字段还有:home,venv,pyhome
  • uid = xxx #指定uWSGI服务器运行时的用户id
  • gid = xxx #指定 uWSGI服务器运行时的用户组id
  • procname-prefix-spaced = xxx #指定工作进程名称的前缀
  • daemonize =%(chdir)/xxx.log # 进程在后台运行,并将日志打印到指定文件
  • logfile-chmod =644 # 指定日志文件的权限

例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
[uwsgi]
chdir=/home/git/www/myapp # 指定项目目录
home=/home/git/www/myapp/.env # 指定python虚拟环境
wsgi-file=myqpp.py # 指定加载的WSGI文件
callable=app # 指定uWSGI加载的模块中哪个变量将被调用
master=true # 启动主线程
processes=4 # 设置工作进程的数量
threads=2 # 设置每个工作进程的线程数
socket=127.0.0.1:8888 # 指定socket地址
vacuum=true # 当服务器退出时自动删除unix socket文件和pid文件
logfile-chmod=644 # 指定日志文件的权限
daemonize=%(chdir)/myapp.log # 进程在后台运行,并将日志打印到指定文件
pidfile=%(chdir)/myqpp.pid # 在失去权限前,将主进程pid写到指定的文件
stats=%(chdir)/uwsgi.status # uwsgi 运行状态文件
uid=git # uWSGI服务器运行时的用户id
gid=git # uWSGI服务器运行时的用户组id
procname-prefix-spaced=myapp # 指定工作进程名称的前缀

uwsgi操作命令:

1
2
3
$ uwsgi uwsgi.ini # 启动uwsgi服务
$ uwsgi --stop myqpp.pid # 停止uwsgi服务
$ uwsgi --connect-and-read uwsgi.status # 查看uwsgi服务状态一天

Nginx配置

在配置Nginx前需要将 uWSGI配置文件http换为socket

1
2
3
4
5
6
7
[uwsgi]
socket=127.0.0.1:3031
wsgi-file=myapp.py
master=true
processes=4
threads=2
stats=127.0.0.1:9191

Nginx配置如下:

1
2
3
4
location / {
include uwsgi_params;
uwsgi_pass 127.0.0.1:3031;
}

通过配置socket=/tmp/uwsgi.sock,启动uWSGI服务器后,它会自动创建一个”/tmp/uwsgi.sock”文件。在Nginx配置中地址指定这文件。这样我们就不用写死端口。

1
2
3
4
location /myapp {
include uwsgi_params;
uwsgi_pass unix:/tmp/uwsgi.sock;
}

运行Flask应用

将Flask应用集成进去很简单,只需要在uWSGI配置文件callable指向Flask启动变量。

1
2
3
4
5
6
from flask import Flask
app = Flask(__name__)

@app.route('/')
def index():
return '<h1>Hello World</h1>'
1
2
3
[uwsgi]
...
callable=app

setuptools

setuptools是python应用的打包和分发工具。通过它你可以将你的应用打包成egg包或tar.gz包。
将应用打成压缩包后你就可以方便发布和部署。
注意: 如果你的Python2 >= 2.7.9或Python3 >= 3.4从python.org安装成功的,则你已经在系统上安装了 pipsetuptools

打包Flask应用

setuptools打包文件名为:setup.py。所有打包规则都写在里面。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
from setuptools import setup,find_packages
#find_packages()自动索引可以引入的Python包 可以传递参数指定搜索包的路径。
#用find_packages(‘src’)就表明只在”src”子目录下搜索所有的Python包

setup(
name="MyApp", # 应用名
version='1.0', # 版本号
packages=find_packages(exclude=["*.tests", "*.tests.*", "tests.*", "tests"]), # 引入安装包并排除测试包
include_package_data= True, # 启用清单文件MANIFEST.in 这个文件一般指明非python代码但应用会使用的静态文件
zip_safe=False, #不将应用以zip压缩
exclude_package_date={'':['.gitignore']}, #排除项目上指定的文件
install_requires=[ # 依赖列表
'Flask>=0.10',
'Flask-SQLAlchemy>=1.5,<=2.1'
],

author = "Billy He",
author_email = "billy@bjhee.com",
description = "This is a sample package",
license = "MIT",
keywords = "hello world example",
url = "http://example.com/HelloWorld/", # 项目主页
long_description=__doc__, # 从代码中获取文档注释
)

MANIFEST.in 文件:

1
2
recursive-include yourapplication/templates *
recursive-include yourapplication/static *

安装执行文件

创建egg包

1
$ python setup.py bdist_egg

该命令将在当前目录下dist 创建一个egg文件。文件名格式是:应用-版本号-Python版本.egg
注意: python的egg 是一个包含所有包数据的文件包,类式Java的jar文件,是个工程打包文件,便于安装部署。

创建tar.gz包

1
$ python setup.py sdist --formats=gztar

创建的文件类型是tar.gz

安装应用

1
$ python setup.py install

将应用安装到当前Python环境的 site-packages目录下。这样其他应该就可以像导入标准库一样导入它。

Fabric3

Fabric是一个Python写的SSH任务管理库。它可以实现Python应用的自动打包发布部署。
Fabric3是最新的python(2.7或3.4+)版本。它和Fabric无法共存,但保存对Python2的兼容。

安装

1
2
$ pip uninstall Fabric
$ pip install Fabric3

Hello world

Fabric默认会去查找目录下 fabfile.py 文件,如果你的配置文件名不是这可以通过 -f 传递给 Fabric。

1
2
def hello():
print('Hello world!')
1
2
3
4
$ fab hello
Hello world!

Done.

你也可以向放传递参数:

1
2
def hello(name="world"):
print("Hello %s!" % name)
1
2
3
4
$ fab hello:name=Fynn
Hello Fynn!

Done.

环境变量

Fabric中的env变量是个Python字典,用于设定Fabric中配置值。常用来设置远程服务器域名和SSH账号。

  • env.host
    • Default:None
    • 设定一个域名的字符串
  • env.hosts
    • Default:[]
    • 设定一组域名的字符串(域名部分)
  • env.password
    • Default:None
    • 设定SSH账户密码
  • env.passwords
    • Default:{}
    • 设定一组远程服务器SSH账户密码。key是完整的域名(env.host_string),value是密码
  • env.port
    • Default: None
    • 设定远程服务器端口

常用API

  • local: 执行本地命令,例如:local('ls -la')
  • run: 执行远程命令,例如: run('ls -la')
  • lcd: 切换本地目录
  • cd: 切换远程目录
  • put:上传本地文件到远程主机
  • get: 从远程主机下载文件到本地
  • reboot: 重启远程主机。reboot()
  • sudo: sudo执行远程命令
  • @task : 函数装饰器,装饰的函数fab才能调用
  • @runs_once : 函数装饰器,装饰的函数只执行一次

例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
from fabric.api import *

# 远程服务器登陆使用的用户名
env.user = 'appuser'
# 需要进行操作的服务器地址
env.hosts = ['server1.example.com', 'server2.example.com']

def pack():
# 以 tar 归档的方式创建一个新的代码分发
local('python setup.py sdist --formats=gztar', capture=False)

def deploy():
# 之处发布产品的名称和版本
dist = local('python setup.py --fullname', capture=True).strip()
# 将代码归档上传到服务器当中的临时文件夹内
put('dist/%s.tar.gz' % dist, '/tmp/yourapplication.tar.gz')
# 创建一个文件夹,进入这个文件夹,然后将我们的归档解压到那里
run('mkdir /tmp/yourapplication')
with cd('/tmp/yourapplication'):
run('tar xzf /tmp/yourapplication.tar.gz')
# 使用我们虚拟环境下的 Python 解释器安装我们的包
run('/var/www/yourapplication/env/bin/python setup.py install')
# 现在我们的代码已经部署成功了,可以删除这个文件夹了
run('rm -rf /tmp/yourapplication /tmp/yourapplication.tar.gz')
# 最终生成 .wsgi 文件,以便于 mod_wsgi 重新加载应用程序
run('touch /var/www/yourapplication.wsgi')

参考

http://docs.fabfile.org/en/1.14/index.html
https://uwsgi-docs.readthedocs.io/en/latest/index.html#
https://henulwj.github.io/2016/04/20/uwsgi-common-use-parameters/
http://yansu.org/2013/06/07/learn-python-setuptools-in-detail.html