【集群】云原生批调度实战:调度器测试与监控工具 kube-scheduling-perf 实操注意事项说明

【集群】云原生批调度实战:调度器测试与监控工具 kube-scheduling-perf 实操注意事项说明

Fre5h1nd Lv6

本系列《云原生批调度实战:Volcano 深度解析》计划分为以下几篇,点击查看其它内容。

  1. 云原生批调度实战:Volcano 深度解析(一)批处理背景需求与Volcano特点
  2. 云原生批调度实战:Volcano 深度解析(二)Volcano调度流程与调度状态
  3. 云原生批调度实战:Volcano 安装与初试
  4. 云原生批调度实战:调度器测试与监控工具 kube-scheduling-perf
  5. 云原生批调度实战:调度器测试与监控工具 kube-scheduling-perf 实操注意事项说明
  6. 云原生批调度实战:调度器测试监控结果

💡简介

📖 文档定位:本文为 kube-scheduling-perf 项目的实际部署篇,与 理论介绍文档 形成互补。理论介绍文档重点解析项目的架构设计和自动化原理,而本文则专注于解决实际部署过程中的各种技术难题。

适用场景:如果您已经阅读了理论介绍文档,并计划在实际环境中部署和使用 kube-scheduling-perf 工具,那么本文档将为您提供必要的技术支持和故障排除指南。

🖼️背景

为什么需要这份注意事项文档?

kube-scheduling-perf 项目虽然提供了完善的自动化测试框架,但在实际部署过程中,由于以下因素,用户往往会遇到各种技术障碍:

1. 环境差异

  • 网络环境:国内用户访问海外镜像仓库时经常遇到网络超时问题
  • 系统版本:不同Linux发行版和内核版本对Docker、Kubernetes的支持程度不同
  • 硬件配置:老旧服务器可能无法运行最新版本的容器和工具

2. 权限和配置问题

  • 用户权限:Docker容器运行时的用户权限配置不当
  • 目录权限:自动生成的目录和文件所有权问题
  • 系统配置:内核参数、cgroup配置等系统级设置

3. 版本兼容性

  • Go版本:不同Go版本对测试代码的兼容性差异
  • Docker版本:容器运行时版本与Kubernetes版本的匹配问题
  • Kubernetes版本:API版本变化导致的兼容性问题

文档价值

本文档基于实际部署经验总结,提供了:

  • 系统性的问题分类:将常见问题按类型进行归类
  • 详细的解决方案:每个问题都提供具体的解决步骤
  • 预防性建议:帮助用户提前避免可能遇到的问题
  • 故障排除指南:快速定位和解决部署过程中的技术难题

希望通过本文档,帮助大家避免重复踩坑,提高部署效率,顺利运行测试工具~

🔨注意事项

注意1:加速镜像拉取

在国内环境下需要使用CDN加速镜像拉取[2]

Go 相关包

Makefile文件中替换GOPROXY ?= https://proxy.golang.org,directGOPROXY ?= https://mirrors.aliyun.com/goproxy/,direct

Docker 相关包

在执行 make 命令时,由于需要从海外服务器拉取镜像,频繁出现超时错误:

1
2
Error response from daemon: Head "https://asia-east1-docker.pkg.dev/v2/k8s-artifacts-prod/images/kwok/kwok/manifests/v0.6.1": dial tcp 142.250.157.82:443: i/o timeout
Error response from daemon: Get "https://registry.k8s.io/v2/": net/http: request canceled while waiting for connection (Client.Timeout exceeded while awaiting headers)

DaoCloud 镜像仓库提供了非常方便的解决方案,感恩!使用 DaoCloud 镜像加速,只需要在镜像前加上前缀 m.daocloud.io/

修改内容

1. Makefile 配置

1
2
3
4
5
# 修改前
IMAGE_PREFIX ?=

# 修改后
IMAGE_PREFIX ?= m.daocloud.io/

2. 脚本配置
hack/local-registry-with-load-images.sh 中:

