最近在工作中碰到一个问题,服务器上的 Sidekiq 崩溃之后,由于我们在服务器端并没有对 Sidekiq 进行监控和管理,当察觉到 Sidekiq 进程崩溃时已经有大量的 background job 停留在队列中无法执行。 这无疑是一件相当痛苦的事,所以我开始找寻办法去解决这一问题。 而实际上,Sidekiq 的作者就已经给出了最好的解决方案 (可以参考他博客的文章Sidekiq and Upstart)——用 Ubuntu 自带的Upstart去保证 Sidekiq 不会因为 Ruby 虚拟机崩溃等原因停止工作,用Inspeqtor结合 Upstart 去监控你的 Sidekiq(Sidekiq 进程失败时发送邮件通知)。
Upstart 是 Ubuntu 系统自带的基于事件的初始化常驻程序,可以在系统开机和关机时启动和关闭服务,在系统运行时对服务进行监督。
Upstart 可以让被监督的进程在非正常关闭时,自动重启该进程,保证其在系统中正常稳定地运行。
可以使用系统自带的工具去解决问题,相比较使用第三方工具而言,无疑是更好的方式。所以我选择了 Upstart 来管理服务器上的 Sidekiq 进程。
PS: Upstart 除了可以在 Ubuntu 系统上使用外,在 Debian、Fedora、openSUSE、Chrome OS 等发行版上也有相关支持。而在最近两年一些 Linux 发行版的新版本中(Ubuntu 15.04, CentOS 7 等),可以使用更先进的 Systemd 实现 Upstart 的功能。Upstart 的详细信息,可以参考 Ubuntu 官方的Upstart Cookbook。
在服务器的/etc/init 目录下新建一个*.conf
文件,在这里我新建了一个名为sidekiq.conf
的配置文件。
# /etc/init/sidekiq.conf - Sidekiq config
description "Sidekiq Background Worker"
# 在系统开机时启动服务,关机时关闭服务
start on runlevel [2345]
stop on runlevel [06]
# 在进程崩溃时自动重启进程
respawn
# 30秒之内尝试3次重启,如果失败,则放弃重启
respawn limit 3 30
# 正常退出所接收到的信号,0(Linux程序正常退出码)
# TERM是通过sidekiqctl停止sidekiq时发送的信号
# 除了这两种信号之外,其余任何终止sidekiq的方式都会触发respawn
normal exit 0 TERM
# 通过reload指令向正在运行的进程发送USR1信号(对于Sidekiq来说,USR1意味着停止接收新的background job)
reload signal USR1
# 通过upstart启动的sidekiq实例的序号
instance $index
script
# this script runs in /bin/sh by default
# respawn as bash so we can source in rbenv
exec /bin/bash <<'EOT'
# Pick your poison :) Or none if you're using a system wide installed Ruby.
# rbenv
# source /home/apps/.bash_profile
# OR
# source /home/apps/.profile
# OR system:
# source /etc/profile.d/rbenv.sh
#
# rvm
# source /home/apps/.rvm/scripts/rvm
# Logs out to /var/log/upstart/sidekiq.log by default
cd /var/www/app
# 注:这里必须在bundle exec前加上exec,否则会在另外一个进程(不受Upstart监控)中开启sidekiq
# sidekiq -i 的参数和之前传入是实例序号index一致
exec bundle exec sidekiq -i ${index} -e production
EOT
end script
只需要上面这个简单的配置文件,就完成了 Upstart 中 sidekiq 的配置。接下来,我们需要通过 Upstart 来启动 sidekiq,这样才可以让 sidekiq 进程在 Upstart 的监督下运行。
# 由于在系统中可以开启多个sidekiq进程,所以你可以通过指定sidekiq进程的编号方便你对某一个sidekiq进程进行管理
# 下列启动序号为0的sidekiq实例(在我们我应用中,只需要一个sidekiq进程)
# 启动sidekiq
$ sudo service sidekiq start index=0
# 终止sidekiq
$ sudo service sidekiq stop index=0
# 重启sidekiq
$ sudo service sidekiq restart index=0
在通过 Upstart 的方式启动 sidekiq 之后,sidekiq 进程如果遇到异常情况造成崩溃,之后立刻重生(重新启动一个 sidekiq 进程)。你可以尝试使用 kill 的方式强制关闭 sidekiq,会发现 sidekiq 不会在你的进程列表中消失(pid 会改变)。现在你只能通过以上命令中的 stop 来关闭 sidekiq。
sidekiq 部署的最佳实践,是尽可能早的让 sidekiq 处于 quiet 状态(停止接受新的 job,只处理当前在队列中的 job),并且尽可能晚的重启 sidekiq。
在/etc/init/sidekiq.conf 中我们曾指定过 reload signal USR1,这表示向当前 Upstart 进程发送 reload 命令时,传送一个名为 USR1 的信号过去。USR1 是 sidekiq 系统内置的一个信号量,当 sidekiq 接收到这个信号时,便会进入 quiet 状态。
在你的部署脚本的早期阶段执行以下命令:
$ sudo service sidekiq reload index=0
重启 sidekiq 也十分地方便,使用 Upstart 自带的 restart 即可,restart 命令会停止当前任务,之后再次启动。不过 restart 命令和单纯地先执行 stop 然后执行 start 不一样,restart 命令会保留当前任务停止时的各项配置,在重新启动时从硬盘中读取这些信息。这样就不会造成如果在重启时,如果正好有任务还在队列中执行,却因为重启而停止导致没有执行完的情况了。 在你的部署脚本中尽量靠后的位置运行重启命令:
$ sudo service sidekiq restart index=0
PS: 由于 sudo 需要输入密码,而在自动化部署时无法输入密码。而我们启动 Upstart 是通过 service 的方式,所以可以让部署用户在执行 service 命令时不需要输入密码。 打开 sudo 配置文件:
$ sudo visudo
在打开的配置文件中加上一行(用你的系统用户名替换 current_user)
current_user ALL=NOPASSWD:/usr/sbin/service
Inspeqtor 是 Sidekiq 作者自己写的一个监控工具,可以对系统中的开启的服务(通过 init.d, systemd 或者 upstart 启动的进程)进行监控。 同时也可以监控进程的内存占用量和 CPU 使用量。你可以针对不同的进程配置相应的监控规则,在违反这些规则时重启进程或者向你发送邮件告知。 在之前我们通过配置 Upstart 启动 Sidekiq,所以可以使用 Inspeqtor 对 Sidekiq 进程进行监控(这里主要想实现的目标,是在 Sidekiq 崩溃重启时可以收到邮件通知)。
PS:Inspeqtor 和 God, bluepill 等基于 Ruby 的监控工具不同,Inspeqtor 不需要依赖于 Ruby,所以安装也更加方便。
Inspeqtor 的安装教程可以在官方 wiki中找到,在这里我只列出 Ubuntu 系统中的安装方式。
$ curl -L https://bit.ly/InspeqtorDEB | sudo bash
$ sudo apt-get install inspeqtor
这里同样参考自 wiki,有兴趣的朋友可以好好研读。 CentOS 6.5 / Ubuntu with Upstart
$ initctl start inspeqtor
$ initctl stop inspeqtor
$ initctl restart inspeqtor
inspeqtor 的 log 会在/var/log/upstart/inspeqtor.log 中输出。
对发送 email 的配置在 inspeqtor 的全局配置文件/etc/inspeqtor/inspeqtor.conf 中。 可以通过配置 gmail,普通的 smtp 邮箱和服务器本机的 smtp 邮件服务三种方式发送邮件。 这里我列出普通 smtp 邮箱的配置
# /etc/inspeqtor/inspeqtor.conf
send alerts via email with
username bubba,
password "correct horse battery staple",
smtp_server smtp.example.com,
to_email [email protected],
tls_port 587
更多的配置可以查看官方 wiki 中的Global Configuration
PS: inspeqtor 标准版只能指定一个收件邮箱,如果你想让多个邮箱接收 inspeqtor 邮件,可以使用邮件组。
我们之前通过使用 Upstart 实现了让 sidekiq 意外崩溃时的自动重生,在这里我们需要的只是在 sidekiq 崩溃重启时发生邮件通知我们。 由于 inspeqtor 默认会在其监控下进程的 pid 改变时发送邮件通知,所以监控 sidekiq 的配置文件非常简单。 新建配置文件/etc/inspeqtor/services.d/sidekiq.inq。
PS: 按道理说,在这里我们只需要添加一个配置文件即可,不需要添加监控规则(因为 pid 改变时会自动发送邮件),但是我尝试过,如果不添加一条监控规则,inspeqtor 是不会对 sidekiq 进行监控的。所以我随意添加了一条 CPU 占用率 2 次大于 95% 时发送邮件通知的规则。
# /etc/inspeqtor/services.d/sidekiq.inq
check service sidekiq
if cpu:user > 95% for 2 cycles then alert
配置完成后,执行 restart 指令重新启动 inspeqtor 便可以在 sidekiq 进程崩溃时收到邮件。
之前曾经提到过,inspeqtor 在进程的 pid 变化时会触发 alert(发送邮件)。而我们在部署时是会对 sidekiq 进行重启的(pid 会改变),那么在默认情况下,部署时会收到 inspeqtor 发来的通知邮件。很显然,这样的邮件是没有必要的。所以 inspeqtor 提供了针对部署时的指令,可以在应用部署期间暂停对进程的监控,这样就不会收到不必要的通知了。
# 在部署开始时运行这条指令(建议在部署脚本的第一步运行)
$ inspeqtorctl start deploy
# 在部署结束时运行(建议在部署脚本的最后执行)
$ inspeqtorctl finish deploy
PS: inspeqtorctl 默认情况下需要 sudo 才可以执行,可以通过修改 inspeqtor 的 Upstart 配置文件(/etc/init/inspeqtor.conf)指定当前用户所在 group,group 中的用户执行 inspeqtorctl 就不需要输入 sudo 了。
# /etc/init/inspeqtor.conf
setgid current_user_group