之前我介绍了 puma 的三种启动方式, normal,hot, 以及 phased 启动方式
虽然从效果上来说,phased restart 启动方式最好,配置也最简单,只需要替换 kill 信号量到 SIGUSR1 就可以了,
但是 phased restart 也有非常明显的问题:
那么 退而求其次,我们能否在保证 0 downtime 的情况下解决这个问题呢?
答案是 利用 socket.activation 实现 hot restart。建立了 socket.activation 之后,在 puma.service 在重启的时候,消息会先进入这个 socket.activation 进行处理,此时访问并不会返回失败,而是 holding 中,当 puma.service 重启完毕之后 这个 socket 就会将存储的连接丢给 puma.service 来处理。
我这里举个例子 比如 我们原来有一个 叫做 puma.service 的 systemd 服务,为了建立对应的 socket 我们需要在 /etc/systemd/system/ 中建立 puma.socket , 注意 这里名字必须和 service 对应
[Unit]
Description=Puma HTTP Server Accept Sockets
[Socket]
ListenStream=0.0.0.0:9292
# AF_UNIX domain socket
# SocketUser, SocketGroup, etc. may be needed for Unix domain sockets
# ListenStream=/run/puma.sock
# Socket options matching Puma defaults
NoDelay=true
ReusePort=true
Backlog=1024
[Install]
WantedBy=sockets.target
特别注意的是,这里的 ListenStream 必须和 puma.rb 中的 bind 对应起来(不对应 socket 就不知道往哪转发了)
之后我们要修改 /etc/systemd/system/puma.service 将依赖添加进去
[Unit]
Description=Puma HTTP Server
After=network.target
Requires=puma.socket # 看这里 添加这个依赖
[Service]
# Puma supports systemd's `Type=notify` and watchdog service
# monitoring, if the [sd_notify](https://github.com/agis/ruby-sdnotify) gem is installed,
# as of Puma 5.1 or later.
# On earlier versions of Puma or JRuby, change this to `Type=simple` and remove
# the `WatchdogSec` line.
Type=notify
# If your Puma process locks up, systemd's watchdog will restart it within seconds.
WatchdogSec=10
User=deploy
WorkingDirectory=/var/www/pushconfig/current
ExecStart=/home/deploy/.rvm/bin/rvm 2.7.3 do bundle exec puma --keep-file-descriptors -C /var/www/pushconfig/shared/puma.rb
# 注意这里的--kep-file-descriptor 如果是使用的 bundle exec, 一定要加这个参数
# ExecReload=/bin/kill -TSTP $MAINPID
ExecReload=/bin/kill -SIGUSR2 $MAINPID #注意这里,可以换为 USR2, 用来启动
StandardOutput=append:/var/www/pushconfig/shared/log/puma_access.log
StandardError=append:/var/www/pushconfig/shared/log/puma_error.log
# ......
之后 需要运行以下指令
当然在进行这些操作之前,建议你先停止老的版本的 puma.service
当启动完毕之后 puma 的 hot restart 功能就设置完毕了 此时,无论你如何重启,用户都不会直接 503
如何验证呢?你在这个时候可以 stop puma.service 甚至 kill 掉 puma.service 的主进程!
按照常规,这个时候你访问网站 网站的 nginx 肯定是报 503,但是现在,感觉是访问卡了一下,之后正常 200 了!
为何出现这个情况呢?是因为 puma.socket 首先会先将你的访问 hold 住,同时去 找自己同名的 service 去处理,如果 service 没启动,就会启动这个 service
如果你是用 stop 命令 来关闭 service 这个时候你也会看到 一个 warning
deploy@rifle:/etc/systemd/system$ sudo systemctl stop PumaSSLGuala.service
Warning: Stopping PumaSSLGuala.service, but it can still be activated by:
PumaSSLGuala.socket
当启动完毕 service 后 socket 就会将之前暂存的连接丢给 service 继续处理。于是之前的访问就能收到 新启动的 puma 返回的 200
你看 连 kill 都没问题,restart 自然不在话下
可以自行测试,重启 puma 的时候,另外个 terminal 拼命访问,感觉就像是网突然卡了一下,接着正常了。
这是我配置时候总结出来的一些注意点
--keep-file-descriptors
参数,否则 puma 就启动失败,报 `for_fd': Bad file descriptor - not a socket file descriptor 这个错最后说下 capistrano-puma,其实我并不是手写 systemd 的 service,而是使用的 capistrano-puma 的 systemd 帮我生成 service 的文件。
如果你跟我一样,也是用 capistrano-puma 生成的 puma 的 systemd 服务,那么遗憾的告诉你 capistrano-puma 目前(2021 年 08 月 26 日,5.0.4 版本)还不支持 好消息是 已经有网友发了个 PR https://github.com/seuros/capistrano-puma/pull/324 可以生成这种 hot restart 的方式的 坐等官方 merge