从零开始自动部署Django项目(三):使用uWSGI emperor管理进程

引言

在上一篇从零开始自动部署Django项目(二):使用Python编写Git Hooks,笔者直接通过Python模拟正常的人肉linux命令来确定python debug server是否在指定端口运行,如果正在运行则先杀掉该进程,在更新了Git仓库之后再人肉启动python debug server。

咦,好像有哪里不对,为什么不直接删掉文件,然后进程不就自动结束了吗?这样子就不用检查端口是否有进程在运行了。

相信大家都试过,在linux下如果先开启python debug server进程,然后把进程的相关文件都删掉,而进程还能正常运行。这是因为linux下的删除(rm)其实是减少对文件的link数目,虽然删除了文件,但是打开的进程还保持着对文件的一个link,因此在执行了rm之后文件并不会被立即删除,只有link数为0的时候才会被删除。

因此,笔者希望在服务器上运行一个进程管理工具监控Git仓库,每当仓库更新的时候,进程管理工具就自动重启python debug server,而Git Hooks只要负责仓库的更新就可以了。

在Python的进程管理工具中,supervisor应该是比较广为人知,而uWSGI emperor的曝光度似乎并不太高,然而基于nginx+uwsgi+django的标配,笔者还是对uWSGI emperor的折腾比较期待的。
uWSGI文档传送门:Quickstart for Python/WSGI applications

准备

  • uWSGI与uwsgi是同一样东西吗?它们有什么区别?

    先给出结论:uWSGI是一个生产用服务器,而uwsgi是一种网络通信协议(在uWSGI文档中也承认了这个名字的选择真的是wrong name choice)。
    Django为开发者提供了一个开发调试用的服务器,只要通过python manage.py runserver就可以在默认的运行端口8000提供服务,但是这个服务器不能在生产环境中使用,需要更为稳定的服务器提供服务,而uWSGI也是Django官方文档中推荐使用的生产服务器。
    至于uwsgi协议则多用于与反向代理服务器的内部信息通信,nginx支持对uwsgi协议的解析,而为什么使用uwsgi协议而不是在内部继续使用http协议进行通信,uWSGI的文档给出以下解释:

    Why not simply use HTTP as the protocol?
    A good question with a simple answer: HTTP parsing is slow, really slow. Why should we do a complex task twice? The web server has already parsed the request! The uwsgi protocol is very simple to parse for a machine, while HTTP is very easy to parse for a human. As soon as humans are being used as servers, we will abandon the uwsgi protocol in favor of the HTTP protocol. All this said, you can use uWSGI via Native HTTP support, FastCGI, ZeroMQ and other protocols as well.

    uWSGI文档传送门:Frequently Asked Questions (FAQ)

  • 折腾环境选择
    笔者开发使用的mac系统:

    uname -a
    Darwin bogon 15.6.0 Darwin Kernel Version 15.6.0: Thu Jun 23 18:25:34 PDT 2016; root:xnu-3248.60.10~1/RELEASE_X86_64 x86_64

    要部署到的centos服务器系统:

    Linux localhost.localdomain 3.10.0-327.28.3.el7.x86_64 #1 SMP Thu Aug 18 19:05:49 UTC 2016 x86_64 x86_64 x86_64 GNU/Linux

配置文件uwsgi.ini

首先新建一个名为uwsgi_test的Django项目:

uwsgi_test
    ├── manage.py
    ├── uwsgi_test
    │   ├── __init__.py
    │   ├── __init__.pyc
    │   ├── settings.py
    │   ├── settings.pyc
    │   ├── urls.py
    │   ├── urls.pyc
    │   ├── wsgi.py
    │   └── wsgi.pyc
    └── uwsgi_test.ini

uwsgi_test.ini配置如下:

[uwsgi]
chdir = /path/to/uwsgi_test
module = uwsgi_test.wsgi
master = true
processes = 4
http = :8080
vaccum = true

当在Mac系统上运行如下的uwsgi的启动命令之后就能访问浏览器的8080端口并看到Django的默认欢迎界面了。

uwsgi  --ini uwsgi_test.ini

在以上的配置中生成了一个主进程和四个进程,以及一个Http路由(Http Router)进程,其中主进程负责当4个进程中有进程die的时候重新生成一个进程,而Http Router进程则负责将请求转发给WSGI Application。

*** Operational MODE: preforking ***
WSGI app 0 (mountpoint='') ready in 0 seconds on interpreter 0x7f9c5ae000d0 pid: 8514 (default app)
*** uWSGI is running in multiple interpreter mode ***
spawned uWSGI master process (pid: 8514)
spawned uWSGI worker 1 (pid: 8515, cores: 1)
spawned uWSGI worker 2 (pid: 8516, cores: 1)
spawned uWSGI worker 3 (pid: 8517, cores: 1)
spawned uWSGI worker 4 (pid: 8518, cores: 1)
spawned uWSGI http 1 (pid: 8519)

