Kubernetes GPU 节点 Xid 119 后 Pod 一直重启:Startup probe failed connection refused 排查

结论

这次故障可以收敛成两个结论:

  1. 节点 Ready 不代表每张 GPU 都健康。 某张卡已进入 GPU requires reset、内核报 Xid 119Xid 154,但 kubectl get node 仍是 Ready,CPU、内存、Pod 状态都正常。只看控制面会得到"节点没问题"的误导性结论。
  2. GPU 修好后 Pod 仍反复重启,真因不是 GPU 二次故障,而是 startupProbe 窗口太紧、误杀了慢启动的 vLLM。 应用在 :8000/ping 还没监听时探针连续 connection refused,kubelet 判定启动失败、杀容器重启。最后一次能起来,是因为第二轮启动完整跑完了 vLLM 初始化,并在下一轮 startup probe 杀它之前把 HTTP 服务拉起来。

排查时间线(从入口到定位):

  1. 入口不是 Kubernetes 告警,而是云厂商发来的维修任务通知:某台 GPU 云服务器存在硬件隐患,要求在计划维护时间前授权停机维修。
  2. 登录 Kubernetes 工具机后,节点本身仍是 Ready,CPU、内存、Pod 状态也没有明显异常。
  3. 继续下钻到 GPU 层,才确认某张卡进入 GPU requires reset,内核有 Xid 119Xid 154。节点虽然仍是 Ready,但该卡已经不能正常服务。
  4. 节点重启或 GPU 重新初始化后 GPU 恢复,但某个业务 Pod 仍反复重启,问题转移到了 startup probe。

环境

以下版本号均为占位,替换成你自己的即可。

- Kubernetes: v1.34.x(containerd + crictl)
- GPU: NVIDIA GeForce RTX 5090 D ×8 / 驱动 <DRIVER_VERSION> / CUDA <CUDA_VERSION>
- 推理服务: vLLM <VLLM_VERSION>,冷启动约 600–800s(含 graph capture + warmup)
- OS / 内核: <OS_RELEASE> / <KERNEL_VERSION>
- 探针: startupProbe 走 httpGet :8000/ping

原始告警:云服务器维修任务通知

这次排查的入口是一条云厂商通知,核心内容大致如下,敏感字段已替换:

监控到您的服务器存在隐患,可能导致云服务器高负载或宕机。

云账号:<CLOUD_ACCOUNT_ID>
影响主机内网 IP:192.168.1.1
机器别名:<GPU_NODE_NAME>
项目:<PROJECT_NAME>
实例 ID:<INSTANCE_ID>
实例规格:<GPU_INSTANCE_TYPE>
可用区:<REGION_ZONE>
触发时间:2026-05-29 08:15:53
计划维护时间:2026-06-02 08:15:52

为尽快修复隐患,需要授权停机处理。
如超过 48 小时未授权,系统将在计划维护时间默认发起维护。

这类告警的关键点是:它不是 Kubernetes 控制面直接报错,而是云服务器底层维修通知。第一反应不能直接重启或删除 Pod,而是先确认三件事:

  • 这台机器是否仍在集群里 Ready。
  • 节点上的业务是否已经受影响。
  • 云厂商提示的硬件隐患是否已经能在操作系统或 GPU 驱动层看到证据。

因此排查顺序应从低风险只读命令开始:先看节点和 Pod,再看 GPU exporter、nvidia-smi、内核 Xid。

节点 Ready,但单卡已经异常

先看节点,容易得到一个误导性的结论:节点是正常的。

kubectl get node 192.168.1.1 -o wide

示例输出:

NAME          STATUS   ROLES    AGE   VERSION        INTERNAL-IP
192.168.1.1   Ready    <none>   80d   v1.34.x        192.168.1.1

节点 condition 也可能都是好的:

kubectl describe node 192.168.1.1 | sed -n '/Conditions:/,/Addresses:/p'

关键输出:

MemoryPressure   False   KubeletHasSufficientMemory
DiskPressure     False   KubeletHasNoDiskPressure
PIDPressure      False   KubeletHasSufficientPID
Ready            True    KubeletReady

但 GPU exporter 日志已经在报错:

kubectl logs -n kube-system <GPU_EXPORTER_POD> --since=2h \
  | egrep -i 'error|fail|xid|critical|warn|unknown' \
  | tail -20

示例输出:

E0529 08:24:54 exporter.go:199] Can't get device 0, error Unknown Error
E0529 08:25:09 exporter.go:199] Can't get device 0, error Unknown Error

这类错误说明不能只看 kubectl get node。节点 Ready 只能说明 kubelet 还在上报,不代表每张 GPU 都健康。

nvidia-smi 能枚举 8 张卡,但一张卡需要 reset

进入节点后执行只读检查:

nvidia-smi -L
nvidia-smi --query-gpu=index,uuid,pci.bus_id,temperature.gpu,power.draw,power.limit,utilization.gpu,memory.used,memory.total --format=csv,noheader,nounits
nvidia-smi

