GitLab パッケージにバンドルされている Puma インスタンスを設定します。

PumaはRubyアプリケーション用の高速、マルチスレッド、高同時HTTP 1.1サーバです。GitLabのユーザー向け機能を提供するRailsアプリケーションのコアを実行します。

メモリ使用量の削減

メモリ使用量を削減するために、Pumaはワーカープロセスをフォークします。ワーカーが作成されるたびに、プライマリプロセスとメモリを共有します。ワーカーが追加のメモリを使うのは、メモリページを変更したり追加したりするときだけです。このため、ワーカーが追加のウェブリクエストを処理するにつれて、Puma ワーカーはより多くの物理メモリを使用するようになります。時間の経過とともに使用されるメモリ量は、GitLabの使用状況によって異なります。GitLabユーザーが使用する機能が多ければ多いほど、時間の経過とともに予想されるメモリ使用量は多くなります。

無秩序なメモリ増加を止めるために、GitLab Railsアプリケーションは監視スレッドを実行し、ワーカーが与えられた常駐セットサイズ(RSS) しきい値を一定時間超えると自動的に再起動します。

GitLabはメモリの上限をデフォルトで1200Mb 。デフォルト値をオーバーライドするには、per_worker_max_memory_mb に新しい RSS リミットをメガバイト単位で設定します:

  1. /etc/gitlab/gitlab.rb を編集します:

    puma['per_worker_max_memory_mb'] = 1024 # 1GB
    
  2. GitLab を再設定します:

    sudo gitlab-ctl reconfigure
    

ワーカーが再起動されると、GitLabを実行する容量が短時間減少します。ワーカーが頻繁に入れ替わる場合は、per_worker_max_memory_mb を高い値に設定してください。

ワーカー数はCPUコアに基づいて計算されます。ワーカーが頻繁に再起動される場合(1分間に1回以上)、4~8ワーカーの小規模なGitLabデプロイではパフォーマンスにイシューが発生する可能性があります。

サーバーに空きメモリがある場合は、1200 以上の高い値が有効です。

ワーカーの再起動を監視

GitLab はメモリ使用量が多いためにワーカーが再起動されると、ログイベントを発行します。

以下は/var/log/gitlab/gitlab-rails/application_json.log におけるログイベントの例です:

{
  "severity": "WARN",
  "time": "2023-01-04T09:45:16.173Z",
  "correlation_id": null,
  "pid": 2725,
  "worker_id": "puma_0",
  "memwd_handler_class": "Gitlab::Memory::Watchdog::PumaHandler",
  "memwd_sleep_time_s": 5,
  "memwd_rss_bytes": 1077682176,
  "memwd_max_rss_bytes": 629145600,
  "memwd_max_strikes": 5,
  "memwd_cur_strikes": 6,
  "message": "rss memory limit exceeded"
}

memwd_rss_bytes は実際に消費されたメモリ量、memwd_max_rss_bytesper_worker_max_memory_mbで設定された RSS 制限値です。

ワーカータイムアウトの変更

デフォルトの Puma のタイムアウトは 60 秒です

note
puma['worker_timeout'] 設定は最大リクエスト時間を設定しません。

ワーカータイムアウトを 600 秒に変更するには、次のようにします:

  1. /etc/gitlab/gitlab.rb を編集します:

    gitlab_rails['env'] = {
       'GITLAB_RAILS_RACK_TIMEOUT' => 600
     }
    
  2. GitLab を再設定します:

    sudo gitlab-ctl reconfigure
    

メモリに制約のある環境ではPumaクラスターモードを無効化

caution
この機能は試験的なものであり、予告なく変更される場合があります。この機能は本番環境では使用できません。この機能を使用する場合は、まず本番環境以外でテストしてください。詳細は既知のイシューを参照してください。

使用可能な RAM が 4 GB 未満のメモリ制約のある環境では、Pumaクラスタ化モードを無効にすることを検討してください。

workers の数を0 に設定して、メモリ使用量を数百 MB 削減します:

  1. /etc/gitlab/gitlab.rb を編集します:

    puma['worker_processes'] = 0
    
  2. GitLab を再設定します:

    sudo gitlab-ctl reconfigure
    

デフォルトで設定されているクラスター化モードとは異なり、アプリケーションを処理する Puma プロセスは一つだけです。Pumaワーカーとスレッドの設定の詳細については、Pumaの要件を参照してください。

この設定で Puma を実行することの欠点はスループットが低下することですが、これはメモリに制約のある環境では公平なトレードオフと考えることができます。

