本文来自一次容器化 GitLab 偶发慢请求排查。主机名、域名、地址、项目名和访问凭据均已省略。

一、现象

GitLab 登录页多数时候可以正常打开,容器健康检查也显示 healthy,但用户仍会遇到随机慢和超时。多次请求耗时差异很大,日志中出现:

  • RedisClient::ReadTimeoutError
  • Waited 1.0 seconds
  • rack-timeout
  • 容器历史状态包含 OOMKilled=true

只看 HTTP 200 或容器健康状态,很容易误判为“服务正常”。

二、证据链

排查同时采集了容器、主机和应用三个层面的指标。

2.1 GitLab 容器

  • 容器内存限制约 5 GiB;
  • 实际使用已接近限制;
  • 容器内存在十余个 Puma worker;
  • 单个 worker 占用数百 MiB;
  • 配置文件没有显式限制 Puma、Sidekiq 和监控组件资源。

2.2 宿主机

  • 主机内存和 Swap 均处于高压状态;
  • Swap 几乎被用满;
  • vmstat 显示较高 IO wait;
  • 多个进程长时间处于 blocked 状态;
  • Redis 的 RDB 后台保存进一步增加了内存和磁盘 IO 压力。

2.3 Redis

GitLab 日志中的 Redis 超时来自 Omnibus 自带的内部 Redis,并不是同机运行的外部业务 Redis。内部 Redis 数据量很小,真正的问题是应用进程和主机资源争用。

外部 Redis 则承载了多个系统的数据,规模达到数 GiB、百万级 Key。不同业务共用一个 Redis,会带来:

  • 持久化时 IO 峰值叠加;
  • 无法为不同业务设置独立内存策略;
  • 安全边界模糊;
  • 清理和故障恢复风险较高。

三、为什么默认配置不合适

GitLab 的默认资源模型并不知道容器只有约 5 GiB 内存。过多 Puma worker 会提高理论并发,却也会快速消耗内存,并把压力传递给 Swap 和磁盘。

历史活动审计显示,该实例主要用于少量运维配置仓库:

  • 没有活跃的 Merge Request 流程;
  • 没有 CI Runner 和构建记录;
  • 没有复杂 Webhook 或自动化集成;
  • 用户量和代码活动都较低。

因此没有必要维持面向高并发场景的 worker 数量。

四、已执行的止血操作

维护窗口内执行了以下操作:

  1. 备份 GitLab 主配置文件;
  2. 将 Puma worker 数显式调整为 2;
  3. 执行 gitlab-ctl reconfigure
  4. 重启 Puma 并确认新配置生效;
  5. 复查容器内存和页面响应时间。

调整后,GitLab 容器内存从约 4.85 GiB 降到约 3.23 GiB,登录页响应恢复到稳定的秒级以内,Puma 进程数量与配置一致。

五、不能混在一起做的事情

本次只先恢复 GitLab 稳定性,没有立刻同时改造外部 Redis。原因是同时修改应用资源、Redis 认证、持久化和网络策略,会让故障归因变得困难。

更安全的顺序是:

  1. 先降低 GitLab 内存压力;
  2. 观察超时和 OOM 是否停止增长;
  3. 识别外部 Redis 中各类 Key 的来源;
  4. 将不同业务拆分到独立实例;
  5. 再收口监听地址、防火墙和认证;
  6. 最后整理多入口 DNS 和反向代理配置。

六、Redis 后续治理

外部 Redis 不应长期由多个重要系统无边界共用。建议至少按用途拆分:

  • 运维平台缓存与队列;
  • 文件协作系统缓存;
  • 监控和性能数据。

每个实例独立设置:

  • 最大内存;
  • 淘汰策略;
  • RDB/AOF 持久化方式;
  • 认证与网络访问控制;
  • 备份、恢复和监控告警。

清理 Key 时不能使用 FLUSHALL 之类的全局操作,应先按前缀、数据库和客户端连接来源确认数据归属。

七、经验总结

  • healthy 只表示健康检查通过,不代表服务没有资源瓶颈;
  • 应用报 Redis 超时,不等于 Redis 本身数据量过大;
  • 容器内存限制、worker 数、Swap 和磁盘 IO 必须一起分析;
  • 调整并发参数前,要先了解真实使用规模;
  • 先做可回滚的止血,再做跨系统架构治理;
  • 资源优化必须用变更前后的指标证明效果。