Rack アプリケーション向けの HTTP サーバ Unicorn の基本操作

Written by @dr_taka_n at 2011/07/30 23:55 [, ]

Unicorn とは?

Rails、Rack アプリケーションを動作させるコンテナとしては、PassengerThin、Mongrel などの選択肢がある。 それ以外にも Unicorn という Rack アプリケーション向けの HTTP サーバがあり、今回試しに使ってみたのでそのメモ。

設計方針に特徴的な部分があり、以下の記事に詳しい。

github の記事の結論で書かれているが、

Passenger is awesome. Mongrel is awesome. Thin is awesome.

Use what works best for you. Decide what you need and evaluate the available options based on those needs. Don’t pick a tool because GitHub uses it, pick a tool because it solves the problems you have.

それぞれの HTTP サーバが良い点を持っており、自身のサービスの用途、目的に応じて適切なものを使用するのがよいでしょうと。

サーバの良し悪しを測る項目の1項目として ab を使ったベンチなどがよくとられている。 確かにスループットにある程度のレベルのものは要求したいが、それよりも運用に入った際の安定性、信頼性、何か問題が起きた際のリカバリ含めた可用性の部分を気にしていた。

今回 Unicorn を試してみようと思ったのは、そこにあり、先の記事にも、以下の記載があり、Unicorn を試してみることにした。

Honestly, I don’t care. I want a production environment that can gracefully handle chaos more than I want something that’s screaming fast. I want stability and reliability over raw speed.

ここで記載するのは Unicorn を動作させるまでの基本的な設定と Unicorn を操作する基本的なオペレーションのメモになる。

  • Unicorn のインストールと事前準備
  • Unicorn の設定ファイルを用意する
  • Unicorn サーバを起動する
  • nginx と Unicorn を連携する
  • Unicorn を停止する
  • Unicorn の設定の再読込
  • サービスの提供を止めずにプログラムの再読込

Unicorn のインストールと事前準備

今回のお試し環境は以下の通りとなる。

  • アプリには簡単な Sinatra Hello World アプリを使用
  • Unicorn サーバのフロントは nginx をリバースプロキシとしてたてる

Unicorn 以外に nginx と Sinatra gem がインストールされている必要がある。

Unicorn は RubyGems で提供されているので gem コマンドを使用してインストールする。

# gem install unicorn

