diff --git a/unicorn+daemontools/Dockerfile b/unicorn+daemontools/Dockerfile new file mode 100644 index 0000000..ef8a3dc --- /dev/null +++ b/unicorn+daemontools/Dockerfile @@ -0,0 +1,26 @@ +FROM ruby:2.7.4 + +RUN apt-get update -qq && apt-get install wget && apt-get install psmisc + +WORKDIR /sample +COPY ["./src/Gemfile", "./"] + +RUN gem install bundler --version 1.17.2 + +# See: http://cr.yp.to/daemontools/install.html +# See: http://cr.yp.to/daemontools/start.html +# See: http://thedjbway.b0llix.net/daemontools/installation.html +RUN mkdir -p /package && chmod 1755 /package && cd /package && \ + wget http://cr.yp.to/daemontools/daemontools-0.76.tar.gz && gunzip daemontools-0.76.tar && tar -xpf daemontools-0.76.tar && rm -f daemontools-0.76.tar && \ + cd admin/daemontools-0.76 && \ + wget http://thedjbway.b0llix.net/patches/daemontools-0.76.sigq12.patch && patch -t -p1 < ./daemontools-0.76.sigq12.patch && \ + wget http://qmail.org/moni.csi.hu/pub/glibc-2.3.1/daemontools-0.76.errno.patch && patch -t -p1 < ./daemontools-0.76.errno.patch && \ + package/install && \ + mkdir -p /service && chmod 755 /service + +COPY ["./run", "/service/sample_server/run"] + +RUN mkdir -p /service/sample_server/log && echo "#!/bin/sh\\nexec multilog t ./main" >> /service/sample_server/log/run && chmod 755 /service/sample_server/log/run && \ + chmod +t /service/sample_server + +COPY . /sample diff --git a/unicorn+daemontools/README.md b/unicorn+daemontools/README.md new file mode 100644 index 0000000..541b18c --- /dev/null +++ b/unicorn+daemontools/README.md @@ -0,0 +1,30 @@ +# Playground: Unicorn Graceful Restart under daemontools + +```sh +docker image build --file ./Dockerfile --tag sample_unicorn_daemontools:t01 --rm . +docker container run -it --rm sample_unicorn_daemontools:t01 /bin/bash +``` + +```bash +/command/svscanboot & + +pstree --show-pids +ps -f -o user,pid,ppid,start,command --forest + +svstat /service/sample_server/ + +curl localhost:8080 -w "\n" + +# Graceful Restart +svc -2 /service/sample_server # SIGUSR2 + QUIT to old master. See unicorn.conf.rb +pstree --show-pids +ps -f -o user,pid,ppid,start,command --forest + +svstat /service/sample_server/ +svstat /service/sample_server/ +svstat /service/sample_server/ # => pid が毎回変わってることが確認できる + +cat unicorn_stderr.log # => エラーで永遠に失敗してるのがわかる + +tai64nlocal < /service/sample_server/log/main/current +``` diff --git a/unicorn+daemontools/run b/unicorn+daemontools/run new file mode 100755 index 0000000..04c7c3f --- /dev/null +++ b/unicorn+daemontools/run @@ -0,0 +1,9 @@ +#!/bin/sh + +exec 2>&1 \ +sh -c ' + cd /sample + bundle install + exec bundle exec unicorn ./src/config.ru --config-file ./src/unicorn.conf_with_fork_hook.rb +' + diff --git a/unicorn+daemontools/src/Gemfile b/unicorn+daemontools/src/Gemfile new file mode 100644 index 0000000..7745df1 --- /dev/null +++ b/unicorn+daemontools/src/Gemfile @@ -0,0 +1,4 @@ +source 'https://rubygems.org' + +gem 'unicorn', '~> 6.0' +gem 'rack', '~> 2.2', '>= 2.2.3' diff --git a/unicorn+daemontools/src/config.ru b/unicorn+daemontools/src/config.ru new file mode 100644 index 0000000..def6b2b --- /dev/null +++ b/unicorn+daemontools/src/config.ru @@ -0,0 +1,4 @@ +# coding: utf-8 + +require_relative 'sample.rb' +run Sample.new diff --git a/unicorn+daemontools/src/sample.rb b/unicorn+daemontools/src/sample.rb new file mode 100644 index 0000000..797d41f --- /dev/null +++ b/unicorn+daemontools/src/sample.rb @@ -0,0 +1,9 @@ +class Sample + def call(env) + [ + 200, + { 'Content-Type' => 'text/html' }, + ['ok'], + ] + end +end diff --git a/unicorn+daemontools/src/unicorn.conf_with_fork_hook.rb b/unicorn+daemontools/src/unicorn.conf_with_fork_hook.rb new file mode 100644 index 0000000..1eff280 --- /dev/null +++ b/unicorn+daemontools/src/unicorn.conf_with_fork_hook.rb @@ -0,0 +1,38 @@ +# See: https://github.com/defunkt/unicorn/blob/master/examples/unicorn.conf.minimal.rb +# See: https://github.com/defunkt/unicorn/blob/master/examples/unicorn.conf.rb + +worker_processes 2 + +stderr_path './unicorn_stderr.log' +stdout_path './unicorn_stdout.log' + +pid 'unicorn.pid' + +listen 8080, :tcp_nopush => true + +# See: https://github.com/defunkt/unicorn/blob/93e154e16b87f943a20fa720e002c67c9d17c30b/examples/unicorn.conf.rb#L71 +before_fork do |server, worker| + # The following is only recommended for memory/DB-constrained + # installations. It is not needed if your system can house + # twice as many worker_processes as you have configured. + + # This allows a new master process to incrementally + # phase out the old master process with SIGTTOU to avoid a + # thundering herd (especially in the "preload_app false" case) + # when doing a transparent upgrade. The last worker spawned + # will then kill off the old master process with a SIGQUIT. + 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 + + # Throttle the master from forking too quickly by sleeping. Due + # to the implementation of standard Unix signal handlers, this + # helps (but does not completely) prevent identical, repeated signals + # from being lost when the receiving process is busy. + sleep 1 +end diff --git a/unicorn+server-starter+daemontools/Dockerfile b/unicorn+server-starter+daemontools/Dockerfile new file mode 100644 index 0000000..328473a --- /dev/null +++ b/unicorn+server-starter+daemontools/Dockerfile @@ -0,0 +1,38 @@ +FROM ruby:2.6.2 + +WORKDIR /sample + +RUN apt-get update -qq && apt-get install -y wget psmisc gcc perlbrew + +# See: http://cr.yp.to/daemontools/install.html +# See: http://cr.yp.to/daemontools/start.html +# See: http://thedjbway.b0llix.net/daemontools/installation.html +RUN mkdir -p /package && chmod 1755 /package && cd /package && \ + wget http://cr.yp.to/daemontools/daemontools-0.76.tar.gz && gunzip daemontools-0.76.tar && tar -xpf daemontools-0.76.tar && rm -f daemontools-0.76.tar && \ + cd admin/daemontools-0.76 && \ + wget http://thedjbway.b0llix.net/patches/daemontools-0.76.sigq12.patch && patch -t -p1 < ./daemontools-0.76.sigq12.patch && \ + wget http://qmail.org/moni.csi.hu/pub/glibc-2.3.1/daemontools-0.76.errno.patch && patch -t -p1 < ./daemontools-0.76.errno.patch && \ + package/install && \ + mkdir -p /service && chmod 755 /service + +RUN mkdir -p /service/sample_server/log && echo "#!/bin/sh\\nexec multilog t ./main" >> /service/sample_server/log/run && chmod 755 /service/sample_server/log/run && \ + chmod +t /service/sample_server + +ENV PERLBREW_ROOT=/perl5 +RUN perlbrew available && perlbrew init && \ + echo ". ${PERLBREW_ROOT}/etc/bashrc" >> ~/.bashrc && \ + mkdir -p ${PERLBREW_ROOT}/perl-5.16.1 && mkdir -p ${PERLBREW_ROOT}/dists && mkdir -p ${PERLBREW_ROOT}/build/perl-5.16.1 && \ + perlbrew install perl-5.16.1 --notest && \ + echo ". ${PERLBREW_ROOT}/perls/perl-5.16.1/bin" >> /.bashrc && \ + echo ". ${PERLBREW_ROOT}/bin" >> /.bashrc && \ + perlbrew switch perl-5.16.1 && \ + perlbrew install-cpanm && \ + /perl5/bin/cpanm KAZUHO/Server-Starter-0.35.tar.gz --notest + +COPY ["./src/Gemfile", "./"] +COPY ["./src/Gemfile.lock", "./"] +RUN gem install bundler --version 1.17.3 && bundle install + +COPY ["./run", "/service/sample_server/run"] + +COPY . /sample diff --git a/unicorn+server-starter+daemontools/README.md b/unicorn+server-starter+daemontools/README.md new file mode 100644 index 0000000..96d45ab --- /dev/null +++ b/unicorn+server-starter+daemontools/README.md @@ -0,0 +1,306 @@ +# Playground: Unicorn Graceful Restart under p5-Server-Starter under daemontools + +```sh +docker image build --file ./Dockerfile --tag sample_unicorn_server-starter:t01 --rm . +docker container run -it --rm sample_unicorn_server-starter:t01 /bin/bash +``` + +```bash +/command/svscanboot & + +svc -h /service/sample_server # 即 HUP を打ってみる。bundle install を run から切り離せば待つ必要もなくなる。 + +svstat /service/sample_server/ + +pstree --show-pids +ps -f -o user,pid,ppid,start,command --forest + +cat unicorn_stderr.log +cat unicorn_stdout.log + +tai64nlocal < /service/sample_server/log/main/current +``` + +### 結果例 + +```log +root@47cd46c66784:/sample# tai64nlocal < /service/sample_server/log/main/current | grep start_server -A 10 +2022-02-18 08:31:23.237055500 start_server (pid:326) starting now... +2022-02-18 08:31:23.238142500 starting new worker 592 +2022-02-18 08:31:28.991692500 received HUP, spawning a new worker +2022-02-18 08:31:28.992160500 starting new worker 601 +2022-02-18 08:31:29.996245500 new worker is now running, sending QUIT to old workers:592 +2022-02-18 08:31:29.996245500 sleeping 100 secs before killing old workers +2022-02-18 08:33:09.933859500 killing old workers +2022-02-18 08:33:09.933953500 old worker 592 died, status:0 +``` + +```log +root@47cd46c66784:/sample# cat unicorn_stderr.log +I, [2022-02-18T08:31:23.461519 #592] INFO -- : inherited addr=/sample/unicorn.sock fd=4 +I, [2022-02-18T08:31:23.461608 #592] INFO -- : Refreshing Gem list +I, [2022-02-18T08:31:24.473761 #593] INFO -- : worker=0 ready +I, [2022-02-18T08:31:25.480121 #595] INFO -- : worker=1 ready +I, [2022-02-18T08:31:26.482959 #597] INFO -- : worker=2 ready +I, [2022-02-18T08:31:27.484877 #598] INFO -- : worker=3 ready +I, [2022-02-18T08:31:28.470886 #599] INFO -- : worker=4 ready +I, [2022-02-18T08:31:29.212151 #601] INFO -- : inherited addr=/sample/unicorn.sock fd=4 +I, [2022-02-18T08:31:29.212231 #601] INFO -- : Refreshing Gem list +I, [2022-02-18T08:31:29.472998 #602] INFO -- : worker=5 ready +I, [2022-02-18T08:31:30.223256 #604] INFO -- : worker=0 ready +I, [2022-02-18T08:31:30.474109 #605] INFO -- : worker=6 ready +I, [2022-02-18T08:31:31.229106 #606] INFO -- : worker=1 ready +I, [2022-02-18T08:31:31.476540 #607] INFO -- : worker=7 ready +I, [2022-02-18T08:31:32.231580 #608] INFO -- : worker=2 ready +I, [2022-02-18T08:31:32.478037 #609] INFO -- : worker=8 ready +I, [2022-02-18T08:31:33.233978 #610] INFO -- : worker=3 ready +I, [2022-02-18T08:31:33.479339 #611] INFO -- : worker=9 ready +I, [2022-02-18T08:31:34.236632 #612] INFO -- : worker=4 ready +I, [2022-02-18T08:31:34.480501 #613] INFO -- : worker=10 ready +I, [2022-02-18T08:31:35.244125 #614] INFO -- : worker=5 ready +I, [2022-02-18T08:31:35.483051 #615] INFO -- : worker=11 ready +I, [2022-02-18T08:31:36.248951 #616] INFO -- : worker=6 ready +I, [2022-02-18T08:31:36.485388 #617] INFO -- : worker=12 ready +I, [2022-02-18T08:31:37.250963 #618] INFO -- : worker=7 ready +I, [2022-02-18T08:31:37.487495 #619] INFO -- : worker=13 ready +I, [2022-02-18T08:31:38.253185 #620] INFO -- : worker=8 ready +I, [2022-02-18T08:31:38.488686 #621] INFO -- : worker=14 ready +I, [2022-02-18T08:31:39.255847 #623] INFO -- : worker=9 ready +I, [2022-02-18T08:31:39.490106 #624] INFO -- : worker=15 ready +I, [2022-02-18T08:31:40.258105 #625] INFO -- : worker=10 ready +I, [2022-02-18T08:31:40.491594 #626] INFO -- : worker=16 ready +I, [2022-02-18T08:31:41.261037 #627] INFO -- : worker=11 ready +I, [2022-02-18T08:31:41.493270 #628] INFO -- : worker=17 ready +I, [2022-02-18T08:31:42.263224 #629] INFO -- : worker=12 ready +I, [2022-02-18T08:31:42.494892 #630] INFO -- : worker=18 ready +I, [2022-02-18T08:31:43.265231 #631] INFO -- : worker=13 ready +I, [2022-02-18T08:31:43.496380 #632] INFO -- : worker=19 ready +I, [2022-02-18T08:31:44.267613 #633] INFO -- : worker=14 ready +I, [2022-02-18T08:31:44.497784 #634] INFO -- : worker=20 ready +I, [2022-02-18T08:31:45.269862 #636] INFO -- : worker=15 ready +I, [2022-02-18T08:31:45.499256 #637] INFO -- : worker=21 ready +I, [2022-02-18T08:31:46.272096 #638] INFO -- : worker=16 ready +I, [2022-02-18T08:31:46.500759 #639] INFO -- : worker=22 ready +I, [2022-02-18T08:31:47.274400 #640] INFO -- : worker=17 ready +I, [2022-02-18T08:31:47.502027 #641] INFO -- : worker=23 ready +I, [2022-02-18T08:31:48.276360 #642] INFO -- : worker=18 ready +I, [2022-02-18T08:31:48.503138 #643] INFO -- : worker=24 ready +I, [2022-02-18T08:31:49.279334 #644] INFO -- : worker=19 ready +I, [2022-02-18T08:31:49.505107 #645] INFO -- : worker=25 ready +I, [2022-02-18T08:31:50.282006 #646] INFO -- : worker=20 ready +I, [2022-02-18T08:31:50.506451 #647] INFO -- : worker=26 ready +I, [2022-02-18T08:31:51.284508 #648] INFO -- : worker=21 ready +I, [2022-02-18T08:31:51.508247 #649] INFO -- : worker=27 ready +I, [2022-02-18T08:31:52.286523 #650] INFO -- : worker=22 ready +I, [2022-02-18T08:31:52.509271 #651] INFO -- : worker=28 ready +I, [2022-02-18T08:31:53.288582 #653] INFO -- : worker=23 ready +I, [2022-02-18T08:31:53.510520 #654] INFO -- : worker=29 ready +I, [2022-02-18T08:31:54.291145 #656] INFO -- : worker=24 ready +I, [2022-02-18T08:31:54.513216 #657] INFO -- : worker=30 ready +I, [2022-02-18T08:31:55.293766 #658] INFO -- : worker=25 ready +I, [2022-02-18T08:31:55.515098 #659] INFO -- : worker=31 ready +I, [2022-02-18T08:31:56.296158 #660] INFO -- : worker=26 ready +I, [2022-02-18T08:31:56.517493 #661] INFO -- : worker=32 ready +I, [2022-02-18T08:31:57.298975 #662] INFO -- : worker=27 ready +I, [2022-02-18T08:31:57.520154 #663] INFO -- : worker=33 ready +I, [2022-02-18T08:31:58.301773 #664] INFO -- : worker=28 ready +I, [2022-02-18T08:31:58.501008 #665] INFO -- : worker=34 ready +I, [2022-02-18T08:31:59.283109 #666] INFO -- : worker=29 ready +I, [2022-02-18T08:31:59.503303 #667] INFO -- : worker=35 ready +I, [2022-02-18T08:32:00.286617 #668] INFO -- : worker=30 ready +I, [2022-02-18T08:32:00.505692 #669] INFO -- : worker=36 ready +I, [2022-02-18T08:32:01.289584 #670] INFO -- : worker=31 ready +I, [2022-02-18T08:32:01.508669 #671] INFO -- : worker=37 ready +I, [2022-02-18T08:32:02.293001 #672] INFO -- : worker=32 ready +I, [2022-02-18T08:32:02.510398 #673] INFO -- : worker=38 ready +I, [2022-02-18T08:32:03.299397 #674] INFO -- : worker=33 ready +I, [2022-02-18T08:32:03.512301 #592] INFO -- : master process ready +I, [2022-02-18T08:32:03.512912 #675] INFO -- : worker=39 ready +I, [2022-02-18T08:32:03.541357 #592] INFO -- : reaped # worker=6 +I, [2022-02-18T08:32:03.541479 #592] INFO -- : reaped # worker=7 +I, [2022-02-18T08:32:03.541617 #592] INFO -- : reaped # worker=9 +I, [2022-02-18T08:32:03.541682 #592] INFO -- : reaped # worker=10 +I, [2022-02-18T08:32:03.541727 #592] INFO -- : reaped # worker=11 +I, [2022-02-18T08:32:03.541779 #592] INFO -- : reaped # worker=14 +I, [2022-02-18T08:32:03.541835 #592] INFO -- : reaped # worker=15 +I, [2022-02-18T08:32:03.541875 #592] INFO -- : reaped # worker=17 +I, [2022-02-18T08:32:03.541922 #592] INFO -- : reaped # worker=20 +I, [2022-02-18T08:32:03.541963 #592] INFO -- : reaped # worker=22 +I, [2022-02-18T08:32:03.542015 #592] INFO -- : reaped # worker=25 +I, [2022-02-18T08:32:03.543050 #592] INFO -- : reaped # worker=30 +I, [2022-02-18T08:32:03.554937 #592] INFO -- : reaped # worker=12 +I, [2022-02-18T08:32:03.555038 #592] INFO -- : reaped # worker=19 +I, [2022-02-18T08:32:03.555121 #592] INFO -- : reaped # worker=21 +I, [2022-02-18T08:32:03.555190 #592] INFO -- : reaped # worker=23 +I, [2022-02-18T08:32:03.555235 #592] INFO -- : reaped # worker=26 +I, [2022-02-18T08:32:03.555273 #592] INFO -- : reaped # worker=27 +I, [2022-02-18T08:32:03.555332 #592] INFO -- : reaped # worker=29 +I, [2022-02-18T08:32:03.555374 #592] INFO -- : reaped # worker=31 +I, [2022-02-18T08:32:03.555410 #592] INFO -- : reaped # worker=32 +I, [2022-02-18T08:32:03.555528 #592] INFO -- : reaped # worker=35 +I, [2022-02-18T08:32:03.556193 #592] INFO -- : reaped # worker=8 +I, [2022-02-18T08:32:03.556906 #592] INFO -- : reaped # worker=16 +I, [2022-02-18T08:32:03.557052 #592] INFO -- : reaped # worker=34 +I, [2022-02-18T08:32:03.557102 #592] INFO -- : reaped # worker=36 +I, [2022-02-18T08:32:03.565356 #592] INFO -- : reaped # worker=13 +I, [2022-02-18T08:32:03.565438 #592] INFO -- : reaped # worker=18 +I, [2022-02-18T08:32:03.565477 #592] INFO -- : reaped # worker=24 +I, [2022-02-18T08:32:03.565517 #592] INFO -- : reaped # worker=28 +I, [2022-02-18T08:32:03.565617 #592] INFO -- : reaped # worker=33 +I, [2022-02-18T08:32:03.565689 #592] INFO -- : reaped # worker=37 +I, [2022-02-18T08:32:03.565725 #592] INFO -- : reaped # worker=39 +I, [2022-02-18T08:32:03.567901 #592] INFO -- : reaped # worker=38 +I, [2022-02-18T08:32:04.301691 #676] INFO -- : worker=34 ready +I, [2022-02-18T08:32:04.306813 #592] INFO -- : reaped # worker=5 +I, [2022-02-18T08:32:05.304866 #677] INFO -- : worker=35 ready +I, [2022-02-18T08:32:05.309831 #592] INFO -- : reaped # worker=4 +I, [2022-02-18T08:32:06.308785 #679] INFO -- : worker=36 ready +I, [2022-02-18T08:32:06.314034 #592] INFO -- : reaped # worker=3 +I, [2022-02-18T08:32:07.312228 #680] INFO -- : worker=37 ready +I, [2022-02-18T08:32:07.317254 #592] INFO -- : reaped # worker=2 +I, [2022-02-18T08:32:08.316086 #682] INFO -- : worker=38 ready +I, [2022-02-18T08:32:08.321175 #592] INFO -- : reaped # worker=1 +I, [2022-02-18T08:32:09.319042 #601] INFO -- : master process ready +I, [2022-02-18T08:32:09.319558 #684] INFO -- : worker=39 ready +I, [2022-02-18T08:32:09.420403 #592] INFO -- : reaped # worker=0 +I, [2022-02-18T08:32:09.420573 #592] INFO -- : master complete +``` + +```log +root@47cd46c66784:/sample# cat unicorn_stdout.log +old_gen 0, old_pid: +old_gen 0, old_pid: +old_gen 0, old_pid: +old_gen 0, old_pid: +old_gen 0, old_pid: +old_gen 0, old_pid: +old_gen 1, old_pid: 592 +old_gen 0, old_pid: +old_gen 1, old_pid: 592 +old_gen 0, old_pid: +old_gen 1, old_pid: 592 +old_gen 0, old_pid: +old_gen 1, old_pid: 592 +old_gen 0, old_pid: +old_gen 1, old_pid: 592 +old_gen 0, old_pid: +old_gen 1, old_pid: 592 +old_gen 0, old_pid: +old_gen 1, old_pid: 592 +old_gen 0, old_pid: +old_gen 1, old_pid: 592 +old_gen 0, old_pid: +old_gen 1, old_pid: 592 +old_gen 0, old_pid: +old_gen 1, old_pid: 592 +old_gen 0, old_pid: +old_gen 1, old_pid: 592 +old_gen 0, old_pid: +old_gen 1, old_pid: 592 +old_gen 0, old_pid: +old_gen 1, old_pid: 592 +old_gen 0, old_pid: +old_gen 1, old_pid: 592 +old_gen 0, old_pid: +old_gen 1, old_pid: 592 +old_gen 0, old_pid: +old_gen 1, old_pid: 592 +old_gen 0, old_pid: +old_gen 1, old_pid: 592 +old_gen 0, old_pid: +old_gen 1, old_pid: 592 +old_gen 0, old_pid: +old_gen 1, old_pid: 592 +old_gen 0, old_pid: +old_gen 1, old_pid: 592 +old_gen 0, old_pid: +old_gen 1, old_pid: 592 +old_gen 0, old_pid: +old_gen 1, old_pid: 592 +old_gen 0, old_pid: +old_gen 1, old_pid: 592 +old_gen 0, old_pid: +old_gen 1, old_pid: 592 +old_gen 0, old_pid: +old_gen 1, old_pid: 592 +old_gen 0, old_pid: +old_gen 1, old_pid: 592 +old_gen 0, old_pid: +old_gen 1, old_pid: 592 +old_gen 0, old_pid: +old_gen 1, old_pid: 592 +old_gen 0, old_pid: +old_gen 1, old_pid: 592 +old_gen 0, old_pid: +old_gen 1, old_pid: 592 +old_gen 0, old_pid: +old_gen 1, old_pid: 592 +old_gen 0, old_pid: +old_gen 1, old_pid: 592 +old_gen 0, old_pid: +old_gen 1, old_pid: 592 +old_gen 0, old_pid: +old_gen 1, old_pid: 592 +old_gen 0, old_pid: +old_gen 1, old_pid: 592 +old_gen 1, old_pid: 592 +old_gen 1, old_pid: 592 +old_gen 1, old_pid: 592 +old_gen 1, old_pid: 592 +old_gen 1, old_pid: 592 +``` + +```sh +root@47cd46c66784:/sample# ps -f -o user,pid,ppid,start,command --forest +USER PID PPID STARTED COMMAND +root 1 0 08:31:01 /bin/bash +root 13 1 08:31:06 /bin/sh /command/svscanboot +root 15 13 08:31:06 \_ svscan /service +root 17 15 08:31:06 | \_ supervise sample_server +root 326 17 08:31:14 | | \_ /usr/bin/perl /usr/local/bin/start_server --path=/sample/unicorn.sock --signal-on-term=QUIT --signal-on-hup=QUIT --status-file=/sample/sample_server.status --pid-file=/sample/sampl +root 601 326 08:31:28 | | \_ unicorn master /sample/src/config.ru --config-file /sample/src/unicorn.production.rb +root 604 601 08:31:29 | | \_ unicorn worker[0] /sample/src/config.ru --config-file /sample/src/unicorn.production.rb +root 606 601 08:31:30 | | \_ unicorn worker[1] /sample/src/config.ru --config-file /sample/src/unicorn.production.rb +root 608 601 08:31:31 | | \_ unicorn worker[2] /sample/src/config.ru --config-file /sample/src/unicorn.production.rb +root 610 601 08:31:32 | | \_ unicorn worker[3] /sample/src/config.ru --config-file /sample/src/unicorn.production.rb +root 612 601 08:31:33 | | \_ unicorn worker[4] /sample/src/config.ru --config-file /sample/src/unicorn.production.rb +root 614 601 08:31:34 | | \_ unicorn worker[5] /sample/src/config.ru --config-file /sample/src/unicorn.production.rb +root 616 601 08:31:35 | | \_ unicorn worker[6] /sample/src/config.ru --config-file /sample/src/unicorn.production.rb +root 618 601 08:31:36 | | \_ unicorn worker[7] /sample/src/config.ru --config-file /sample/src/unicorn.production.rb +root 620 601 08:31:37 | | \_ unicorn worker[8] /sample/src/config.ru --config-file /sample/src/unicorn.production.rb +root 623 601 08:31:38 | | \_ unicorn worker[9] /sample/src/config.ru --config-file /sample/src/unicorn.production.rb +root 625 601 08:31:39 | | \_ unicorn worker[10] /sample/src/config.ru --config-file /sample/src/unicorn.production.rb +root 627 601 08:31:40 | | \_ unicorn worker[11] /sample/src/config.ru --config-file /sample/src/unicorn.production.rb +root 629 601 08:31:41 | | \_ unicorn worker[12] /sample/src/config.ru --config-file /sample/src/unicorn.production.rb +root 631 601 08:31:42 | | \_ unicorn worker[13] /sample/src/config.ru --config-file /sample/src/unicorn.production.rb +root 633 601 08:31:43 | | \_ unicorn worker[14] /sample/src/config.ru --config-file /sample/src/unicorn.production.rb +root 636 601 08:31:44 | | \_ unicorn worker[15] /sample/src/config.ru --config-file /sample/src/unicorn.production.rb +root 638 601 08:31:45 | | \_ unicorn worker[16] /sample/src/config.ru --config-file /sample/src/unicorn.production.rb +root 640 601 08:31:46 | | \_ unicorn worker[17] /sample/src/config.ru --config-file /sample/src/unicorn.production.rb +root 642 601 08:31:47 | | \_ unicorn worker[18] /sample/src/config.ru --config-file /sample/src/unicorn.production.rb +root 644 601 08:31:48 | | \_ unicorn worker[19] /sample/src/config.ru --config-file /sample/src/unicorn.production.rb +root 646 601 08:31:49 | | \_ unicorn worker[20] /sample/src/config.ru --config-file /sample/src/unicorn.production.rb +root 648 601 08:31:50 | | \_ unicorn worker[21] /sample/src/config.ru --config-file /sample/src/unicorn.production.rb +root 650 601 08:31:51 | | \_ unicorn worker[22] /sample/src/config.ru --config-file /sample/src/unicorn.production.rb +root 653 601 08:31:52 | | \_ unicorn worker[23] /sample/src/config.ru --config-file /sample/src/unicorn.production.rb +root 656 601 08:31:53 | | \_ unicorn worker[24] /sample/src/config.ru --config-file /sample/src/unicorn.production.rb +root 658 601 08:31:54 | | \_ unicorn worker[25] /sample/src/config.ru --config-file /sample/src/unicorn.production.rb +root 660 601 08:31:55 | | \_ unicorn worker[26] /sample/src/config.ru --config-file /sample/src/unicorn.production.rb +root 662 601 08:31:56 | | \_ unicorn worker[27] /sample/src/config.ru --config-file /sample/src/unicorn.production.rb +root 664 601 08:31:57 | | \_ unicorn worker[28] /sample/src/config.ru --config-file /sample/src/unicorn.production.rb +root 666 601 08:31:58 | | \_ unicorn worker[29] /sample/src/config.ru --config-file /sample/src/unicorn.production.rb +root 668 601 08:31:59 | | \_ unicorn worker[30] /sample/src/config.ru --config-file /sample/src/unicorn.production.rb +root 670 601 08:32:00 | | \_ unicorn worker[31] /sample/src/config.ru --config-file /sample/src/unicorn.production.rb +root 672 601 08:32:01 | | \_ unicorn worker[32] /sample/src/config.ru --config-file /sample/src/unicorn.production.rb +root 674 601 08:32:02 | | \_ unicorn worker[33] /sample/src/config.ru --config-file /sample/src/unicorn.production.rb +root 676 601 08:32:03 | | \_ unicorn worker[34] /sample/src/config.ru --config-file /sample/src/unicorn.production.rb +root 677 601 08:32:04 | | \_ unicorn worker[35] /sample/src/config.ru --config-file /sample/src/unicorn.production.rb +root 679 601 08:32:05 | | \_ unicorn worker[36] /sample/src/config.ru --config-file /sample/src/unicorn.production.rb +root 680 601 08:32:06 | | \_ unicorn worker[37] /sample/src/config.ru --config-file /sample/src/unicorn.production.rb +root 682 601 08:32:07 | | \_ unicorn worker[38] /sample/src/config.ru --config-file /sample/src/unicorn.production.rb +root 684 601 08:32:08 | | \_ unicorn worker[39] /sample/src/config.ru --config-file /sample/src/unicorn.production.rb +root 18 15 08:31:06 | \_ supervise log +root 20 18 08:31:06 | \_ multilog t ./main +root 16 13 08:31:06 \_ readproctitle service errors: .............................................................................................................................................................. +root 716 1 08:34:53 ps -f -o user,pid,ppid,start,command --forest +``` diff --git a/unicorn+server-starter+daemontools/run b/unicorn+server-starter+daemontools/run new file mode 100755 index 0000000..f52a630 --- /dev/null +++ b/unicorn+server-starter+daemontools/run @@ -0,0 +1,17 @@ +#!/bin/sh + +exec 2>&1 \ +sh -c '\ + export BUNDLE_SILENCE_ROOT_WARNING=1; \ + cd /sample && bundle install; \ + exec \ + start_server \ + --path=/sample/unicorn.sock \ + --signal-on-term=QUIT \ + --signal-on-hup=QUIT \ + --dir=/sample \ + --status-file=/sample/sample_server.status \ + --pid-file=/sample/sample_server.pid \ + --kill-old-delay=100 \ + -- bundle exec unicorn /sample/src/config.ru --config-file /sample/src/unicorn.production.rb \ +' diff --git a/unicorn+server-starter+daemontools/src/Gemfile b/unicorn+server-starter+daemontools/src/Gemfile new file mode 100644 index 0000000..ea8f321 --- /dev/null +++ b/unicorn+server-starter+daemontools/src/Gemfile @@ -0,0 +1,4 @@ +source 'https://rubygems.org' + +gem 'unicorn', '5.1.0' +gem 'rack', '2.2.3' diff --git a/unicorn+server-starter+daemontools/src/Gemfile.lock b/unicorn+server-starter+daemontools/src/Gemfile.lock new file mode 100644 index 0000000..45edb83 --- /dev/null +++ b/unicorn+server-starter+daemontools/src/Gemfile.lock @@ -0,0 +1,20 @@ +GEM + remote: https://rubygems.org/ + specs: + kgio (2.11.4) + rack (2.2.3) + raindrops (0.20.0) + unicorn (5.1.0) + kgio (~> 2.6) + raindrops (~> 0.7) + +PLATFORMS + ruby + x86_64-darwin-19 + +DEPENDENCIES + rack (= 2.2.3) + unicorn (= 5.1.0) + +BUNDLED WITH + 1.17.3 diff --git a/unicorn+server-starter+daemontools/src/config.ru b/unicorn+server-starter+daemontools/src/config.ru new file mode 100644 index 0000000..def6b2b --- /dev/null +++ b/unicorn+server-starter+daemontools/src/config.ru @@ -0,0 +1,4 @@ +# coding: utf-8 + +require_relative 'sample.rb' +run Sample.new diff --git a/unicorn+server-starter+daemontools/src/sample.rb b/unicorn+server-starter+daemontools/src/sample.rb new file mode 100644 index 0000000..797d41f --- /dev/null +++ b/unicorn+server-starter+daemontools/src/sample.rb @@ -0,0 +1,9 @@ +class Sample + def call(env) + [ + 200, + { 'Content-Type' => 'text/html' }, + ['ok'], + ] + end +end diff --git a/unicorn+server-starter+daemontools/src/unicorn.production.rb b/unicorn+server-starter+daemontools/src/unicorn.production.rb new file mode 100644 index 0000000..65cc422 --- /dev/null +++ b/unicorn+server-starter+daemontools/src/unicorn.production.rb @@ -0,0 +1,63 @@ +# See: https://github.com/defunkt/unicorn/blob/master/examples/unicorn.conf.minimal.rb +# See: https://github.com/defunkt/unicorn/blob/master/examples/unicorn.conf.rb + +worker_processes 40 + +stderr_path './unicorn_stderr.log' +stdout_path './unicorn_stdout.log' + +status_file = "/sample/sample_server.status" + +preload_app true + +listen "/sample/unicorn.sock", backlog: 2048 + +timeout 7 + +# use correct Gemfile on restarts +before_exec do |_server| + ENV['BUNDLE_GEMFILE'] = "/sample/src/Gemfile" +end + +if ENV.key?('SERVER_STARTER_PORT') + fds = ENV['SERVER_STARTER_PORT'].split(';').map { |pair| + _path_or_port, fd = pair.split('=', 2) + fd + } + ENV['UNICORN_FD'] = fds.join(',') + ENV.delete('SERVER_STARTER_PORT') +else + listen ENV['PORT'] || '10080' +end + +before_fork do |_server, _worker| + defined?(ActiveRecord::Base) && ActiveRecord::Base.connection.disconnect! + + # Throttle the master from forking too quickly by sleeping. Due + # to the implementation of standard Unix signal handlers, this + # helps (but does not completely) prevent identical, repeated signals + # from being lost when the receiving process is busy. + sleep 1 +end + +after_fork do |server, worker| + defined?(ActiveRecord::Base) && ActiveRecord::Base.establish_connection + + begin + # This allows a new master process to incrementally + # phase out the old master process with SIGTTOU to avoid a + # thundering herd (especially in the "preload_app false" case) + # when doing a transparent upgrade. The last worker spawned + # will then kill off the old master process with a SIGQUIT. + pids = File.readlines(status_file).map { |line| line.chomp.split(':') }.to_h + old_gen = ENV['SERVER_STARTER_GENERATION'].to_i - 1 + old_pid = pids[old_gen.to_s] + $stdout.puts "old_gen #{old_gen}, old_pid: #{old_pid}" + if old_pid + sig = (worker.nr + 1) >= server.worker_processes ? :QUIT : :TTOU + Process.kill(sig, old_pid.to_i) + end + rescue Errno::ENOENT, Errno::ESRCH => e + $stderr.puts "#{e.class} #{e.message}" + end +end diff --git a/unicorn+systemd/Dockerfile b/unicorn+systemd/Dockerfile new file mode 100644 index 0000000..8c14612 --- /dev/null +++ b/unicorn+systemd/Dockerfile @@ -0,0 +1,28 @@ +# https://hub.docker.com/r/centos/systemd/ +FROM centos/systemd + +RUN yum -y update && yum install -y git && yum -y install psmisc && yum install -y which && \ + git clone https://github.com/rbenv/rbenv.git ~/.rbenv && \ + echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> ~/.bash_profile && \ + echo 'eval "$(rbenv init -)"' >> ~/.bash_profile && \ + source ~/.bash_profile && \ + (~/.rbenv/bin/rbenv init || true) && \ + git clone https://github.com/rbenv/ruby-build.git ~/.rbenv/plugins/ruby-build && \ + ~/.rbenv/plugins/ruby-build/install.sh && \ + yum install -y gcc bzip2 openssl-devel readline-devel zlib-devel && \ + yum install -y tar && yum install -y make && \ + CONFIGURE_OPTS='--disable-install-rdoc' rbenv install 2.7.2 && rbenv global 2.7.2 && rbenv rehash + +WORKDIR /sample +COPY ["./src/Gemfile", "./"] + +ENV PATH /root/.rbenv/shims:/root/.rbenv/bin:$PATH + +RUN gem install bundler --version 1.17.2 && bundle install + +COPY . /sample +COPY ["./sample_app.service", "/etc/systemd/system/sample_app.service"] + +RUN systemctl enable sample_app.service + +CMD ["/usr/sbin/init"] diff --git a/unicorn+systemd/README.md b/unicorn+systemd/README.md new file mode 100644 index 0000000..bc0ec3b --- /dev/null +++ b/unicorn+systemd/README.md @@ -0,0 +1,24 @@ +# Playground: Unicorn Graceful Restart under systemd + +```sh +docker image build --file ./Dockerfile --tag sample_unicorn_systemd:t01 --rm . +docker rm -f sample_unicorn_systemd +docker container run --detach -it --rm --privileged -v /sys/fs/cgroup:/sys/fs/cgroup:ro --name sample_unicorn_systemd sample_unicorn_systemd:t01 +docker exec -it sample_unicorn_systemd /bin/bash +``` + +```sh +systemctl status sample_app.service + +curl localhost:8080 -w "\n" + +cat unicorn.pid +pstree --show-pids +ps -x -o user,pid,ppid,start,command --forest | grep -e unicorn -e PID | grep -v grep + +systemctl reload sample_app.service + +cat unicorn.pid +pstree --show-pids +ps -x -o user,pid,ppid,start,command --forest | grep -e unicorn -e PID | grep -v grep +``` diff --git a/unicorn+systemd/sample_app.service b/unicorn+systemd/sample_app.service new file mode 100644 index 0000000..84dff86 --- /dev/null +++ b/unicorn+systemd/sample_app.service @@ -0,0 +1,15 @@ +[Unit] +Description=unicorn sample app + +[Service] +Type=forking +PIDFile=/sample/unicorn.pid +WorkingDirectory=/sample +ExecStart=/root/.rbenv/shims/bundle exec --keep-file-descriptors unicorn /sample/src/config.ru --config-file /sample/src/unicorn.conf_with_fork_hook.rb --daemonize +ExecReload=/bin/kill -USR2 $MAINPID +KillMode=process +KillSignal=SIGQUIT +NonBlocking=true + +[Install] +WantedBy=multi-user.target diff --git a/unicorn+systemd/src/Gemfile b/unicorn+systemd/src/Gemfile new file mode 100644 index 0000000..7745df1 --- /dev/null +++ b/unicorn+systemd/src/Gemfile @@ -0,0 +1,4 @@ +source 'https://rubygems.org' + +gem 'unicorn', '~> 6.0' +gem 'rack', '~> 2.2', '>= 2.2.3' diff --git a/unicorn+systemd/src/config.ru b/unicorn+systemd/src/config.ru new file mode 100644 index 0000000..def6b2b --- /dev/null +++ b/unicorn+systemd/src/config.ru @@ -0,0 +1,4 @@ +# coding: utf-8 + +require_relative 'sample.rb' +run Sample.new diff --git a/unicorn+systemd/src/sample.rb b/unicorn+systemd/src/sample.rb new file mode 100644 index 0000000..797d41f --- /dev/null +++ b/unicorn+systemd/src/sample.rb @@ -0,0 +1,9 @@ +class Sample + def call(env) + [ + 200, + { 'Content-Type' => 'text/html' }, + ['ok'], + ] + end +end diff --git a/unicorn+systemd/src/unicorn.conf_with_fork_hook.rb b/unicorn+systemd/src/unicorn.conf_with_fork_hook.rb new file mode 100644 index 0000000..a1fb3f9 --- /dev/null +++ b/unicorn+systemd/src/unicorn.conf_with_fork_hook.rb @@ -0,0 +1,38 @@ +# See: https://github.com/defunkt/unicorn/blob/master/examples/unicorn.conf.minimal.rb +# See: https://github.com/defunkt/unicorn/blob/master/examples/unicorn.conf.rb + +worker_processes 2 + +stderr_path './unicorn_stderr.log' +stdout_path './unicorn_stdout.log' + +pid '/sample/unicorn.pid' + +listen 8080, :tcp_nopush => true + +# See: https://github.com/defunkt/unicorn/blob/93e154e16b87f943a20fa720e002c67c9d17c30b/examples/unicorn.conf.rb#L71 +before_fork do |server, worker| + # The following is only recommended for memory/DB-constrained + # installations. It is not needed if your system can house + # twice as many worker_processes as you have configured. + + # This allows a new master process to incrementally + # phase out the old master process with SIGTTOU to avoid a + # thundering herd (especially in the "preload_app false" case) + # when doing a transparent upgrade. The last worker spawned + # will then kill off the old master process with a SIGQUIT. + 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 + + # Throttle the master from forking too quickly by sleeping. Due + # to the implementation of standard Unix signal handlers, this + # helps (but does not completely) prevent identical, repeated signals + # from being lost when the receiving process is busy. + sleep 1 +end diff --git a/unicorn/Dockerfile b/unicorn/Dockerfile new file mode 100644 index 0000000..f1e9bb9 --- /dev/null +++ b/unicorn/Dockerfile @@ -0,0 +1,9 @@ +FROM ruby:2.7.4 + +RUN apt-get update -qq && apt-get install psmisc + +WORKDIR /sample +COPY ["./src/Gemfile", "./"] + +RUN gem install bundler --version 1.17.2 && bundle install +COPY . /sample diff --git a/unicorn/README.md b/unicorn/README.md new file mode 100644 index 0000000..f218c98 --- /dev/null +++ b/unicorn/README.md @@ -0,0 +1,27 @@ +# Playground: Unicorn Graceful Restart + +```sh +docker image build --file ./Dockerfile --tag sample:t01 --rm . +docker container run -it --rm sample:t01 /bin/bash +``` + +```bash +bundle exec unicorn ./src/config.ru --config-file ./src/unicorn.simple_conf.rb --daemonize + +curl localhost:8080 -w "\n" + +pstree --show-pids +ps -x -o user,pid,ppid,start,command --forest | grep -e unicorn -e PID | grep -v grep + +cat unicorn.pid + +# `USR2` シグナルの送信だけで旧プロセスも停止させるような hook を設定することは可能だが、ここではプロセスの状態を確認しやすいよう、明示的にやる。 +# See: https://github.com/defunkt/unicorn/blob/93e154e16b87f943a20fa720e002c67c9d17c30b/examples/unicorn.conf.rb#L71 +kill -USR2 `cat unicorn.pid` +pstree --show-pids +ps -x -o user,pid,ppid,start,command --forest | grep -e unicorn -e PID | grep -v grep +cat unicorn.pid +kill -QUIT `cat unicorn.pid.oldbin` +pstree --show-pids +ps -x -o user,pid,ppid,start,command --forest | grep -e unicorn -e PID | grep -v grep +``` diff --git a/unicorn/src/Gemfile b/unicorn/src/Gemfile new file mode 100644 index 0000000..7745df1 --- /dev/null +++ b/unicorn/src/Gemfile @@ -0,0 +1,4 @@ +source 'https://rubygems.org' + +gem 'unicorn', '~> 6.0' +gem 'rack', '~> 2.2', '>= 2.2.3' diff --git a/unicorn/src/config.ru b/unicorn/src/config.ru new file mode 100644 index 0000000..def6b2b --- /dev/null +++ b/unicorn/src/config.ru @@ -0,0 +1,4 @@ +# coding: utf-8 + +require_relative 'sample.rb' +run Sample.new diff --git a/unicorn/src/sample.rb b/unicorn/src/sample.rb new file mode 100644 index 0000000..797d41f --- /dev/null +++ b/unicorn/src/sample.rb @@ -0,0 +1,9 @@ +class Sample + def call(env) + [ + 200, + { 'Content-Type' => 'text/html' }, + ['ok'], + ] + end +end diff --git a/unicorn/src/unicorn.simple_conf.rb b/unicorn/src/unicorn.simple_conf.rb new file mode 100644 index 0000000..ec7a1e0 --- /dev/null +++ b/unicorn/src/unicorn.simple_conf.rb @@ -0,0 +1,11 @@ +# See: https://github.com/defunkt/unicorn/blob/master/examples/unicorn.conf.minimal.rb +# See: https://github.com/defunkt/unicorn/blob/master/examples/unicorn.conf.rb + +worker_processes 2 + +stderr_path './unicorn_stderr.log' +stdout_path './unicorn_stdout.log' + +pid 'unicorn.pid' + +listen 8080, :tcp_nopush => true