使用 Upstart 管理 Unicorn w/rbenv + bundler binstubs w/ruby-local-exec shebang

cod*_*ger 5 ruby ruby-on-rails upstart unicorn

好吧,这是在融化我的大脑。这可能与我不太了解 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
Run Code Online (Sandbox Code Playgroud)

这工作得很好 - 独角兽开始很棒。不好的是,检测到的 PID 不是 Unicorn master 的,而是一个sh进程。这本身也不是那么糟糕 - 如果我没有使用自动独角兽零停机部署策略。因为在我发送-USR2给我的独角兽主人后不久,一个新主人出现了,而旧主人死了……这个sh过程也是如此。所以 Upstart 认为我的工作已经死了,如果我愿意,我不能再用它重新启动restart或停止它stop

我玩过配置文件,尝试将 -D 添加到 Unicorn 行(如下所示$APP_ROOT/bin/unicorn -c $APP_ROOT/config/unicorn.rb -E production -D:)来守护 Unicorn,然后我添加了该expect daemon行,但这也不起作用。我也试过expect fork了。所有这些事情的各种组合都可能导致startstop挂起,然后 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')
Run Code Online (Sandbox Code Playgroud)

此外,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 "$@"
Run Code Online (Sandbox Code Playgroud)

所以有一个exec我担心的地方。它启动了一个 Ruby 进程,它启动了 Unicorn,它可能会也可能不会自我守护,这一切都发生在一个sh进程中……这让我严重怀疑 Upstart 跟踪所有这些废话的能力。

我正在尝试做的甚至可能吗?据我所知,expectUpstart 中的节只能被告知(通过daemonfork)预计最多有两个叉子。

Bry*_*rns 7

我从 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
Run Code Online (Sandbox Code Playgroud)

我的 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
Run Code Online (Sandbox Code Playgroud)

所以:在启动时,Upstart 脚本找不到 pidfile,所以它运行 unicorn_rails,它一直在运行。

稍后,我们重新部署我们的应用程序,Capistrano 任务通过以下方式触发应用程序重启:

kill -USR2 `cat /var/www/my_app/shared/pids/unicorn.pid`
Run Code Online (Sandbox Code Playgroud)

这告诉旧的 Unicorn master 开始一个新的 Unicorn master 进程,当新的 master 启动 worker 时,Unicorn before_fork 块向旧的 master 发送 TTOU 信号以关闭旧的 worker(优雅地),然后在只剩下一个 worker 时退出.

QUIT 会导致旧的 master 退出(但只有在有新的 worker 已经处理负载时才会退出),因此“bundle exec unicorn_rails”在 unicorn 脚本中返回。该脚本然后循环,查看现有的 pidfile,并等待进程退出。直到下一次部署它才会退出,但如果它退出,我们将再次循环;只要主人死了,我们也会再次循环。

如果bash脚本本身死亡,新贵将重新启动它,因为这是它看(你看,如果你做的过程status my_app-新贵报告bash脚本的PID你还是可以stop my_app,或者restart my_app,它没有做任何的婉约东西。


小智 3

事实上,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`
Run Code Online (Sandbox Code Playgroud)

不像 pid 跟踪那么性感,但至少您不必编写整个 init.d 脚本。

(顺便说一句,这与 shebangs/execs 无关。这两个东西的工作方式就像运行常规可执行文件一样,因此它们不会导致任何额外的分叉)。