Mot-clé - init

Fil des billets

samedi 22 octobre 2016

Init script for Rails app served by Unicorn with RVM

Here is an init script for a Rails app served by Unicorn with RVM.

#!/bin/bash
### BEGIN INIT INFO
# Provides:          APP_NAME, with Unicorn serving
# Required-Start:    $all
# Required-Stop:     $network $local_fs $syslog
# Default-Start:     2 3 4 5
# Default-Stop:      0 1 6
# Short-Description: Start APP_NAME unicorn at boot
# Description:       Enable APP_NAME at boot time.
### END INIT INFO

set -u
set -e

# Change these to match your app:
APP_NAME="APP_NAME"
APP_ROOT="/path/to/app"
PID="/path/to/app/tmp/pids/unicorn.pid"
ENV="production"
RVM="2.3.0@gemset"
USER="user"

UNICORN_OPTS="-D -E $ENV -c $APP_ROOT/config/unicorn.rb"

SET_PATH="cd $APP_ROOT; rvm use $RVM > /dev/null;"
CMD="$SET_PATH unicorn $UNICORN_OPTS"

old_pid="$PID.oldbin"

cd $APP_ROOT || exit 1

sig () {
	test -s "$PID" && kill -$1 `cat $PID`
}

oldsig () {
	test -s $old_pid && kill -$1 `cat $old_pid`
}

start () {
        echo "Starting $APP_NAME unicorn..."
        sig 0 && echo -e >&2 "\e[31mAlready running" && exit 0
        su - $USER -c "$CMD" > /dev/null
        echo -e "$APP_NAME has \e[32mstarted\e[0m, PID is `cat $PID`"
}

stop () {
        echo "Stopping $APP_NAME unicorn (signal QUIT)..."
        sig QUIT && echo -e "$APP_NAME has \e[32mstopped" && exit 0
        echo >&2 "Not running"
}

case ${1-help} in
start)
	start
	;;
stop)
	stop
	;;
force-stop)
	echo "Force stopping $APP_NAME unicorn (signal TERM)..."
	sig TERM && echo "$APP_NAME has stopped" &&exit 0
	echo -e >&2 "\e[31mNot running"
	;;
reload)
	echo "Reloading $APP_NAME unicorn (signal USR2)..."
	sig USR2 && echo -e "$APP_NAME has \e[32mreloaded" && exit 0
	echo -e >&2 "\e[31mCouldn't reload\e[0m, starting instead"
	start
	;;
status)
	sig 0 && echo -e "$APP_NAME \e[32mrunning\e[0m with PID `cat $PID`" && exit 0
	echo -e "$APP_NAME is \e[31mnot running!"
	;;
*)
 	echo >&2 "Usage: $0 <start|stop|reload|status|force-stop>"
 	exit 0
 	;;
esac

Beware that the reload calls USR2. In the documentation of Unicorn, it is said that USR2 should be followed by QUIT. Otherwise the good signal to reload is HUP.

However, I use the following unicorn.rb config file inspired from Github's and you will see that it specifies that the new instance of Unicorn shall send a QUIT signal to the old one!

rails_root = "/path/to/app"
rails_env = ENV['RAILS_ENV'] || 'production'

# 4 workers, may be changed to 1 for the tests
worker_processes (rails_env == 'production' ? 4 : 1) 

# Load rails+app into the master before forking workers
# for super-fast worker spawn times
preload_app true

# Restart any workers that haven't responded in 30 seconds
timeout 30

before_fork do |server, worker|
  ##
  # When sent a USR2, Unicorn will suffix its pidfile with .oldbin and
  # immediately start loading up a new version of itself (loaded with a new
  # version of our app). When this new Unicorn is completely loaded
  # it will begin spawning workers. The first worker spawned will check to
  # see if an .oldbin pidfile exists. If so, this means we've just booted up
  # a new Unicorn and need to tell the old one that it can now die. To do so
  # we send it a QUIT.
  # Using this method we get 0 downtime deploys.

  old_pid = rails_root + '/tmp/pids/unicorn.pid.oldbin'
  if File.exists?(old_pid) && server.pid != old_pid
    begin
      Process.kill("QUIT", File.read(old_pid).to_i)
    rescue Errno::ENOENT, Errno::ESRCH
      # someone else did our job for us
    end
  end
end

after_fork do |server, worker|
  # Unicorn master loads the app then forks off workers - because of the way
  # Unix forking works, we need to make sure we aren't using any of the parent's
  # sockets, e.g. db connection
  ActiveRecord::Base.establish_connection
end