【Rails】Webサーバー「Unicorn」の基本情報と実装方法

はじめに

Railsアプリを本番環境で稼働させるには、クライアントからのリクエストを捌くWebサーバーを導入する必要があります。WebサーバーはクライアントからのリクエストをRailsアプリに伝達し、Railsアプリで処理されたレスポンスをクライアントに返すという役割を担います。

本記事では、Webサーバー「Unicorn」の使い方についてまとめています。

Unicornの基本

Unicornについて

UnicornとはRackアプリケーション用のWebサーバーです。

開発者のみがリクエストを送信する開発環境と違い、本番環境ではいつ、どれくらいのリクエストが送信されてくるかはわかりません。24時間365日、間断なくリクエストが送信されてくるかもしれませんし、複数のリクエストが同時発生的に送信されてくる可能性もあります。

Unicornは送信されてきた多数のリクエストを捌き、分散してアプリケーションに伝達するという機能を持ちます。リクエストをアプリケーションへ伝達するとアプリケーションからレスポンスが戻ってくるまで待機し、最終的にリクエストに対するレスポンスをクライアントに返します。

この画像はクライアントからRailsアプリへリクエストを送信したときの概念図です。リモートサーバーにあるUnicornがクライアントからのリクエストを受け取り、アプリケーションに伝達していることがわかります(レスポンスシーケンスは省略しています)。

この概念図を見て、「Rackってなに? Railsはどこ?」「なんでWebサーバーがふたつあるの?」など疑問があるかと思いますので、詳しく解説していきます。

Rackってなに? Railsはどこ?

Rackとは、WebサーバーとRubyで作られたWebフレームワークを繋ぐためのAPIを提供するアプリケーションサーバーです。「Rubyで作られたWebフレームワーク」とはもちろんRuby on Railsのことですが、他にもSinatraというWebフレームワークがあります。

Rackというアプリケーションサーバーという土台があり、その上にRuby on RailsやSinatraが乗っていると思ってください。概念的にはRack = Ruby on Railsと思っていただいて構いません。

Webサーバーには本記事の主役であるUnicorn以外にも、PumaやThin、WEBrickといったたくさんの種類があります。これらのWebサーバーとWebフレームワーク(Ruby on Rails/Sinatra)が、互いを意識することなく柔軟に通信できるようにしているのがRackというわけです。

なんでWebサーバーがふたつあるの?

Webサーバーがクライアントからのリクエストを捌く機能を持っていることは理解していただけたかと思います。では、上の画像にはなぜUnicornとNginxというふたつのWebサーバーが存在しているのでしょうか。UnicornあるいはNginxのどちらかだけではだめなのでしょうか。

まず、Rackは一般的なWebサーバー(Nginx/Apache)と繋ぐことを想定して作られていません。上の画像において、NginxとRackの間の「×」は互いに接続できないことを示しています。そのため、Rackと繋ぐことができる専用のWebサーバーが必要となります。

「Rackってなに? Railsはどこ?」セクションで、Webサーバーにはたくさんの種類があるということを書きましたが、このWebサーバーとは「Rack専用のWebサーバー」を意味します。ですので、UnicornもPumaもThinもWEBrickもすべて「Rack専用のWebサーバー」です。

しかし、UnicornなどのRack専用のWebサーバーはブロッキングI/Oモデル1で実行されているため、クライアントからのリクエストを非同期で処理することができません。通信状態が不安定なクライアントがUnicornのプロセスを占有している場合、その間は他のクライアントからのリクエストを処理することができなくなってしまいます。

こうしたUnicornなどのRack専用のWebサーバーの弱点を補うのがNginxです。Nginxは低速クライアントをバッファリングします。NginxとUnicornの間の接続は非常に高速なため、Unicornが低速なクライアントのリクエストを受信することがなくなります。

このようにUnicornとNginxは互いの弱点を補間する関係にあるので、どちらのWebサーバーも必要になるというわけです。

  1. I/O処理時に対象が準備完了になるまで待機し続けること。

Unicornの動作

Unicornにはマスターとワーカーという概念があります。マスターはアプリケーションのインスタンスを持つ唯一のプロセスで、複数のワーカープロセスを生成し、所有します。マスタープロセスがワーカープロセスを生成することをフォークと呼びます。

