GKE AI/ML 训练集群中 JobSet 出现重启循环怎么排查
2026/9/15 20:31:42 网站建设 项目流程

GKE AI/ML 训练集群中 JobSet 出现重启循环怎么排查

【免费下载链接】skillsAgent Skills for Google products and technologies项目地址: https://gitcode.com/GitHub_Trending/skills29/skills

在 GKE 集群上跑大规模 AI/ML 训练任务时,一个常见现象是 JobSet 反复被控制器重启:任务刚跑到一半就回到起点,restarts计数不断增长。重启循环的原因可能来自节点池层面(Spot VM 被抢占、计划内维护、宿主机故障),也可能来自应用层面(coordinator worker 崩溃、集合通信超时)。本文按 SKILL.md 给出的诊断工作流,用 Cloud Monitoring 指标查询(MQL / PromQL)和 Cloud Logging 日志查询(LQL)四步定位根因,并给出文档对应的处理方式。适用的前提是:你的负载是 GKE 上的 JobSet 训练工作负载,而不是普通部署或集群创建问题——这个技能文档明确说明不用于后两者。

前提条件与排查变量

开始查询前,确认两个环境前提(来自文档 Prerequisites):

  • 集群的kube-state-metrics已启用JobSet metrics package,否则查不到kube_jobset_restarts这类指标;
  • Google Cloud 项目已启用 Cloud Logging 和 Cloud Monitoring。

然后把五个上下文变量定下来,后续所有查询模板都用它们填充:

