Назад | Перейти на главную страницу

Использование Upstart для управления Unicorn w / rbenv + binstubs Bundler w / ruby-local-exec shebang

Хорошо, это плавит мой мозг. Возможно, это связано с тем, что я не понимаю Upstart так хорошо, как следовало бы. Заранее извиняюсь за длинный вопрос.

Я пытаюсь использовать Upstart для управления основным процессом Unicorn в приложении Rails. Вот мой ток /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 также. Различные комбинации всего этого могут вызвать start и stop зависнуть, и тогда Upstart действительно запутается в состоянии задания. Затем мне нужно перезагрузить компьютер, чтобы это исправить.

Я думаю, что у Upstart возникают проблемы с обнаружением, когда / если Unicorn разветвляется, потому что я использую rbenv + ruby-local-exec 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 отслеживать всю эту ерунду.

Возможно ли то, что я пытаюсь сделать? Насколько я понимаю, expect строфу в Upstart можно сказать только (через daemon или fork) рассчитывать максимум на две вилки.

Я выбрал немного другое решение от SpamapS .. Я также запускаю приложение с preload_app = true, управляемое Upstart.

Когда я сам искал решение этой проблемы, я использовал "exec" Upstart для запуска своего приложения ("exec bundle exec unicorn_rails бла-бла"). Затем я нашел ваш вопрос и заставил меня понять, что вместо использования «exec» Upstart для указания моего исполняемого файла я мог бы использовать раздел сценария, который будет запускаться в собственном процессе, процесс, который 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

Before_fork в моем конфигурационном файле Unicorn такой же, как и в примере с сайта 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 старому мастеру, чтобы отключить старые рабочие (изящно), а затем ВЫЙТИ, когда останется только один рабочий .

Этот QUIT приводит к завершению работы старого мастера (но только после того, как новые рабочие уже обрабатывают нагрузку), поэтому "bundle exec unicorn_rails" возвращается в скрипте единорога. Затем этот сценарий зацикливается, видит существующий файл pid и ожидает завершения процесса. Он не выйдет до следующего развертывания, но, если это произойдет, мы вернемся снова; мы также будем повторять цикл каждый раз, когда мастер умирает.

Если сам сценарий bash умирает, Upstart перезапускает его, потому что это процесс, который он наблюдает (как вы видите, если когда-нибудь status my_app - Upstart сообщает PID сценария bash. Вы все еще можете stop my_app, или restart my_app, которые не делают ничего изящного.

В самом деле, ограничение выскочки состоит в том, что он не может отслеживать демонов, которые делают то, что делает единорог ... то есть fork / exec и завершают свой основной процесс. Вы не поверите, но sshd делает то же самое с SIGHUP, и, если вы посмотрите, /etc/init/ssh.conf гарантирует, что sshd работает на переднем плане. Это также одна из причин, по которой apache2 все еще использует сценарий init.d.

Похоже, что Gunicorn фактически демонизирует себя при получении SIGUSR1 путем разветвления и последующего выхода. Это могло бы сбить с толку любого менеджера процессов, который пытается сохранить процесс.

Я думаю, у вас есть два варианта. 1 - просто не использовать SIGUSR1 и останавливать / запускать пулемет, когда он вам нужен.

Другой вариант - не использовать отслеживание 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 / execs. Обе эти вещи работают так же, как запуск обычного исполняемого файла, поэтому они не вызывают дополнительных форков).

Согласно upstart документации, вы можете сказать выскочке не отправлять TERM и KILL сигнализирует службе, сказав это в своем pre-stop блок. Все, что тебе нужно сделать, это сказать start в твоем pre-stop и он прервет отправку сигнала.

Вместе с трюком Брайана, который выяснил, что script также может включать бесконечный цикл и не быть фактическим процессом единорога - я создал пример, который работает с использованием этой техники.

Пример довольно простой, он обрабатывает отправку USR2 при запуске выскочки stop unicorn. И он также возродит нового единорога, если по какой-то причине все единороги умерли сами по себе.

Код и вывод тестов здесь - https://gist.github.com/kesor/6255584