ユニコーンとユニコーンワーカーキラーの理解

注:GitLab 13.0から、PumaはGitLabオールインワンパッケージベースのインストールやGitLab Helmチャートデプロイで使用されるデフォルトのウェブサーバです。

Unicorn

UnicornはRubyとCで書かれたデーモンで、Ruby on Railsアプリケーションをロードして実行することができます。

Unicornはマルチプロセスアーキテクチャを採用しており、利用可能なCPUコアを有効活用し(プロセスは異なるコアで実行可能)、耐障害性を高めています(ほとんどの障害は1つのプロセスだけにとどまり、GitLabを完全にダウンさせることはできません)。 起動時に、Unicornの「マスター」プロセスはGitLabアプリケーションコードを含むクリーンなRuby環境をロードし、このクリーンな初期環境を継承した「ワーカー」を生成します。 マスター」はリクエストを処理することはなく、ワーカーに任されます。 オペレーティングシステムのネットワークスタックは、受信したリクエストをキューに入れ、ワーカーに分配します。

完璧な世界では、マスタはワーカーのプールを一度だけ生成し、ワーカーは時間の終わりまで次々と入ってくるウェブリクエストを処理します。 現実には、ワーカープロセスはクラッシュしたりタイムアウトしたりします。マスタはワーカーがリクエストの処理に時間がかかりすぎることに気づくと、SIGKILL (‘kill -9’) を使ってワーカープロセスを終了させます。 ワーカープロセスがどのように終了したかに関わらず、マスタプロセスはそれを新しい「クリーンな」プロセスに再び置き換えます。 Unicorn は「クラッシュした」ワーカーを、ユーザのリクエストを落とすことなく置き換えることができるように設計されています。

これは、unicorn_stderr.logにおける Unicorn ワーカーのタイムアウトの様子です。 マスタープロセスの PID は以下の 56227 です。

[2015-06-05T10:58:08.660325 #56227] ERROR -- : worker=10 PID:53009 timeout (61s > 60s), killing
[2015-06-05T10:58:08.699360 #56227] ERROR -- : reaped #<Process::Status: pid 53009 SIGKILL (signal 9)> worker=10
[2015-06-05T10:58:08.708141 #62538]  INFO -- : worker=10 spawned pid=62538
[2015-06-05T10:58:08.708824 #62538]  INFO -- : worker=10 ready

調整可能なオプション

Unicornで調整できる主なオプションは、ワーカープロセスの数と、Unicornマスターがワーカープロセスを終了するリクエストタイムアウトです。 これらの設定を調整したい場合は、Omnibus GitLab Unicorn settings documentationを参照ください。

ユニコーンワーカー殺し

GitLab にはメモリリークがあります。 これらのメモリリークは、Unicorn ワーカーのような長時間稼働するプロセスで現れます (Unicorn のマスタープロセスは、おそらくユーザーリクエストを処理しないため、メモリリークすることは知られていません)。

このようなメモリリークを管理しやすくするために、GitLabにはunicorn-worker-killer gemが用意されています。 このgemはUnicornワーカーにモンキーパッチを適用し、16リクエストごとにメモリのセルフチェックを行うようにします。 Unicornワーカーのメモリが事前に設定した上限を超えた場合は、ワーカープロセスは終了します。 その後、Unicornマスターは自動的にワーカープロセスを置き換えます。

これはメモリリークを処理する堅牢な方法です。Unicornはワーカーが「クラッシュ」しても処理できるように設計されているので、ユーザーリクエストが落ちることはありません。 unicorn-worker-killer gemは、_リクエストの合間に_ワーカープロセスを終了させるように設計されているので、ユーザーリクエストには影響しません。Unicornワーカーキラーの最小・最大メモリしきい値(バイト単位)は、以下の値を設定することで設定できます/etc/gitlab/gitlab.rb

  • GitLab12.7以降が対象です:

     unicorn['worker_memory_limit_min'] = "1024 * 1 << 20"
     unicorn['worker_memory_limit_max'] = "1280 * 1 << 20"
    
  • GitLab12.6以前が対象です:

     unicorn['worker_memory_limit_min'] = "400 * 1 << 20"
     unicorn['worker_memory_limit_max'] = "650 * 1 << 20"
    

そうでない場合は、GITLAB_UNICORN_MEMORY_MINGITLAB_UNICORN_MEMORY_MAX環境変数を設定します。

これは、unicorn_stderr.logでUnicornワーカーのメモリを再起動したときの様子です。 Worker 4 (PID 125918)が自分自身を点検し、終了を決定していることがわかります。 しきい値のメモリは254802235バイトで、約250MBでした。 GitLabでは、このしきい値は200MBと250MBの間のランダムな値です。 その後、マスタープロセス(PID 117565)がワーカープロセスを回収し、PID 127549で新しい’worker 4’を生成します。

[2015-06-05T12:07:41.828374 #125918]  WARN -- : #<Unicorn::HttpServer:0x00000002734770>: worker (pid: 125918) exceeds memory limit (256413696 bytes > 254802235 bytes)
[2015-06-05T12:07:41.828472 #125918]  WARN -- : Unicorn::WorkerKiller send SIGQUIT (pid: 125918) alive: 23 sec (trial 1)
[2015-06-05T12:07:42.025916 #117565]  INFO -- : reaped #<Process::Status: pid 125918 exit 0> worker=4
[2015-06-05T12:07:42.034527 #127549]  INFO -- : worker=4 spawned pid=127549
[2015-06-05T12:07:42.035217 #127549]  INFO -- : worker=4 ready

上の GitLab.com のログのスニペットで目立つのは、’worker 4’ がわずか 23 秒しかリクエストを処理していないことです。 これは、現在の GitLab.com の設定とトラフィックでは正常な値です。

GitLabのサイトによっては、Unicornのメモリ再起動が頻繁に起こり、管理者を混乱させることがあります。 通常は、それは赤信号です。