配置选项:http,socket与http-socket

  • 当选择http的选项的时候,uWSGI就会启动一个Http服务器(或者叫Http Router)来负责把请求传给WSGI的Application,因此,所有的浏览器都能直接访问由uWSGI通过http选项启动的端口(此时uWSGI使用的是HTTP协议进行通信)。此时,Http Router就相当于一个nginx负责反向代理,然后在内部通过uwsgi协议或其他协议传给处理的进程。
    (uWSGI官方文档对Http选项以及Http Router的一些讲解传送门:Native HTTP support
    (uWSGI官方文档传送门:Things to know (best practices and “issues”) READ IT !!!

  • 当选择socket选项的时候,此时uWSGI就会通过默认的uwsgi协议与外界进行通信了,因此socket选项一般是与nginx反向服务器搭配进行,并且不会启动Http Router进程,如果此时直接通过浏览器访问端口(Http协议),uWSGI是不会进行响应的。其中,官方文档更推荐通过.sock文件进行信息通信而非TCP端口。

  • 当选择http-socket选项的时候,也不会产生Http Router进程,而uWSGI中所有负责处理的进程都会通过Http协议与外界通信。

在centos系统上运行配置文件

当笔者在centos系统上部署相同的项目和配置文件时,uWSGI却报了一个奇怪的错误。

The -s/--socket option is missing and stdin is not a socket.

笔者在上面使用的明明是http选项,为什么会出现socket选项呢?
这是因为不同的linux发行版的包安装工具所安装的uWSGI,有时候会以模块的方式安装,并且在运行的时候默认不会加载这些模块,比如http选项就需要http的plugin。因此官方建议下载uWSGI的源码下来自行build。
uWSGI官方文档解释如下:

Installing uWSGI with Python support
When you start learning uWSGI, try to build from official sources: using distribution-supplied packages may bring you plenty of headaches. When things are clear, you can use modular builds (like the ones available in your distribution).

Installing via your package distribution is not covered (would be impossible to make everyone happy), but all of the general rules apply.

One thing you may want to take into account when testing this quickstart with distro-supplied packages, is that very probably your distribution has built uWSGI in modular way (every feature is a different plugin that must be loaded). To complete this quickstart, you have to prepend –plugin python,http to the first series of examples, and –plugin python when the HTTP router is removed (if this doesn’t make sense to you, just continue reading).

因此,在ini配置文件中加上:

plugin = python, http

就能解决这个问题了。

uWSGI emperor

OK,在前面的介绍中,uWSGI终于能正常运行了。下面介绍正餐emperor模式吧。

emperor翻译为中文的意思其实就是皇帝的意思,那么既然有皇帝,那就肯定有“臣子”(vassals),其中“臣子”就是指实际运行的一个app实例,在这里就是uwsgi_test.ini配置文件了。

emperor模式开启的命令是:

sudo uwsgi --emperor /path/to/vassals/ 

其中,vassals文件夹里包含了各个app的配置文件,这个文件夹将会被emperor一直监视,只要有配置文件的修改或新建,app实例就会被重新加载或新建,可以通过软链接将实际项目中的配置文件链接到vassal文件夹目录下:

ln -s /path/to/uwsgi_test.ini /path/to/vassals/

uWSGI官方文档展示了uWSGI emperor的功能:

  • Whenever an imperial monitor detects a new configuration file, a new uWSGI instance will be spawned with that configuration.
  • Whenever a configuration file is modified (its modification time changed, so touch –no-dereference may be your friend), the corresponding app will be reloaded.
  • Whenever a config file is removed, the corresponding app will be stopped.
  • If the emperor dies, all the vassals die.
  • If a vassal dies for any reason, the emperor will respawn it.

在文档中,emperor监视的是配置文件目录,但是问题来了,笔者希望每次Git仓库更新一次就重新启动实例,而如果这次更新并没有对配置文件进行修改,实例自然就不会重新启动了。
幸好配置文件提供了touch-reload选项,只要指定文件夹发生改动就重启实例:

touch-reload = /path/to/uwsgi_test
# or touch-reload = .git/index

最后一个问题,在上一篇 从零开始自动部署Django项目(一):开发配置与生产配置中,笔者通过环境变量来确定Django配置的加载,因此在uWSGI的配置文件中还需要加上对环境变量的添加:

env = DJANGO_PRODUCTION_SETTINGS=TRUE

最后,使用nohup命令启动uWSGI emperor即可实现进程管理(别忘了Git Hooks需修改为只拉取仓库更新)

完整的配置

[uwsgi]
plugin = python, http
env = DJANGO_PRODUCTION_SETTINGS=TRUE

chdir = /path/to/uwsgi_test
module = umefit.wsgi

master=True
processes = 4
http = :8080
vaccum=True

touch-reload = /path/to/uwsgi_test

最后祝大家国庆快乐,哈哈。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章