Debian下使用Nginx+uWSGI部署Flask应用

这段时间准备做网站的应用。没有做过这方面的内容,选型比较困难。最终选了较为简单的Flask框架作为起点。

Flask框架有本OReilly动物系列的《Flask Web开发》俗称 狗书。对于未接触过网站开发的人,想叩开网络开发大门的人呢来说值得一看。涵盖的内容和基础都有了,后续想要再升入就比较容易。

部署环境:
1、Debian 7.5 32位
2、Nginx 1.6.2(系统自带)
3、Python 3.5.1 (Stable)
4、uWSGI 2.0.13.1(Stable/LTS)

Server IP:192.168.1.7 (内部测试服务器地址,后面会直接用这个地址测试)
部署目录: /home/abc/blog/

系统自带的是Python2的版本,这里使用的是Py3。不影响部署。

安装Python3版本可以参考《Debian安装 python 3.5.1》

部署的程序是狗书上的Blog实例,使用SQLite作为数据库。

如想使用MySQL作为数据库,参考《python3的flask工程数据库从SQLite迁移到MySQL》

一、安装 Nginx

直接使用系统提供的包比较容易。

$ sudo apt-get install nginx

安装最新版本,可以去官网下载最新稳定版本自己编译。

参照: 编译部署NGINX

二、安装uWSGI

直接使用pip进行安装比较便捷

pip install uwsgi

提示: 如果安装完成后,敲uwsgi命令找不到,可能是uwsgi不在搜索目录内。找到uwsgi 把命令链接到 /use/bin 下,方便后面使用。

基本测试

创建一个WSGI application程序test.py

# test.py
def application(env, start_response):
    start_response('200 OK', [('Content-Type','text/html')])
    return [b"Hello World"] # python3
    #return ["Hello World"] # python2

创建这个文件主要是为后面测试uWSGI是否正常工作,方便排障。

运行 uWSGI:

uwsgi --http :8000 --wsgi-file test.py

在本地浏览器中输入 http://192.168.1.7:8000 ,看到hello world 恭喜你,uWSGI正常运作。

三、配置Nginx

uWSGI虽然很高效,可以直接访问。不过Nginx、apache做前端web服务器更专业。这些就交给专业的Nginx,uWSGI就负责内部路由就可以了。

创建一个flaskblog_nginx.conf配置

# flask blog configuration

upstream flaskblog {
    # server unix:///tmp/flaskblog.sock; 
    server 127.0.0.1:3031;
}
server {
    listen 80;
    # server_name _;
    location / {
        uwsgi_pass     flaskblog;
        include     /home/abc/blog/uwsgi_params;
    }
}

上面配置中有个 uwsgi_params的文件,是Nginx和uWSGI交互的配置参数。可以直接从Nginx的安装目录找到他,复制到你的工程配置目录中。

找不到可以搜索一下

$ sudo find / -name uwsgi_params

配置文件连接到Nginx的站点启动目录中

$ sudo ln -s /home/abc/blog/faskblog_nginx.conf /etc/nginx/sites-enabled/

注: 站点启动目录是在nginx.conf配置文件的http段配置的。

重启Nignx

$ sudo /etc/init.d/nginx restart

启动uWSGI的test.py

$ uwsgi --socket 127.0.0.1:3031 --wsgi-file test.py

在本地浏览器中访问 http://192.168.1.7 ,看到hello world。恭喜!Nginx+uWSGI正常运作。

如果无法正常访问。如出现502 Bad Gateway,也不用急排查一下问题。