1
2
3
4
5
# 修改前
IMAGE_PREFIX="${IMAGE_PREFIX:-}"

# 修改后
IMAGE_PREFIX="${IMAGE_PREFIX:-m.daocloud.io/}"

工作原理

  • 镜像处理流程:所有镜像通过 hack/local-registry-with-load-images.sh 脚本处理
  • DaoCloud 加速:脚本会自动从 m.daocloud.io/ 拉取镜像,然后推送到本地仓库 localhost:5001/
  • 容器内 Docker:即使使用容器内的 Docker,也会通过 IMAGE_PREFIX 环境变量传递镜像前缀

注意2:内核版本适配

问题说明

如之前KIND安装博客所述,本人所使用服务器内核版本过低(3.10.0-1160.71.1.el7.x86_64),无法运行较高版本的Kubernetes和Kind,权衡之计是进行版本降级以解决兼容性问题。

  • 降级之后,仍然会收到报错:✗ Preparing nodes 📦 ; Command Output: WARNING: Your kernel does not support cgroup namespaces. Cgroup namespace setting discarded.
    • 具体分析后发现 Kind 自动添加 cgroupns 参数:从日志中可以看到,kind 在创建集群时自动添加了 –cgroupns=private 参数,这是较新版本 kind 的默认行为。
    • 解决方案:修改 kind 配置,./hack/kind-with-local-registry.sh中,在kind create ...之前增加以下代码:
      1
      2
      3
      4
      5
      6
      7
      ...
      # 新增以下代码:Disable cgroup namespaces for older kernels
      export KIND_EXPERIMENTAL_DISABLE_CGROUP_NAMESPACES=true

      # Create kind cluster with containerd registry configuration
      kind create cluster --config "${KIND_CONFIG:-}" --name "${KIND_CLUSTER_NAME:-kind}"
      ...

版本降级目标

  • Go版本: 1.24 → 1.23.10
  • Kind版本: v0.27.0 → v0.19.0
  • Kubernetes版本: v1.25.3 → v1.27.1

修改列表

Go

  • go.mod中:go 1.24go 1.23.10
  • Makefile中:GO_IMAGE ?= $(IMAGE_PREFIX)docker.io/library/golang:1.24GO_IMAGE ?= $(IMAGE_PREFIX)docker.io/library/golang:1.23.10

Kind

  • go.mod中:sigs.k8s.io/kind v0.27.0sigs.k8s.io/kind v0.19.0

节点Kubernetes:

  • go.mod中:修改k8s配置 v0.32.1v0.27.1
    1
    2
    3
    4
    5
    k8s.io/api v0.27.1
    k8s.io/apimachinery v0.27.1
    k8s.io/apiextensions-apiserver v0.27.1 // indirect
    k8s.io/client-go v0.27.1 // indirect
    k8s.io/component-base v0.27.1 // indirect
  • ./cluster目录下的kueuevolcanoyunikornoverview四个目录中修改kind.yaml文件:docker.io/kindest/node:v1.32.2docker.io/kindest/node:v1.27.1

go.sum 版本管理文件[3]

  • 修改go.mod后,需要删除go.sum并执行go mod tidy以重新生成go.sum以匹配新的依赖版本。
  • 必要时开启CDN镜像加速export GOPROXY=https://mirrors.aliyun.com/goproxy/,direct
  • 如果一直出现奇怪的错误,例如go: github.com/wzshiming/kube-scheduling-perf/gopath/pkg/mod/github.com/pkg/errors@v0.9.1: import path "github.com/wzshiming/kube-scheduling-perf/gopath/pkg/mod/github.com/pkg/errors@v0.9.1" should not have @version,可能是因为已安装的旧版本未删除,应该删除旧的 gopath 并重新构建,以确保参数生效:
    1
    2
    sudo rm -rf gopath
    make

