Table of Contents
Open Table of Contents
Unicorn とは?
Rails、Rack アプリケーションを動作させるコンテナとしては、Passenger、Thin、Mongrel などの選択肢がある。 それ以外にも Unicorn という Rack アプリケーション向けの HTTP サーバがあり、今回試しに使ってみたのでそのメモ。
設計方針に特徴的な部分があり、以下の記事に詳しい。
- Unicorn! - GitHub
Unicorn を使用している github の記事 - Unicorn で Sinatra アプリをデプロイしてみた - 射撃しつつ前転
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 からリンクのある以下のページで確認できる。
- Sample verbose configuration file for Unicorn
- Minimal sample configuration file for Unicorn (必要最小限の設定例)
上記の最低限の設定を設定を流用し、以下の設定ファイルを用意する。
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 サーバが動作している状態となる。