reloadできるunicorn+railsのDockerイメージ

reloadできるunicorn+railsのDockerイメージを作れそうだったので作ってみた。

以下、登場人物。

Dockerfile

FROM ubuntu:xenial
MAINTAINER Genki Sugawara <sgwr_dts@yahoo.co.jp>

USER root
WORKDIR /

RUN apt-get update
RUN apt-get install -y ruby
RUN apt-get install -y ruby-dev
RUN apt-get install -y build-essential
RUN apt-get install -y zlib1g-dev
RUN apt-get install -y libxml2-dev
RUN apt-get install -y libsqlite3-dev
RUN apt-get install -y nodejs
RUN gem install rails
RUN rails new hello --skip-bundle
RUN sed -i '/puma/d' hello/Gemfile
RUN echo 'gem "unicorn"' >> hello/Gemfile
RUN bundle config --global silence_root_warning 1
RUN cd /hello && bundle
ADD unicorn.conf.rb /hello/config/

ADD init.sh /
RUN chmod +x /init.sh

EXPOSE 8080

CMD ["/init.sh"]

init.sh

#!/bin/bash
RUNNING=1

function unicorn_pid() {
  cat /hello/tmp/pids/unicorn.pid
}

function stop() {
  SIGNAL=$1
  PID=$(unicorn_pid)
  send_signal $SIGNAL

  for i in {1..60}; do
    ps -eo pid | egrep -q "^ +$PID$"
    [ $? -ne 0 ] && break
    echo -n .
    sleep 1
  done

  RUNNING=0
}

function send_signal() {
  SIGNAL=$1
  kill -$SIGNAL $(unicorn_pid)
}

for SIGNAL in $(trap -l | awk -v RS='[\t\n]' '$0 != ""{print $2}'); do
  case $SIGNAL in
    SIGINT | SIGKILL | SIGTERM)
      trap "stop $SIGNAL" $SIGNAL
      ;;
    SIGWINCH)
      ;;
    *)
      trap "send_signal $SIGNAL" $SIGNAL
      ;;
  esac
done

cd /hello
bundle exec unicorn_rails -c config/unicorn.conf.rb -E production -D

while true; do
  if [ $RUNNING -eq 0 ]; then
    break;
  fi

  sleep 1
done

echo unicorn has stopped

unicorn.conf.rb

rails_root = File.expand_path('../..', __FILE__)

worker_processes 4

listen 8080, :tcp_nopush => true

timeout 30

pid "#{rails_root}/tmp/pids/unicorn.pid"

stderr_path "#{rails_root}/log/unicorn.stderr.log"
stdout_path "#{rails_root}/log/unicorn.stdout.log"

preload_app true

check_client_connection false

run_once = true

before_fork do |server, worker|
  defined?(ActiveRecord::Base) and
    ActiveRecord::Base.connection.disconnect!

  if run_once
    run_once = false
  end

  old_pid = "#{server.config[:pid]}.oldbin"
  if old_pid != server.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
    end
  end
end

after_fork do |server, worker|
  defined?(ActiveRecord::Base) and
    ActiveRecord::Base.establish_connection
end

reloadしてみる

まずdocker build

$ docker build -t app .

起動して中身を確認。

$ docker run --name my-app app
$ docker exec my-app ps awxuf
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root       123  0.0  0.1  34424  2872 ?        Rs   18:32   0:00 ps awxuf
root         1  0.1  0.1  18056  2848 ?        Ss   18:31   0:00 /bin/bash /init.sh
root        14  3.9  3.5 195808 72116 ?        Sl   18:31   0:02 unicorn_rails master -c config/unicorn.conf.rb -E production -D
root        17  0.0  3.2 195808 67080 ?        Sl   18:31   0:00  \_ unicorn_rails worker[0] -c config/unicorn.conf.rb -E production -D
root        20  0.0  3.2 195808 67080 ?        Sl   18:31   0:00  \_ unicorn_rails worker[1] -c config/unicorn.conf.rb -E production -D
root        23  0.0  3.2 195808 67016 ?        Sl   18:31   0:00  \_ unicorn_rails worker[2] -c config/unicorn.conf.rb -E production -D
root        26  0.0  3.2 195808 67080 ?        Sl   18:31   0:00  \_ unicorn_rails worker[3] -c config/unicorn.conf.rb -E production -D
root       122  0.0  0.0   4380   708 ?        S    18:32   0:00 sleep 1

masterのpidは14

reloadしてみる。

$ docker kill -s USR2 my-app
my-app
$ docker exec my-app ps awxuf
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root       231  0.0  0.1  34424  2944 ?        Rs   18:33   0:00 ps awxuf
root         1  0.0  0.1  18060  2860 ?        Ss   18:31   0:00 /bin/bash /init.sh
root       182  7.2  3.5 195812 73632 ?        Sl   18:32   0:02 unicorn_rails master -c config/unicorn.conf.rb -E production -D
root       188  0.0  3.3 195812 68044 ?        Sl   18:32   0:00  \_ unicorn_rails worker[0] -c config/unicorn.conf.rb -E production -D
root       191  0.0  3.2 195812 67336 ?        Sl   18:32   0:00  \_ unicorn_rails worker[1] -c config/unicorn.conf.rb -E production -D
root       194  0.0  3.2 195812 67340 ?        Sl   18:32   0:00  \_ unicorn_rails worker[2] -c config/unicorn.conf.rb -E production -D
root       197  0.0  3.2 195812 67344 ?        Sl   18:32   0:00  \_ unicorn_rails worker[3] -c config/unicorn.conf.rb -E production -D
root       230  0.0  0.0   4380   676 ?        S    18:33   0:00 sleep 1

masterのpidは182。 pidが変わっていることを確認。

SIGTERMでgracefulな終了。

$ docker kill -s TERM my-app
$ docker run --name my-app app
.unicorn has stopped