9

好吧,这正在融化我的大脑。这可能与我不太了解 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 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中使用了 rbenv + shebang 。$APP_ROOT/bin/unicorn这里是:

#!/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 跟踪所有这些废话的能力。

我想要做的甚至可能吗?据我了解,expectUpstart 中的节只能被告知(通过daemonfork)期望最多两个分叉。

4

1 回答 1

15

您的新贵工作需要进行配置,以便新贵确切知道它分叉了多少次。而且它只能分叉一次或两次,不能再分叉了。

在 unix 领域,有两个关键的系统调用有助于程序运行:forkexec.

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

exec运行一个新程序,替换调用exec.

当您简单地在 shell 中运行命令时,shell 会在后台调用fork以创建一个具有自己 id 的新进程,并且该新进程(经过一些设置后)会立即调用exec以启动您键入的命令。这就是大多数程序的运行方式,无论是通过 shell 还是您的窗口管理器或其他方式。请参阅 C 中的系统函数,它在大多数脚本语言中也有变体。

如果你认为它效率低下,那你可能是对的。这就是从昔日以来在 unix 中的做法,而且显然没有人愿意改变它。原因之一是在 上没有替换很多东西exec,包括(有时)打开的文件,以及进程的用户和组 ID。

另一个原因是已经花费了很多精力来提高fork效率,而且他们实际上做得很好——在现代 unix 中(在 CPU 的帮助下)fork实际上只复制了很少的过程。我想没有人愿意放弃所有这些工作。

并且,(暂停生效)进程 pid。

展示:

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 的变体,包括 shell、C、perl、ruby 和 python。但不是java。

因此,考虑到所有这些,你需要做的是确保你的新贵工作正常进行,确保它分叉的次数与新贵认为的相同。

ruby-local-exec 中的exec行实际上是一件好事,它可以防止分叉。也load不会启动新进程,它只是将代码加载到现有的 ruby​​ 解释器中并运行它。

但是,您的 shell 脚本在这一行中分叉:

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

为了防止这种情况,您可以将其更改为

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

如果你这样做,AFAICT 独角兽根本不应该分叉,而且你不需要告诉暴发户期待分叉。

于 2012-04-07T06:22:12.457 回答