Ruby puma 只会配 phased-restart ? 是时候配一个 hot restart 了

jicheng1014 · 2021年08月26日 · 最后由 zj0713001 回复于 2021年08月27日 · 275 次阅读

之前我介绍了 puma 的三种启动方式, normal, hot, 以及 phased 启动方式

虽然从效果上来说, phased restart 启动方式最好, 配置也最简单, 只需要替换 kill 信号量到 SIGUSR1 就可以了,

但是 phased restart 也有非常明显的问题:

  1. 当 gemfile 有更新的时候, phased 更新会出现找不到 gem 的 问题
  2. 无法修改 config/puma.rb 文件, 以及部分 yml 修改无效
  3. 新老程序的 worker 会同时存在, 有的业务场景一旦新逻辑上线, 再走老逻辑就会出问题

那么 退而求其次,我们能否在保证 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

# ......

之后 需要运行以下指令

  • systemctl daemon-reload # 用于重新加载 systemd, 毕竟文件变动了
  • systemctl enable puma.socket puma.service # 启用 socket 的重启后自动运行
  • systemctl start puma.socket puma.service # 启动

当然在进行这些操作之前, 建议你先停止老的版本的 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 拼命访问, 感觉就像是网突然卡了一下, 接着正常了。

这是我配置时候总结出来的一些注意点

  1. jruby 不支持 socket activation , 所以如果你是用 jruby 跑的, 对不起 用不了
  2. socket 里 bind , 必须和 puma 中的 puma bind 的地址是一致的 。 比如你 puma.rb 监听 9292 端口, 那你 socket 就必须是 9292
  3. socket 和 service 必须同名。 比如 你的 puma 的 service 叫 app.service 那你的 socket 就得是 app.socket
  4. 最后, 也是坑了我一阵的,是如果你跟我一样 在操作 puma 的 service 里, 是以 bundle exec 来启动 puma 的, 请务必在后面加入 --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

1 楼 已删除

https://github.com/windy/mina-ng-puma 要是用 puma 的话可以试试我写的一个 gem 包.

lyfi2003 回复

puma 5 之后的版本移除了 daemon 模式转而推荐 systemd 的方式跑啦

也可以使用 K8s ,配置成蓝绿发布。

a112121788 回复

别提了 我 mac 的 k8s 到现在都还没跑起来

jicheng1014 回复

确实 k8s 更好一些。。。

需要 登录 后方可回复, 如果你还没有账号请 注册新账号