Unicorn を使用した Rails アプリケーションのデプロイ
最終更新日 2024年10月16日(水)
Table of Contents
Heroku では、Unicorn の代わりに Puma Web サーバーを使用することをお勧めします。Unicorn を使用している場合は、アプリケーションがスロークライアント攻撃から保護されません。
リクエストを並列に処理する Web アプリケーションの方が、一度に 1 つのリクエストしか処理しない Web アプリケーションよりはるかに効率的に dyno リソースを使用します。そのため、本番環境のサービスを開発および実行する場合は常に、並列リクエスト処理を使用することをお勧めします。
Rails フレームワークは最初、一度に 1 つのリクエストを処理するように設計されました。このフレームワークは、この設計から、1 つの Ruby プロセスでのリクエストの並列処理を可能にするスレッドセーフ実装に徐々に移行しています。ただし、これは現在ほとんどの Ruby アプリケーションでサポートされていません。
Unicorn Web サーバーを使用すると、1 つの dyno で複数の Ruby プロセスを実行することにより、すべての Rails アプリケーションを並列に実行できます。
このガイドでは、Unicorn Web サーバーを使用して、新しい Rails アプリケーションを Heroku にデプロイする方法について説明します。基本的な Rails のセットアップについては、Rails スターターガイドを参照してください。
新しいデプロイは、必ずステージング環境でテストしてから本番環境アプリケーションにデプロイしてください。詳細は、「アプリの複数の環境の管理」を参照してください。
Unicorn サーバー
Unicorn は、フォークされたプロセスを使用して複数の受信リクエストを並列に処理する Rack HTTP サーバーです。
アプリケーションへの Unicorn の追加
Gemfile
最初に、Unicorn をアプリの Gemfile に追加します。
gem 'unicorn'
bundle install
を実行して、バンドルをローカルに設定します。
設定
Unicorn の設定ファイルを config/unicorn.rb
、または好きなパスに作成します。単純な Rails アプリケーションでは、次の基本設定を推奨します。
# config/unicorn.rb
worker_processes Integer(ENV["WEB_CONCURRENCY"] || 3)
timeout 15
preload_app true
before_fork do |server, worker|
Signal.trap 'TERM' do
puts 'Unicorn master intercepting TERM and sending myself QUIT instead'
Process.kill 'QUIT', Process.pid
end
defined?(ActiveRecord::Base) and
ActiveRecord::Base.connection.disconnect!
end
after_fork do |server, worker|
Signal.trap 'TERM' do
puts 'Unicorn worker intercepting TERM and doing nothing. Wait for master to send QUIT'
end
defined?(ActiveRecord::Base) and
ActiveRecord::Base.establish_connection
end
上記では、監視に ActiveRecord と New Relic を使用する標準の Rails アプリを前提としています。その他の使用可能な設定操作については、Unicorn のドキュメントを参照してください。
同時実行数の値を手動で設定するには、heroku config:set WEB_CONCURRENCY
を使用します。アプリケーションの負荷テストを実行して、アプリに適切な値を見つけます。
Unicorn ワーカープロセス
worker_processes Integer(ENV["WEB_CONCURRENCY"] || 3)
Unicorn は各 dyno 内で複数の OS プロセスをフォークして、Rails アプリがスレッドセーフでなくても複数の並列リクエストをサポートできるようにします。Unicorn の用語では、これらはワーカープロセスと呼ばれます (独自の dyno で実行される Heroku ワーカープロセスと混同しないようにしてください)。
フォークされた各 OS プロセスが追加のメモリを消費します。これにより、1 つの dyno で実行できるプロセスの数が制限されます。一般的な Rails メモリフットプリントでは、2 ~ 4 個の Unicorn ワーカープロセスが実行されることが想定されます。アプリケーションで許可されるプロセスの数は、具体的なメモリフットプリントに応じて増減します。アプリケーションの高速チューニングを可能にするために、この数を環境設定で指定することをお勧めします。アプリケーションログの R14 エラー (メモリ割り当ての超過) をロギングアドオンまたは heroku logs
のいずれかで監視します。
アプリの事前ロード
preload_app true
# ...
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
アプリケーションを事前ロードすると、個々の Unicorn worker_processes
の起動時間が短縮されると共に、before_fork
および after_fork
呼び出しを使用して個々のワーカーの外部接続を管理できるようになります。上記の設定では、これらの呼び出しはワーカープロセスごとの Postgres 接続を正しく確立するために使用されます。
New Relic ではまた、Unicorn アプリでのより正確なデータ収集のために preload_app true
も推奨しています。preload_app true
での New Relic の使用については、New Relic のドキュメントを参照してください。
シグナルの処理
before_fork do |server, worker|
Signal.trap 'TERM' do
puts 'Unicorn master intercepting TERM and sending myself QUIT instead'
Process.kill 'QUIT', Process.pid
end
# ...
end
after_fork do |server, worker|
Signal.trap 'TERM' do
puts 'Unicorn worker intercepting TERM and doing nothing. Wait for master to send QUIT'
end
# ...
end
POSIX シグナルは、特定のイベントまたは状態の変化を示すプロセス間通信の形式です。従来、QUIT
は、ただちに終了してコアダンプを生成するようプロセスに指示するために使用されています。TERM
は、終了するようプロセスに指示するが、その後プロセスがクリーンアップできるようにするために使用されます。
Unicorn は、グレースフルシャットダウンを示すために QUIT
シグナルを使用します。マスタープロセスは、このシグナルを受信すると、処理中のリクエストをすべて完了した後にグレースフルシャットダウンするすべてのワーカーに QUIT
シグナルを送信します。これらのワーカープロセスがシャットダウンすると、マスタープロセスは終了します。
Heroku は、dyno 内のすべてのプロセスに、その dyno がシャットダウンされることを示すために TERM
シグナルを使用します。上記の設定により、この TERM
シグナルは確実に、ワーカーがシグナルを捕捉して無視するという Unicorn モデルに正しく変換されます。マスターは、QUIT
シグナルを捕捉して自身に送信することにより、グレースフルシャットダウンプロセスを起動します。
Heroku は、プロセスがグレースフルシャットダウンするまで 10 秒間待ちます。その後、強制シャットダウンのためにすべてのプロセスに KILL
シグナルが送信されます。個々のリクエストが 10 秒より長くかかった場合、そのリクエストは中断されます。グレースフルシャットダウンの失敗を示すアプリケーションログ内のエントリに注意してください。
タイムアウト
Heroku のルーターは、リクエストタイムアウトが発生する前に 30 秒間の期間を適用します。リクエストがルーター経由で dyno に配信された後、dyno はレスポンスを返すまでに 30 秒の余裕があります。それを過ぎると、ルーターはカスタマイズ可能なエラーページを返します。これは、ハングアップしたリクエストがリソースを停止させないようにするために行われます。ルーターはクライアントにレスポンスを返しますが、クライアントがレスポンスを受信しても、Unicorn ワーカーは引き続きそのリクエストを処理しようとします。つまり、ハングアップしたリクエストのために、ワーカーはいつまでも停止する可能性があります。アプリケーションのリクエストがリクエストタイムアウトを超えて dyno を停止させないようにするには、Rack::Timeout gem と Unicorn のタイムアウト設定の両方を使用することをお勧めします。
Unicorn のタイムアウト
Unicorn には、設定可能なタイムアウト設定があります。Unicorn でのタイムアウトのカウントダウンは、リクエストがアプリケーションによって処理されるときに開始され、そこからレスポンスが返されたときに終了します。リクエストの処理が指定された時間を超えた場合、マスターはリクエストを処理しているワーカーに SIGKILL を送信します。
timeout 15
15 秒のタイムアウトをお勧めします。15 秒のタイムアウトでは、リクエストの処理が 15 秒を超えた場合、マスタープロセスはワーカープロセスに SIGKILL
を送信します。これにより H13 エラーコードが生成され、それがログに表示されます。デバッグに役立つスタックトレースが生成されるわけではありません。
Rack::Timeout
Rack::Timeout の制限が設定されている場合は、リクエストが閉じられ、長時間実行されるコードの将来のデバッグに使用できるスタックトレースがログに生成されます。最初に、gem をインストールする必要があります。
# Gemfile
gem 'rack-timeout'
$ bundle install
その後、タイムアウトを設定できます。
# config/initializers/timeout.rb
Rack::Timeout.timeout = 10 # seconds
Ruby 1.9/2.0 の Rack::Timeout
では、信頼できない可能性がある Ruby の stdlib Timeout
ライブラリが使用されます。Heroku では、Rack::Timeout を使用し、Unicorn のタイムアウトを設定することをお勧めします。両方のタイムアウトシステムを使用しているとき、Rack::Timeout によって生成されるスタックトレースをデバッグに使用する予定がある場合は、Rack::Timeout 値を Unicorn のタイムアウトより小さくします。
Procfile
Procfile
で Unicorn を Web プロセスのサーバーとして設定し、その設定ファイルを指します。
web: bundle exec unicorn -p $PORT -c ./config/unicorn.rb
注意
事前ロードとその他の外部サービス
他の外部接続に注意して、それらが Unicorn のフォーキングモデルと適切に動作することを確認してください。上記のサンプル設定で確認できるように、このアプリはその ActiveRecord 接続を before_fork
ブロックで削除し、ワーカープロセスの after_fork
で再接続します。その他のサービスも同様のパターンに従います。たとえば、Unicorn アプリで Resque を使用するための設定ブロックを次に示します。
before_fork do |server, worker|
# ...
# If you are using Redis but not Resque, change this
if defined?(Resque)
Resque.redis.quit
Rails.logger.info('Disconnected from Redis')
end
end
after_fork do |server, worker|
# ...
# If you are using Redis but not Resque, change this
if defined?(Resque)
Resque.redis = ENV['<REDIS_URL>']
Rails.logger.info('Connected to Redis')
end
end
REDIS_URL
環境設定を Redis プロバイダーからのものに対応するように変更します。
dalli memcache クライアントなどの多くの一般的な gem では、Unicorn のワーカープロセスモデルとの互換性をドキュメントで説明しています。問題が発生している場合は、詳細について gem のドキュメントを確認してください。
アセット
最適なパフォーマンスを得るには、アセットを CDN の背後でホストして、Web dyno を動的コンテンツにのみサービスを提供するように解放します。
Database connections
並列 Web サーバーを本番環境で実行すると、各 dyno には複数のデータベース接続が必要になります。並列 Web サーバーで多数の Rails アプリを実行するには、Active Record がこれらの接続を接続プール内に作成して管理する方法や、開発データベースに関する接続制限を理解する必要があります。これらのトピックについての詳細は、DevCenter の記事「Concurrency and Database Connections」(並列性とデータベース接続) を参照してください。
スロークライアント
アプリケーションとワークロードの特定の組み合わせでは、Unicorn が最適な選択肢ではない場合があります。特に、アプリケーションが大きな本文ペイロードを含むリクエストをスロークライアントから受信する場合は、別の Web サーバーを使用する方が適している可能性があります。その例として、WiFi、4G、またはその他の高速ネットワーク上にない携帯電話からユーザーによってアップロードされた画像を受信するアプリがあります。
この問題は、クライアントから低速で送信されるリクエストの受信中に Unicorn ワーカーが停止することによって発生します。すべての Unicorn ワーカーが停止すると、新しいリクエストはキューに入れられ、アプリでは、通常より長いリクエストのキューイング時間や、場合によっては H12 エラー が発生する可能性があります。
Puma、Thin、または Rainbows! は、スロークライアントによって生成された負荷の下でより適切に動作する可能性のある代替の Web サーバーです。Heroku でアプリを実行している Web サーバーを変更するには、Procfile で web
プロセスタイプの別のコマンドを指定するだけです。