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

好吧,这正在融化我的大脑。 这可能与我不了解暴发户的事实有关。 很抱歉提出长期的问题。

我试图用Upstart来pipe理一个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 

这很好 – 独angular兽开局很好。 不好的是,检测到的PID不是独angular兽的主人,这是一个过程。 如果我不使用自动化Unicorn零宕机时间部署策略,那么本身并不是那么糟糕。 因为不久之后,我发送-USR2到我的独angular兽主,一个新的主人产生,而旧的主人死亡…这个过程也是如此。 所以Upstart认为我的工作已经死了,我不能再重新启动它,或者如果我想stop

我玩过configuration文件,尝试添加-D到Unicorn产品线(像这样: $APP_ROOT/bin/unicorn -c $APP_ROOT/config/unicorn.rb -E production -D )来守护Unicorn,我添加了expect daemon ,但是这也不起作用。 我试过expect fork 。 所有这些事情的各种组合可以导致startstop挂起,然后暴发户真的很困惑的工作状态。 然后我必须重新启动机器来修复它。

我认为Upstart在检测Unicorn是否分叉时会遇到问题,因为我在$APP_ROOT/bin/unicorn脚本中使用rbenv + ruby-local-exec 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 eg 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来告诉最多期望两个叉。

事实上,新贵的局限性在于,它不能跟踪那些做独angular兽的守护进程,即作为fork / exec并退出主进程。 信不信由你,sshd在SIGHUP上做同样的事情,如果你看,/etc/init/ssh.conf确保sshd在前台运行。 这也是apache2仍然使用init.d脚本的原因之一。

听起来像gunicorn实际上有一种守护自己当SIGUSR1分叉,然后退出。 这会让那些试图保持stream程活跃的stream程pipe理者感到困惑。

我想你有两个select。 1只是不使用SIGUSR1,并在需要时停止/启动gunicorn。

另一个select是不使用新贵的PID跟踪,只是这样做:

 start on .. stop on .. pre-start exec gunicorn -D --pid-file=/run/gunicorn.pid post-stop exec kill `cat /run/gunicorn.pid` 

不像pid跟踪那么性感,但至less你不需要编写一个完整的init.d脚本。

(顺便说一句,这与shebangs / execs无关,这两个东西就像运行一个普通的可执行文件一样工作,所以它们不会造成任何额外的分支)。

我从SpamapS中select了一个不同的解决scheme..我也运行一个preload_app = true的应用程序,由Upstartpipe理。

当我自己想要解决这个问题时,我一直在使用Upstart的“exec”来启动我的应用程序(“exec bundle exec unicorn_rails blah blah”)。 然后我发现你的问题,这让我意识到,不是使用Upstart的“exec”来指定我的可执行文件,而是使用一个脚本节,这个脚本节将在自己的进程中运行,即Upstart会观看的进程。

所以,我的Upstartconfiguration文件包含这个:

 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 

我的Unicornconfiguration文件中的before_fork与独angular兽网站http://unicorn.bogomips.org/examples/unicorn.conf.rb中的示例中的build议一样:

 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脚本没有findpidfile,所以它运行unicorn_rails,它继续运行。

稍后,我们重新部署我们的应用程序,并通过Capistrano任务触发应用程序重新启动:

 kill -USR2 `cat /var/www/my_app/shared/pids/unicorn.pid` 

这告诉旧的独angular兽主人开始一个新的独angular兽主进程,并且当新主人启动工人时,独angular兽before_fork块向旧主人发送TTOU信号以closures老工人(优雅地),然后退出,只有一个工人离开。

QUIT会导致旧的主服务器退出(但是只有在新服务器已经处理了负载的情况下),所以“bundle exec unicorn_rails”返回到独angular兽脚本中。 该脚本然后循环,看到现有的pidfile,并等待进程退出。 它将不会退出,直到下一次部署,但如果是的话,我们将再次循环; 每当主人死亡,我们也会再循环。

如果bash脚本本身死了,Upstart会重新启动它,因为这是它正在监视的过程(就像你看到的是你的status my_app – Upstart报告了bash脚本的PID,你仍然可以stop my_app ,或者restart my_app ,没有做任何优雅的东西。

根据upstart文件,你可以先告诉你不要在你的pre-stop区块中发送TERMKILL信号给服务。 所有你需要做的就是说start你的pre-stop ,它会中止信号发送。

再加上布赖恩的上述技巧,他发现一个script也可以包含一个无限循环,而不是实际的独angular兽进程 – 我创build了一个使用这种技术的例子。

这个例子非常简单,它运行新贵的stop unicorn时会处理发送USR2 。 如果因为某种原因,独angular兽自己死亡,它也会重生一个新的独angular兽。

代码和testing的输出在这里 – https://gist.github.com/kesor/6255584