(OOM) のメモリ不足を回避するために、十分なスワップを用意してください。詳細はメモリ要件を参照してください。

プーマ・シングルモードに関する既知の問題

Puma をシングルモードで実行すると、一部の機能がサポートされません:

詳細については、エピック5303を参照してください。

RuggedでPumaを使用する際のパフォーマンス上の注意点

Git リポジトリの保存に NFS が使用されているデプロイでは、GitLab はRugged を使用することでパフォーマンスを向上させるためにGit への直接アクセスを使用します。

機能フラグによって無効化されていない限り、Gitへの直接アクセスが利用可能でPumaがシングルスレッドで実行されている場合、Ruggedの使用は自動的に有効になります。

MRI RubyはGlobal VM Lock(GVL) を使用しています。GVL により、MRI Ruby はマルチスレッドになりますが、最大でもシングルコアで動作します。

Gitには集中的なI/Oオペレーションが含まれます。Ruggedがスレッドを長時間使用すると、リクエストを処理しているかもしれない他のスレッドが飢餓状態に陥る可能性があります。シングルスレッドモードで動作しているPumaでは、同時に処理されるリクエストはせいぜい一つなので、このイシューは発生しません。

GitLabはRuggedの使用を削除するよう取り組んでいます。現在ではRuggedを使わなくても十分なパフォーマンスが得られていますが、場合によってはRuggedを使ったほうが有益なこともあります。

マルチスレッドのPumaでRuggedを実行することの注意点と、Gitalyの許容可能なパフォーマンスを考慮し、Pumaのマルチスレッド(Pumaが複数のスレッドで実行されるように設定されている場合)が使用されている場合、Ruggedの使用を無効にします。

このデフォルトの動作は、状況によっては最適な設定ではないかもしれません。デプロイにおいてRuggedが重要な役割を果たす場合は、ベンチマークを実施して最適な設定を見つけることをお勧めします:

  • 最も安全な選択肢は、シングルスレッドのPumaで開始することです。
  • RuggedをマルチスレッドのPumaで強制的に使用するには、機能フラグを使用します。

SSLでリッスンするPumaの設定

LinuxパッケージインストールでデプロイされたPumaは、デフォルトでUnixソケットをリッスンします。代わりにHTTPSポートでリッスンするようにPumaを設定するには、以下の手順に従います:

  1. PumaがリッスンするアドレスのSSL証明書キーペアを生成します。以下の例では、127.0.0.1

    note
    カスタム認証局(CA)からの自己署名証明書を使用する場合は、他の GitLab コンポーネントから信頼されるようにドキュメントに従ってください。
  2. /etc/gitlab/gitlab.rb を編集します:

    puma['ssl_listen'] = '127.0.0.1'
    puma['ssl_port'] = 9111
    puma['ssl_certificate'] = '<path_to_certificate>'
    puma['ssl_certificate_key'] = '<path_to_key>'
       
    # Disable UNIX socket
    puma['socket'] = ""
    
  3. GitLab を再設定します:

    sudo gitlab-ctl reconfigure
    
note
Unixソケットに加えて、Prometheusがスクレイピングするメトリクスを提供するために、Pumaは8080番ポートでHTTPをリッスンします。現在のところ、PrometheusにHTTPSでスクレイピングさせることはできません。したがって、Prometheusのメトリクスを失うことなくこのHTTPリスナーをオフにすることは技術的に不可能です。

暗号化されたSSLキーの使用

GitLab 16.1 で導入されました

