网志版:https://dev.to/kevinluo201/systemd-ubuntu-sidekiq-6-d9c
自从有了 Sidekiq 后,所有事情都变得容易了。 所有需要长时间执行的程式,我全往 sidekiq 扔,我再也不用怕 CloudFlare 的 524(请求超时) 了 (咦?) 在 production 的环境执行 sidekiq 虽不困难但对于 Linux 没那麽熟的人来说还满多细节要注意的, Sidekiq 6 又好像跟之前变化颇大。 这次想分享一下将 Sidekiq 6 运行在 ubuntu 20.04 的经验 : )
要知道在 production 环境怎麽运行 sidekiq, 首先要知道在 development 环境是怎麽跑的:
bundle exec sidekiq
其实就那麽容易... 你可以再加一些参数像 -C xxxx.yml
去指定要读取的设定档。不过我们先维持这样
基本上你在 production 环境可以做一样的事,如果你想让它更像一个”真的服务”,也可以直接加个“&”在后面让它跑在背景:
RAILS_ENV=production bundle exec sidekiq &
如果真的纯粹只是想要在 production 环境跑 sidekiq,其实这样就够了
但光这样做好像没符合我们的目标:
好,为了达到我们的目标,我们需要用 systemd
systemd
是 Linux 系统专门来管理各式「服务程式」的程式,其实就是 daemon 所以才是 system*d*。比如 mysql, apache, nginx, redis 这些都可以用它来管,事实上 systemd
是多数 Linux 版本预设的 Service Manager。
有 systemd
,,我们可以:
systemctl start/stop/restart
任何服务systemd
的内容还有一堆,不过目前知道这样就足够了,剩下的就自行 google 吧 xD
看起来 systemd 可以符合我们想做的事,就用 systemd 来操作 sidekiq 吧!
在 systemd 中,每个服务都被视为一个「单元」(Unit)
要新增一个 sidekiq 的服务单元,我们可以新增一个档案 /lib/systemd/system/sidekiq.service
(另外,用来部署 rails 的使用者叫 deployer
)
# /lib/systemd/system/sidekiq.service
# 我们的 service 叫 sidekiq
[Unit]
Description=sidekiq
After=syslog.target network.target
# 这个 Type=simple 只是 systemd 要如何判断你的服务成功执行
[Service]
Type=simple
WorkingDirectory=/path/to/your/app
# 如果是用 rbenv:
# ExecStart=/bin/bash -lc 'exec /home/deploy/.rbenv/shims/bundle exec sidekiq -e production'
# 如果直接用系统安装的 ruby:
# ExecStart=/usr/local/bin/bundle exec sidekiq -e production
# 如果是用 rvm ,用 ruby 2.6.5 也无特定 gemset
# ExecStart=/home/deploy/.rvm/gems/ruby-2.6.5/wrappers/bundle exec sidekiq -e production
# 如果是用 rvm ,用 ruby 2.6.5 且有特定 gemset
# ExecStart=/home/deploy/.rvm/gems/ruby-2.6.5@gemset-name/wrappers/bundle exec sidekiq -e production
# 如果是用 rvm ,用而且专案有 .ruby-version 指定版本
ExecStart=/home/deploy/.rvm/bin/rvm in /path/to/your/app/current do bundle exec sidekiq -e production
User=deployer
Group=deployer
UMask=0002
# 这行可以大大降低 Ruby memory 用量
# 我也是抄来放上
# 不过有看 MALLOC_ARENA_MAX=2 的意思是限制 sidekiq 只能用2个 tread_pool
# https://www.mikeperham.com/2018/04/25/taming-rails-memory-bloat/
Environment=MALLOC_ARENA_MAX=2
# 如果挂掉就重启
RestartSec=1
Restart=on-failure
# log 会记在 /var/log/syslog
StandardOutput=syslog
StandardError=syslog
# 这个服务的id 是 sidekiq
SyslogIdentifier=sidekiq
[Install]
WantedBy=multi-user.target
systemd
会去找 /lib/systemd/system/
下所有的档案,所以 sidekiq.service
已经可以被 systemd
找到了。
我们接着可以执行下列指令
# 重读所有服务
sudo systemctl daemon-reload
# enable sidekiq.service 所以它可以在开机后自动运行
sudo systemctl enable sidekiq.service
# 启动 sidekiq
sudo service sidekiq start
# 在 /var/log/syslog 看有没有 sidekiq 的 log
sudo cat /var/log/syslog
# 可以用 ps 看 sidekiq 执行了或
sudo ps aux | grep sidekiq
# 或直接用 systemctl 来看目前运行中的服务
sudo systemctl status
我用 Capistrano 来做自动部署以避免错误。
部署完当然希望 sidekiq 重启,这样它才读到最新的程式码。
要把重启 sidekiq 加到 Capistrano 的流程中,其实只要安装 gem capistrano-sidekiq
就可以了。
在 Gemfile 加入:
# Gemfile
gem 'capistrano-sidekiq', group: :development
再到 Capfile 加入:
# Capfile
require 'capistrano/sidekiq'
# 加入 sidekiq 的 rake tasks
install_plugin Capistrano::Sidekiq
# 设定要用 systemd 去控制 sidekiq
install_plugin Capistrano::Sidekiq::Systemd
这样设定好, cap production deploy
时就会依序执行下列工作:
上面其实我故意漏说了一件事。
上面新增的 service unit 其实是全系统范围的,也就是要用 sudo
去执行 systemctl
的
如果我们希望一般的使用者也可以使用 systemd 的话,我们必须要做一个使用者自己的 sidekiq.service
而且 capistrano/sidekiq
其实预设是要用一般使用者的权限去执行 systemctl 来重启 Sidekiq 的
当然我们也可以去改 capistrano/sidekiq
的设定,让它用 root 的权限去操作 systemctl,我只是想说明其实有多种选择,每种都可以。而且一开始我的 sidekiq.service
怎麽都跑不起来就是因为我没注意到这件事....所以想特别提一下。
If you have already enabled the system-wise sidekiq.service
, you need to disable it and delete the service-unit:
如果你刚刚已经安装好了全系统级别的 sidekiq.servive,你需要执行下面步骤去取消它:
# 先停止 sidekiq
sudo systemctl stop sidekiq
# 不要启用 sidekiq.service
sudo systemctl disable sidekiq.service
# 删掉
sudo rm /lib/systemd/system/sidekiq.service
一般使用者级别的服务单元要放在 ~/.config/systemd/user/
下,systemd 会去检查下面的档案。我们再新增一次 sidekiq.service
,它稍有一些不同:
[Unit]
Description=sidekiq
After=syslog.target network.target
[Service]
# 如果是用 Sidekiq 6.0.6 以后的版本,可以改成 notify 并配合 WatchdogSec
# 否则沿用 simple
Type=notify
WatchdogSec=10
WorkingDirectory=/path/to/your/app/current
# 如果是用 rbenv:
# ExecStart=/bin/bash -lc 'exec /home/deploy/.rbenv/shims/bundle exec sidekiq -e production'
# 如果直接用系统安装的 ruby:
# ExecStart=/usr/local/bin/bundle exec sidekiq -e production
# 如果是用 rvm ,用 ruby 2.6.5 也无特定 gemset
# ExecStart=/home/deploy/.rvm/gems/ruby-2.6.5/wrappers/bundle exec sidekiq -e production
# 如果是用 rvm ,用 ruby 2.6.5 且有特定 gemset
# ExecStart=/home/deploy/.rvm/gems/ruby-2.6.5@gemset-name/wrappers/bundle exec sidekiq -e production
# 如果是用 rvm ,用而且专案有 .ruby-version 指定版本
ExecStart=/home/deploy/.rvm/bin/rvm in /path/to/your/app/current do bundle exec sidekiq -e production
ExecReload=/usr/bin/kill -TSTP $MAINPID
# 这行可以大大降低 Ruby memory 用量
# 我也是抄来放上
# 不过有看 MALLOC_ARENA_MAX=2 的意思是限制 sidekiq 只能用2个 tread_pool
# https://www.mikeperham.com/2018/04/25/taming-rails-memory-bloat/
Environment=MALLOC_ARENA_MAX=2
# 如果挂掉就重启
RestartSec=1
Restart=on-failure
# log 会记在 /var/log/syslog
StandardOutput=syslog
StandardError=syslog
# 这个服务的id 是 sidekiq
SyslogIdentifier=sidekiq
[Install]
WantedBy=default.target
我们再跑以下指令来启用/启动 sidekiq.service
:
systemctl --user daemon-reload
systemctl --user enable sidekiq.service
# 现在可以控制 sidekiq 啦
systemctl --user {start,stop,restart} sidekiq
现在 capistrano/sidekiq
应该可以正常运作了,用 capistrano 部署看看吧 : )
sidekiq:quiet
01 systemctl --user reload sidekiq
01 Failed to reload sidekiq.service: Job type reload is not applicable for unit sidekiq.service.
✘ 01 deployer@xxxxxxxx 0.067s
解法
* 加 ExecReload=/bin/kill -TSTP $MAINPID
进 sidekiq.service
* target 其实是一组 service unit,执行 target 时全部服务会一起执行
* 用 systemctl list-units —type=target
去看能用的 target
* Sidekiq 6 不再支援 log 的转向,请看 Logging · mperham/sidekiq Wiki · GitHub,一律看 /var/log/syslog
* 可以用 bundle exec sidekiq 2>&1 | logger -t sidekiq
为 log 加上 sidekiq 的 tag
sidekiq.service
大部分我也是从 sidekiq repo 看来的啦:sidekiq/sidekiq.service at master · mperham/sidekiq · GitHub