Prhub

#52264 [CI][AMD] Improve Kubernetes failure diagnostics

原始 PR 作者 AndreasKaratzas 合并时间 2026-08-18 07:09 文件变更 1 提交数 6 评论 4 代码增减 +448 / -110

执行摘要

重构 AMD CI 失败诊断,采集 K8s 与 cgroup 证据

PR body 明确指出目标是“Collect Kubernetes-scoped AMD CI failure evidence while keeping the failing test log concise and immediately visible”(采集 Kubernetes 作用域内的 AMD CI 失败证据,同时保持失败测试日志简洁、立即可见)。关联 Issue #475 说明这是两仓库迁移的第一阶段,用来替代旧的将诊断内联写进终端日志的做法,避免诊断信息丢失,并消除自定义上传器与上传超时的重复逻辑。

值得精读,尤其适合关注 CI 可观测性和 Bash 脚本工程化的读者。重点关注两点:一是 cgroup v2 路径解析的实现方式(结合 mountinfo 还原宿主机路径),二是 artifact 路径逃逸校验与 symlink 拒绝策略,这是 CI 安全设计的好范例。合并前请确认 ci-infra#475 已合入。

讨论亮点

该 PR 没有实质性的技术讨论线程,khluu 直接给予 APPROVED。唯一的 review 来自 claude[bot] 的自动提示,说明仓库配置了手动 review 流程,不构成技术意见。核心设计决策都写在 PR body 与关联 Issue #475 中:包括依赖合并顺序、artifact 路径 pattern 的精确匹配、以及避免重复上传的迁移策略。

实现拆解

  1. 新增 Kubernetes 环境变量与清理机制:在脚本头部引入 amd_diagnostics_workspace、amd_diagnostics_pod_name、amd_diagnostics_k8s_namespace、amd_diagnostics_k8s_node_name,从 VLLM_CI_* 或 POD_NAME、NODE_NAME、HOSTNAME 等 Downward API 注入值获取;同时在 clear_ci_orchestration_env 中清理 VLLM_CI_K8S_POD_NAME 与 VLLM_CI_K8S_NAMESPACE,避免跨任务环境残留污染。

  2. 重构诊断执行函数:将原 run_amd_diagnostic 拆分为 run_failure_diagnostic,并新增 append_failure_diagnostic_section、append_failure_diagnostic_note、append_failure_diagnostic_file 等辅助函数;所有命令输出从 tee 改为直接重定向到日志文件,并引入 25 秒总预算与 5 秒单命令超时,防止诊断采集阻塞 CI 主流程。

  3. 采集 cgroup v2 与进程资源证据:实现 resolve_current_cgroup_v2_dir,通过 /proc/self/cgroup 与 /proc/self/mountinfo 解析当前进程在宿主机视角下的 cgroup v2 目录;collect_cgroup_diagnostics 读取 memory、cpu、cpuset、pids、io 等关键文件;collect_process_diagnostics 抓取 shell 进程的 limits、status 关键字段及按 RSS 排序的 top 100 进程快照。

  4. 采集挂载、设备与 GPU 证据并强化路径安全:记录 checkout、workspace、tmp、shared-memory 等挂载信息,检查设备节点权限、GPU 健康与网络时间戳;对写入的 artifact 路径做逃逸校验,拒绝通过 symlink 解析到 checkout 之外的路径,避免 PR 控制 symlink 导致 CI 泄露或破坏。

  5. 输出摘要与日志组织:最终打印无第三方依赖的两列 Bash 摘要,不携带 emoji 短代码或独立 Buildkite 诊断分组;保留 --- Test log 边界,使 pytest 失败与基础设施摘要保持在同一最后展开的 section 中;移除自定义上传超时,交由 Buildkite 在命令退出后的 post-command 阶段统一上传。

测试配套:没有新增独立测试用例,验证依赖真实 Buildkite 构建(PR 中提供 amd-ci/builds/12112 与 ci/builds/84117 的测试链接),并与 ci-infra#475 联合验证。

文件 模块 状态 重要度
.buildkite/scripts/hardware_ci/run-amd-test.sh CI 脚本 modified 5.86

关键符号

run_failure_diagnostic append_failure_diagnostic_section append_failure_diagnostic_note append_failure_diagnostic_file decode_mountinfo_path resolve_current_cgroup_v2_dir collect_cgroup_files collect_cgroup_diagnostics collect_process_diagnostics collect_mount_diagnostic

关键源码片段