其它

  • 之后./hack/local-registry-with-load-images.sh会自动提前拉取镜像。
  • 注意:如果你是通过 Makefile 自动构建 bin/kind,请务必删除旧的 bin/kind 并重新构建,以确保新参数生效:
    1
    2
    sudo rm -f bin/kind
    make bin/kind
  • 注意:同理,如果修改版本前已经下载了相关go包,也应该删除旧的 gopath 并重新构建,以确保参数生效:
    1
    2
    sudo rm -rf gopath
    make

注意3:Go 版本兼容性问题

问题说明

降级 Go 版本后,测试代码中出现编译错误:

1
test/yunikorn/batch_job_test.go:10:29: t.Context undefined (type *"testing".T has no field or method Context, but does have unexported field context)

原因分析

t.Context() 方法在 Go 1.23.10 中可能不被完全支持或存在兼容性问题,导致测试代码无法编译。

解决方案

将所有测试文件中的 t.Context() 替换为 context.Background()

修改文件

1. test/yunikorn/batch_job_test.go

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 修改前
import (
"testing"
"github.com/wzshiming/kube-scheduling-perf/test/utils"
)

func TestInit(t *testing.T) {
err := provider.AddNodes(t.Context())
// ...
}

// 修改后
import (
"context"
"testing"
"github.com/wzshiming/kube-scheduling-perf/test/utils"
)

func TestInit(t *testing.T) {
err := provider.AddNodes(context.Background())
// ...
}

2. test/volcano/batch_job_test.go

  • 同样添加 "context" 导入
  • 将所有 t.Context() 替换为 context.Background()

3. test/kueue/batch_job_test.go

  • 同样添加 "context" 导入
  • 将所有 t.Context() 替换为 context.Background()

注意4:Docker 容器权限问题

问题说明

执行 make 命令时出现权限错误:

1
mv: 无法将"./logs" 移动至"./tmp/logs": 权限不够

检查发现多个目录(./logs./bin./gopath./registry-data)的归属者是 root,而不是当前用户(当使用非 root 用户时)。

原因分析

Makefile 中的 GO_IN_DOCKER 命令使用 Docker 容器执行,容器内进程默认以 root 用户运行,导致创建的文件/目录归属 root。

解决方案

1. 修改 Makefile,让容器以当前用户身份运行

1
2
3
4
5
6
7
8
9
10
# 修改前
GO_IN_DOCKER = docker run --rm --network host \
-v $(shell pwd):/workspace/ -w /workspace/ \
-e GOOS=$(GOOS) -e CGO_ENABLED=0 -e GOPATH=/workspace/gopath/ -e GOPROXY=$(GOPROXY) $(GO_IMAGE)

# 修改后
GO_IN_DOCKER = docker run --rm --network host \
-u $(shell id -u):$(shell id -g) \
-v $(shell pwd):/workspace/ -w /workspace/ \
-e GOOS=$(GOOS) -e CGO_ENABLED=0 -e GOPATH=/workspace/gopath/ -e GOPROXY=$(GOPROXY) $(GO_IMAGE)

2. 创建目录权限修复脚本

hack/ensure-directories.sh

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#!/usr/bin/env bash
set -o errexit
set -o nounset
set -o pipefail

DIR="$(dirname "${BASH_SOURCE[0]}")"
ROOT_DIR="$(realpath "${DIR}/..")"

# Function to ensure directory has correct ownership
ensure_directory() {
local dir="$1"
local owner=$(stat -c '%U' "$dir" 2>/dev/null || echo "none")

if [[ "$owner" == "root" ]]; then
echo "Removing root-owned directory: $dir"
sudo rm -rf "$dir"
fi

mkdir -p "$dir"
chmod 755 "$dir"
echo "Created/updated directory: $dir"
}

# Create all necessary directories
for d in logs bin gopath registry-data output results tmp; do
ensure_directory "${ROOT_DIR}/${d}"
done

3. 在 Makefile 中集成权限修复

1
2
3
4
5
6
7
8
9
10
11
.PHONY: ensure-directories
ensure-directories:
./hack/ensure-directories.sh

