自從有了 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
其實就那麼容易... 你可以再加一些參數像 -C xxxx.yml
去指定要讀取的設定檔。不過我們先維持這樣
基本上你在 production 環境可以做一樣的事,如果你想讓它更像一個”真的服務”,也可以直接加個 “&” 在後面讓它跑在背景:
RAILS_ENV=production bundle exec sidekiq &
如果真的純粹只是想要在 production 環境跑 sidekiq ,其實這樣就夠了
但光這樣做好像沒符合我們的目標:
- 重開機或程式掛掉後sidekiq不會自動運行
- 部署完後也不會自動運行
- 最重要的是,這感覺好像不是很”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
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
我用 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
時就會依序執行下列工作:
- 停止從 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
一般使用者級別的服務單元要放在 ~/.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
我們再跑以下指令來啟用/啟動 sidekiq.service
:
systemctl --user daemon-reload
systemctl --user enable sidekiq.service
# 現在可以控制 sidekiq 啦
systemctl --user {start,stop,restart} sidekiq
現在 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]
部署時跑 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
解法
* 加 ExecReload=/bin/kill -TSTP $MAINPID
進 sidekiq.service
出現 target 找不到
* target 其實是一組 service unit,執行 target 時全部服務會一起執行
- 用
systemctl list-units —type=target
去看能用的 target
-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
參考資料
- 其實
sidekiq.service
大部分我也是從 sidekiq repo 看來的啦: sidekiq/sidekiq.service at master · mperham/sidekiq · GitHub - GitHub - seuros/capistrano-sidekiq: Sidekiq integration for Capistrano
Top comments (0)