uWSGI re-fork master 踩坑记

uWSGI re-fork master 踩坑记

Zero downtime Python application deployment

0x00 参考文档

  1. The Art of Graceful Reloading
  2. uWSGI graceful Python code deploy

如上文档选定了实现 zero downtime deployment 方案:re-fork master

0x01 First Try

uWSGI 配置文件:

[uwsgi]
chdir = /home/foo/helloworld
module = helloworld.wsgi
processes = 4
socket = /var/run/uwsgi/app.sock
pidfile = /var/run/uwsgi/app.pid
vacuum = false
master-fifo = /tmp/app.new.fifo
master-fifo = /tmp/app.running.fifo
if-exists = /tmp/app.running.fifo
hook-accepting1-once = writefifo:/tmp/app.running.fifo q
endif =
hook-accepting1-once = writefifo:/tmp/app.new.fifo 1P
  1. 首先配置了两个 master-fifo,app.new.fifo 为默认,参考 The Master FIFO

  2. 期望的部署情况:

  3. 第一次部署时,部署前无任何 uWSGI 进程,此时 app.running.fifoapp.new.fifo 均不存在。uWSGI 进程启动,此时 master fifo 为 app.new.fifoapp.running.fifo 文件不存在故 hook-accepting1-once = writefifo:/tmp/app.running.fifo q 不会被执行,hook-accepting1-once = writefifo:/tmp/app.new.fifo 1P 在 uWSGI 第一个 worker 能够接收请求时执行一次将 master fifo 切换成 1 即 app.running.fifo 并更新 pid 文件中 master 进程 pid

  4. 再次部署时,使用 echo f > /tmp/app.running.fifo 通知 uWSGI re-fork master,uWSGI re-fork 一组新的 uWSGI 进程,app.running.fifo 已存在,故 hook-accepting1-once = writefifo:/tmp/app.running.fifo q 在 uWSGI 第一个 worker 能够接收请求时执行一次通知旧的 uWSGI 进程优雅退出(即停止接收新的请求并在处理完已经接收的请求后退出),hook-accepting1-once = writefifo:/tmp/app.new.fifo 1P 也执行一次将 master fifo 切换成 1 即 app.running.fifo 并更新 pid 文件中 master 进程 pid

看起来很完美,实际尝试发现:

  1. master fifo 文件生成是异步的,第一次部署时执行 hook-accepting1-once = writefifo:/tmp/app.new.fifo 1Papp.new.fifo 可能还没有创建,导致启动失败。详细可参考 GitHub issue: Zerg dance: writefifo race condition
  1. 解决了第一个问题后,尝试进行第二次部署(即 echo f > /tmp/app.running.fifo),实际发现有时成功有时 echo 会一直阻塞住,lsof | grep fifo 发现 uWSGI 加载的 app.running.fifo 处于删除状态然而 /tmp/app.running.fifo 文件是存在的。GitHub 上搜索发现相关 issue: Race condition graceful restart while fork master

After #race_condition_point there is 2 ways:

1. the old master receive 'q' and delete and create fifo-file, after that new master delete and create fifo-file and change master-fifo to 1

2. the new master delete and create fifo file, change master-fifo to 1 and after this old master delete and create fifo file (because we send 'q' him)

显然第二种情况下会导致上述的情况从而导致 echo 阻塞。

0x02 Second Try

新的 uWSGI 配置文件:

[uwsgi]
chdir = /home/foo/helloworld
module = helloworld.wsgi
processes = 4
socket = /var/run/uwsgi/app.sock
pidfile = /var/run/uwsgi/app.pid
vacuum = false
master-fifo = /tmp/app.new.fifo
master-fifo = /tmp/app.running.fifo
master-fifo = /tmp/app.quit.fifo
if-exists = /tmp/app.running.fifo
hook-accepting1-once = writefifo:/tmp/app.running.fifo 2q
endif =
hook-accepting1-once = spinningfifo:/tmp/app.new.fifo 1P

增加了 app.quit.fifo,使用 echo f > /tmp/app.running.fifo 触发部署时,hook-accepting1-once = writefifo:/tmp/app.running.fifo 2q 通知旧的 master 进程切换 master-fifo 到 2 即 app.quit.fifo 后优雅退出(q)

Works great.

0x03 Caveats

  1. re-fork master 很强大也很危险,如果用到了 uWSGI 的 attach-daemon 等管理后台进程,由于 re-fork master 也会新开启一组后台进程,需要保证这些后台进程可以启动多个而没有副作用。
  2. uwsgi --build-plugin 如果没有任何输出,应尝试安装缺失的依赖如 python, gcc 等。
Some rights reserved
Except where otherwise noted, content on this page is licensed under a Creative Commons Attribution-NonCommercial 4.0 International license.