DEV Community

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

Posted on • Edited on

2

利用 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

    參考資料

    Heroku

    This site is built on Heroku

    Join the ranks of developers at Salesforce, Airbase, DEV, and more who deploy their mission critical applications on Heroku. Sign up today and launch your first app!

    Get Started

    Top comments (0)

    Sentry image

    See why 4M developers consider Sentry, “not bad.”

    Fixing code doesn’t have to be the worst part of your day. Learn how Sentry can help.

    Learn more

    👋 Kindness is contagious

    Please leave a ❤️ or a friendly comment on this post if you found it helpful!

    Okay