异常时,某张卡仍能被枚举,但字段已经异常:

GPU 7: NVIDIA GeForce RTX 5090 D (UUID: GPU-xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)

nvidia-smi 表格里会看到:

|   7  NVIDIA GeForce RTX 5090 D | 00000000:E1:00.0 N/A |
| ERR!   40C   P5   N/A / N/A    | 11969MiB / 32607MiB | N/A |
|                                |                     | ERR! |

如果查更详细字段,可能出现:

retired_pages.pending: [N/A]
remapped_rows.failure: [GPU requires reset]

继续看内核日志:

dmesg -T | egrep -i 'NVRM|Xid|GSP|reset' | tail -120

关键输出:

NVRM: Xid (PCI:0000:e1:00): 119, Timeout after 45s of waiting for RPC response from GPU GSP
NVRM: Xid (PCI:0000:e1:00): 154, GPU recovery action changed from None to GPU Reset Required

到这里可以确认:这不是 Kubernetes 调度层的假异常,而是 GPU/驱动层已经把这张卡标记为需要 reset。

定位影响哪个 Pod

GPU 异常时,业务 Pod 可能仍然是 Running/Ready,因为探针只测 HTTP,不一定真实测 GPU 推理。

先看节点上的 Pod:

kubectl get pods -n <NAMESPACE> -o wide --field-selector spec.nodeName=192.168.1.1

再在节点上把异常 GPU UUID 映射到容器。示例思路如下:

BAD_UUID=GPU-xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx

for id in $(crictl ps -q); do
  crictl inspect "$id" 2>/dev/null \
    | egrep -A3 -B8 "$BAD_UUID|NVIDIA_VISIBLE_DEVICES|io.kubernetes.pod.name|io.kubernetes.pod.namespace"
done

如果看到:

"io.kubernetes.pod.name": "<AFFECTED_POD>"
"io.kubernetes.pod.namespace": "<NAMESPACE>"
"NVIDIA_VISIBLE_DEVICES": "GPU-xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"

就能确定这张卡对应哪个业务实例。

在 Pod 内再验证一次:

kubectl exec -n <NAMESPACE> <AFFECTED_POD> -- sh -lc '
echo NVIDIA_VISIBLE_DEVICES=$NVIDIA_VISIBLE_DEVICES
timeout 8 nvidia-smi
curl -sS --max-time 3 http://127.0.0.1:8000/ping || true
'

异常时可能看到:

NVIDIA_VISIBLE_DEVICES=GPU-xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
ERR! / N/A / No running processes found

这说明 Pod 的 HTTP 层可能还活着,但 GPU 计算链路已经不可信。

GPU 恢复后,为什么 Pod 还会重启

节点重启或 GPU 重新初始化后,nvidia-smi 可能已经恢复正常:

0, GPU-..., 00000000:21:00.0, 61, 362.04, 575.00, 81, 18440, 32607
1, GPU-..., 00000000:31:00.0, 38, 21.75, 575.00, 0, 11865, 32607
...
7, GPU-..., 00000000:E1:00.0, 49, 69.57, 575.00, 0, 11845, 32607

内核里也没有新的 Xid:

dmesg -T | egrep -i 'Xid|GSP|GPU Reset Required|requires reset' | tail -20

这时如果某个 Pod 仍然反复重启,需要看 Pod 事件,而不是继续盯着 GPU。

kubectl describe pod -n <NAMESPACE> <POD_NAME>

典型事件:

Warning  Unhealthy  Startup probe failed: Get "http://192.168.1.1:8000/ping": dial tcp 192.168.1.1:8000: connect: connection refused
Normal   Killing    Container <CONTAINER_NAME> failed startup probe, will be restarted

这说明 kubelet 杀容器的原因是 startup probe 失败。connection refused 代表端口没监听,不是网络超时。

进 Pod 内部测试同样能验证:

kubectl exec -n <NAMESPACE> <POD_NAME> -- sh -lc '
date
(command -v ss >/dev/null && ss -lntp || netstat -lntp 2>/dev/null || true) | grep 8000 || true
curl -sS --max-time 2 -v http://127.0.0.1:8000/ping || true
'

示例输出:

* Trying 127.0.0.1:8000...
* connect to 127.0.0.1 port 8000 failed: Connection refused
curl: (7) Failed to connect to 127.0.0.1 port 8000

为什么最后一次又起来了

看容器状态:

kubectl get pod -n <NAMESPACE> <POD_NAME> -o wide
kubectl describe pod -n <NAMESPACE> <POD_NAME> | egrep -A5 -B3 'State:|Last State:|Ready:|Restart Count|Started|Finished|Exit Code|Reason:'

恢复后的状态:

