边缘集群部署 - 边缘节点接入与项目组配置

云端 cube-studio + CloudCore 就绪后,把 NAT 后的边缘节点 keadm join 进来、装 CNI、修 kube-proxy,并在 CubeStudio 配边缘项目组时读这篇(云-边主线 Phase E/F)

02-部署安装 / 部署方式 / 边缘集群
keadm joinedgecorecontainerdCNI pluginsloopbackkube-proxykubeconfigNFS mountedgeStreammetaServer边缘项目组SERVICE_EXTERNAL_IPnode_selectorminioNodePortworkflow-controller-configmap

边缘集群部署 - 边缘节点接入与项目组配置

本篇对应云-边主线的 Phase E(边缘节点接入)Phase F(CubeStudio 项目组配置)。承接 04-云端CubeStudio与CloudCore

来源:docs/部署/kubeedge部署边缘集群cube-studio.md 第 6~7 节 + docs/部署/边缘集群方式部署平台.md 的部署流程。


六、Phase E:边缘节点接入

E.1 处理存量 docker

边缘机原装 docker-ce底层已有 containerd 1.7.3,只需停 docker.service 保留 containerd:

systemctl stop docker.service docker.socket
systemctl disable docker.service docker.socket

E.2 containerd 调整

# SystemdCgroup = true(v1.7.x 语法)
sed -i 's|SystemdCgroup = false|SystemdCgroup = true|' /etc/containerd/config.toml

# registry mirror(同云端)
mkdir -p /etc/containerd/certs.d/docker.io
cat > /etc/containerd/certs.d/docker.io/hosts.toml <<EOF
server = "https://registry-1.docker.io"
[host."https://docker.m.daocloud.io"]
  capabilities = ["pull", "resolve"]
EOF

# config_path(v1.7.x 在 [plugins."io.containerd.grpc.v1.cri".registry] 节下)
python3 - <<'PY'
import re
p = "/etc/containerd/config.toml"
with open(p) as f: s = f.read()
s = re.sub(
    r'(\[plugins\."io\.containerd\.grpc\.v1\.cri"\.registry\][^\[]*?config_path\s*=\s*)"[^"]*"',
    r'\1"/etc/containerd/certs.d"', s, flags=re.DOTALL)
open(p, "w").write(s)
PY

systemctl restart containerd

ccr.ccs.tencentyun.com 的凭证文件 /root/.docker/config.json(docker-ce 时代留下的)nerdctl 会自动读取,无需再配。

E.3 挂载云端 NFS

apt-get install -y -qq nfs-common
mkdir -p /data/nfs
mount -t nfs -o vers=4,rsize=1048576,wsize=1048576,hard,timeo=600,intr \
  47.114.127.214:/data/nfs /data/nfs

# 持久化(autofs 风格,断网不阻塞开机)
echo "47.114.127.214:/data/nfs /data/nfs nfs vers=4,rsize=1048576,wsize=1048576,hard,timeo=600,intr,_netdev,noauto,x-systemd.automount,x-systemd.idle-timeout=600 0 0" >> /etc/fstab

# /data/k8s 软链到 NFS(与云端结构一致,cube-studio hostPath 可直接跨端共享)
if [ -d /data/k8s ]; then mv /data/k8s /data/k8s.local.bak.$(date +%s); fi
ln -sf /data/nfs/k8s /data/k8s

验证

echo "hello from edge $(date)" > /data/nfs/edge-test.txt   # 边缘写
ssh root@47.114.127.214 cat /data/nfs/edge-test.txt        # 云端读

E.4 keadm join

KEADM_VER=1.23.0
wget -q https://gh-proxy.com/https://github.com/kubeedge/kubeedge/releases/download/v${KEADM_VER}/keadm-v${KEADM_VER}-linux-amd64.tar.gz -O /tmp/keadm.tar.gz
mkdir -p /tmp/keadm-extract
tar -xzf /tmp/keadm.tar.gz -C /tmp/keadm-extract
cp $(find /tmp/keadm-extract -name keadm -type f | head -1) /usr/local/bin/keadm

TOKEN=<从云端 keadm gettoken 取>
keadm join \
  --token=$TOKEN \
  --cloudcore-ipport=47.114.127.214:10000 \
  --kubeedge-version=v1.23.0 \
  --remote-runtime-endpoint=unix:///run/containerd/containerd.sock \
  --cgroupdriver=systemd \
  --edgenode-name=edge-pc

E.5 edgecore.yaml 开关

# edgeStream(让 kubectl logs/exec 穿透到边缘 pod)
# 只改 edgeStream 段里的 enable,别用全局 sed 否则会把 websocket/quic 也误动
python3 - <<'PY'
p = "/etc/kubeedge/config/edgecore.yaml"
lines = open(p).readlines()
out, in_es = [], False
indent_es = None
for L in lines:
    s = L.lstrip()
    ind = len(L) - len(s)
    if s.startswith("edgeStream:"):
        in_es = True; indent_es = ind; out.append(L); continue
    if in_es:
        if ind <= indent_es and s.strip() and not s.startswith("#"):
            in_es = False
        elif s.startswith("enable:"):
            L = " " * ind + "enable: true\n"; in_es = False
    out.append(L)
open(p, "w").writelines(out)
PY

systemctl restart edgecore
systemctl is-active edgecore

metaServer(让边缘本地能 kubectl get)按需打开 modules.metaManager.metaServer.enable: true。改 edgecore.yaml 切忌全局 sed 's/false/true/g'websocket.enablequic.enable 互斥,详见 07-踩坑速查

