はじめに
Railsアプリケーションを本番環境で安定して稼働させるには、Webサーバーの導入が不可欠です。Webサーバーは、クライアントからのHTTPリクエストをRailsアプリに伝達し、アプリで処理されたレスポンスをクライアントに返す役割を担います。
本記事では、Railsアプリ用のアプリケーションサーバー「Unicorn」の基本的な使い方を解説します。Unicornの役割や設定方法、Nginxとの連携方法など、Unicornを使いこなすために必要な知識を詳しく説明していきます。
Unicornの基本
Unicornについて
UnicornとはRackアプリケーション用のアプリケーションサーバーです。
開発者のみがリクエストを送信する開発環境と違い、本番環境ではいつ、どれくらいのリクエストが送信されてくるかはわかりません。24時間365日、間断なくリクエストが送信されてくるかもしれませんし、複数のリクエストが同時発生的に送信されてくる可能性もあります。
Unicornは送信されてきた多数のリクエストを捌き、分散してRackアプリケーションに伝達するという機能を持ちます。リクエストをアプリケーションへ伝達するとアプリケーションからレスポンスが戻ってくるまで待機し、最終的にリクエストに対するレスポンスをクライアントに返します。
※図中には「Unicorn (Web Server)」と記載されていますが、正しくは「Unicorn (Application Server)」です
この画像はクライアントからRailsアプリへリクエストを送信したときの概念図です。リモートサーバーにあるUnicornがクライアントからのリクエストを受け取り、アプリケーションに伝達していることがわかります(レスポンスシーケンスは省略しています)。
この概念図を見て、「Rackってなに? Railsはどこ?」「なんでUnicornとNginxを組み合わせるの?」など疑問があるかと思いますので、詳しく解説していきます。
Rackってなに? Railsはどこ?
※図中には「Web Servers」と記載されていますが、正しくは「Application Servers」です
Rackとは、Rack専用のアプリケーションサーバーとRubyで作られたWebアプリケーションフレームワークを繋ぐためのAPIを提供するアプリケーションサーバーです。「Rubyで作られたWebアプリケーションフレームワーク」とはもちろんRuby on Railsのことですが、他にもSinatraというWebアプリケーションフレームワークがあります。
Rackというアプリケーションサーバーという土台があり、その上にRuby on RailsやSinatraが乗っていると思ってください。概念的にはRack = Ruby on Railsと思っていただいて構いません。
Rack専用のアプリケーションサーバーには本記事の主役であるUnicorn以外にも、PumaやThin、WEBrickといったたくさんの種類があります。これらのRack専用のアプリケーションサーバーとWebアプリケーションフレームワーク(Ruby on Rails/Sinatra)が、互いを意識することなく柔軟に通信できるようにしているのがRackというわけです。
なんでUnicornとNginxを組み合わせるの?
なぜUnicornとNginxというふたつのサーバーを組み合わせる必要があるのでしょうか。その理由を具体的に説明します。
- 負荷分散とスケーラビリティ
- Nginxはリバースプロキシとして機能し、複数のUnicornワーカープロセスに負荷を分散する
- これにより、アプリケーションの処理能力とスケーラビリティが向上する
- 静的ファイルの効率的な処理
- Nginxは静的ファイル(画像、CSS、JavaScript等)の配信に非常に優れている
- Unicornに静的ファイルの処理を任せるよりも、Nginxで処理する方が効率的
- セキュリティの強化
- Nginxはバッファオーバーフロー攻撃やDDoS攻撃などに対する保護機能を提供する
- アプリケーションサーバー(Unicorn)を直接インターネットに晒さずに済む
- SSL/TLS終端
- Nginxで SSL/TLS 通信を終端することで、Unicornの負荷を軽減可能
- リクエストのバッファリングと圧縮
- Nginxはリクエストをバッファリングし、必要に応じて圧縮することができる
- これにより、Unicornの処理効率が向上する
- 柔軟なルーティング
- Nginxの柔軟なルーティング機能を利用して、複雑なリクエスト処理やリダイレクトを実現可能
- ゼロダウンタイムデプロイの実現
- Nginxを使用することで、Unicornのワーカープロセスを順次再起動しながら、サービスを中断せずにアプリケーションを更新できる
- 長時間接続の処理
- WebSocketなどの長時間接続をNginxで処理し、Unicornの負荷を軽減可能
- ログ管理とモニタリング
- Nginxの詳細なログ機能を利用して、アクセス解析やトラブルシューティングが容易になる
これらの理由により、UnicornとNginxを組み合わせることで、高性能で安定したWebアプリケーション環境を構築することができます。
特に大規模なトラフィックを処理する必要がある場合や、セキュリティを重視する場合に、この組み合わせは非常に効果的です。
Unicornの動作
Unicornのプロセスモデル
Unicornは、マスタープロセスと複数のワーカープロセスを使用するプリフォークモデルを採用しています。
- マスタープロセス
- アプリケーションをロードし、設定を読み込む
- 子プロセス(ワーカー)を生成・管理する
- シグナルハンドリングを行い、子プロセスの終了を監視する
- ワーカープロセス
- マスタープロセスによって生成する
- アプリケーションロジックを実行し、HTTPリクエストを処理する
- 処理が完了したら、マスタープロセスに制御を返す
リクエストの処理の流れ
- クライアントからHTTPリクエストが送信される
- Webサーバー(例:Nginx)がリクエストを受け取り、Unicornのソケットに転送する
- マスタープロセスがリクエストを受け取り、利用可能なワーカープロセスに割り当てる
- 割り当てられたワーカープロセスがアプリケーションロジックを実行し、レスポンスを生成する
- レスポンスがマスタープロセスに返され、Webサーバーを経由してクライアントに送信される
メモリ管理
Unicornは、Copy-on-write (CoW) メモリモデルを活用してメモリ効率を高めています。
- マスタープロセスがアプリケーションをロードし、メモリ上に展開する
- ワーカープロセスが起動する際、マスタープロセスのメモリ領域を共有する
- ワーカープロセスが何かしらの変更を加えた場合、その部分のメモリ領域がコピーされる
- これにより、メモリ使用量を抑えつつ、各ワーカープロセスが独立して動作できる
設定と制御
Unicornの動作は、設定ファイルで制御されます。
- ワーカープロセスの数、タイムアウト値、リスニングソケットなどを設定できる
- シグナルを使用して、マスタープロセスを制御できる
QUIT
:グレースフルシャットダウンを実行TERM
:即時終了USR2
:ゼロダウンタイムデプロイ用のマスタープロセスの再起動
- 設定ファイルはRubyで記述されるため、動的な設定が可能
プリフォークモデルとCopy-on-writeを活用することで、高速なリクエスト処理と効率的なメモリ管理を実現しています。Nginxなどのリバースプロキシと組み合わせることで、Railsアプリケーションを安定して稼働させることができます。
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
オプション指定時)。具体的な処理の流れは以下のようになります。
- 現在のプロセスIDが保存してある
unicorn.pid
をunicorn.pid.oldbin
にコピー - 新しいUnicornのプロセスを起動(新しいプロセスIDを
unicorn.pid
に保存) 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の使い方については以下の記事を参照してください。
まとめ
以上、Unicornを使ってRailsアプリケーションを本番環境で稼働させる方法を詳しく解説しました。Unicornを導入することで、以下のような利点が得られます。
- 高いスケーラビリティ
- マスタープロセスが複数のワーカープロセスを管理し、リクエストを効率的に処理できる。
- ゼロダウンタイムデプロイ
- preload_appオプションを使うことで、アプリケーションを停止せずにデプロイが可能。
- 高速なリクエスト処理
- プリフォークモデルにより、リクエストごとにプロセスを生成する必要がない。
Unicornを使えば、Railsアプリケーションを安定して本番環境で稼働させることができます。ただし、Unicornだけでは不十分で、Nginxとの連携が重要です。
ぜひUnicornを活用して、高性能で信頼性の高いWebアプリケーションを構築してみてください。