好吧,这真是让我头疼。这可能与我对 Upstart 的了解不够有关。问题太长了,在此先致歉。
我正在尝试使用 Upstart 来管理 Rails 应用程序的 Unicorn 主进程。这是我当前的/etc/init/app.conf
:
description "app"
start on runlevel [2]
stop on runlevel [016]
console owner
# expect daemon
script
APP_ROOT=/home/deploy/app
PATH=/home/deploy/.rbenv/shims:/home/deploy/.rbenv/bin:$PATH
$APP_ROOT/bin/unicorn -c $APP_ROOT/config/unicorn.rb -E production # >> /tmp/upstart.log 2>&1
end script
# respawn
这很好用 - Unicorn 启动得很好。不好的是检测到的 PID 不是 Unicorn 主服务器的,而是进程的sh
。这本身也不算太糟 - 如果我不使用自动化的 Unicorn 零停机部署策略的话。因为在我-USR2
向我的 Unicorn 主服务器发送消息后不久,一个新的主服务器就出现了,而旧的主服务器就死了……进程也死了sh
。所以 Upstart 认为我的工作已经死了,我不能再用 重新启动它,restart
也不能用 停止它了stop
。
我尝试了配置文件,尝试在 Unicorn 行中添加 -D(如下所示$APP_ROOT/bin/unicorn -c $APP_ROOT/config/unicorn.rb -E production -D
:)来守护 Unicorn,我添加了该expect daemon
行,但这也没有用。我expect fork
也试过了。所有这些事情的各种组合都可能导致start
挂起stop
,然后 Upstart 对作业的状态感到非常困惑。然后我必须重新启动机器来修复它。
我认为 Upstart 在检测 Unicorn 是否分叉时遇到了问题,因为我ruby-local-exec
在$APP_ROOT/bin/unicorn
脚本中使用了 rbenv + shebang。如下所示:
#!/usr/bin/env ruby-local-exec
#
# This file was generated by Bundler.
#
# The application 'unicorn' is installed as part of a gem, and
# this file is here to facilitate running it.
#
require 'pathname'
ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../../Gemfile",
Pathname.new(__FILE__).realpath)
require 'rubygems'
require 'bundler/setup'
load Gem.bin_path('unicorn', 'unicorn')
此外,该ruby-local-exec
脚本如下所示:
#!/usr/bin/env bash
#
# `ruby-local-exec` is a drop-in replacement for the standard Ruby
# shebang line:
#
# #!/usr/bin/env ruby-local-exec
#
# Use it for scripts inside a project with an `.rbenv-version`
# file. When you run the scripts, they'll use the project-specified
# Ruby version, regardless of what directory they're run from. Useful
# for e.g. running project tasks in cron scripts without needing to
# `cd` into the project first.
set -e
export RBENV_DIR="${1%/*}"
exec ruby "$@"
所以,exec
我担心的是,它启动了一个 Ruby 进程,而 Ruby 进程又启动了 Unicorn,Unicorn 可能会也可能不会将自身守护进程化,而这一切都是从一个sh
进程开始的……这让我严重怀疑 Upstart 是否有能力跟踪所有这些无用的东西。
我试图做的事情是否可行?据我了解,expect
Upstart 中的节只能通过daemon
或fork
来指示最多预期两个分叉。
答案1
事实上,upstart 的一个限制是它无法跟踪执行 unicorn 所做工作的守护进程,也就是 fork/exec 并退出其主进程。信不信由你,sshd 在 SIGHUP 上做同样的事情,如果你仔细查看,就会发现 /etc/init/ssh.conf 确保 sshd 在前台运行。这也是 apache2 仍然使用 init.d 脚本的原因之一。
听起来 gunicorn 在收到 SIGUSR1 时实际上会通过分叉然后退出来将自己变成守护进程。对于任何试图保持进程活动的进程管理器来说,这都会令人困惑。
我认为你有两个选择。1 是在需要时不使用 SIGUSR1 并停止/启动 gunicorn。
另一个选择是不使用 upstart 的 pid 跟踪,只需执行以下操作:
start on ..
stop on ..
pre-start exec gunicorn -D --pid-file=/run/gunicorn.pid
post-stop exec kill `cat /run/gunicorn.pid`
虽然不如 pid 跟踪那么性感,但至少你不必编写整个 init.d 脚本。
(顺便说一句,这与 shebangs/execs 无关。它们的工作方式与运行常规可执行文件一样,因此它们不会导致任何额外的分叉)。
答案2
我选择了一个与 SpamapS 略有不同的解决方案。我也在运行一个由 Upstart 管理的 preload_app = true 的应用程序。
当我自己想解决这个问题时,我一直在使用 Upstart 的“exec”来启动我的应用程序(“exec bundle exec unicorn_rails blah blah”)。然后我发现了你的问题,这让我意识到,我可以使用脚本节来指定我的可执行文件,而不是使用 Upstart 的“exec”,该脚本节将在其自己的进程中运行,即 Upstart 将监视的进程。
因此,我的 Upstart 配置文件包括以下内容:
respawn
script
while true; do
if [ ! -f /var/www/my_app/shared/pids/unicorn.pid ]; then
# Run the unicorn master process (this won't return until it exits).
bundle exec unicorn_rails -E production -c /etc/unicorn/my_app.rb >>/var/www/my_app/shared/log/unicorn.log
else
# Someone restarted the master; wait for the new master to exit.
PID=`cat /var/www/my_app/shared/pids/unicorn.pid`
while [ -d /proc/$PID ]; do
sleep 2
done
# If we get here, the master has exited, either because someone restarted
# it again (in which case there's already a new master running), or
# it died for real (in which case we'll need to start a new process).
# The sleep above is a tradeoff between polling load and mimizing the
# restart delay when the master dies for real (which should hopefully be
# rare).
fi
done
end script
我的 Unicorn 配置文件中的 before_fork 与 Unicorn 站点示例中所建议的一样,http://unicorn.bogomips.org/examples/unicorn.conf.rb:
before_fork do |server, worker|
ActiveRecord::Base.connection.disconnect! if defined?(ActiveRecord::Base)
old_pid = '/var/www/my_app/shared/pids/unicorn.pid.oldbin'
if server.pid != old_pid
begin
sig = (worker.nr + 1) >= server.worker_processes ? :QUIT : :TTOU
Process.kill(sig, File.read(old_pid).to_i)
rescue Errno::ENOENT, Errno::ESRCH
# someone else did our job for us
end
end
sleep 0.5
end
因此:在启动时,Upstart 脚本找不到 pidfile,因此它运行 unicorn_rails,并持续运行。
稍后,我们重新部署我们的应用程序,并且 Capistrano 任务通过以下方式触发应用程序重启:
kill -USR2 `cat /var/www/my_app/shared/pids/unicorn.pid`
这告诉旧的 Unicorn 主服务器启动一个新的 Unicorn 主服务器进程,并且当新主服务器启动工作进程时,Unicorn before_fork 块向旧的主服务器发送 TTOU 信号以关闭旧的工作进程(优雅地),然后在只剩下一个工作进程时退出。
QUIT 会导致旧主进程退出(但只有在有新工作进程处理负载时才会退出),因此 unicorn 脚本中会返回“bundle exec unicorn_rails”。然后该脚本会循环,查看现有的 pidfile,并等待进程退出。它直到下一次部署才会退出,但如果它退出,我们会再次循环;主进程死机时,我们也会再次循环。
如果 bash 脚本本身终止,Upstart 将重新启动它,因为这是它正在监视的进程(如您所见status my_app
- Upstart 报告 bash 脚本的 PID)。您仍然可以使用stop my_app
或restart my_app
,但它们不会执行任何优雅的操作。
答案3
根据upstart
文档,您可以通过在块中说明来告诉 upstart 不要向服务发送TERM
和信号。您所要做的就是在您的块中说明,它将中止信号发送。KILL
pre-stop
start
pre-stop
结合 Bryan 上述的技巧,他发现 ascript
也可以包含一个无限循环,而不是实际的独角兽进程——我创建了一个使用此技术的示例。
这个例子相当简单,它处理USR2
运行 upstart 时的发送stop unicorn
。如果由于某种原因所有独角兽都自行死亡,它还会重生一只新的独角兽。
测试的代码和输出在这里 -https://gist.github.com/kesor/6255584