E.6 装 CNI 插件(⭐ 四大坑之 2,必做)

keadm join 安装的 edgecore 只放了 flannel 一个 CNI 插件/opt/cni/bin/。pod 启动时需要 loopback(必需)、bridgehost-localportmap 等基础插件,否则报 failed to find plugin "loopback" in path [/opt/cni/bin],pod sandbox 建不起来、永远 NotReady / ContainerCreating。

mkdir -p /opt/cni/bin
wget https://gh-proxy.com/https://github.com/containernetworking/plugins/releases/download/v1.4.0/cni-plugins-linux-amd64-v1.4.0.tgz -O /tmp/cni.tgz
tar -xzf /tmp/cni.tgz -C /opt/cni/bin
ls /opt/cni/bin   # 应有 loopback、bridge、host-local、portmap、flannel 等 20 个左右
systemctl restart edgecore

E.7 修 kube-proxy kubeconfig(⭐ 四大坑之 3,必做)

kubeadm init 生成的 kube-proxy configmap 里 server: https://<私网 IP>:6443。边缘在公网连不到私网 IP,kube-proxy 一直 i/o timeoutservice iptables 规则永远不会同步到边缘节点。后果:

  • ClusterIP 在边缘上 curl 不通;
  • CubeStudio 给边缘 service 配的 externalIPs=[<边缘内网IP>] + NodePort 在边缘节点上也不通(依赖 edge 上 kube-proxy 建规则)。

修复(云端执行):

# 前提:kubeadm init 时必须加 --apiserver-cert-extra-sans=<公网 IP>(Phase A 已有),否则证书不匹配
kubectl get cm -n kube-system kube-proxy -o yaml | \
  sed 's|server: https://172.30.159.159:6443|server: https://47.114.127.214:6443|' | \
  kubectl apply -f -

# 删边缘节点上的 kube-proxy pod 让它拿新配置
kubectl delete pod -n kube-system -l k8s-app=kube-proxy --field-selector spec.nodeName=edge-pc --grace-period=0 --force

# 验证(边缘节点上):应 > 0,说明 service 规则已同步
sudo iptables-save | grep -c KUBE-SVC

E.8 云端标签 + 验证

# ⚠️ 注意: node-role.kubernetes.io/edge 必须空值(= 后面不要跟任何东西),见 06 节最大坑
kubectl label node edge-pc \
  serving=true cpu=true org=edge \
  node-role.kubernetes.io/edge= \
  notebook=true gpu=false --overwrite

kubectl rollout restart deployment cloudcore -n kubeedge   # 让 cloudcore 重读 EdgeNodes

kubectl get nodes -o wide
# 期望:edge-pc Ready, containerd://1.7.3, v1.32.x-kubeedge-v1.23.0

七、Phase F:CubeStudio 项目组配置

F.1 minio 暴露 NodePort

workflow artifacts 需要 pipeline 任务跨网访问 minio:

kubectl patch svc minio -n kubeflow -p \
  '{"spec":{"type":"NodePort","ports":[
    {"name":"http","port":9000,"targetPort":9000,"nodePort":30900,"protocol":"TCP"},
    {"name":"http-console","port":9001,"targetPort":9001,"nodePort":30901,"protocol":"TCP"}]}}'

# workflow-controller-configmap 里的 minio endpoint 改公网
kubectl get cm -n kubeflow workflow-controller-configmap -o yaml | \
  sed "s|endpoint: minio.kubeflow:9000|endpoint: 47.114.127.214:30900|" | \
  kubectl apply -f -

这一步对应旧文档 边缘集群方式部署平台.md 里的部署流程要点:"修改 kubeflow/workflow-controller-configmap,把 endpoint: minio.kubeflow:9000 改为 minio 的公网 IP 端口"——因为边缘网络的任务无法通过 k8s 服务名访问中心网络的 minio。

F.2 Web 配置边缘项目组

登录 http://47.114.127.214/ → 项目管理 → 新增"边缘项目组":

  • SERVICE_EXTERNAL_IP:填边缘节点的内网 IP(如 192.168.3.22),边缘网内用户能用此 IP:NodePort 访问;
  • 挂到该组的 notebook / pipeline 任务 / 推理服务会自动打 serving=true 等 label,被 scheduler 调度到 edge 节点;
  • ⚠️ 这个 IP 是边缘网内访问用的,公网路径走不通(by-design,见 01-原理与适用场景)。

按调度方向使用:

  • notebook:在边缘服务器上部署的 notebook,会自动使用边缘服务器 IP 打开,所以只有你能访问的边缘服务器才能运行;
  • pipeline:pipeline 本身不需要提供服务,可以在中心网络下查看任务的运行和日志;
  • 推理服务:在边缘服务器上部署的推理服务,会自动使用边缘服务器 IP 打开,同样要求你能访问该边缘服务器。

F.3 代码侧节点选择器位置

  • myapp/models/model_serving.py:51,232:推理默认 cpu=true;serving=true
  • myapp/models/model_job.py:385,742:训练/任务默认 cpu=true;train=true
  • myapp/models/base.py:316get_default_node_selector(按 GPU / org 组装最终 selector)

⚠️ 说明:原文写 model_job.py:188,545,当前代码里 node_selector 默认值实际在 model_job.py:385:742(值仍为 cpu=true;train=true),此处已按代码改正;base.py 函数定义在第 316 行。


下一步:端到端验证与"最大坑 label 硬编码",见 06-验证与最大坑-label硬编码

最后更新 2026-07-04完整文档以官方仓库为准:GitHub Wiki