【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サーバー」です。

しかし、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アップグレードまとめ
# はじめに Ruby on Railsに限らず、何らかのフレームワークを使ってWebシステムを構築している場合、フレームワークのアップグレード作業は避けて通れません。 一般的にフレームワークはバージョン毎にEOL (End of Life [...]
2022年10月1日 14:32
【Rails】ユーザー登録時に行うメールアドレス認証機能の実装方法
# はじめに ユーザー登録/解除やログイン/ログアウトといった認証機能の導入に「devise」というGemを使っている人は多いと思います。「devise」では以下のように記述するだけで、ユーザー登録時に確認メールを送付しメールアドレス認証を行う機 [...]
2022年9月24日 14:24
【Rails】モデルに列挙型(enum)を定義し、使いこなす方法
# はじめに Railsはモデルでカラム名と同名の列挙型(enum)を定義することで、カラムと列挙型の変数を紐付けることができます。カラムと列挙型の変数を紐付けると、カラムに対して様々な便利な使い方ができるようになります。 本記事では、モデ [...]
2022年9月3日 10:29
【Rails】RailsでCORSとPreflight requestの設定を行う方法
# はじめに RailsアプリをAPIサーバーとして構築するには、CORS (Cross-Origin Resource Sharing)と Preflight requestの設定を行う必要があります。APIサーバーは外部からの要求に対して処理 [...]
2022年8月27日 10:44
【Ruby】Bundlerを使ってRubyGemsを作成/公開する方法
# はじめに Bundlerを使ってRubyGemsを作成および公開する方法について説明します。Bundlerを使わずにRubyGemsを作成/公開する方法については以下の記事を参照してください。 <iframe class="hatena [...]
2022年7月12日 23:18
【Ruby】RubyGemsを作成/公開する方法
# はじめに RubyGemsを作成および公開する方法について説明します。Bundlerを使ってRubyGemsを作成する方法については以下の記事を参照してください。 <iframe class="hatenablogcard" style [...]
2022年7月11日 21:52
【Rails】M1チップ搭載MacでRuby on Railsの開発環境構築
# はじめに M1チップ搭載MacにRuby on Railsの開発環境を構築する手順を記載します。 - MacBook Air (M1, 2020) - macOS Monterey 12.3.1 # Homebrew ## [...]
2022年5月5日 11:56
【Rails】Rakeタスクの基本情報と作成・実行方法
# はじめに Railsには標準でRakeというGemが同梱されています。RakeはRubyで実装されたMake(UNIX系のOSで使用できるコマンド)のようなビルド作業を自動化するツールです。Ruby Make、略してRakeというわけですね。 [...]
2022年3月7日 22:12