HSFZMUN 4.0 部署小记
技术流水账一篇,记录踩过的坑
Channels 异构
Django Channels 官方文档宣称 channels 的最佳配置是使用其自带的服务器组件 Daphne,但在开发中我发现 daphne 处理普通请求比在 WSGI 架构下慢了好几倍,更何况使用 daphne 派发静态文件是十分不切实际的。于是我将 http.request
和 websocket.*
两个 channel 解耦,前者使用 nginx 配合 uwsgi 处理,后者使用 nginx 反向代理至 daphne 处理。这样一来便可充分利用两种架构的优势。
旧架构:
新架构:
环境
- Ubuntu Server 16.04(与开发环境相同)
- python 3.5.2
- virtualenv & virtualenvwrapper
- nginx
- uwsgi
- redis(作为缓存和 Channel Layer)
Channels Session-based Authentication
channels 有提供基于 HTTP Session 的认证方式,但由于 websocket 和 http 在此不是同域请求(端口号不同),主域名下的 cookies 不会随 websocket 请求发送。故在发起 websocket 链接时要带上一个 GET 参数 session_key
。在模板中该参数可由 { {request.session.session_key} }
获得。
uwsgi
安装:
$ sudo pip install uwsgi # 全局安装
编写配置文件 /etc/uwsgi/sites/hsfzmun.ini
:
[uwsgi]
uid = git
chdir = /home/git/hsfzmun/server
module = config.wsgi:application
home = /home/git/virtualenvs/hsfzmun # Virtualenv 路径
master = true
processes = 10
socket = /var/www/hsfzmun.sock # 使用 Unix Socket 与 nginx 通信
chmod-socket = 666
vacuum = true
配置 systemd 服务(uwsgi.service
):
[Unit]
Description=uWSGI Module
[Service]
ExecStartPre=/bin/bash -c 'mkdir -p /run/uwsgi; chown git:www-data /run/uwsgi'
ExecStart=/usr/local/bin/uwsgi --emperor /etc/uwsgi/sites --touch-reload=/home/git/hsfzmun/server/uwsgi_params
Restart=always
KillSignal=SIGQUIT
Type=notify
NotifyAccess=all
[Install]
WantedBy=multi-user.target
uwsgi 有个优点:可以通过 --touch-reload
参数简洁地重启服务,这样只需一条 touch
命令便可以完成新代码的部署。
daphne
daphne 有两个模块:Interface Server 和 Workers。前者负责处理 Websocket、long-polling 等请求,并将其抽象化为 channels 传递给 Workers,Workers 则负责执行具体的业务逻辑。这么做有一个好处就是 降低了底层与业务逻辑的耦合度,即使业务层崩溃也不会使连接断开,同时也减少了新代码部署对整个系统的印象。部署新代码时只需重启 Workers 即可。
daphnei.service
:
[Unit]
Description=Daphne Interface Server
[Service]
ExecStart=/home/git/cmd/daphnei
Restart=always
KillSignal=SIGQUIT
Type=simple
NotifyAccess=all
[Install]
WantedBy=multi-user.target
daphnew.service
:
[Unit]
Description=Daphne Worker
[Service]
ExecStart=/home/git/cmd/daphnew
Type=simple
KillSignal=SIGQUIT
Restart=always
NotifyAccess=all
[Install]
WantedBy=multi-user.target
此处踩了一个坑:起初 Type
处的值是 Notify
,因为我是仿照 uwsgi.service
编写这两个文件的。Notify
型服务要求主进程自行通知 systemd
自己的启动/停止状态,但 daphne 没有这样的机制,从而导致 systemctl
阻塞,并以 90 秒为周期不断重启服务。幸好 StackOverflow 上的大神指出了这个错误。
这里我将具体的服务脚本独立出来,因为 ExecStart
不支持编写多行语句。
cmd/daphnei
:
#!/bin/bash
cd /home/git/hsfzmun/server
/home/git/virtualenvs/hsfzmun/bin/daphne -b 0.0.0.0 -p 8001 -v2 config.asgi:channel_layer --access-log=/var/www/daphnei.log
cmd/daphnew
:
#!/bin/bash
cd /home/git/hsfzmun/server
/home/git/virtualenvs/hsfzmun/bin/python manage.py runworker --threads 5 --only-channels=websocket.*
此处 daphne 有个 bug:理论上 verbosity 不应该影响程序的行为,但如果不加 -v2
参数 nginx 会报 502 Bad Gateway
。
nginx
此处的难点是反向代理 websockets,因为 nginx 默认不识别 websocket 协议。为了能正确指定协议头,只能将所有 websocket 请求路由到某一子路径下。
nginx.service
:
server {
listen 80;
server_name <ip> product;
# 静态文件
location /static {
alias /var/www/static;
}
# 用户上传的文件
location /m {
alias /var/www/media;
}
# uwsgi 反向代理
location / {
include /var/www/uwsgi_params;
uwsgi_pass unix:/var/www/hsfzmun.sock;
}
# daphne 反向代理
location /ws {
proxy_pass http://0.0.0.0:8001;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_redirect off;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Host $server_name;
proxy_read_timeout 36000s;
proxy_send_timeout 36000s;
}
}
Git 自动化部署
去年的 WiseCity 3.0 采用 Github 中转的方式进行部署,这对 Github 的稳定性有很高的要求。这次则将生产服务器作为 Git Server,使用 Git Hooks 进行自动化部署,大大提高了生产效率。
创建 git 用户:
$ useradd git
$ passwd git
设置 SSH authorized_keys(略)。
创建仓库:
$ cd ~
$ git init --bare hsfzmun.git
创建钩子 hooks/post-receive
:
#!/bin/bash
# Checkout Repository
GIT_WORK_TREE=/home/git/hsfzmun git checkout -f
# Activate Virtualenv
cd /home/git/hsfzmun/server
export LC_ALL=C
export VIRTUALENVWRAPPER_PYTHON=/usr/bin/python3
export WORKON_HOME=~/virtualenvs
source /usr/local/bin/virtualenvwrapper.sh
workon hsfzmun
# Internationalization
./manage.py compilemessages
# Collect Static Files
./manage.py collectstatic --noinput
# Migrate Databases
./manage.py migrate
# Restart uWSGI
touch ./uwsgi_params
# Restart Daphne Workers
sudo systemctl restart daphnew --no-block
echo "All operations done."
此钩子会在每次 push 之后执行,自动更新静态文件和数据库表并重启相关服务。
本地添加远程仓库(略)。
作者:hsfzxjy
链接:
许可:CC BY-NC-ND 4.0.
著作权归作者所有。本文不允许被用作商业用途,非商业转载请注明出处。
OOPS!
A comment box should be right here...But it was gone due to network issues :-(If you want to leave comments, make sure you have access to disqus.com.