变量含义
{project_id}项目 ID
{cluster_name}集群名
{workload_name}出问题的 JobSet 名称
{namespace}工作负载所在命名空间
{issue_time}问题发生时间(文档记为T

如果某些值不确定,文档的做法是先查集群资源或日志确认,查不到就保留{variable}占位符继续推进。时间窗口按文档规则计算:{start_time}={issue_time} - 30m{end_time}={issue_time} + 30m,即取问题时刻前后各 30 分钟。

另外文档给出一条执行规则:如果 API 查询遇到403 Permission Denied、认证错误或网络隔离,不要进入认证排障循环,而是用已拿到的变量填充查询模板、检查本地已有的遥测或 mock 数据文件,继续完成诊断与建议。

第一步:确认 JobSet 是否真的在循环重启

先验证“重启循环”这个现象本身,并确认重启频率。

MQL 查询(Cloud Monitoring 的 MQL Explorer / 可视化图表):

fetch prometheus_target | metric 'prometheus.googleapis.com/kube_jobset_restarts/gauge' | filter resource.cluster_name == '{cluster_name}' && metric.jobset_name == '{workload_name}' | align next_older(1m) | every 1m | group_by [metric.jobset_name], [val: max(value)]

或者等价的 PromQL:

kube_jobset_restarts{jobset_name="{workload_name}", cluster="{cluster_name}"}

判断逻辑:restarts非零或持续增长,说明控制器正在因 worker 失败或中断而主动重启 JobSet。如果这条指标没有增长,问题可能不在 JobSet 重启上,不必往下走。确认之后进入第二步。

第二步:查节点池中断事件(抢占、维护、宿主机终止)

这一步判断重启是否由节点池层面的物理事件触发。

指标查询(MQL):

fetch k8s_node_pool | metric 'kubernetes.io/node_pool/interruption_count' | filter cluster_name == '{cluster_name}' | align next_older(10m) | every 10m | group_by [metric.interruption_type, metric.interruption_reason, metadata.system.node_pool_name], [val: sum(value)]

PromQL 等价写法:

sum by (interruption_type, interruption_reason, node_pool_name, cluster_name) ( avg_over_time(kubernetes_io:node_pool_interruption_count{cluster_name="{cluster_name}"}[10m]) )

再配合节点池生命周期日志(LQL,注意替换时间窗口):

resource.type="gke_nodepool" AND resource.labels.cluster_name="{cluster_name}" AND timestamp >= "{start_time}" AND timestamp <= "{end_time}"

文档给出的日志事件判断标准:

  • PreemptionEvent:Spot VM 被抢占,或节点被缩容;
  • MaintenanceEvent:节点池更新或 Google 计划内维护;
  • TerminationEvent:严重的宿主机故障,需要进一步看interruption_reason或日志确认宿主机问题。

Failure Signatures 里给了对应的文档示例日志。Spot 抢占的示例(文档示例,实际节点名不同):

resource.type="gke_nodepool" severity=INFO "PreemptionEvent: Node 'gke-tpu-pool-12345-abcde' in node pool 'tpu-pool' was preempted."

宿主机硬件故障的示例(文档示例)通常伴随Host Errorkernel panic类 payload:

May 20 08:12:15 gke-tpu-pool-12345-abcde kernel: [ 9876.543210] BUG: unable to handle kernel paging request at 000000000002008 May 20 08:12:16 gke-tpu-pool-12345-abcde systemd[1]: node-problem-detector.service: Main process exited, code=exited, status=1/FAILURE

以及对应的 Cloud Logging 过滤匹配(文档示例):

resource.type="k8s_node" severity=ERROR "TerminationEvent: Node 'gke-tpu-pool-12345-abcde' was terminated due to: Host Error"

如果 Spot 池的抢占计数很高,根因基本可以锁定为抢占,直接进入“根据根因执行处理”。否则继续第三步。

第三步:把异常节点关联到物理宿主机

这一步的目的是确认是否存在某台坏宿主机反复弄挂 coordinator pod。

先查不健康的节点(Ready=False):

fetch k8s_node | metric 'kubernetes.io/node/status_condition' | filter cluster_name == '{cluster_name}' && metric.condition == 'Ready' && metric.status == 'False' | align next_older(1m) | every 1m | group_by [node_name, metadata.user.gke_nodepool], [val: max(value)]

PromQL 等价写法:

sum by (status, condition, node_pool_name) ( kubernetes_io:node_status_condition{cluster_name="{cluster_name}", condition="Ready", status="False"} )

再查节点到 GCE 宿主机 ID 的拓扑映射:

fetch k8s_node | metric 'kubernetes.io/node/cpu/total_cores' | filter cluster_name == '{cluster_name}' | align next_older(1m) | every 1m | group_by [node_name, metadata.user.gce_topology_host, metadata.user.gke_nodepool], [val: max(value)]

同时用 LQL 过滤节点故障日志:

resource.type="k8s_node" AND resource.labels.cluster_name="{cluster_name}" AND (textPayload:"host error" OR textPayload:"kernel panic" OR textPayload:"hardware failure" OR textPayload:"NodeNotReady") AND timestamp >= "{start_time}" AND timestamp <= "{end_time}"

判断逻辑:找出处于Ready=FalseUnknown状态的节点,通过metadata.user.gce_topology_host关联到 GCE 物理宿主机 ID,检查同一台宿主机是否在多次尝试中反复故障。如果是,就命中了文档中的“隔离坏宿主机”处理路径。

第四步:看 Pod 状态与 coordinator worker 日志

文档要求先做 A、B 两项整体评估,再去看具体 worker 日志,顺序不能颠倒。

A. Pod 生命周期阶段分布:

fetch k8s_pod | metric 'kubernetes.io/pod/status/phase' | filter cluster_name == '{cluster_name}' && pod_name ==~ '{workload_name}.*' | align next_older(10m) | every 10m | group_by [metric.phase], [val: count()]

PromQL 等价写法:

sum by (phase) ( avg_over_time(kube_pod_status_phase{cluster="{cluster_name}", pod=~"{workload_name}.*"}[10m]) )

B. 不可调度的 Pod:

fetch k8s_pod | metric 'kubernetes.io/pod/status/unschedulable' | filter cluster_name == '{cluster_name}' && pod_name ==~ '{workload_name}.*' | align next_older(10m) | every 10m | group_by [pod_name], [val: max(value)]

Failure Signatures 中给出了这类问题的文档示例信号:当 autoscaler 找不到足够容量或 compact placement 组时,pod 会停留在Pending/Unschedulable

resource.type="k8s_pod" severity=WARNING "0/8 nodes are available: 8 Insufficient tpu, 8 node(s) did not match Topology Spread Constraints."

C. coordinator worker 容器日志——重点看 slice 0 的 worker 0(即 coordinator),分析 NCCL 超时、集合通信问题或 MegaScale hang:

resource.type="k8s_container" AND resource.labels.cluster_name="{cluster_name}" AND labels."k8s-pod/jobset_sigs_k8s_io/jobset-name"="{workload_name}" AND timestamp >= "{start_time}" AND timestamp <= "{end_time}"

如果训练因网络丢包或某个 worker 失败而挂起,其余 worker 最终会因等待集合通信超时而报错。Failure Signatures 给出的文档示例日志模式:

[rank0]:[2026-05-20 08:14:30,123] torch.distributed.elastic.multiprocessing.api: [ERROR] Child process 12345 died with exit code 1 [rank0]:[2026-05-20 08:14:35,456] NCCL WARN : [Proxy Service] Failed to send, connection reset by peer [rank0]:[2026-05-20 08:14:40,000] NCCL WARN: Collective collectiveName/1234567890abcde timeout after 1800 seconds

根据根因执行处理

文档给出两条处理路径,风险等级不同。

路径 1:抢占与自动扩缩容优化(低风险)——当第二步显示 Spot VM 抢占计数很高时:

  • 把关键长训练负载换到GKE Reserved / On-Demand VM,或使用Compact Placement Policies减少碎片整理带来的中断;
  • 依据:消除现货市场抢占,减少训练重启。

路径 2:隔离故障宿主机(高风险)——当第三步确认某个gce-topology-host在多次尝试中反复触发重启时,文档建议:

  1. cordon / drain 对应的 GKE 节点;
  2. 删除底层 GCE VM 实例以触发实例重建;
  3. 向 Google Cloud Support 提交工单,注明物理宿主机 ID。

注意这条路径的实际影响:cordon 和 drain 会把该节点上的负载迁走,删除 VM 实例属于破坏性操作,且只有确认宿主机反复故障时才走这条路。文档给出的预期结果是 GKE auto-repair 会在健康的物理机上重建 VM 实例,从而打破无限重启循环。

可选:先用只读脚本校验查询写法

仓库里有一个 validate_queries.sh 脚本,会对 SKILL.md 中用到的 LQL 过滤器和 PromQL 做只读的 dry-run 校验:LQL 过滤用gcloud logging read --limit=1试探,PromQL 通过curl请求 Monitoring 的 Prometheus 查询接口检查能否编译。脚本不做任何写操作;它要求gcloud已配置(PROJECT_ID环境变量或gcloud config的项目),在未认证或离线时会打印警告并跳过对应项,只有 PromQL 编译失败(非 200/401/403/000)才以非零码退出。在权限受限的环境里跑一遍,可以提前知道哪些查询语句本身写得对不对。

排查边界与检查清单

这个诊断流程只覆盖 JobSet 中断、重启、抢占这类问题;普通的 GKE 集群创建、基础部署和非 JobSet 应用问题不在范围内。文档自带的完整清单可以贴在执行环境里:

  • 收集上下文变量,算出{start_time}{issue_time} - 30m)和{end_time}{issue_time} + 30m
  • 查询 JobSet 重启次数
  • 检查节点池中断事件(Spot 抢占 vs 硬件终止)
  • 查询节点到宿主机的映射,检查节点日志中的物理宿主机错误
  • 检查 pod 时间线状态与 coordinator worker 容器日志
  • 给出调度策略建议(On-demand vs Spot)或隔离故障宿主机

【免费下载链接】skillsAgent Skills for Google products and technologies项目地址: https://gitcode.com/GitHub_Trending/skills29/skills

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询