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同步,请勿在站点侧修改)