配置 GitLab 包内捆绑的 Puma 实例

Puma 是一个用于 Ruby 应用程序的快速、多线程和高度并发的 HTTP 1.1 服务器。它运行提供极狐GitLab 面向用户的功能的核心 Rails 应用程序。

减少内存使用

为了减少内存使用,Puma 派生 worker 进程。每次创建 worker 时,它都会与主进程共享内存。Worker 仅在更改或添加到其内存页面时才使用额外的内存。随着 worker 处理额外的网络请求,这可能会导致 Puma worker 随着时间的推移使用更多的物理内存。随着时间的推移使用的内存量取决于极狐GitLab 的使用情况。极狐GitLab 用户使用的功能越多,随着时间的推移,预期的内存使用量就越高。

为了阻止不受控制的内存增长,Rails 应用程序运行一个 supervision 线程,如果他们在一定时间内超过给定的驻留集大小 (RSS) 阈值,该线程会自动重启 worker。

极狐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
    

当 worker 重新启动时,运行极狐GitLab 的能力会在短时间内降低。如果 worker 更换得太频繁,请将 per_worker_max_memory_mb 设置为更高的值。

Worker 计数是根据 CPU 内核计算的。如果 worker 过于频繁地重新启动(每分钟一次或更多次),具有 4-8 个 worker 的小型部署可能会遇到性能问题。

如果服务器有空闲内存,则建议设置 1200 或更高的值。

监控 worker 重启

如果 worker 由于高内存使用而重新启动,极狐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_bytes 是通过 per_worker_max_memory_mb 设置的 RSS 限制。

更改 worker 超时

默认 Puma 超时为 60 秒

note puma['worker_timeout'] 没有设置最大请求持续时间。

要将 worker 超时更改为 600 秒:

  1. 编辑 /etc/gitlab/gitlab.rb

    gitlab_rails['env'] = {
       'GITLAB_RAILS_RACK_TIMEOUT' => 600
     }
    
  2. 重新配置极狐GitLab:

    sudo gitlab-ctl reconfigure
    

在内存受限的环境中禁用 Puma 集群模式

caution 这是一个实验性的 Alpha 功能,如有更改,恕不另行通知。该功能尚未准备好用于生产用途。如果您想使用此功能,我们建议您先使用非生产数据进行测试。有关更多详细信息,请参阅 已知问题

在可用 RAM 少于 4GB 的内存受限环境中,请考虑禁用 Puma 集群模式

workers 的数量设置为 0 以减少数百 MB 的内存使用量:

  1. 编辑 /etc/gitlab/gitlab.rb

    puma['worker_processes'] = 0
    
  2. 重新配置极狐GitLab:

    sudo gitlab-ctl reconfigure
    

与默认设置的集群模式不同,只有一个 Puma 进程将为应用程序提供服务。 有关 Puma worker 和线程设置的详细信息,请参阅 Puma 要求

在这种配置中运行 Puma 的缺点是吞吐量降低,这在内存受限的环境中可以被认为是公平的权衡。

请记住有足够的可用交换空间以避免内存不足 (OOM) 情况。 查看内存要求,了解详细信息。

Puma 单一模式已知问题

在单一模式下运行 Puma 时,不支持某些功能:

  • 分阶段重启
  • Memory killers <!– ## 将 Puma 与 Rugged 一起使用时的性能警告

对于使用 NFS 存储 Git 仓库的部署,极狐GitLab 通过 Rugged 使用直接 Git 访问来提高性能。

如果直接 Git 访问可用并且 Puma 正在单线程运行,则会自动启用 Rugged 的使用,除非它被功能标志禁用。

MRI Ruby 使用全局 VM 锁 (GVL)。 GVL 允许 MRI Ruby 是多线程的,但最多只能在单个内核上运行。

Git 包括密集的 I/O 操作。当 Rugged 长时间使用一个线程时,可能正在处理请求的其他线程可能会饿死。在单线程模式下运行的 Puma 没有这个问题,因为最多同时处理一个请求。

目前正在努力移除 Rugged 的使用。尽管目前没有 Rugged 的性能是可以接受的,但在某些情况下,使用它运行可能仍然有益。

鉴于使用多线程 Puma 运行 Rugged 的警告以及 Gitaly 可接受的性能,如果使用 Puma 多线程(当 Puma 配置为使用多个线程运行时),我们将禁用 Rugged 使用。

在某些情况下,此默认行为可能不是最佳配置。如果 Rugged 在您的部署中发挥重要作用,我们建议您进行基准测试以找到最佳配置:

  • 最安全的选择是从单线程 Puma 开始。
  • 要强制 Rugged 与多线程 Puma 一起使用,您可以使用功能标志。 –>

将 Puma 配置为通过 SSL 监听

