1. 程式人生 > >從零開始自動部署Django專案(三):使用uWSGI emperor管理程序

從零開始自動部署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.

  • 折騰環境選擇
    筆者開發使用的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

最後祝大家國慶快樂,哈哈。