State:          Running
Started:        Fri, 29 May 2026 10:27:02 +0800
Last State:     Terminated
Reason:         Completed
Exit Code:      0
Finished:       Fri, 29 May 2026 10:27:01 +0800
Ready:          True
Restart Count:  1

日志里能看到启动耗时:

INFO gpu_model_runner.py: Graph capturing finished in 575 secs
INFO core.py: init engine (profile, create kv cache, warmup model) took 615.09 seconds

后面开始有业务请求:

INFO service_func: Received infer_param: {...}

所以最后一次起来的原因是:第二轮启动跑完了模型初始化,并在下一轮 startup probe 杀它之前把 8000/ping 服务拉起来。前面的 Triton 报错更像 autotuning 中的候选配置失败和回退,会拖慢启动,但没有成为最终致命错误:

OutOfMemoryError: out of resource: triton_mm Required: 110592 Hardware limit:101376

处理建议

1. 先止血:不要把 GPU 异常节点继续当健康节点使用

当出现 GPU requires reset、Xid 119/154 时,不建议只因为节点 Ready 就继续调度业务。至少要先保护调度入口:

kubectl cordon 192.168.1.1

如果已有业务占着其他正常 GPU,是否 drain 要看业务容忍度。生产环境不要无脑驱逐。

2. 确认影响面:定位异常卡对应的 Pod

重点确认三件事:

  • 异常 GPU UUID 是哪张卡
  • 哪个 Pod 的 NVIDIA_VISIBLE_DEVICES 绑定了这张卡
  • 这个 Pod 的 HTTP 探针是否仍然能代表真实 GPU 推理健康

如果 HTTP 活着但 GPU 不可用,探针需要改造,否则会出现"Pod Ready 但计算不可用"。

3. 恢复 GPU:选择 GPU reset 或节点重启

如果卡已经是 GPU Reset Required,只读排查无法恢复。常见恢复方式是:

  • 业务确认窗口后做 GPU reset
  • 或重启节点,让驱动和 GPU 重新初始化

本次恢复后可以看到新的 GPU UUID,说明节点或驱动层完成了重新初始化。

4. 防止启动探针误杀慢启动服务

这类 vLLM 服务启动阶段可能超过 10 分钟,startup probe 不能按普通 HTTP 服务配置。

原配置示例:

startupProbe:
  httpGet:
    path: /ping
    port: 8000
  initialDelaySeconds: 600
  periodSeconds: 10
  timeoutSeconds: 1
  failureThreshold: 20

如果实际启动耗时经常在 600 到 800 秒之间,这个窗口太紧。可以考虑:

startupProbe:
  httpGet:
    path: /ping
    port: 8000
  initialDelaySeconds: 900
  periodSeconds: 10
  timeoutSeconds: 2
  failureThreshold: 30

更好的做法是让应用暴露分层健康检查:

  • /startup:模型加载和 warmup 完成后返回成功
  • /livez:进程存活即可
  • /readyz:能接真实请求时才成功

这样 startup probe 不会误杀慢启动进程,readiness probe 也不会过早放流量。

快速参考

判断 GPU 是否真的异常

nvidia-smi
nvidia-smi --query-gpu=index,uuid,pci.bus_id,temperature.gpu,power.draw,power.limit,utilization.gpu,memory.used,memory.total --format=csv,noheader,nounits
dmesg -T | egrep -i 'NVRM|Xid|GSP|reset' | tail -120

重点看:

  • ERR!
  • N/A
  • GPU requires reset
  • Xid 119
  • Xid 154
  • GSP Timeout

定位异常 GPU 对应 Pod

BAD_UUID=GPU-xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx

for id in $(crictl ps -q); do
  crictl inspect "$id" 2>/dev/null \
    | egrep -A3 -B8 "$BAD_UUID|NVIDIA_VISIBLE_DEVICES|io.kubernetes.pod.name|io.kubernetes.pod.namespace"
done

判断是不是探针导致重启

kubectl describe pod -n <NAMESPACE> <POD_NAME> \
  | egrep -A5 -B3 'State:|Last State:|Ready:|Restart Count|Unhealthy|Killing|Started|Finished|Exit Code|Reason:'

如果看到:

Startup probe failed: connect: connection refused
Container failed startup probe, will be restarted

说明是 startup probe 杀的,不是进程自己崩。

排查端口是否监听

kubectl exec -n <NAMESPACE> <POD_NAME> -- sh -lc '
(command -v ss >/dev/null && ss -lntp || netstat -lntp 2>/dev/null || true) | grep 8000 || true
curl -sS --max-time 2 -v http://127.0.0.1:8000/ping || true
'

三条铁律

  • 节点 Ready 不代表每张 GPU 都健康。
  • HTTP /ping 不等于 GPU 推理链路健康。
  • 慢启动模型服务要给 startup probe 足够窗口,否则 kubelet 会制造重启循环。
posted @ 2026-05-29 11:12  Hello_worlds  阅读(14)  评论(0)    收藏  举报