一、问题描述
KubePi 管理面板中,点击按钮获取 dev-37 集群的资源列表(如 Deployments)时, 响应时间从正常的 ~1 秒劣化到 ~10 秒。
请求示例:
GET http://192.168.2.28:9999/kubepi/api/v1/proxy/dev-37/k8s/apis/apps/v1/deployments?search=true&pageNum=1&pageSize=10
连接其他集群(如 test-environment、dev1、arm)均正常,仅 dev-37 缓慢。
二、环境信息
| 项目 | 值 |
|---|---|
| KubePi 宿主机 | 192.168.2.28 |
| KubePi 容器 | kubepi-kubepi-1 (Docker Compose, 端口 9999) |
| KubePi 镜像 | ghcr.io/taozhioooo/kubepi:master (自定义 fork) |
| KubePi 数据库 | BoltDB (8MB) |
| dev-37 API Server | 192.168.2.37:6443 (k8s v1.33.4, 单节点控制面) |
| dev-37 运行时 | containerd v2.2.1 |
| 正常集群对比 | test-environment (k8s v1.14.1, 192.168.2.20) |
三、诊断过程
3.1 HAR 抓包分析
通过 Chrome DevTools 导出的 HAR 文件,锁定慢请求的关键数据:
| 指标 | 值 |
|---|---|
| wait (TTFB) | 10,078 ms |
| blocked | 3.2 ms |
| receive | 0.9 ms |
| 响应体大小 | 81,337 bytes |
| HTTP 状态码 | 200 OK |
结论: 瓶颈在服务端处理(wait=10s),不是网络传输问题。
3.2 排除网络层
# 从 kubepi 容器内 ping dev-37 API Server
docker exec kubepi-kubepi-1 ping -c 1 192.168.2.37
# 结果: 0.193ms ✓
# 从 kubepi 容器内 curl k8s API (无认证)
docker exec kubepi-kubepi-1 wget -O /dev/null https://192.168.2.37:6443/apis/apps/v1/deployments
# 结果: < 20ms ✓
3.3 排除 k8s API Server 本身
# 从 dev-37 本机用 kubectl 获取全量 deployments
kubectl get deployments --all-namespaces -o json | wc -c
# 结果: 191,402 bytes, 耗时 0.25s ✓
# 从 kubepi 容器内用客户端证书直接调 k8s API
docker exec kubepi-kubepi-1 curl -sk \
--cert cert.pem --key key.pem --cacert ca.pem \
https://192.168.2.37:6443/apis/apps/v1/deployments
# 结果: 302KB, 耗时 0.03s ✓
结论: k8s API Server 响应正常(30ms),问题不在 API 层。
3.4 排除 KubePi 应用本身
# sessions/status 接口响应时间 (从 HAR)
# 请求1: 8ms ✓
# 请求2: 13ms ✓
KubePi 认证、会话管理均正常,仅 proxy 到 dev-37 的请求慢。这说明 KubePi 应用层无问题, 慢点一定出在 proxy 层等待下游响应。
3.5 客户端证书检查
# 检查证书有效期
openssl x509 -in cert.pem -noout -enddate
# 结果: notAfter=Oct 7 07:29:57 2026 GMT ✓
证书未过期。
3.6 etcd 健康检查
# 从 dev-37 本机检查 etcd 健康
kubectl get --raw /healthz/etcd
# 结果: ok (32ms) ✓
3.7 资源量对比
| 集群 | Deployments 数量 | 响应时间 |
|---|---|---|
| dev-37 | 59 | ~10s |
| test-environment | 80+ | ~1s |
| dev1 | 100+ | ~1s |
dev-37 资源量甚至更少,但响应明显更慢,说明不是资源数量导致。
3.8 节点状态 — 关键发现
kubectl get nodes dev-37 -o wide
# STATUS: NotReady
dev-37 的 kubelet 上报状态为 NotReady! 这是转折点。NotReady 意味着 kubelet 无法正常汇报节点状态, 通常与容器运行时(CRI)相关。
3.9 CRI 运行时检查 — 根因定位
# 检查 CRI 版本
crictl version
# 输出: RuntimeVersion 为空!RuntimeApiVersion 为空!
进一步检查 containerd 状态:
systemctl status containerd
# Active: active (running) — 进程在跑
# 但检查 CRI 插件是否注册:
crictl info 2>&1 | head -20
# 关键错误: runtime.v1.RuntimeService 未注册
3.10 配置文件对比 — 最终根因
对比两个集群的 containerd 配置:
| 配置项 | dev-37 (v2.2.1) | test-environment (v1.7.x) |
|---|---|---|
| 插件路径 | io.containerd.grpc.v1.cri |
io.containerd.grpc.v1.cri |
| 配置文件版本 | v1.x 格式(plugins.cri.containerd…) | v1.x 格式 |
| 实际需要 | v2.x 格式([plugins.‘io.containerd.cri.v1.images’]…) | v1.x 格式 |
dev-37 的 containerd 已经升级到 v2.2.1,但配置文件仍然是 v1.x 的插件路径格式, 导致 v2.x containerd 无法正确识别 CRI 插件的配置,CRI 服务未注册。
四、根因总结
- 直接原因: containerd v2.2.1 的
runtime.v1.RuntimeService未注册,kubelet 无法与容器运行时通信 - 根本原因: 升级 containerd 到 v2.x 时,未同步更新配置文件格式。v1.x 的
[plugins.cri.xxx]路径在 v2.x 中不被识别,需要用[plugins.'io.containerd.cri.v1.xxx']格式 - 表现: kubelet 将节点标记为 NotReady → KubePi proxy 请求时等待 k8s API 的超时机制 → 约 10s 才返回
五、解决方案
5.1 更新 containerd 配置
将 /etc/containerd/config.toml 中的 v1.x 插件路径改为 v2.x 格式:
# 旧 (v1.x)
[plugins.cri.containerd]
...
# 新 (v2.x)
[plugins.'io.containerd.cri.v1.images']
...
[plugins.'io.containerd.cri.v1.runtime']
...
5.2 配置私有镜像仓库
如果使用私有镜像仓库(如 registry.docker.gm),在 containerd v2.x 中通过 hosts.toml 配置:
mkdir -p /etc/containerd/certs.d/registry.docker.gm
写入 /etc/containerd/certs.d/registry.docker.gm/hosts.toml:
server = "http://registry.docker.gm"
[host."http://registry.docker.gm"]
skip_verify = true
capabilities = ["pull", "resolve"]
[host."http://registry.docker.gm".header]
Authorization = "Basic <已脱敏>"
注: 上述
Authorization头已脱敏,实际使用时请替换为你自己的 Base64 凭据。 如需修改用户名密码,执行:echo -n '用户名:密码' | base64
同时确认默认配置中 registry 的 config_path 路径包含上述目录:
[plugins.'io.containerd.cri.v1.images'.registry]
config_path = '/etc/containerd/certs.d:/etc/docker/certs.d'
5.3 重启 containerd
systemctl restart containerd
5.4 重启 kubelet
systemctl restart kubelet
5.5 验证恢复
# 1. 确认 CRI 正常
crictl version
# 应输出 RuntimeVersion 和 RuntimeApiVersion
# 2. 确认容器运行正常
crictl ps | head -5
# 3. 确认节点状态
kubectl get nodes dev-37
# STATUS 应为 Ready, CONTAINER-RUNTIME 应为 containerd://2.2.1
# 4. 确认健康检查
kubectl get --raw /readyz
# 应返回 ok
# 5. 确认 KubePi 响应恢复
# 在浏览器中刷新 KubePi 的 Deployments 页面,响应应恢复到 ~1s
六、注意事项
-
操作时机: 控制面节点重启 containerd 期间(约 10-30 秒),API Server 会短暂不可用,已运行的 Pod 不受影响。建议在业务低峰期操作。
-
其他节点: dev-37 的 worker 节点(dev-7, dev-11, dev-13 等)使用的是 containerd v1.7.13,不受此问题影响。如果未来升级 worker 节点的 containerd 到 v2.x,同样需要更新配置文件。
-
配置兼容性: containerd v2.x 不兼容 v1.x 的插件路径配置。升级 containerd 大版本时必须同步更新配置文件,否则 CRI 插件不会加载。
-
kubelet 参数: 确认 kubelet 启动参数中
--pod-infra-container-image与 containerd 中配置的 sandbox 镜像一致:ps aux | grep kubelet | grep pod-infra-container-image
七、问题与解决方案对照表
| 诊断步骤 | 结果 | 是否为根因 |
|---|---|---|
| HAR 抓包分析 | wait=10078ms,服务端处理慢 | 定位方向 |
| 网络延迟测试 | 0.19ms ping, <20ms HTTPS | 排除 |
| k8s API 响应 | kubectl 0.25s, curl 0.03s | 排除 |
| KubePi sessions | 8-13ms 正常 | 排除 |
| 客户端证书 | 有效期到 2026-10 | 排除 |
| etcd 健康 | healthy, 32ms | 排除 |
| 资源量对比 | dev-37 更少但更慢 | 排除 |
| 节点状态 | dev-37 NotReady | 关联 |
| containerd CRI | runtime.v1.RuntimeService 未注册 | 根因 |
| 配置文件对比 | v1.x 插件路径 + v2.x containerd | 根因 |