.PHONY: default
default: ensure-directories
# ... existing content ...

.PHONY: serial-test
serial-test: ensure-directories bin/kind
# ... existing content ...

4. 修改镜像处理脚本

hack/local-registry-with-load-images.sh 中确保 registry-data 目录正确创建:

1
2
# Ensure registry-data directory exists with correct permissions
mkdir -p "${ROOT_DIR}/registry-data"

验证方法

1
2
3
4
5
# 运行权限修复脚本
./hack/ensure-directories.sh

# 检查目录权限
ls -la | grep -E "(logs|bin|gopath|registry-data)"

注意5:Go 构建缓存权限问题

问题说明

在执行 make 命令时出现 Go 构建缓存权限错误:

1
failed to initialize build cache at /.cache/go-build: mkdir /.cache: permission denied

原因分析

  1. Docker 容器用户权限:当使用 -u $(shell id -u):$(shell id -g) 让容器以当前用户身份运行时,容器内的 $HOME 变量变为 /(因为没有为普通用户设置 home 目录)
  2. Go 默认行为:Go 在没有明确设置 GOCACHE 环境变量时,会尝试在 $HOME/.cache/go-build/.cache/go-build 下创建构建缓存
  3. 权限冲突:普通用户没有权限在容器根目录 / 下创建 .cache 目录,导致权限被拒绝

解决方案

1. 设置 Go 构建缓存目录

在 Makefile 的 GO_IN_DOCKER 命令中添加 GOCACHE 环境变量:

1
2
3
4
5
6
7
8
9
10
11
# 修改前
GO_IN_DOCKER = docker run --rm --network host \
-u $(shell id -u):$(shell id -g) \
-v $(shell pwd):/workspace/ -w /workspace/ \
-e GOOS=$(GOOS) -e CGO_ENABLED=0 -e GOPATH=/workspace/gopath/ -e GOPROXY=$(GOPROXY) $(GO_IMAGE)

# 修改后
GO_IN_DOCKER = docker run --rm --network host \
-u $(shell id -u):$(shell id -g) \
-v $(shell pwd):/workspace/ -w /workspace/ \
-e GOOS=$(GOOS) -e CGO_ENABLED=0 -e GOPATH=/workspace/gopath/ -e GOPROXY=$(GOPROXY) -e GOCACHE=/workspace/.cache $(GO_IMAGE)

2. 设计原理

  • 权限一致性:通过设置 GOCACHE=/workspace/.cache,确保 Go 构建缓存在挂载的工作目录下创建,当前用户有完全权限
  • 兼容性:这个修改不会影响其他构建逻辑,只是改变了缓存存储位置
  • 最佳实践:这是使用非 root 用户运行 Docker 容器时的标准做法

验证方法

1
2
3
4
5
# 测试 Go 构建是否正常
make bin/test-kueue

# 检查缓存目录是否创建
ls -la .cache/

预期效果

修改后:

  1. Go 构建缓存会在项目根目录的 .cache 文件夹下创建
  2. 缓存目录属于当前用户,权限正确
  3. 不再出现 permission denied 错误
  4. 构建过程正常进行

注意事项

  • 这个问题只有在使用 -u 参数让容器以非 root 用户运行时才会出现
  • 如果使用默认的 root 用户运行容器,不会有此问题,但会导致生成的文件归 root 所有
  • 设置 GOCACHE 是使用非 root 用户运行 Go 容器的标准做法

注意6:Kueue Webhook 连接问题

问题说明

在执行 Kueue 测试时,经常出现 webhook 连接被拒绝的错误:

1
2
3
Internal error occurred: failed calling webhook "mresourceflavor.kb.io": failed to call webhook: 
Post "https://kueue-webhook-service.kueue-system.svc:443/mutate-kueue-x-k8s-io-v1beta1-resourceflavor?timeout=10s":
dial tcp 10.96.33.70:443: connect: connection refused