问题排查:
1、Nginx中的socket端口配置和运行的uWSGI的端口是否一致
2、端口是否被占用
3、Nginx的站点运行目录是否有问题,看看nginx.conf文件中http段有没有包含。没有包含站点运行目录的在最后加一行。如:include /etc/nginx/sites-enabled/*;
4、uwsgi_params配置文件确定在配置的目录中存在

四、启动站点

app的启动文件是manage.py,根据实际情况改为自己的app启动文件。

$ uwsgi --socket 127.0.0.1:3031 --wsgi-file manage.py --callable app

启动Flask和启动示例test.py多了一个参数 --callable app 是启动Flask的app。

现在就可以使用本地浏览器访问你的网站。

问题:如果无法正常访问,但test.py能正常访问那就可能app有些问题。

错误:Internal Server Error 可能是你的环境不对

上述环境错误,可能是你python的运行没有包。如你用的虚拟环境,运行时没设置。

加运行参数 --virtualenv <path>。这个参数可以告诉uWSGI使用python虚拟环境的目录配置。

uwsgi --socket 127.0.0.1:3031 --wsgi-file manage.py --callable app --virtualenv /home/abc/blog/venv/

再测试一下,看看是否OK!

没OK?创建简单排查工程

创建一个简单的排查问题工程文件testapp.py,里面使用flask框架创建的app。

from flask import Flask
app = Flask(__name__)

@app.route('/')
def index():
    return "<h1>I am app test</h1>"

测试一下,最简单的工程能不能执行。

狗书配置部分

狗书的blog须要配置一些环境变量才能使用。mail章中会用到的邮箱和密码。

提示:测试时可以把环境变量放在当前用户配置文件~/.bash_profile中。免得重新启动系统丢失,省去重新输入的麻烦。

export MAIL_USERNAME='fpack@163.com'
export MAIL_PASSWORD='xxxxxxxxxx'
export FLASKY_ADMIN='fpack@163.com'
export FLASK_CONFIG='production'

export DATABASE_URL='sqlite:////var/www/data.sqlite'

把环境设置为生成模式production。 生成模式的数据库放到/var/www目录下。确保www-data用户能有权限读写。

配置完成使用部署命令,生成生产环境数据库。(在虚拟环境中运行)

(venv)$ python manage.py deploy

或者可以直接把sqlite数据库文件复制到 /var/www 目录中,给www-data用户权限

$ sudo chown www-data:www-data /var/www/data.sqlite

提示:
www-data --- 这个用户和用户组是用于启动网站的。如果你用其它用户或组,那就给那个用户和组设置权限

五、设置站点自动运行

上面已经实现

client <--> Nginx <--> uWSGI <--> Python(Flask)

把先前的所有参数整理一下,最终放到app执行目录中自动执行。

5.1、创建uWSGI的ini文件

创建一个uwsgi的ini配置文件flaskblog_uwsgi.ini。把先前用命令执行的参数放进来

[uwsgi]
socket = 127.0.0.1:3031
# socket = /tmp/flaskblog.sock 
wsgi-file = manage.py 
callable = app 
processes = 4 
threads = 2 
stats = 127.0.0.1:9191
virtualenv = /home/abc/blog/venv/
chdir = /home/abc/blog
# ... may be needed 
#    666 --- very permissive
#    664 --- more sensible
#    777 --- ^_^
# chmod-socket = 664
vacuum = true

新加参数说明:
virtualenv --- 工程虚拟环境目录
chdir --- 工程路径
vacuum -- 退出时清理环境
processes&threads --- 多搞几个进程和线程,让App快点?

使用 uWSGI运行一下看看效果

$ uwsgi flaskblog_uwsgi.ini

这样看起来清爽多了。

5.2、创建uwsgi启动目录

提示:如果有就直接把这个配置链接过去。

uWSGI自启动,有个霸气的名字Emperor mode (皇帝模式)。其实和Nignx的站点启动目录sites-enabled是一样的性质。

既然是皇帝就须要奴隶,建个奴隶围栏,然后把奴隶关进去。

$ sudo mkdir /etc/uwsgi
$ sudo mkdir /etc/uwsgi/vassals
$ sudo ln -s /home/abc/blog/flaskblog_uwsgi.ini /etc/nginx/sites-enabled/

现在有一个奴隶了,不能让他闲着。用鞭子抽他,让他干活。

$ /usr/bin/uwsgi --emperor /etc/uwsgi/vassals --uid www-data --gid www-data --daemonize /var/log/uwsgi-emperor.log

皇帝要休息,不能整天盯着奴隶干活,叫个监工 www-data,把报告放在 /var/log/uwsgi-emperor.log

现在可以在本地浏览器看一下,奴隶们的活干的漂亮不漂亮了。^_^

5.3、收尾放到启动文件中执行。

最后把刚才的命令加到rc.local启动配置中,放在exit 0前面一行。

$ sudo vim /etc/rc.local

重新启动liunx,

$ sudo reboot

这样整个服务器生产环境就搭建好,已经可以正式上线。

六、改用高效的socket内部“管道”

在先前的配置中 socket = /tmp/flaskblog.sock,有这样一句话。这是liunx提供的一种Unix domain sockets的要比使用端口效率更高。

上面没说是因为可能会遇到权限问题,会比较麻烦所以放在最后。出现问题可以改成原来的socket,不影响网站上线,可以慢慢研究权限。

当uwsgi程序启动时,会自动生成一个flaskblog.sock文件。flaskblog_nginx.conf中配置和flaskblog_uwsgi.ini中保持一致就可以了。

把这个文件放在 /tmp 目录下是保证 www-data用户启动的uwsgi和nginx都能正常读写。

使用ls -l看flaskblog.sock文件的权限是www-data

$ ls /tmp -l
total 0
srwxrwxrwx 1 www-data www-data 0 Jun 16 15:36 flaskblog.sock
srwxr-xr-x 1 abc      abc      0 Jun 16 16:57 testapp.sock

如果Nginx访问testapp.sock这个就会出问题,因为没有权限出现502 Bad Gateway

在Nginx的错误日志/var/log/nginx/error.log 中可以看到没权限的提示信息。

$ sudo cat /var/log/nginx/error.log

...
... connect() to unix:///tmp/testapp.sock failed (13: Permission denied) while connecting to upstream ...
...

这种情况通过加权限参数 --chmod-socket=666 让所有用户都有读写权限。

uwsgi --socket /tmp/testapp.sock --wsgi-file test.py --chmod-socket=666

这样就不会出现502错误,Nginx可以正常访问unix socket。

结束!

祝你能顺利完成网站上线,欢迎多多光临我的小站蘑菇房 moguf.com

代码和配置:
github: https://github.com/cmacro/flaskblog