Unicornを起動するとマスタープロセスが生成され、マスタープロセスはアプリケーションの読み込みと複数のワーカープロセスの生成を行います。ワーカープロセスは一定時間が経つとマスタープロセスによって破棄され、また新たなワーカープロセスが生成されます。このワーカープロセスのライフライクルはUnicornを停止するまで繰り返されます。

UnicornがNginx(概念的にはクライアント)からリクエストを受信すると、マスタープロセスはワーカープロセスにリクエストを処理するよう命令します。

Unicornの使い方

Unicornのインストール

Unicornをインストールするには、Gemfileに以下を追記してbundle installを実行します。

Gemfile

group :production do
  # 以下を追記
  gem 'unicorn'
end

Unicornの設定

Unicornを起動する際に必要となる設定ファイルを作成します。作成するディレクトリとファイル名はなんでもいいのですが、config/ディレクトリ配下にunicorn.rbというファイル名で作成するのが一般的です。

設定ファイルは大別して「変数の設定」と「フォーク前後の処理」に分けられます。ここではそれらを分けて説明していますが、実際にはひとつの設定ファイルにまとめて書いてください。

変数の設定

変数の設定は以下の通りです。

unicorn.rb

# Railsアプリのルート
rails_root = File.expand_path('../../', __FILE__)

# Gemfileの場所
ENV['BUNDLE_GEMFILE'] = rails_root + "/Gemfile"

# Unicornの設定
worker_processes  2
timeout           15
working_directory rails_root
pid               File.expand_path 'tmp/pids/unicorn.pid', rails_root
listen            File.expand_path 'tmp/sockets/.unicorn.sock', rails_root
stdout_path       File.expand_path 'log/unicorn.log', rails_root
stderr_path       File.expand_path 'log/unicorn.log', rails_root
preload_app       true

CapistranoでUnicornを起動する際、Gemfileの場所が必要になるので、ここで環境変数に設定しています。

その他の変数の設定は以下の通りです。

設定 説明
worker_processes 同時起動するワーカーの最大数。ワーカーが多ければ多いほどメモリ消費も激しいので、サーバーのスペックとアプリケーションのアクセス数によって決定する。
timeout ワーカーが処理を開始し終了するまでの最大時間(秒数)。設定した時間を超えた場合、そのワーカーは破棄される。
working_directory Unicornの起動コマンドが実行されるディレクトリ。
pid UnicornのプロセスIDを保存しておくファイル。
listen リクエストを受信するポート番号。Nginxとの接続に使用。
stdout_path Unicornの実行ログを出力するファイル。
stderr_path Unicornのエラーログを出力するファイル。
preload_app Unicornの再起動をダウンタイムなしで行う。この設定によりアプリケーションがダウンすることなくデプロイができるようになる(ホットデプロイ)。

フォーク前後の処理

フォーク前後の処理は以下の通りです。

unicorn.rb

# フォークが行われる前の処理
before_fork do |server, worker|
  defined?(ActiveRecord::Base) and ActiveRecord::Base.connection.disconnect!
  old_pid = "#{server.config[:pid]}.oldbin"
  if old_pid != server.pid
    begin
      Process.kill "QUIT", 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

フォーク前後の処理では、大きく分けて「UnicornとActive Recordの切断/接続」と「マスタープロセスの再起動」のふたつを行っています。まず、「UnicornとActive Recordの切断/接続」の処理を抜き出すと以下になります。

unicorn.rb

# フォークが行われる前の処理
before_fork do |server, worker|
  defined?(ActiveRecord::Base) and ActiveRecord::Base.connection.disconnect!
end

# フォークが行われた後の処理
after_fork do |server, worker|
  defined?(ActiveRecord::Base) and ActiveRecord::Base.establish_connection
end

フォーク前にActive Recordとの接続を切断し、フォーク後に再び接続しています。これは、マスタープロセスで使用しているActive Recordとの接続をワーカープロセスで使用させないために行っているそうです(明確に説明している情報が見つかりませんでした)。

続いて「マスタープロセスの再起動」の処理を抜き出すと以下になります。

unicorn.rb

