跳转至

GitLab Web 502 故障报告(webservice 容器 OOM 与启动期误杀)

1. 故障时间

项 值
首次 502 2026-09-20 22:10:55(北京时间,日志 UTC 14:10:55Z)
最后一次 502 2026-09-20 22:13:38
服务恢复(本次) 2026-09-20 22:13:46(Puma 重启完成)
根因消除 2026-09-20 22:20:01(helm revision 2 生效)
隐患持续期 22:13:46 – 22:24(容器仍处 OOM 边缘,随时可能复发)

2. 现象

访问 https://gitlab.rainchen.top(含登录页与登录后的初始化引导页)返回 GitLab 定制 502 页面:

HTTP 502: Waiting for GitLab to boot
It can take up to a few minutes for GitLab to boot completely.
This page will automatically reload every 5 seconds.

浏览器每 5–6 秒自动刷新,每次刷新均继续 502(22:13:38 的刷新即为用户截图)。

3. 影响范围

项 说明
受影响 GitLab Web UI 与 API:22:10:55 – 22:13:46 约 3 分钟完全不可用
受影响 22:13:46 – 22:24 处于"可用但与 OOM 临界"状态,随时可能再次全站 502
不受影响 Git over SSH(gitlab-shell + TCPRoute)、容器镜像仓库(registry 独立 Deployment)
不受影响 数据层:PostgreSQL(CNPG gitlab-pg)、Redis、MinIO、Gitaly 全程健康
数据 无数据损坏或丢失;Gitaly/PG/MinIO 未参与故障
入口链路 Traefik、公网隧道、Envoy Gateway 均正常,非入口故障

4. 根因原理

webservice Pod 内有两个容器协同:gitlab-workhorse 接收入口流量并转发到本 Pod 的 Puma(127.0.0.1:8080),webservice 容器运行 Rails + Puma(cluster 模式,2 workers × 4 threads)。Puma 未监听时,workhorse 返回内置 502 页 —— 即用户看到的那一页。

两个独立缺陷叠加:

缺陷 1(主因):容器 memory limit 低于实际常驻水位,触发 cgroup OOM 循环

  • 变更前 limits.memory: 3Gi,而 webservice 容器重启后约 6 分钟即回升至 2892Mi/3072Mi,常态贴顶。
  • chart 默认 puma.disableWorkerKiller: true(见 gitlab/charts/gitlab/charts/webservice/values.yaml),Puma worker 内存超阈值不会自我回收,只增不减。
  • 内存触顶后由内核 cgroup OOM killer(CONSTRAINT_MEMCG)杀掉 Puma 的全部 ruby 进程,容器以 137/OOMKilled 退出。
  • 容器重启后 Puma 需 preload 整个 Rails 应用(实测约 3 分钟),期间 8080 无监听 → workhorse 持续返回 502。

缺陷 2(放大器):liveness 探针在 preload 期误杀容器

  • chart 默认 liveness:initialDelaySeconds 20 / periodSeconds 60 / failureThreshold 3,无 startupProbe。
  • Puma preload 需约 3 分钟,超过探针判定期 → 新容器在"尚未就绪"时被判死并 SIGKILL(lastState=Error,exit 137),容器反复重启、难以稳定就绪,进一步拉长 502 窗口。

放大器:OOM 期间 HPA 扩容

webservice HPA 目标为 cpu 1 核(min 1 / max 2)。OOM 重启与 preload 阶段 CPU 飙高,HPA 将副本扩到 2,而新副本自身也要 preload(实测 945m CPU / 1.4Gi 内存),加剧节点与响应竞争;同期 HPA 还出现 FailedGetResourceMetric(metrics API 短时无数据)。

5. 证据摘要

取证时间:2026-09-20 22:14 – 22:19;主机:集群 master(kubeconfig 192.168.31.8:16443)、节点 k8s-node-02(192.168.31.14)、k8s-node-03(192.168.31.15)。

证据 1:workhorse 返回 502(502 页面来源)

kubectl -n server logs gitlab-webservice-default-86644d9cd7-vbfd7 -c gitlab-workhorse --since=30m

过滤 "status":502 后共 58 条,时间窗 22:10:55 – 22:13:38,关键行:

error: badgateway: failed to receive response: dial tcp [::1]:8080: connect: connection refused
"status":502  "uri":"/"            "time":"2026-09-20T14:13:38Z"