原因分析

  1. Webhook 服务未就绪:Kueue 的 webhook 服务在部署后需要时间启动和初始化
  2. 证书生成延迟:webhook 服务器需要生成 TLS 证书,这个过程可能需要几秒钟
  3. 服务端点未配置:webhook 服务的端点(endpoints)可能还未正确配置
  4. 缺少等待机制:原有的部署流程没有等待 webhook 服务完全就绪

解决方案

1. 创建 Webhook 等待脚本

hack/wait-for-webhook.sh

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
#!/usr/bin/env bash
set -o errexit
set -o nounset
set -o pipefail

# 获取脚本参数
SCHEDULER_NAME="${1:-}"
if [[ -z "$SCHEDULER_NAME" ]]; then
echo "Usage: $0 <scheduler-name>"
echo "Example: $0 kueue"
exit 1
fi

# 设置变量
KUBECONFIG="${KUBECONFIG:-./kubeconfig.yaml}"
NAMESPACE="${SCHEDULER_NAME}-system"
WEBHOOK_SERVICE="${SCHEDULER_NAME}-webhook-service"

echo "等待 ${SCHEDULER_NAME} webhook 服务就绪..."

# 等待 webhook 证书生成
echo "检查 webhook 证书..."
for i in $(seq 1 30); do
if kubectl --kubeconfig="$KUBECONFIG" get secret -n "$NAMESPACE" "${SCHEDULER_NAME}-webhook-server-cert" >/dev/null 2>&1; then
echo "✓ Webhook 证书已生成"
break
fi
if [[ $i -eq 30 ]]; then
echo "✗ 等待 webhook 证书超时"
exit 1
fi
sleep 2
done

# 等待 webhook Pod 就绪
echo "等待 webhook Pod 就绪..."
kubectl --kubeconfig="$KUBECONFIG" wait --for=condition=ready pod -l control-plane=controller-manager -n "$NAMESPACE" --timeout=120s

# 等待 webhook 服务端点就绪
echo "等待 webhook 服务端点..."
for i in $(seq 1 30); do
if kubectl --kubeconfig="$KUBECONFIG" get endpoints -n "$NAMESPACE" "$WEBHOOK_SERVICE" -o jsonpath='{.subsets[0].addresses}' | grep -q .; then
echo "✓ Webhook 服务端点已就绪"
break
fi
if [[ $i -eq 30 ]]; then
echo "✗ 等待 webhook 服务端点超时"
exit 1
fi
sleep 2
done

echo "✓ ${SCHEDULER_NAME} webhook 服务已完全就绪"

2. 修改 Kueue 部署流程

clusters/kueue/Makefile 中的 create-kueue 目标中添加 webhook 等待:

1
2
3
4
5
6
7
8
.PHONY: create-kueue
create-kueue:
KUBECONFIG=$(KUBECONFIG) kubectl kustomize ../../schedulers/kueue | ../../hack/local-registry-with-load-images.sh
KUBECONFIG=$(KUBECONFIG) kubectl create -k ../../schedulers/kueue
KUBECONFIG=$(KUBECONFIG) kubectl patch deploy -n kueue-system kueue-controller-manager --type json \
-p '[{"op": "replace", "path": "/spec/template/spec/containers/0/resources", "value": {"requests":{"cpu": "500m"}, "limits":{"cpu": $(LIMIT_CPU)}}}, {"op": "replace", "path": "/spec/template/spec/containers/1/resources", "value": {"requests":{"cpu": "500m"}, "limits":{"cpu": $(LIMIT_CPU)}}}]'
sleep 1
KUBECONFIG=$(KUBECONFIG) ../../hack/wait-for-webhook.sh kueue

3. 设计原理

参考 Makefile 中现有的 wait 目标设计:

1
2
3
4
5
6
7
8
9
.PHONY: wait
wait:
-for i in $$(seq 1 60); do \
KUBECONFIG=$(KUBECONFIG) kubectl wait -A \
--for=condition=Ready=True pod \
--all \
--timeout=100s >/dev/null 2>&1 && break; \
done
sleep 1

