边缘集群部署 - 端到端验证与最大坑(label 硬编码)
承接 05-边缘节点接入与项目组配置。本篇是云-边主线的 Phase G 端到端验证,以及跨多个 KubeEdge 版本都会踩的最大坑。
来源:
docs/部署/kubeedge部署边缘集群cube-studio.md第 8~9 节。
八、Phase G:端到端验证
G.1 Web 公网
curl -sI http://47.114.127.214/frontend/
# HTTP/1.1 200 OK
G.2 NFS 双向
# 云端
echo "cloud-$(date +%s)" > /data/nfs/cloud-write.txt
# 边缘
cat /data/nfs/cloud-write.txt
G.3 云端训练 Pod
kubectl run cloud-training-test \
--image=docker.m.daocloud.io/library/busybox:1.36.0 \
--restart=Never \
--overrides='{"spec":{"nodeSelector":{"train":"true"}}}' \
--command -- sh -c "echo hello; sleep 30"
kubectl get pod cloud-training-test -o wide # NODE=cube
G.4 边缘推理 Pod(快速用一键脚本)
cd install/kubernetes/kubeedge
bash 50-verify.sh edge-pc
# ✅ POD edge-verify Running on edge-pc
如果一直 Pending / ContainerCreating 不前进,按严重性逐项检查:
| 现象 | 对应坑 |
|---|---|
kubectl describe pod 只有 Scheduled 事件 |
本页第九节(label 非空) |
edgecore log 有 failed to find plugin "loopback" |
CNI 插件缺失,见 05 E.6 / 07 |
边缘上 iptables-save | grep KUBE-SVC 空 |
kube-proxy kubeconfig IP,见 05 E.7 / 07 |
| 从公网 istio 访问边缘服务 502 | by-design,不是 bug,见 01 / 07 |
G.5 边缘推理服务访问
CubeStudio 为边缘 service 建 <svc>-external(externalIPs=[边缘内网 IP],port=NodePort):
# 在和边缘同网段的机器上(192.168.3.0/24)
curl -sI http://192.168.3.22:<nodeport>/docs
# HTTP/1.1 200 OK
web UI 里显示的服务访问 URL 就是这个。公网用户访问不到。
九、【最大坑】KubeEdge edge 节点 label 硬编码
⭐ 四大坑之 1。跨 v1.20.0 / v1.22.1 / v1.23.0 均复现。
现象
- 边缘节点 Ready,但新建的 Pod/Deployment 永远
Pending,kubectl describe只有Scheduled ... to edge-pc,无后续事件; - cloudcore 日志反复输出
downstream.go:128] there are 0 nodes need to sync config map, operation: MODIFIED; - cloudcore 只会 dispatch 节点刚加入时被 DS controller 直接设
nodeName的那几个 pod(kube-proxy / kube-flannel / node-exporter / edge-eclipse-mosquitto),之后一切静默。
根因(已定位到 KubeEdge 源码)
cloud/pkg/edgecontroller/controller/downstream.go:
func (dc *DownstreamController) syncPod() {
...
if !dc.lc.IsEdgeNode(pod.Spec.NodeName) {
continue // ← 所有 edge pod 在这里被静默丢弃
}
...
}
LocationCache.EdgeNodes 是通过固定 label selector 填充的:
// common/constants/default.go
EdgeNodeRoleKey = "node-role.kubernetes.io/edge"
EdgeNodeRoleValue = "" // ← 必须空字符串!
keadm join 时会自动打上 node-role.kubernetes.io/edge=(空值)。但如果之后手动 kubectl label node <edge> node-role.kubernetes.io/edge=true 去"强化"这个角色标记,label 值被覆盖成 true,labels.SelectorFromSet({key:""}) 就匹配不到了,所有 edge pod 从此静默丢弃。这就是 cube-studio web 界面上在边缘项目组里看到 pod 一直 Pending 的全部原因。
⚠️ 待核实:上述 KubeEdge 源码路径(
cloud/pkg/edgecontroller/controller/downstream.go、common/constants/default.go)来自上游 KubeEdge 项目,不在本仓库 CubeStudio 代码里,无法在本仓库核对行号。结论(edge label 必须空值)已被作者跨三个版本验证,按现象使用即可。
修复
# 检查:值必须是空字符串
kubectl get node <edge> -o jsonpath='{.metadata.labels.node-role\.kubernetes\.io/edge}'
# 修复(若非空)
kubectl label node <edge> node-role.kubernetes.io/edge-
kubectl label node <edge> node-role.kubernetes.io/edge=
# 让 cloudcore 重建 LocationCache
kubectl rollout restart deployment cloudcore -n kubeedge
重启后 cloudcore debug 日志(--v=4)应立刻看到:
message_dispatcher.go:140] [DispatchDownstream] dispatch Message to edge:
Resource:node/<edge>/<ns>/pod/<your-pod> Operation: update / insert
边缘 crictl pods 也会看到新 sandbox,pod 从 Pending 变 ContainerCreating → Running。
对 CubeStudio 的特别提醒
CubeStudio 在项目组页面可能让你打 node-role.kubernetes.io/edge=true(看起来更像 K8s 风格)。不要这样打。要么不动(keadm join 默认就对),要么 = 空值。其他用于 CubeStudio 自己调度的标签(org=edge、serving=true 等)不受影响,可正常打。
更多次要的坑(CNI / kube-proxy 详解、镜像大小预算、NFS stale handle、DaemonSet CrashLoop 等)见 07-踩坑速查。