# フォークが行われる前の処理
before_fork do |server, worker|
  old_pid = "#{server.config[:pid]}.oldbin"
  if old_pid != server.pid
    begin
      Process.kill "QUIT", File.read(old_pid).to_i
    rescue Errno::ENOENT, Errno::ESRCH
    end
  end
end

preload_appを有効にしていると、ダウンタイムなしでUnicornの再起動が行うことができます(-USR2オプション指定時)。具体的な処理の流れは以下のようになります。

  1. 現在のプロセスIDが保存してあるunicorn.pidunicorn.pid.oldbinにコピー
  2. 新しいUnicornのプロセスを起動(新しいプロセスIDをunicorn.pidに保存)
  3. unicorn.pid.oldbinを参照して古いプロセスを停止

一時的にふたつのプロセスが起動されることになり、それによってダウンタイムがなくなるという仕組みです。そして、3.を実行しているのが上記の「マスタープロセスの再起動」になります。

Unicornの起動/停止/再起動

Unicornの起動

Unicornを起動するには以下のコマンドを実行します。

$ bundle exec unicorn -c config/unicorn.rb [-E $RAILS_ENV] [-D]

-Eオプションをつけると環境を指定できます。-Dオプションをつけるとデーモンプロセス(常駐プロセス)として起動します。

Unicornの停止

Unicornを停止するには以下のコマンドを実行します。

$ kill -QUIT `cat /path/to/unicorn.pid`

Unicornの再起動

Unicornを再起動するには以下のコマンドを実行します。

# 通常の再起動
$ kill -HUP `cat /path/to/unicorn.pid`

# 緩やかな再起動
$ kill -USR2 `cat /path/to/unicorn.pid`

Capistranoによるホットデプロイを行うには、必ず-USR2オプションを指定して再起動を行います。なお、アプリのデプロイ時にはUnicornの再起動を行う必要がありますが、-HUP-USR2の再起動では修正が反映されないときがあります。そのときはUnicornの停止と起動を行なってください。

Capistranoとの連携

Capistranoを使ってデプロイの自動化を行っている場合、Unicornの起動または再起動を自動化することができます。Capistranoの使い方については以下の記事を参照してください。

まとめ

Railsアプリを本番環境で稼働させるにはUnicornなどのWebサーバーが必要になります。初めは何をしているかわかりづらい分野ではありますが、本番環境で安定した運用をするためにも頑張ってUnicornを導入してみてください。

本記事を参考にして、Unicornの使い方を覚えていただければと思います。

関連記事

【Rails】Railsアプリのデバッグ《マルチデバイス篇》
# はじめに 近年のWebアプリはレスポンシブ対応が当たり前になっています。最低でもPCとスマートフォンに対応したデザイン、ときにはその中間のタブレットに対応したデザインなんかも作成する必要があります。 Webアプリの開発はPCを使って行う [...]
2021年5月23日 13:02
【Rails】Railsアプリのデバッグ《Better Errors篇》
# はじめに Railsアプリの開発中になんらかのエラーが発生すると、デフォルトでは以下のような画面が表示されます(画像をクリックすると拡大します)。 <a class="gallery" data-group="gallery" href [...]
2021年5月22日 19:42
【Rails】Railsアプリのデバッグ《byebug篇》
# はじめに Ruby on Railsに限りませんが、アプリの開発中にはエラーは付き物です。なにかしらのエラーが発生したときに、エラーの原因を特定しエラー箇所を修正することをデバッグと言います。Railsではデバッグの手助けとなる機能があらかじ [...]
2021年5月22日 15:30
【Rails】レンダリング(renderメソッド)でアンカー指定を行う
# はじめに 通常、Railsでアンカー付きのリクエストを発生させるには`redirect_to`を使います。 ```rb redirect_to root_path(anchor: 'target') ``` では、`rende [...]
2021年5月20日 10:47
【Rails】Bundler 2.2.x以降は開発者が適切なプラットフォームを追加する必要がある
# 事象 昔作ったRailsアプリを久しぶりに修正しデプロイしようとしたところ、以下のエラーが出力されました。 ```bash # 実行コマンド Running $HOME/.rbenv/bin/rbenv exec bundle ch [...]
2021年5月18日 16:18