webhook 等待脚本采用相同的设计模式:

  • 循环检查:使用 for 循环定期检查状态
  • 超时机制:设置合理的超时时间避免无限等待
  • 详细日志:提供清晰的进度信息
  • 错误处理:在超时或失败时提供明确的错误信息

验证方法

1
2
3
4
5
6
7
8
# 手动测试 webhook 等待脚本
cd clusters/kueue
KUBECONFIG=./kubeconfig.yaml ../../hack/wait-for-webhook.sh kueue

# 检查 webhook 服务状态
kubectl get pods -n kueue-system -l control-plane=controller-manager
kubectl get service kueue-webhook-service -n kueue-system
kubectl get endpoints kueue-webhook-service -n kueue-system

预期效果

修改后,每次执行 make prepare-kueue 时:

  1. Kueue 集群正常启动
  2. Webhook 服务自动部署
  3. 脚本等待 webhook 完全就绪
  4. 测试可以正常进行,不再出现连接拒绝错误

注意7:目录结构说明

核心目录

  • hack/: 存放辅助脚本,如权限修复、镜像处理、结果保存等

    • ensure-directories.sh: 确保目录权限正确
    • local-registry-with-load-images.sh: 处理镜像拉取和本地仓库
    • save-result-images.sh: 保存测试结果和监控图表
    • kind-with-local-registry.sh: 创建带本地仓库的 kind 集群
  • clusters/: 各调度器的集群配置和生命周期管理

    • kueue/: Kueue 调度器集群配置
    • volcano/: Volcano 调度器集群配置
    • yunikorn/: YuniKorn 调度器集群配置
    • overview/: 监控集群配置(Prometheus + Grafana)
  • test/: 测试代码和测试用例

    • utils/: 通用测试工具和辅助函数
    • kueue/: Kueue 调度器测试代码
    • volcano/: Volcano 调度器测试代码
    • yunikorn/: YuniKorn 调度器测试代码

生成目录

  • bin/: 自动生成的二进制文件
    • kind: 用于创建 Kubernetes 集群的工具
    • test-kueue: Kueue 测试可执行文件
    • test-volcano: Volcano 测试可执行文件
    • test-yunikorn: YuniKorn 测试可执行文件
  • gopath/: Go 模块缓存和依赖
    • pkg/mod/: Go 模块缓存
    • src/: 源代码(如果使用 GOPATH 模式)

数据目录

  • logs/: 审计日志和测试日志
    • kube-apiserver-audit.*.log: Kubernetes API 服务器审计日志
    • 其他测试过程中生成的日志文件
  • registry-data/: 本地 Docker 仓库数据
    • 存储从远程仓库拉取的镜像
    • 供 kind 集群使用的本地镜像仓库
  • output/: 测试输出和监控数据
    • panel-*.png: Grafana 监控图表
    • 其他测试输出文件
  • results/: 测试结果归档
    • 按时间戳组织的测试结果目录
    • 包含环境变量、日志、输出等完整信息
  • tmp/: 临时文件目录
    • 测试过程中的临时文件
    • 结果归档前的临时存储


  • 希望这篇博客对你有帮助!如果你有任何问题或需要进一步的帮助,请随时提问。
  • 如果你喜欢这篇文章,欢迎动动小手给我一个follow或star。

🗺参考文献

[1] Github - kube-scheduling-perf

[2] Go 国内加速镜像

[3] 深入理解 Go Modules 的 go.mod 与 go.sum

  • 标题: 【集群】云原生批调度实战:调度器测试与监控工具 kube-scheduling-perf 实操注意事项说明
  • 作者: Fre5h1nd
  • 创建于 : 2025-07-01 11:17:44
  • 更新于 : 2025-07-14 20:14:49
  • 链接: https://freshwlnd.github.io/2025/07/01/k8s/k8s-scheduler-performance-test-debug/
  • 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。
评论