Puma 与 Linux 软件包安装一起部署时,默认通过 Unix 套接字进行侦听。要将 Puma 配置为通过 HTTPS 端口进行侦听,请按照以下步骤操作:

  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 套接字,Puma 还在端口 8080 上侦听 HTTP,提供要被 Prometheus 抓取的指标。目前不可能让 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
    

    输入两次密码来写加密文件。在此示例中,我们使用 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 实例上的未加密 SSL 密钥。

从 Unicorn 切换到 Puma

note 对于基于 Helm 的部署,请参阅 webservice chart 文档

Puma 是默认的 Web 服务器,不再支持 Unicorn。

Puma 具有多线程架构,与 Unicorn 等多进程应用服务器相比,它使用的内存更少。在 JihuLab.com 上,我们看到内存的消耗减少了 40%。大多数 Rails 应用程序请求通常包含一定比例的 I/O 等待时间。

在 I/O 等待期间,MRI Ruby 将 GVL 释放给其它线程。因此,多线程 Puma 仍然可以处理比单个进程更多的请求。

当切换到 Puma 时,由于两个应用程序服务器之间的差异,任何 Unicorn 服务器配置都不会自动继承。

从 Unicorn 切换到 Puma:

  1. 确定合适的 Puma worker 和 thread 设置
  2. /etc/gitlab/gitlab.rb 中,将任何自定义 Unicorn 设置转换为 Puma。

    下表总结了在使用 Linux 包时,哪些 Unicorn 配置键与 Puma 中的配置键对应,哪些没有对应的配置键。

    Unicorn Puma
    unicorn['enable'] puma['enable']
    unicorn['worker_timeout'] puma['worker_timeout']
    unicorn['worker_processes'] puma['worker_processes']
    n/a puma['ha']
    n/a puma['min_threads']
    n/a puma['max_threads']
    unicorn['listen'] puma['listen']
    unicorn['port'] puma['port']
    unicorn['socket'] puma['socket']
    unicorn['pidfile'] puma['pidfile']
    unicorn['tcp_nopush'] n/a
    unicorn['backlog_socket'] n/a
    unicorn['somaxconn'] puma['somaxconn']
    n/a puma['state_path']
    unicorn['log_directory'] puma['log_directory']
    unicorn['worker_memory_limit_min'] n/a
    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. (可选)对于多节点部署,将负载均衡器配置为使用 readiness check。

Puma 故障排查

Puma 占用 100% CPU 后出现 502 错误

当无法从 Puma 工作器听到响应后,就会出现 Web 服务器超时,然后就会出现此错误。如果在此进程中,CPU 是用来飙升至 100%,那么可能是有某些操作花费的时间超出了正常时长。

要修复此问题,我们首先需要找出发生了什么。以下提示仅建议在您不介意用户受到影响的情况下使用。否则,请跳至下一节。

  1. 加载出现问题的 URL。
  2. 运行 sudo gdb -p <PID> 连接到 Puma 进程。
  3. 在 GDB 窗口中,输入:

    call (void) rb_backtrace()
    
  4. 这会强制进程来生成一个 Ruby backtrace。检查 /var/log/gitlab/puma/puma_stderr.log 以获取 backtrace。例如,您可能会看到:

    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 工作器的超时时间。对于 Linux 软件包安装的用户,您可以编辑 /etc/gitlab/gitlab.rb 来将超时时间从 60s 改为 600s:

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

对于自行编码安装的实例,请设置环境变量。可参考Puma 工作器超时

配置极狐GitLab 以使配置生效。

在不影响其他用户的情况下排查故障

之前的部分是需要连接到运行的 Puma 进程,这可能会对在这期间尝试访问极狐GitLab 的用户产生不可预知的影响。如果您担心在生产系统上影响其他用户,您可以单独运行一个 Rails 进程来调试问题:

  1. 登录到您的极狐GitLab 账号。
  2. 拷贝引起问题的 URL(比如, https://jihulab.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。就会展示出 CPU 使用率达到 100% 的 Ruby 进程。记录下此 PID。
  7. 遵循之前部分中的步骤 2,使用 GDB 调试此进程。

极狐GitLab:API 不可访问

当极狐GitLab Shell 尝试通过内部 API(比如,http://localhost:8080/api/v4/internal/allowed)来请求认证时就会出现此错误,而且会导致有些检查失败。引起此问题的原因可能有很多:

  1. 数据库连接超时(比如,PostgreSQL 或 Redis)
  2. Git 勾子或推送规则错误。
  3. 访问仓库错误(比如,stale NFS handles)

要诊断此问题,尝试重现此问题,然后通过 top 命令来查看是否有 Puma 进程处于忙碌状态。然后尝试使用上面提到的 gdb 技巧。此外,使用 strace 可能有助于隔离问题:

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

如果您无法隔离出现问题的 Puma 工作器,您可以在所有的工作器上运行 strace 来查看 /internal/allowed 端点被卡住的地方:

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

/tmp/puma.txt 中的输出可能会帮助对根因进行诊断。

相关主题