使用 Upstart 管理 Unicorn(带有 rbenv + bundler binstubs 和 ruby​​-local-exec shebang)

使用 Upstart 管理 Unicorn(带有 rbenv + bundler binstubs 和 ruby​​-local-exec shebang)

好吧,这真是让我头疼。这可能与我对 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 是否有能力跟踪所有这些无用的东西。

我试图做的事情是否可行?据我了解,expectUpstart 中的节只能通过daemonfork来指示最多预期两个分叉。

答案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_apprestart my_app,但它们不会执行任何优雅的操作。

答案3

根据upstart文档,您可以通过在块中说明来告诉 upstart 不要向服务发送TERM和信号。您所要做的就是在您的块中说明,它将中止信号发送。KILLpre-stopstartpre-stop

结合 Bryan 上述的技巧,他发现 ascript也可以包含一个无限循环,而不是实际的独角兽进程——我创建了一个使用此技术的示例。

这个例子相当简单,它处理USR2运行 upstart 时的发送stop unicorn。如果由于某种原因所有独角兽都自行死亡,它还会重生一只新的独角兽。

测试的代码和输出在这里 -https://gist.github.com/kesor/6255584

相关内容