URI 分布:/favicon.ico 29 次、/ 24 次、/admin/registrations/profile/new 4 次、/admin/registrations/groups 1 次 —— 与用户"登录后(初始化引导页)报 502"的描述一致。

证据 2:容器因 OOM 被杀

kubectl -n server get pod gitlab-webservice-default-86644d9cd7-vbfd7 -o jsonpath='{.status.containerStatuses[*].lastState}'
webservice lastState={"terminated":{"exitCode":137,"reason":"OOMKilled","finishedAt":"2026-09-20T14:10:55Z"}}  restartCount=3

证据 3:内核 cgroup OOM(排除节点级内存压力)

ssh root@192.168.31.14 'journalctl -k --since "3 days ago" | grep CONSTRAINT_MEMCG'
Sep 20 22:10:55 oom-kill:constraint=CONSTRAINT_MEMCG,...oom_memcg=.../kubepods-burstable-pod77d6848b_5be9_42c0_8ea7_868dc0800fa7.slice/...
Sep 20 22:10:55 Memory cgroup out of memory: Killed process 778812 (ruby) anon-rss:1478148kB
Sep 20 22:10:55 Memory cgroup out of memory: Killed process 778226 (ruby) anon-rss:1352580kB
Sep 20 22:10:55 Memory cgroup out of memory: Killed process 778816 (ruby) anon-rss:1459636kB

CONSTRAINT_MEMCG 表明是容器 cgroup limit 触发;同时 kubectl describe node k8s-node-02 的 MemoryPressure False、kubectl top node 显示节点仅用 4903Mi/16G,排除节点内存不足。

证据 4:内存水位贴顶(常态隐患)

kubectl -n server top pod -l app=webservice --containers
vbfd7   webservice   2892Mi      # limit 3072Mi,水位 94%
v74jx   webservice   1428Mi      # 新副本 preload 中

证据 5:liveness 在启动期误杀(次因)

kubectl -n server get pod gitlab-webservice-default-86644d9cd7-v74jx -o jsonpath='{.status.containerStatuses[*].lastState}'
kubectl -n server get events -n server | grep webservice
webservice lastState={"terminated":{"exitCode":137,"reason":"Error","finishedAt":"2026-09-20T14:17:22Z"}}   # 非 OOMKilled
Normal  Unhealthy  Liveness probe failed: Get "http://10.244.89.148:8080/-/liveness": dial tcp ... connection refused

该副本位于 k8s-node-03,而该节点当日无 CONSTRAINT_MEMCG 记录(近 3 天 4 次 OOM 均发生在 09-19 及更早)→ 判定为 kubelet 因 liveness 失败杀容器,非内存原因。

6. 解决方案

修改 gitlab-deploy-v2/values-gitlab.yaml 的 gitlab.webservice 段,随后 helm upgrade:

配置项 变更前 变更后 作用
resources.requests.memory 2Gi 3Gi 调度水位贴近实际需求
resources.limits.memory 3Gi 6Gi 留出内存增长余量,消除 cgroup OOM
deployment.startupProbe 未设置 httpGet /-/liveness:8080,initialDelay 10s / period 10s / timeout 5s / failureThreshold 30 启动完成前不执行 liveness,最长容忍 310s,消除 preload 期误杀

实施命令与回滚步骤见本目录 SOP.md。

7. 验证结果

验证项 命令 结果
副本就绪 kubectl -n server get pod -l app=webservice 2/2 Running,RESTARTS 0
无新 5xx kubectl -n server logs <pod> -c gitlab-workhorse --since=15m \| grep -c '"status":5' 0
页面可用 curl -sk -o /dev/null -w '%{http_code}' https://gitlab.rainchen.top/ 302(跳登录页)
登录页 同上,路径 /users/sign_in 200
内存水位 kubectl -n server top pod -l app=webservice --containers webservice 2541Mi / 6Gi(41%)
探针生效 kubectl -n server get deploy gitlab-webservice-default -o jsonpath='{...startupProbe}' startupProbe 已渲染,旧 pod 未再被误杀
幂等 相同参数再次 helm upgrade revision 3,Pod 名与 IP 未变、AGE 连续递增 → 零变更
稳定性探测 连续 20 次 curl https://gitlab.rainchen.top/users/sign_in 200 × 20,无失败

