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

时间:2011-12-29 11:41:35

标签: ruby-on-rails ruby unicorn upstart

好吧,这正在融化我的大脑。这可能与我不理解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

这很好 - 独角兽开局很棒。不太好的是,检测到的PID不是Unicorn主机,而是sh进程。这本身并不是那么糟糕 - 如果我没有使用自动化的Unicorn零停机时间部署策略。因为我发送-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脚本中使用了rbenv + $APP_ROOT/bin/unicorn 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进程,它启动了Unicorn,它可能会或可能不会自己进行守护,这一切都是从sh进程发生的......这让我严重怀疑Upstart跟踪所有进程的能力这个废话。

我正在尝试做甚么可能吗?根据我的理解,Upstart中的expect节只能告诉(通过daemonfork),最多可以预期两个叉子。

1 个答案:

答案 0 :(得分:15)

需要配置您的新贵作业,以便新贵真正知道它分叉的次数。并且它只能分叉一次或两次,不再需要。

在unix land中,有两个关键系统调用可以帮助运行程序:forkexec

fork复制调用它的进程。一个进程调用fork,并将控制权返回给两个进程。每个进程必须从fork返回的值中识别它(父对象或子对象)(有关详细信息,请参见手册页)。

exec运行一个新程序,替换名为exec的进程。

当您在shell中运行命令时,shell会调用fork来创建一个具有自己的id的新进程,并且该新进程(在某些设置之后)会立即调用{{1} }启动您键入的命令。这是大多数程序运行的方式,无论是shell还是窗口管理器或其他任何程序。   请参阅C中的system函数,该函数在大多数脚本语言中也有变体。

如果您认为效率低下,那么您可能是对的。这就是自从以前几天在unix中完成它的方式,而且很明显没人会改变游戏。其中一个原因是exec上有许多被替换的东西,包括(有时)打开的文件,以及进程的用户和组ID。

另一个原因是,在现代的unix(在CPU的帮助下)exec已经花了很多精力来使fork有效,并且他们实际上已经做得非常好了。实际上很少复制过程。我想没有人愿意抛弃所有这些工作。

并且(暂停效果)进程pid。

演示:

fork

大多数流行语言都有fork和exec的变体,包括shell,C,perl,ruby和python。但不是java。

因此,考虑到所有这些,您需要做的是让新手工作成功,确保它与upstart认为的相同次数。

ruby​​-local-exec中的mslade@mickpc:~$ echo $$ 3652 mslade@mickpc:~$ bash mslade@mickpc:~$ echo $$ 6545 mslade@mickpc:~$ exec bash mslade@mickpc:~$ echo $$ 6545 mslade@mickpc:~$ exit exit mslade@mickpc:~$ echo $$ 3652 行实际上是一件好事,它可以防止fork。另外exec不会启动新进程,它只是将代码加载到现有的ruby解释器中并运行它。

但是你的shell脚本在这行中分叉:

load

要防止这种情况,您只需将其更改为

即可
$APP_ROOT/bin/unicorn -c $APP_ROOT/config/unicorn.rb -E production # >> /tmp/upstart.log 2>&1

如果你这样做,AFAICT独角兽根本就不应该分叉,你也不需要告诉新贵要求叉子。