DEV Community

Cover image for 利用 systemd 在 Ubuntu 執行 Sidekiq 6
LuoKevin
LuoKevin

Posted on • Updated on

利用 systemd 在 Ubuntu 執行 Sidekiq 6

自從有了 Sidekiq 後,所有事情都變得容易了。
所有需要長時間執行的程式,我全往 sidekiq 扔,我再也不用怕 CloudFlare 的 524(請求超時) 了(咦?)
在 production 的環境執行 sidekiq 雖不困難但對於 Linux 沒那麼熟的人來說還滿多細節要注意的,
Sidekiq 6 又好像跟之前變化頗大。
這次想分享一下將 Sidekiq 6 運行在 ubuntu 20.04 的經驗 : )

目標

  • 運行 Sidekiq (廢話...)
  • 重開機或 sidekiq 掛了的話, sidekiq 會自動重啟
  • 當我用 Capistrano 部署新的程式碼時,sidekiq 會讀新的程式碼並重啟

我們怎麼執行 Sidekiq?

要知道在 production 環境怎麼運行 sidekiq, 首先要知道在 development 環境是怎麼跑的:

bundle exec sidekiq
Enter fullscreen mode Exit fullscreen mode

其實就那麼容易... 你可以再加一些參數像 -C xxxx.yml 去指定要讀取的設定檔。不過我們先維持這樣

基本上你在 production 環境可以做一樣的事,如果你想讓它更像一個”真的服務”,也可以直接加個 “&” 在後面讓它跑在背景:

RAILS_ENV=production bundle exec sidekiq &
Enter fullscreen mode Exit fullscreen mode

如果真的純粹只是想要在 production 環境跑 sidekiq ,其實這樣就夠了

但光這樣做好像沒符合我們的目標:

  1. 重開機或程式掛掉後sidekiq不會自動運行
  2. 部署完後也不會自動運行
  3. 最重要的是,這感覺好像不是很”Pro”。即使我們不是用 Sidekiq Pro , 應該也可以 Pro 一點

好,為了達到我們的目標,我們需要用 systemd

systemd是什麼?

systemd 是 Linux 系統專門來管理各式「服務程式」的程式,其實就是 daemon 所以才是 systemD。比如 mysql, apache, nginx, redis 這些都可以用它來管,事實上 systemd 是多數 Linux 版本預設的 Service Manager。
systemd,,我們可以:

  • systemctl start/stop/restart 任何服務
  • 可以啟用(enable)服務, 啟用的服務在系統重開時會自動開始運行
  • 你可以指定當程式掛掉後,該做什麼事,例如重啟該服務

systemd 的內容還有一堆,不過目前知道這樣就足夠了,剩下的就自行 google 吧xD
看起來 systemd 可以符合我們想做的事,就用 systemd 來操作 sidekiq 吧!

將 Sidekiq 變成一個服務單元(service unit)

在 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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

讓 Capistrano 重啟 Sidekiq

我用 Capistrano 來做自動部署以避免錯誤。
部署完當然希望 sidekiq 重啟,這樣它才讀到最新的程式碼。
要把重啟sidekiq 加到 Capistrano 的流程中,其實只要安裝 gem capistrano-sidekiq 就可以了。
在 Gemfile 加入:

# Gemfile
gem 'capistrano-sidekiq', group: :development
Enter fullscreen mode Exit fullscreen mode

再到 Capfile 加入:

# Capfile
require 'capistrano/sidekiq'
# 加入 sidekiq 的 rake tasks
install_plugin Capistrano::Sidekiq
# 設定要用 systemd 去控制 sidekiq
install_plugin Capistrano::Sidekiq::Systemd
Enter fullscreen mode Exit fullscreen mode

這樣設定好, cap production deploy 時就會依序執行下列工作:

  • 停止從 redis 拿工作
  • 停止 Sidekiq 服務
  • 開啟 Sidekiq 服務

做一個一般使用者的 sidekiq.service

上面其實我故意漏說了一件事。
上面新增的 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
Enter fullscreen mode Exit fullscreen mode

一般使用者級別的服務單元要放在 ~/.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

# 注意這邊是 default.target
[Install]
WantedBy=default.target
Enter fullscreen mode Exit fullscreen mode

我們再跑以下指令來啟用/啟動 sidekiq.service:

systemctl --user daemon-reload 
systemctl --user enable sidekiq.service

# 現在可以控制 sidekiq 啦
systemctl --user {start,stop,restart} sidekiq
Enter fullscreen mode Exit fullscreen mode

現在 capistrano/sidekiq 應該可以正常運作了,用 capistrano 部署看看吧 : )

其它問題

用 user-wise systemd 啟動 Sidekiq 但它好像有啟動一下但又停止運作了

這其實是我自己遇到的一個問題,我發現怎麼Sidekiq 的 queue 好長都沒有輪詢(toll)?
但我一登入用 ps aux | grep sidekiq 查看 sidekiq 又是正常執行中,佇列中工作也突然開始消化,我當下還跟客戶笑稱這是「薛丁格的隊列」:看它時才會做,不看時不會做...。
搞半天原來 user-wise systemd 只允許「在線上」的使用者執行服務...,當最後一個session 結束時,那些服務也跟著停止。
所以一般如果一台主機就只有一個 rails 服務時,應該是不用 user-wise 去執行的 systemd...用system-wise 的即可。
不過有 systemd 有提供一個指令 loginctl 來控制用戶登錄的狀態,用下面的指令即可保持使用者登入的狀態囉:

loginctl enable-linger [user_name]
Enter fullscreen mode Exit fullscreen mode

部署時跑 sidekiq:quiet 出現以下錯誤

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
Enter fullscreen mode Exit fullscreen mode

解法
* 加 ExecReload=/bin/kill -TSTP $MAINPID 進 sidekiq.service

出現 target 找不到

* target 其實是一組 service unit,執行 target 時全部服務會一起執行

  • systemctl list-units —type=target 去看能用的 target
Enter fullscreen mode Exit fullscreen mode

-L log/sidekiq.log 無效

* Sidekiq 6 不再支援 log 的轉向,請看 Logging · mperham/sidekiq Wiki · GitHub,一律看 /var/log/syslog
  • 可以用 bundle exec sidekiq 2>&1 | logger -t sidekiq 為 log 加上 sidekiq 的 tag
  • Enter fullscreen mode Exit fullscreen mode

    參考資料

    Discussion (0)