7.1 待确认项:22:29:25 的一次瞬时 502(非 GitLab 后端)

观察期 22:29:25 的探测中出现 1 次 /users/sign_in 返回 502,随后连续 20 次探测全部 200。取证结论:该 502 不是 GitLab 后端返回的。

侧 同期记录
Envoy 数据面(envoy-server-gitlab-gw-*) 该请求 "response_code":0、"response_flags":"DC"、"response_code_details":"downstream_remote_disconnect"、duration:3770 —— 表示 Envoy 尚未回包,下游(入口链路侧)连接已断开
webservice / workhorse 该时刻无任何 5xx 记录(--since=5m \| grep -c '"status":5' = 0),upstream 10.244.89.139:8181 正常

推断(待实地确认):502 由更上游的入口链路(k8s-master-01 上的 Traefik / 公网隧道)在到 Envoy 的连接中断时返回。需在 ingress_tunnel_deploy/ 主题侧取隧道(frp 或同类)日志与 Traefik 访问日志比对同一时间窗(2026-09-20 22:29:25 – 22:29:32)后确认。本次未取证,故不下定论。

8. 预防建议(按风险排序)

优先级 建议 依据
高 同步评估 sidekiq:当前 1742Mi/3Gi(58%),与 webservice 同源风险(同一 3Gi limit 模板) kubectl top pod 实测
高 接入容器内存水位与重启告警:container_memory_working_set_bytes / limit > 80%、kube_pod_container_status_restarts_total 增长 本次故障全靠人工发现;集群已有 kube-prometheus-stack 但 GitLab 未暴露 ServiceMonitor(gitlab-deploy-v2/SOP.md 风险表已记录)
中 观察 24–48h webservice 内存曲线;若仍持续增长逼近 6Gi,再启用 Puma 自愈:puma.disableWorkerKiller: false + workerMaxMemory(需与 limit 匹配) 本次未启用,避免引入 worker 级重启抖动
中 为 metrics-server 增加可用性监控:本次出现 FailedGetResourceMetric,HPA 无法评估 CPU,扩缩容行为不可预期 kubectl get events -n server 告警事件
低 若内存仍偏紧,可将 webservice.workerProcesses 降为 1(自用规模足够),内存占用近乎减半 chart 默认 2 workers × 4 threads

附录:行动项

事项 责任人 期限 状态
sidekiq 内存 limit 评估与调整 待指定 待定 未开始
接入 GitLab 容器内存水位与重启告警 待指定 待定 未开始
24–48h 内存曲线观察,决定是否启用 Puma worker killer 待指定 待定 观察中
更新 gitlab-deploy-v2/RUNBOOK.md:补充本场景(Web 502 / webservice OOM)处置流程 待指定 待定 未开始
在 ingress_tunnel_deploy/ 主题侧比对隧道与 Traefik 日志,确认 22:29:25 瞬时 502 来源(见 7.1) 待指定 待定 未开始

附:故障时间线

时间(北京) 事件
22:10:55 node-02 内核 cgroup OOM 杀掉 vbfd7 的 webservice 容器(3 个 ruby 进程),容器 OOMKilled / 137
22:10:55 – 22:13:46 Puma 重启并 preload,8080 无监听;workhorse 对全部入口请求返回 502(累计 58 次)
22:12:46 – 22:13:38 浏览器自动刷新持续 502;22:13:38 即用户截图时刻
22:13:46 Puma 就绪,/ 恢复 302,服务恢复
22:13:51 HPA 扩出的新副本 v74jx 启动
22:17:22 v74jx 的 webservice 因 liveness 在 preload 期判定失败被 kubelet 杀掉(Error / 137,非 OOM)
22:14 – 22:19 排查取证(workhorse 日志、容器状态、内核日志、节点指标、HPA 事件)
22:20:01 修复生效:helm revision 2(内存 3Gi→6Gi、新增 startupProbe),滚动更新
22:24 新副本 59998b794d-8xttj 2/2 就绪、零重启,旧副本退出
22:25:44 幂等复跑 helm revision 3,Pod 未重建(零变更)

来源:/home/rain/Documents/k8s_ins/gitlab-502-webservice-oom/INCIDENT-20260920-webservice-oom-502.md | 最后更新:2026-09-20 22:30(由 build/sync.py 同步,请勿在站点侧修改)