Pumaは暗号化された秘密鍵の使用をサポートしており、実行時に復号化することができます。次の説明では、この設定方法を説明します:

  1. キーをパスワードで暗号化します:

    openssl rsa -aes256 -in /path/to/ssl-key.pem -out /path/to/encrypted-ssl-key.pem
    

    暗号化されたファイルを書き込むには、パスワードを2回入力します。この例では、some-password-here を使用します。

  2. パスワードを表示するスクリプトまたは実行ファイルを作成します。例えば、/var/opt/gitlab/gitlab-rails/etc/puma-ssl-key-password にパスワードをエコーする基本スクリプトを作成します:

    #!/bin/sh
    echo some-password-here
    

    実運用環境では、パスワードをディスクに保存することは避け、Vaultのようなパスワードを取得するためのセキュリティ機構を使用する必要があることに注意してください。例えば、スクリプトは次のようになります:

    #!/bin/sh
    export VAULT_ADDR=http://vault-password-distribution-point:8200
    export VAULT_TOKEN=<some token>
       
    echo "$(vault kv get -mount=secret puma-ssl-password)"
    
  3. Puma プロセスに、スクリプトを実行し、暗号化された鍵を読み取るのに十分な権限があることを確認します:

    chown git:git /var/opt/gitlab/gitlab-rails/etc/puma-ssl-key-password
    chmod 770 /var/opt/gitlab/gitlab-rails/etc/puma-ssl-key-password
    chmod 660 /path/to/encrypted-ssl-key.pem
    
  4. /etc/gitlab/gitlab.rb を編集し、puma['ssl_certificate_key'] を暗号化キーに置き換え、puma['ssl_key_password_command] を指定します:

    puma['ssl_certificate_key'] = '/path/to/encrypted-ssl-key.pem'
    puma['ssl_key_password_command'] = '/var/opt/gitlab/gitlab-rails/etc/puma-ssl-key-password'
    
  5. GitLab を再設定します:

    sudo gitlab-ctl reconfigure
    
  6. GitLabが正常に起動したら、GitLabインスタンスに保存されていた暗号化されていないSSLキーを削除できるはずです。

UnicornからPumaへの切り替え

note
Helm ベースのデプロイについては、webservice Chart ドキュメントを参照してください。

GitLab 13.0から、Pumaがデフォルトのウェブサーバーとなり、Unicornは無効になりました。GitLab 14.0では、UnicornはLinuxパッケージから削除され、サポートされなくなりました。

Pumaはマルチスレッドアーキテクチャを採用しており、Unicornのようなマルチプロセスのアプリケーションサーバーよりもメモリ使用量が少なくなっています。GitLab.comでは、メモリ消費量が40%削減されました。Railsアプリケーションのリクエストには通常、I/O待ち時間が含まれます。

I/O待ち時間の間、RubyはGVLを他のスレッドにリリースします。そのため、マルチスレッドのPumaはシングルプロセスよりも多くのリクエストに対応することができます。

Pumaに切り替えた場合、2つのアプリケーションサーバーの違いにより、Unicornサーバーの設定は自動的に引き継が_れません_。

UnicornからPumaに切り替えるには:

  1. 適切な Pumaワーカーとスレッドの設定を決定します。
  2. /etc/gitlab/gitlab.rb で、Unicorn のカスタム設定を Puma に変換します。

    以下の表は、Unicornのどの設定キーがLinuxパッケージ使用時のPumaの設定キーに対応し、どの設定キーに対応するものがないかをまとめたものです。

    UnicornPuma
    unicorn['enable']puma['enable']
    unicorn['worker_timeout']puma['worker_timeout']
    unicorn['worker_processes']puma['worker_processes']
    該当なしpuma['ha']
    該当なしpuma['min_threads']
    該当なしpuma['max_threads']
    unicorn['listen']puma['listen']
    unicorn['port']puma['port']
    unicorn['socket']puma['socket']
    unicorn['pidfile']puma['pidfile']
    unicorn['tcp_nopush']該当なし
    unicorn['backlog_socket']該当なし
    unicorn['somaxconn']puma['somaxconn']
    該当なしpuma['state_path']
    unicorn['log_directory']puma['log_directory']
    unicorn['worker_memory_limit_min']該当なし
    unicorn['worker_memory_limit_max']puma['per_worker_max_memory_mb']
    unicorn['exporter_enabled']puma['exporter_enabled']
    unicorn['exporter_address']puma['exporter_address']
    unicorn['exporter_port']puma['exporter_port']
  3. GitLab を再設定します:

    sudo gitlab-ctl reconfigure
    
  4. オプション。複数ノードのデプロイでは、ロードバランサーが準備チェックを使用するように設定します。

Puma のトラブルシューティング

502 Pumaが100%のCPUでスピンした後のゲートウェイのタイムアウト

このエラーは、Pumaワーカーからの応答がないままWebサーバーがタイムアウト(デフォルト:60秒)した場合に発生します。この処理中に CPU が 100% になった場合は、何か時間がかかっている可能性があります。

このイシューを解決するには、まず何が起こっているのかを知る必要があります。以下のヒントは、ユーザーがダウンタイムに影響されても構わない場合にのみお勧めします。そうでない場合は、次のセクションに進んでください。

  1. 問題のあるURLをロード
  2. sudo gdb -p <PID> を実行して Puma プロセスにアタッチします。
  3. GDBウィンドウで、次のように入力します:

    call (void) rb_backtrace()
    
  4. これでプロセスがRubyのバックトレースを生成します。/var/log/gitlab/puma/puma_stderr.log でバックトレースを確認してください。例えば

    from /opt/gitlab/embedded/service/gitlab-rails/lib/gitlab/metrics/sampler.rb:33:in `block in start'
    from /opt/gitlab/embedded/service/gitlab-rails/lib/gitlab/metrics/sampler.rb:33:in `loop'
    from /opt/gitlab/embedded/service/gitlab-rails/lib/gitlab/metrics/sampler.rb:36:in `block (2 levels) in start'
    from /opt/gitlab/embedded/service/gitlab-rails/lib/gitlab/metrics/sampler.rb:44:in `sample'
    from /opt/gitlab/embedded/service/gitlab-rails/lib/gitlab/metrics/sampler.rb:68:in `sample_objects'
    from /opt/gitlab/embedded/service/gitlab-rails/lib/gitlab/metrics/sampler.rb:68:in `each_with_object'
    from /opt/gitlab/embedded/service/gitlab-rails/lib/gitlab/metrics/sampler.rb:68:in `each'
    from /opt/gitlab/embedded/service/gitlab-rails/lib/gitlab/metrics/sampler.rb:69:in `block in sample_objects'
    from /opt/gitlab/embedded/service/gitlab-rails/lib/gitlab/metrics/sampler.rb:69:in `name'
    
  5. 現在のスレッドを見るには、以下を実行してください:

    thread apply all bt
    
  6. gdb でのデバッグが終わったら、必ずプロセスから切り離して終了してください:

    detach
    exit
    

これらのコマンドを実行する前に Puma プロセスが終了すると、GDB はエラーをレポーターします。これらのコマンドを実行する前に Puma プロセスが終了すると、GDB はエラーを報告します。さらに時間を稼ぐには、Puma ワーカーのタイムアウトを上げることができます。Linux パッケージインストールユーザは、/etc/gitlab/gitlab.rb を編集して、60 秒から 600 秒に増やすことができます:

gitlab_rails['env'] = {
        'GITLAB_RAILS_RACK_TIMEOUT' => 600
}

セルフコンパイルインストールの場合は、環境変数を設定してください。Puma Worker timeoutを参照してください。

変更を有効にするために GitLab を再設定します。

他のユーザーに影響を与えないトラブルシューティング

前節では実行中のPumaプロセスにくっつけましたが、この間にGitLabにアクセスしようとしたユーザーには望ましくない影響があるかもしれません。本番システム中に他のユーザーに影響を与えるのが心配な場合は、別の Rails プロセスを実行してイシューをデバッグすることができます:

  1. GitLabアカウントにログインします。
  2. 問題が発生している URL をコピーします(例えば、https://gitlab.com/ABC )。
  3. ユーザーの個人アクセストークンを作成します([ユーザー設定]->[アクセストークン])。
  4. GitLab Rails コンソールを表示します。
  5. Railsコンソールで実行します:

    app.get '<URL FROM STEP 2>/?private_token=<TOKEN FROM STEP 3>'
    

    使用例:

    app.get 'https://gitlab.com/gitlab-org/gitlab-foss/-/issues/1?private_token=123456'
    
  6. 新しいウィンドウでtop を実行します。このRubyプロセスが100%のCPUを使用していることが表示されるはずです。PIDを書き留めてください。
  7. GDBを使う前のセクションのステップ2に従ってください。

GitLab:API にアクセスできません

これは、GitLab Shell が内部 API(例えばhttp://localhost:8080/api/v4/internal/allowed) 経由で作成者の承認を要求しようとして、チェックに失敗した場合によく起こります。このようなことが起こる理由はたくさんあります:

  1. データベース(例えば PostgreSQL や Redis)への接続がタイムアウトした場合。
  2. Gitフックやプッシュルールのエラー
  3. リポジトリへのアクセスエラー(古い NFS ハンドルなど)

この問題を診断するには、問題を再現してみて、top 経由でスピンしている Puma ワーカーがあるかどうかを確認してください。上記のgdb のテクニックを使ってみてください。さらに、strace を使うことで、イシューの切り分けができるかもしれません:

strace -ttTfyyy -s 1024 -p <PID of puma worker> -o /tmp/puma.txt

どの Puma ワーカーがイシューなのか切り分けられない場合は、すべての Puma ワーカーでstrace を実行し、/internal/allowed エンドポイントがスタックする場所を確認してください:

ps auwx | grep puma | awk '{ print " -p " $2}' | xargs  strace -ttTfyyy -s 1024 -o /tmp/puma.txt

/tmp/puma.txt の出力が根本原因の診断に役立つかもしれません。