動作環境のサンプルとして Sinatra アプリケーションを用意する。
(Sinatra がインストールされていない場合には、# gem install sinatra が必要。)

適当なディレクトリ($APP_DIR)に Rack アプリケーションのエントリポイントとなるファイル config.ru を用意し、簡単な HelloWorld アプリケーションを記述しておく。

$APP_DIR/config.ru:

require 'rubygems'
require 'sinatra/base'

class HelloApp < Sinatra::Base
  get '/hello' do
    'Hello World'
  end
end

run HelloApp

Rack の rackup コマンドを使用して、上記のアプリがきちんと動作することを確認しておく。

$ cd $APP_DIR
$ rackup
[2011-07-30 21:15:38] INFO  WEBrick 1.3.1
[2011-07-30 21:15:38] INFO  ruby 1.9.2 (2011-02-18) [i686-linux]
[2011-07-30 21:15:38] INFO  WEBrick::HTTPServer#start: pid=6260 port=9292

ブラウザから、http://localhost:9292/hello の URI で “Hello World” が表示されることを確認しておく。

Unicorn の設定ファイルを用意する

Unicorn は unicorn コマンドを使って起動するが、起動時に設定ファイルを指定して起動することができる。
設定例は、Class: Unicorn::Configurator からリンクのある以下のページで確認できる。

上記の最低限の設定を設定を流用し、以下の設定ファイルを用意する。
Unicorn は TCP/IP ソケットでも待機できるが、以下の設定では Unix ドメインソケットを使った設定のみとしている。

$APP_DIR/unicorn.conf:

worker_processes 2

listen '/tmp/unicorn.sock'

stderr_path File.expand_path('unicorn.log', File.dirname(__FILE__))
stdout_path File.expand_path('unicorn.log', File.dirname(__FILE__))

preload_app true

ログファイルの記載部分を見てもらえるとわかるが、Unicorn の設定ファイルには Ruby の構文が利用可能となっている。

Unicorn サーバを起動する

Unicorn サーバを起動する。
unicorn コマンドを利用して起動することになる。$APP_DIR に移動して、以下のコマンドで起動する。

$ unicorn -c unicorn.conf -D

先ほど作成した Unicorn の設定ファイルを -c オプションで指定し、-D オプションでデーモン起動を行っている。

unicorn はデフォルトで config.ru をアプリケーションのエントリポイントとして認識するので、起動時のディレクトリに存在する Sinatra で書かれた Rack アプリケーションである $APP_DIR/config.ru が上記のコマンドで読み込まれている。

ログファイルを確認してみる。

$ cat unicorn.log
I, [2011-07-30T21:50:47.966116 #6632]  INFO -- : unlinking existing socket=/tmp/unicorn.sock
I, [2011-07-30T21:50:47.966669 #6632]  INFO -- : listening on addr=/tmp/unicorn.sock fd=3
I, [2011-07-30T21:50:47.967666 #6632]  INFO -- : Refreshing Gem list
I, [2011-07-30T21:50:48.343228 #6632]  INFO -- : worker=0 spawning...
I, [2011-07-30T21:50:48.343795 #6632]  INFO -- : worker=1 spawning...
I, [2011-07-30T21:50:48.344260 #6632]  INFO -- : master process ready
I, [2011-07-30T21:50:48.344266 #6635]  INFO -- : worker=0 spawned pid=6635
I, [2011-07-30T21:50:48.344450 #6635]  INFO -- : worker=0 ready
I, [2011-07-30T21:50:48.344672 #6638]  INFO -- : worker=1 spawned pid=6638
I, [2011-07-30T21:50:48.344863 #6638]  INFO -- : worker=1 ready

エラーが表示されておらず、上記のようなログが出力されていれば問題なく起動が行えている。 master プロセスが1つ上がり、worker プロセス2つを起動している。

nginx と Unicorn を連携する

フロントでリクエストを処理する nginx の設定。

最低限の設定というところで、以下の設定を記述する。

  • upstream ディレクティブ(ロードバランシングの指定)で unicornapp というバックエンドサーバを定義し、Unicorn サーバの Unix ドメインソケットを指定しておく。
  • location ディレクティブで upstream に指定した unicornapp バックエンドサーバを指定する
upstream unicornapp {
  server unix:/tmp/unicorn.sock;
}

server {
        listen   80;
        server_name localhost;

        location / {
               proxy_pass http://unicornapp;
        }
}

nginx 設定ファイルの再読込を行い、ブラウザで http://localhost/hello の URI を参照すると先ほどの rackup コマンドを使用して確認した “Hello World” と同じメッセージの画面が表示されていることが確認できる。
(ここで確認している画面は Unicorn で提供されているサービス)

unicorn のログにもリクエストを処理したメッセージが確認できる。

$ tail -f unicorn.log
...
I, [2011-07-30T22:13:27.755913 #7306]  INFO -- : worker=1 ready
I, [2011-07-30T22:13:27.756925 #7303]  INFO -- : worker=0 spawned pid=7303
I, [2011-07-30T22:13:27.757150 #7303]  INFO -- : worker=0 ready
127.0.0.1 - - [30/Jul/2011 22:13:39] "GET /hello HTTP/1.0" 200 11 0.0155

以上で基本的な設定はおしまい。

Unicorn を停止する

Unicorn に対する操作は基本 Unicorn の master プロセスに対してのシグナルでの操作となる。

まずは停止方法。2つの停止方法がある。

  • プロセスを即座に終了する: INT/TERM シグナルを使用
  • 処理を行っているリクエストが完了するのを待ってプロセスを終了する: QUIT シグナルを使用
$ ps -ef | grep unicorn | grep -v grep
hoge      7299     1  0 22:13 ?        00:00:04 unicorn master -c unicorn.conf -D
hoge      7303  7299  0 22:13 ?        00:00:03 unicorn worker[0] -c unicorn.conf -D
hoge      7306  7299  0 22:13 ?        00:00:03 unicorn worker[1] -c unicorn.conf -D

上記でわかるように、master プロセスは 7299 のプロセスIDで動作している。master プロセスに対してシグナルを送る。

Unicorn を gracefully に終了させる場合、

$ kill -QUIT 7299

unicorn.log を確認。

I, [2011-07-30T23:32:13.866125 #7299]  INFO -- : reaped #<Process::Status: pid 7303 exit 0> worker=0
I, [2011-07-30T23:32:13.866403 #7299]  INFO -- : reaped #<Process::Status: pid 7306 exit 0> worker=1
I, [2011-07-30T23:32:13.866683 #7299]  INFO -- : master complete

2つの worker プロセスと master プロセスが終了したことがわかる。

Unicorn の設定の再読込

Unicorn の設定を再読込させる場合は、master プロセスに HUP シグナルを送る。

$ kill -HUP <master のプロセスID>

ログを確認してみると、再読みされていることが確認できる。

I, [2011-07-30T23:35:42.261177 #7768]  INFO -- : reloading config_file=unicorn.conf
I, [2011-07-30T23:35:42.272338 #7768]  INFO -- : Refreshing Gem list
I, [2011-07-30T23:35:42.557187 #7768]  INFO -- : done reloading config_file=unicorn.conf
I, [2011-07-30T23:35:42.557362 #7768]  INFO -- : reaped #<Process::Status: pid 7773 exit 0> worker=0
I, [2011-07-30T23:35:42.557432 #7768]  INFO -- : reaped #<Process::Status: pid 7776 exit 0> worker=1
I, [2011-07-30T23:35:42.557545 #7768]  INFO -- : worker=0 spawning...
I, [2011-07-30T23:35:42.558136 #7768]  INFO -- : worker=1 spawning...
I, [2011-07-30T23:35:42.558726 #7782]  INFO -- : worker=0 spawned pid=7782
I, [2011-07-30T23:35:42.558966 #7782]  INFO -- : worker=0 ready
I, [2011-07-30T23:35:42.559173 #7785]  INFO -- : worker=1 spawned pid=7785
I, [2011-07-30T23:35:42.559430 #7785]  INFO -- : worker=1 ready

サービスの提供を止めずにプログラムの再読込

プログラム(ここで言う Sinatra Hello World アプリ)を再配置するときなど、再度プログラムをロードし直す必要がある。
Unicorn サーバを停止して、再起動でもよいが、サービスの提供を止めずに実施する場合の実施方法は以下の方法となる。

現在のプロセスの状況は以下の通り。

$ ps -ef | grep unicorn | grep -v grep
hoge      7768     1  0 23:35 ?        00:00:00 unicorn master -c unicorn.conf -D
hoge      7782  7768  0 23:35 ?        00:00:00 unicorn worker[0] -c unicorn.conf -D
hoge      7785  7768  0 23:35 ?        00:00:00 unicorn worker[1] -c unicorn.conf -D

ここで何らかのアプリの改修があり、プログラムを再配置したとする。

Unicorn では、既存のプロセスをそのまま残し、新たな Unicorn サーバのセットをもう一セット作成し、リクエストの処理もそちらに引き継いであげることができる。

まず 現在の master プロセスに USR2 シグナルを送る。

$ kill -USR2 7768

Unicorn のプロセスを確認してみる。

$ ps -ef | grep unicorn | grep -v grep
hoge      7768     1  0 23:35 ?        00:00:01 unicorn master (old) -c unicorn.conf -D
hoge      7782  7768  0 23:35 ?        00:00:00 unicorn worker[0] -c unicorn.conf -D
hoge      7785  7768  0 23:35 ?        00:00:00 unicorn worker[1] -c unicorn.conf -D
hoge      7822  7768  1 23:43 ?        00:00:00 unicorn master -c unicorn.conf -D
hoge      7826  7822  0 23:43 ?        00:00:00 unicorn worker[0] -c unicorn.conf -D
hoge      7829  7822  0 23:43 ?        00:00:00 unicorn worker[1] -c unicorn.conf -D

元々あったプロセス(master pid: 7768)に old という表示が入っており、Unicorn サーバのセットがもうワンセット追加されていることがわかる。

新たに生成されたプロセスには、プログラムの更新が反映されている。

この時の Unicorn の動作をログで確認してみると以下の動作になっている。

I, [2011-07-30T23:43:20.117449 #7822]  INFO -- : executing ["/home/hoge/.rvm/gems/ruby-1.9.2-p180/bin/unicorn", "-c", "unicorn.conf", "-D"] (in /home/hoge/Dropbox/work/unicorn-app-sample)
I, [2011-07-30T23:43:20.117641 #7822]  INFO -- : forked child re-executing...
I, [2011-07-30T23:43:20.798555 #7822]  INFO -- : inherited addr=/tmp/unicorn.sock fd=3
I, [2011-07-30T23:43:20.798829 #7822]  INFO -- : Refreshing Gem list
I, [2011-07-30T23:43:21.165052 #7822]  INFO -- : worker=0 spawning...
I, [2011-07-30T23:43:21.165798 #7822]  INFO -- : worker=1 spawning...
I, [2011-07-30T23:43:21.166209 #7826]  INFO -- : worker=0 spawned pid=7826
I, [2011-07-30T23:43:21.166311 #7822]  INFO -- : master process ready
I, [2011-07-30T23:43:21.166401 #7826]  INFO -- : worker=0 ready
I, [2011-07-30T23:43:21.166635 #7829]  INFO -- : worker=1 spawned pid=7829
I, [2011-07-30T23:43:21.166768 #7829]  INFO -- : worker=1 ready

新しいプロセスにリクエストの受付処理は引き継がれているので、古い方のプロセスを落としていく。

まずは、古い方の master プロセスに WINCH シグナルを送り、その worker プロセスを落としてあげる。 WINCH シグナルはその master プロセスの worker プロセスを gracefully に止めてあげる。

$ kill -WINCH 7768

プロセスとログを確認してみる。

$ ps -ef | grep unicorn | grep -v grep
hoge      7768     1  0 23:35 ?        00:00:01 unicorn master (old) -c unicorn.conf -D
hoge      7822  7768  0 23:43 ?        00:00:01 unicorn master -c unicorn.conf -D
hoge      7826  7822  0 23:43 ?        00:00:00 unicorn worker[0] -c unicorn.conf -D
hoge      7829  7822  0 23:43 ?        00:00:00 unicorn worker[1] -c unicorn.conf -D
I, [2011-07-30T23:48:45.648299 #7768]  INFO -- : gracefully stopping all workers
I, [2011-07-30T23:48:45.678491 #7768]  INFO -- : reaped #<Process::Status: pid 7782 exit 0> worker=0
I, [2011-07-30T23:48:45.678664 #7768]  INFO -- : reaped #<Process::Status: pid 7785 exit 0> worker=1

確かに、古い方の Unicorn のプロセスは、master プロセスだけが残り、worker は全て終了している。

一旦ここで念のため、アプリがキチンと動作しているか確認しておく。
問題無いようであれば、古い master プロセスも止めてあげる。

$ kill -QUIT 7768

プロセスとログを確認してみる。

$ ps -ef | grep unicorn | grep -v grep
hoge      7822     1  0 23:43 ?        00:00:01 unicorn master -c unicorn.conf -D
hoge      7826  7822  0 23:43 ?        00:00:00 unicorn worker[0] -c unicorn.conf -D
hoge      7829  7822  0 23:43 ?        00:00:00 unicorn worker[1] -c unicorn.conf -D
I, [2011-07-30T23:51:35.694051 #7768]  INFO -- : master complete

これで、新しいプログラムで Unicorn サーバが動作している状態となる。

blog comments powered by Disqus