web-dev-qa-db-ja.com

Upstartを使用してUnicornw / rbenv + bundler binstubs w / Ruby-local-execshebangを管理する

さて、これは私の脳を溶かしています。それは、私が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で停止したりすることはできなくなりました。

Unicornをデーモン化するためにUnicorn行に-Dを追加しようとして構成ファイルをいじってみました(このように:$APP_ROOT/bin/Unicorn -c $APP_ROOT/config/Unicorn.rb -E production -D)、そしてexpect daemon行を追加しましたが、それは機能しませんでしたどちらか。 expect forkも試しました。これらすべてのさまざまな組み合わせにより、startstopがハングする可能性があり、Upstartはジョブの状態について本当に混乱します。次に、マシンを再起動して修正する必要があります。

Ruby-local-execスクリプトでrbenv + $APP_ROOT/bin/Unicorn Shebangを使用しているため、Upstartで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の能力を真剣に疑っています。

私がやろうとしていることは可能ですか?私の理解では、Upstartのexpectスタンザは、(daemonまたはforkを介して)最大2つのフォークを期待するようにしか指示できません。

5
codykrieger

実際、upstartの制限は、Unicornが実行していることを実行するデーモンを追跡できないことです。つまり、fork/execであり、メインプロセスを終了します。信じられないかもしれませんが、sshdはSIGHUPでも同じことを行います。見てみると、/ etc/init /ssh.confはsshdがフォアグラウンドで実行されていることを確認します。これは、Apache2がまだinit.dスクリプトを使用している理由の1つでもあります。

Gunicornは、フォークして終了することでSIGUSR1を受信すると、実際には一種のデーモン化を行うようです。これは、プロセスを存続させようとするプロセスマネージャーにとって混乱を招くでしょう。

2つの選択肢があると思います。 1は、SIGUSR1を使用せず、必要なときにgunicornを停止/開始することです。

もう1つのオプションは、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/execとは何の関係もありません。どちらも通常の実行可能ファイルを実行するのと同じように機能するため、余分なフォークは発生しません)。

3
SpamapS

SpamapSとは少し異なるソリューションを選択しました。また、Upstartによって管理されるpreload_app = trueでアプリを実行しています。

この問題を自分で解決しようとしていたときは、Upstartの「exec」を使用してアプリを起動していました(「execbundle exec Unicorn_Rails blahblah」)。次に、あなたの質問を見つけました。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シグナルを古いマスターに送信して古いワーカーをオフにし(正常に)、ワーカーが1つだけ残ったら終了します。

そのQUITにより、古いマスターが終了します(ただし、ロードを処理している新しいワーカーが存在する場合のみ)。そのため、「bundleexecUnicorn_Rails」がUnicornスクリプトで返されます。次に、そのスクリプトはループし、既存のpidfileを確認し、プロセスが終了するのを待ちます。次の展開まで終了しませんが、終了した場合は再度ループします。また、マスターが死ぬたびに再びループします。

Bashスクリプト自体が停止した場合、Upstartはスクリプトを再起動します。これは、bashスクリプトが監視しているプロセスであるためです(status my_app --UpstartはbashスクリプトのPIDを報​​告します。あなたはまだすることができます stop my_app、またはrestart my_app、それは優雅なことを何もしません。

7
Bryan Stearns

upstartのドキュメントによると、pre-stopブロックでそのように言うことで、upstartにTERMおよびKILLシグナルをサービスに送信しないように指示できます。 pre-stopstartと言うだけで、信号の送信が中止されます。

scriptにも無限ループが含まれ、実際のユニコーンプロセスではない可能性があることを理解したブライアンによる上記のトリックと一緒に、この手法を使用して機能する例を作成しました。

この例は非常に単純で、upstart USR2の実行時にstop Unicornの送信を処理します。また、何らかの理由ですべてのユニコーンが自力で死亡した場合は、新しいユニコーンがリスポーンします。

テストのコードと出力はこちら- https://Gist.github.com/kesor/6255584

0
Evgeny