
AI 服务器集群中最让人紧张的状态不是某一个 GPU 节点掉线而是同一时刻大量节点集体瘫痪。Astra 集群这次故障发生时监控页面上出现了一排红色多个训练任务在几秒内从 Running 变成 FailedGPU 可用卡数直接归零。第一时间所有人都会怀疑是不是 AI 服务器硬件大批量烧毁但复盘之后发现真正的根因并不是显卡或服务器本身而是存储控制器切换后触发的一次连锁故障。这篇文章以 Astra 集群的这次故障为线索完整还原故障现象、信息采集、根因定位、恢复操作和高可用设计。内容重点不是渲染“天命将至”式的焦虑而是说明 AI 服务器集群为什么会集体瘫痪以及如何通过架构冗余、参数调优、健康检查设计和调度策略避免同样的事件再次发生。适合私有化 AI 平台运维、GPU 集群管理员、Kubernetes 平台工程师和负责 AI 基础设施稳定性的人阅读。1. 先看清AI服务器集群的依赖结构为什么会出现“集体瘫痪”1.1 AI服务器集群不是一堆GPU服务器并联很多初学者会把 AI 服务器集群理解成“多台带 GPU 的服务器放在机柜里通过网络连在一起”。这种理解在单机实验时成立但在生产环境里远远不够。AI 训练和推理任务不只是使用 GPU 计算还需要共享文件系统加载数据集、保存检查点、写日志需要通过分布式调度器分配资源需要通过高速网络完成多机通信。任何一个共享依赖出问题都可能影响大量节点。更关键的是集群里的“节点状态”不仅仅由服务器自身硬件决定还由 Kubernetes、Slurm 这类调度系统的健康检查逻辑决定。一个节点即使物理上完全健康只要健康检查失败调度器就会把它标记为不可用。因此当某个共享组件出现短暂抖动就可能让一批节点同时从集群状态中消失表现为“AI服务器集体瘫痪”。1.2 “集体瘫痪”通常不是所有硬件同时损坏服务器硬件大批量同时损坏的概率很低。更常见的是以下链路共享控制面异常、存储系统切换、网络分区、调度器误判、健康检查脚本产生假阳性或者任务失败后的重试风暴。Astra 集群这次就属于“存储控制器切换 健康检查脚本假失败 任务重试风暴”三者叠加。理解这一点非常重要因为排查思路会完全不同。如果先入为主认为是 GPU 硬件故障就会逐台去跑nvidia-smi、更换板卡而忽略共享存储和健康检查脚本的问题最终延误恢复时间。1.3 Astra集群的简化拓扑Astra 集群的内部结构可以简化为四层层次典型组件故障扩散方式控制面调度器、控制器、etcd、API Server控制面异常会导致节点状态无法更新、任务无法调度计算面GPU 服务器、Kubelet、设备插件节点健康检查失败会导致节点被标记为 NotReady存储面分布式存储、共享文件系统、NAS、SANIO 挂起会让所有依赖数据的任务阻塞进而触发健康检查假失败网络面业务网络、RDMA/IB、DNS网络分区会让节点心跳丢失导致调度器误判节点离线Astra 集群的存储面采用两台存储控制器做主备切换计算节点通过 NFS 协议挂载共享数据集目录。控制面部署在三个控制节点上通过 etcd 维持一致状态。计算节点数量约 30 台每台装有 8 张 GPU 卡。故障发生时存储控制器 A 出现异常自动切换到了控制器 B切换时间大约 3 分钟。问题在于 NFS 客户端默认的超时和重试参数适合普通 Web 业务不一定适合高负载的 AI 训练场景。控制器切换期间所有挂载在共享目录上的计算节点同时进入 IO 等待健康检查脚本又因为无法写入临时文件而判定 GPU 异常最终把大量节点推入了 NotReady 状态。2. 故障现场信息采集不要先猜原因先取证2.1 第一次看到的异常现象Astra 集群故障发生时监控大屏出现以下现象多个节点状态在 Running 和 NotReady 之间反复跳动GPU 可用卡数从 200 多张降到 0训练任务大面积失败调度队列中出现大量 Pending告警平台同时触发节点心跳告警、任务失败告警和存储延迟告警进入服务器执行命令时部分ls、df、cat操作卡住超过 10 秒。这些现象很容易让人误以为是计算节点集体死机。但注意到存储延迟告警也同时出现说明故障很可能从存储层开始向上扩散。因此在做任何重启操作之前先按节点、存储、控制面、网络四层采集现场信息。2.2 采集节点状态的命令集合在 Kubernetes 环境中第一步是查看节点整体状态kubectl get nodes -o wide kubectl describe nodes | grep -E Conditions:|Ready|MemoryPressure|DiskPressure|PIDPressure -A 5 kubectl top nodes如果节点状态为 NotReady需要进一步查看 Kubelet 日志journalctl -u kubelet --since 30 minutes ago --no-pager逐台进入服务器查看硬件和内核日志dmesg -T --levelerr,warn | tail -100 nvidia-smi systemctl status docker df -hIO 挂起时df或ls可能卡住这本身就是一个重要证据。要把“命令卡住”记录下来而不是直接杀进程。2.3 采集存储状态和硬件日志存储侧需要确认控制器切换事件、卷挂载状态和服务延迟mount | grep nfs nfsstat -m cat /etc/fstab cat /proc/mounts | grep nfs如果共享存储有独立管理界面还要查看以下信息控制器之间的心跳日志存储卷的读写延迟曲线存储控制器的 CPU、缓存、磁盘状态是否有磁盘重构或控制器切换事件。网络侧也要同步检查ping 控制面IP ping 存储IP ibstatus | grep -A 3 state网络检查的目的是区分“节点真的离线”和“网络不可达”。如果业务网络正常但 RDMA 网络失败训练任务同样会失败节点状态却可能保持 Ready。2.4 把现象分成三类计算节点、控制节点、存储/网络现场信息采集完成后不要急于定位单一根因。先把证据分类现象类别可能原因需要确认的证据节点 NotReadyKubelet 探活失败、运行时异常、存储挂载卡住Kubelet 日志、容器运行时状态、mount 状态任务大量失败共享文件系统不可用、GPU 卡被标记异常、调度器驱逐任务日志、事件、GPU 状态控制面异常etcd 性能下降、控制器崩溃etcd 慢查询、API Server 超时日志Astra 集群的证据归类后计算节点本身没有硬件损坏GPU 卡都正常问题集中在“存储挂载超时导致 Kubelet 探活失败”这条线上。3. 根因复盘存储控制器切换引发了GPU健康检查假失败3.1 存储控制器发生了什么Astra 集群的共享数据集目录通过 NFS 挂载到所有计算节点。正常情况下数据集目录由存储控制器 A 提供服务控制器 B 处于备用状态。故障当天控制器 A 的磁盘固件报出介质错误存储系统根据预设策略自动切换到控制器 B。控制器切换过程中NFS 服务会中断一段时间。Astra 集群使用的 NFS 挂载参数并没有为中断场景做特殊优化默认的timeo和retrans让客户端长期处于重试状态。所有计算节点上的训练进程在读写检查点文件时被阻塞无法及时释放 IO 栈。真正的问题出现在节点健康检查环节。Astra 集群为 GPU 节点配置了一个自定义健康检查脚本脚本会在共享目录中写入一个临时文件用来验证 GPU 和存储是否正常。当共享目录 IO 卡住时脚本无法创建临时文件直接返回异常状态。Kubelet 收到健康检查失败后把节点标记为 NotReady调度器随后开始驱逐节点上的训练任务。3.2 故障扩散的完整链路从时间线看故障扩散过程可以分为五个阶段存储控制器 A 故障触发切换到控制器 BNFS 服务中断约 3 分钟所有计算节点进入 IO 等待自定义健康检查脚本因无法写临时文件而失败Kubelet 上报节点 NotReady调度器标记节点不可用驱逐训练任务被驱逐的任务在其他节点重新调度但其他节点仍无法读写共享目录任务再次失败并重试形成重试风暴。这个链路中最危险的设计是把“GPU 健康检查”和“存储 IO 探活”耦合在同一个脚本里。健康检查原本应该回答“GPU 是否可用”却因为存储抖动而得出“节点不可用”的结论。这就是典型的假阴性健康检查。3.3 日志和证据故障处置过程中最有力的证据来自以下位置# Kubelet 日志片段 MountVolume.SetUp failed for volume data-volume : mount failed: exit status 32# 健康检查脚本日志片段 [2025-06-20 10:23:15] ERROR: cannot write temp file to /data/checkpoints/.healthcheck [2025-06-20 10:23:16] ERROR: healthcheck failed for node astra-gpu-07# 存储控制器切换日志管理界面导出 Controller A: Disk firmware error on disk 5 Controller A: Starting failover to Controller B Controller B: Received failover request Controller B: VIP changed from 10.0.0.10 to 10.0.0.11三条日志合在一起才能还原完整链路。单看 Kubelet 日志会认为是网络挂载问题单看健康检查脚本会认为是节点问题。这也是故障复盘时最容易忽略的部分必须把所有组件日志放在同一个时间轴上看。3.4 为什么不是AI服务器硬件故障判断 AI 服务器是否硬件故障需要看几个关键指标GPU 是否在nvidia-smi中正常显示服务器 CPU、内存、PCIe 链路是否有硬件报错是否多个节点同时出现相同错误硬件报错是否与共享存储事件时间上重合。Astra 集群中所有 GPU 在nvidia-smi下都正常dmesg中也没有 PCIe AER 报错多个节点的错误同时发生在存储控制器切换之后。因此可以排除“GPU 批量烧毁”和“服务器主板故障”这两类假设。真正的故障点是共享存储切换后引发的连锁反应。4. 修复与恢复按控制面、存储、计算节点顺序操作4.1 第一步隔离故障禁止新任务调度故障恢复最忌讳在主链路尚未稳定时就让所有节点和任务同时回归。Astra 集群恢复操作的第一步是把所有计算节点设置为不可调度避免新任务进入后继续重试。kubectl cordon astra-gpu-01 kubectl cordon astra-gpu-02 # 批量操作可以用循环 for node in $(kubectl get nodes -o name); do kubectl cordon $node done执行后要确认调度器不再把新任务调度到这些节点上。可以使用以下命令查看节点状态kubectl get nodes | grep SchedulingDisabled此时不需要马上驱逐已有任务因为任务可能还在等待存储恢复。先让集群进入“静止状态”是避免重试风暴的关键。4.2 第二步恢复存储控制器和挂载确认存储控制器 B 正常后先不要急着解封节点。要先手动验证共享目录是否可读写。在控制节点上执行mkdir -p /mnt/check mount -t nfs 10.0.0.11:/data/checkpoints /mnt/check touch /mnt/check/test_$(date %s) cat /mnt/check/test_$(date %s) umount /mnt/check验证通过后再对所有计算节点执行重新挂载。如果节点上的 NFS 挂载已经失效可以先卸载再挂载umount -l /data mount /data这里使用umount -l是为了处理“目录处于 busy 状态”的情况。但要注意强制卸载可能会让正在写的进程收到 IO 错误因此一定要先确认训练任务已停止或者已经处于失败状态。如果所有节点都要重新挂载可以准备一个脚本在集群中批量执行ansible gpu_nodes -m shell -a umount -l /data; mount /data; df -h /data4.3 第三步清理异常状态恢复健康检查存储恢复后Astra 集群的自定义健康检查脚本会恢复写临时文件的能力。但已经被 Kubelet 标记的错误状态不会自动消失需要重启 Kubelet 或在节点上删除异常状态记录。比较稳妥的方法是重启 Kubelet而不是重启整台服务器systemctl restart kubelet重启后确认节点状态kubectl get nodes如果节点仍然没有恢复 Ready查看 Kubelet 日志并确认健康检查脚本返回结果/usr/local/bin/gpu-healthcheck.sh echo $?在确认节点状态恢复 Ready 后再逐步解除调度限制kubectl uncordon astra-gpu-01 # 建议每批 3 到 5 台确认资源使用率稳定后再继续4.4 第四步验证恢复效果恢复操作完成不等于故障结束。Astra 集群需要做四层验证层级验证命令预期结果节点kubectl get nodes所有节点 Ready 且 SchedulingEnabledGPUnvidia-smi每张卡状态正常利用率开始变化存储df -h /data; dd 测试共享目录可读写延迟恢复任务提交一个测试任务任务进入 Running 且不失败生产环境不建议用真实训练任务做恢复验证可以准备一个轻量任务镜像只做 GPU 初始化和共享目录写入kubectl apply -f test-gpu-job.yaml测试任务代码核心逻辑如下import os import torch print(GPU count:, torch.cuda.device_count()) path /data/checkpoints/health_ str(os.getpid()) .txt with open(path, w) as f: f.write(ok) print(write ok)如果测试任务能完成并写入文件说明计算、GPU、存储三条链路已经恢复。5. 如何让Astra集群不再“集体瘫痪”高可用和降级设计5.1 控制面高可用避免调度器再次全面误判Astra 集群的控制面已经采用三节点部署但仍然需要关注 etcd 的性能和调度器的故障容忍策略。控制面节点不仅要保证数量还要保证磁盘和网络性能。etcd 的写入延迟过高会导致所有控制器状态更新变慢进而引发更长的故障恢复时间。生产环境建议检查以下参数etcd 数据目录使用本地 SSD避免与业务存储共用控制节点之间网络延迟保持在较低水平对 etcd 设置独立的告警阈值例如 fsync 延迟超过 100ms 就触发告警API Server 和调度器设置合理的--node-monitor-grace-period和--node-monitor-period。学习环境可以单节点跑 Kubernetes但生产环境必须至少部署 3 个控制节点。Astra 集群这次故障中控制面没有成为瓶颈但如果控制面也部署在同一套共享存储上就会反过来影响 etcd 的写入情况会更复杂。5.2 存储面超时、重试、本地盘降级NFS 挂载参数是这次故障的放大器。Astra 集群在存储控制器切换时没有配置合理的超时和重试策略导致所有节点同时被 IO 阻塞。NFS 客户端的关键参数如下参数含义默认值Astra 集群建议调大影响timeo等待响应的超时时间单位 0.1 秒6001500 到 2000容忍更长恢复时间但单次阻塞更久retrans超时后重试次数23 到 5增加重试穿透率减少失败返回soft vs hard超过重试次数后的行为hard视场景而定soft 可能返回 IO 错误hard 可能永久阻塞actimeo文件和目录属性缓存时间330 到 60减少元数据请求但缓存一致性变弱在训练场景中如果共享数据集以只读方式挂载建议使用软挂载加超时限制/data 10.0.0.11:/data nfs4 rw,soft,timeo1500,retrans3,actimeo60 0 0如果任务对数据完整性要求极高则建议使用hard挂载但必须在应用层加入超时控制避免进程永远卡死。此外Astra 集群还应该在每个计算节点保留一定比例的本地 SSD 空间用于存放临时日志和 checkpoints 缓存。共享存储故障时训练进程可以先把检查点写入本地盘等共享存储恢复后再同步这样能避免健康检查脚本因为共享目录不可写而误判 GPU 异常。5.3 GPU健康检查分级、幂等、快速失败健康检查脚本是整个故障扩散的关键节点。一个可靠的健康检查脚本需要满足以下要求不依赖外部共享存储判断 GPU 状态写临时文件时必须指定本地目录单次执行时间不能过长超过 5 秒就应该主动超时多次执行结果应幂等不能因为上一次残留文件而误判失败后需要输出明确原因方便定位。推荐将健康检查拆成两个阶段#!/bin/bash # /usr/local/bin/gpu-healthcheck.sh set -e # 第一阶段GPU 状态检查 GPU_READY$(nvidia-smi --query-gpuindex,count --formatcsv,noheader | wc -l) if [ $GPU_READY -lt 1 ]; then echo FAIL: no gpu detected exit 1 fi # 第二阶段本地临时文件检查 LOCAL_TMP/var/tmp/gpu-health-$$ if ! echo ok $LOCAL_TMP; then echo FAIL: local tmp write failed exit 1 fi rm -f $LOCAL_TMP echo PASS exit 0注意临时文件必须写入本地/var/tmp不要写到/data共享目录。健康检查脚本的职责是确认节点计算资源可用共享目录是否可用应该由存储监控独立负责。两者耦合在一起就会把存储故障错误地放大为计算节点故障。5.4 调度侧节点故障容忍、任务恢复策略、限流调度器在节点被标记为 NotReady 后默认会驱逐上面的 Pod。大批量驱逐会放大故障因此需要配置合理的 PodDisruptionBudget 和任务重试策略。Kubernetes 调度侧可以调整以下配置# scheduler-config.yaml 片段 apiVersion: kubescheduler.config.k8s.io/v1 kind: KubeSchedulerConfiguration profiles: - schedulerName: default-scheduler plugins: multiPoint: enabled: - name: NodeResourceFit - name: NodeAffinity这里不展开所有调度插件。更关键的是在任务清单中加入节点故障容忍spec: tolerations: - key: node.kubernetes.io/not-ready operator: Exists effect: NoExecute tolerationSeconds: 60这个配置表示允许任务在节点 NotReady 后最多容忍 60 秒给调度器留出重新调度时间但不会立即驱逐。任务重试还需要设置restartPolicy和backoffLimit避免失败任务无限重试。spec: restartPolicy: Never backoffLimit: 3对于训练任务建议在应用层实现持久化的参数服务器或定期 checkpoints。任务失败后从最近一个 checkpoint 恢复而不是从零开始。6. 从故障中沉淀的检查清单和最佳实践6.1 故障前巡检检查清单Astra 集群这类 AI 基础设施可以按以下清单做月度巡检检查项命令/操作预期结果NFS 挂载参数cat /etc/fstab已配置超时和重试参数GPU 健康检查bash /usr/local/bin/gpu-healthcheck.sh返回 PASS 且不依赖共享目录节点状态kubectl get nodes全部 Readyetcd 性能etcd metrics 查看 fsync 延迟延迟稳定存储控制器管理界面查看主备状态主备正常无切换告警任务恢复依赖训练任务是否配置 checkpoints每个任务都有关键步骤保存点学习环境可以简化到只检查 GPU 和节点状态。生产环境必须把这些检查项纳入自动巡检系统不能依赖人工在故障后补查。6.2 故障中快速取证清单故障发生时最容易犯的错误是急着重启。正确顺序是先确认监控告警、存储事件、节点事件三个数据源的时间戳执行kubectl get nodes -o wide记录当前节点状态逐台采集dmesg、nvidia-smi、journalctl -u kubelet日志查看共享存储控制器切换事件检查 NFS 挂载状态和健康检查脚本日志先隔离调度再恢复存储最后恢复计算节点复盘时把日志按时间线排列确认故障扩散路径。如果现场日志已经丢失后续复盘会非常被动。建议集群层面的日志采集始终开启至少保留 7 天。6.3 故障后复盘检查清单故障恢复后的复盘会建议按以下结构输出阶段核心问题Astra 集群的结论现象用户看到了什么多个节点 NotReady任务全失败根因触发因素是什么存储控制器切换放大为什么故障迅速扩散健康检查脚本依赖共享目录修复如何恢复的隔离节点、恢复存储、重建状态预防如何避免再发生拆分健康检查、调整挂载参数、增加本地盘降级复盘会议要产出可执行动作不要只写“加强监控”“提升稳定性”这类空话。每个预防措施都要指定负责人和完成时间。6.4 三个最容易忽略的坑第一个坑健康检查脚本把存储探活写进 GPU 探活逻辑里。Astra 集群的教训证明共享目录抖动会让 GPU 状态被误判最终触发节点集体下线。第二个坑NFS 挂载使用默认参数。默认timeo和retrans对普通 Web 服务够用但对高负载训练任务不够弹性。控制器切换期间默认参数可能让所有节点同步阻塞。第三个坑故障恢复时过早解封节点。如果在共享存储没有验证完成之前就把节点设为可调度新任务会继续写失败故障时间会被拉长。恢复操作应该一批一批验证而不是一次性放开所有节点。收尾把故障复盘变成架构能力Astra 集群这次“集体瘫痪”并不是 AI 服务器硬件的末日而是一次架构脆弱点的集中暴露。真正应该保留的不是“天命将至”的焦虑而是一套能快速取证、能判断故障扩散链路、能通过参数和脚本设计消除假阴性健康检查的运维能力。下一步可以做的扩展方向有三个一是把训练任务全部改为支持 checkpoint 断点续训从任务侧降低存储故障带来的影响二是为共享存储搭建独立的健康探测和告警机制让存储延迟和节点状态解耦三是把巡检清单、恢复流程和复盘模板脚本化形成平台自带的故障演练能力。把这几点落地后AI 服务器集群的稳定性就会从“靠运气”变成“靠设计”。