.buildkite/scripts/hardware_ci/run-amd-test.sh infrastructure

唯一变更文件,承载全部 Kubernetes 失败诊断逻辑:新增 cgroup 解析、进程 / 挂载 /GPU 证据采集、路径安全校验和两列摘要输出,是整个 PR 的核心。

# 通过 /proc/self/cgroup 与 /proc/self/mountinfo 解析当前进程所在的 cgroup v2 目录。
# 容器内看到的 cgroup 路径是相对于挂载根的,必须结合 mountinfo 还原宿主机可见的绝对路径。
resolve_current_cgroup_v2_dir() {
    local cgroup_path=""
    local mount_root=""
    local mount_point=""
    local relative_path=""
    local candidate=""
​
    # 从 /proc/self/cgroup 中取第 2 列为空(即 v2 层级)的路径,例如 /docker/abc123
    cgroup_path=$(awk -F: '$2 == "" { print $3; exit }' /proc/self/cgroup 2>/dev/null)
    [[ -n "${cgroup_path}" ]] || return 1
​
    # 遍历 mountinfo 中 cgroup2 类型的挂载,找到能拼出有效目录的挂载点
    while IFS=$'\t' read -r mount_root mount_point; do
        mount_root=$(decode_mountinfo_path "${mount_root}")
        mount_point=$(decode_mountinfo_path "${mount_point}")
​
        # 根据挂载根与 cgroup 路径的关系计算相对路径
        if [[ "${cgroup_path}" == "/" ]]; then
            relative_path=""
        elif [[ "${mount_root}" == "/" ]]; then
            relative_path="${cgroup_path}"
        elif [[ "${cgroup_path}" == "${mount_root}" ]]; then
            relative_path=""
        elif [[ "${cgroup_path}" == "${mount_root}/"* ]]; then
            relative_path="/${cgroup_path#"${mount_root}/"}"
        else
            continue
        fi
​
        candidate="${mount_point%/}${relative_path}"
        # 只有目录真实存在才接受,避免拼出错误路径
        if [[ -d "${candidate}" ]]; then
            printf '%s\n' "${candidate}"
            return 0
        fi
    done < <(awk '
        {
            for (i = 6; i <= NF; i++) {
                if ($i == "-" && $(i + 1) == "cgroup2") {
                    print $4 "\t" $5
                    break
                }
            }
        }
    ' /proc/self/mountinfo 2>/dev/null)
​
    return 1
}

评论区精华

没有提炼出高价值讨论线程

当前评论区没有形成足够清晰的争议点或结论,后续有更多讨论时会体现在这里。

风险与影响

  1. cgroup 路径解析依赖环境细节:resolve_current_cgroup_v2_dir 依赖 /proc/self/cgroup 的字段格式与 mountinfo 中 cgroup2 挂载的表示方式,不同容器运行时或内核版本下可能解析失败;脚本对此有 unavailable 回退,不会中断主流程,但会丢失部分资源证据。
  2. 诊断采集超时风险:虽然设置了 25 秒总预算和 5 秒单命令超时,但如果系统负载极高导致 timeout 命令本身被拖慢,仍可能小幅拉长 CI 失败后的收尾时间。
  3. 路径安全校验可能误拒绝合法路径:拒绝 symlink 解析到 checkout 外的设计偏向保守,某些合法的共享内存或 tmp 挂载路径可能被过滤,导致对应诊断文件缺失。
  4. 跨仓库合并顺序依赖:必须等 ci-infra#475 先合并,否则新的 diagnostics.log 路径在 Buildkite artifact pattern 上匹配不到,导致诊断产物丢失;当前脚本已通过“匹配不到无副作用”的设计保证过渡安全,但顺序颠倒仍是明显风险。

对最终用户无影响,不涉及模型推理或服务逻辑。对 AMD/ROCm CI 的可观测性有显著正面影响:失败时能拿到 Kubernetes 作用域内的 cgroup、进程、挂载、GPU 健康等多维证据,大幅缩短故障根因定位时间。对团队而言,统一了诊断产物路径与上传方式,消除自定义上传器维护成本,并依赖 ci-infra 的标准 artifact 流程。影响范围仅限 AMD CI 脚本,其他平台(NVIDIA、CPU 等)不受影响。

跨仓库依赖顺序 Bash 脚本复杂度高 诊断采集超时敏感 路径安全校验严格

关联 Issue

#475 [CI][AMD] Upload failure diagnostics as Buildkite artifacts

完整报告

参与讨论