简介面向初次接触KubeEdge的运维及开发人员这份资源系统整理了CentOS 7.9环境下K8s v1.22.17与KubeEdge v1.13.1的部署全过程并重点展示如何使用MetalLB负载均衡器为边缘应用打通外部访问链路。教程围绕“准备系统、搭建K8s集群、安装KubeEdge、部署MetalLB、测试验证”五步推进适合需要离线安装、边缘节点接入及配置负载均衡的实战场景。压缩包共15个文件包含7个YAML配置文件用于Calico网络、核心组件、MetalLB及IP地址池等、6个离线镜像tar包覆盖metrics-server、nginx、coreedge、kubeedge、metallb等、1份Markdown部署文档和1个keadm工具压缩包整体约474.64MB结构清晰、便于按需取用。已有242人学习借助文档与配套镜像可快速复现完整环境并通过nginx负载均衡示例验证部署结果是一份能帮助新手少走弯路的KubeEdge入门参考资料。1. 装Kubeedge没跑通十有八九是栽在版本组合上Kubeedge的安装命令其实就那么几条真正让人挠头的从来不是命令本身而是云边两侧的版本匹配关系。我在把模拟项目X的边缘节点接入云端集群时第一轮图省事上了最新版K8s结果keadm init直接抛出版本超范围整套集群推倒重来。后来固定为Centos7.9 K8s v1.22.17(kubeadm) Kubeedge v1.13.1这套组合再搭配MetalLB负载均衡器给边缘服务暴露统一入口整条链路才一次性跑通。如果你正在搭边缘集群、又希望外部流量通过LoadBalancer类型Service稳定打到边缘节点这套方案可以直接照着落地。文章按安装顺序完整记录镜像导入、负载均衡器配置和踩坑排查都写在对应章节卡在哪个环节就去哪节找解法。2. 环境准备Centos7.9与K8s v1.22.17的前置配置安装K8s集群最怕的不是版本号写错而是系统层面的开关没关干净。很多边缘节点加入后一直NotReady根子不在Kubeedge而是SELinux和swap没处理kubelet根本起不来。本章先把系统基础配置做完再讲版本选型和kubeadm三个核心组件的安装。2.1 关闭防火墙、SELinux与交换分区新装的Centos7.9最小化系统先把主机名设好。第一台机器作为云端控制节点通常建议hostname设为cloud-node方便后面在kubectl里区分云边节点。hostnamectl set-hostname cloud-node exec bash接着处理三个系统级开关防火墙、SELinux、swap。这三个不关后面会出现各种诡异的连接超时和Pod调度失败。systemctl stop firewalld systemctl disable firewalld sed -i s/^SELINUXenforcing/SELINUXdisabled/ /etc/selinux/config setenforce 0 swapoff -a sed -i / swap / s/^/#/ /etc/fstabSELinux不建议设成permissive虽然permissive只记录不拦截但K8s对目录挂载和端口绑定的行为依赖文件上下文遇到自定义路径时日志会变得很难追直接disabled一劳永逸。swapoff是因为v1.22版本的kubelet默认不开启SwapOn支持不关swap的话节点会反复跳到NotReady状态。fstab注释掉swap条目是为了防止重启后swap自动挂载回来。内核模块和系统参数也要同步处理。K8s要求加载overlay和br_netfilter两个模块这分别关系到容器层文件系统和iptables转发。cat /etc/modules-load.d/k8s.conf EOF overlay br_netfilter EOF modprobe overlay modprobe br_netfilter cat /etc/sysctl.d/k8s.conf EOF net.bridge.bridge-nf-call-iptables 1 net.ipv4.ip_forward 1 net.bridge.bridge-nf-call-ip6tables 1 EOF sysctl --systemnet.bridge.bridge-nf-call-iptables必须设为1否则Calico和kube-proxy在跨节点转发时会丢包现象是Pod能通同节点、但跨节点访问超时。net.ipv4.ip_forward是MetalLB L2模式应答ARP之后做流量转发的基础不打开的话外部访问打到VIP也到不了后端Pod。这些参数对后面云边节点互通都有影响建议所有节点一次性配好。2.2 版本选型Kubeedge v1.13.1 兼容哪些K8s版本版本选型容易被新手跳过但踩过坑的人都知道这是整套环境的地基。Kubeedge v1.13.1官方支持Kubernetes v1.22到v1.24这个区间v1.13之前的老版本只支持到v1.19左右而K8s从v1.25开始移除了PodSecurityPolicy老版本Kubeedge的准入校验逻辑会直接失效。选v1.22.17是一个保守且稳妥的位置v1.22仍在kubeadm默认支持范围内而且v1.13.1对应的云边组件与它验证最充分。CloudCore组件通过list-watch方式连接K8s API Server如果云边版本差距太大watch事件的结构体字段对不上边缘节点要么一直NotReady要么消息到了EdgeCore侧被静默丢弃。这里顺便说一个常见的版本认知误区Kubeedge不是越新越好而是必须匹配你的K8s版本区间。比如你在v1.22集群上硬上Kubeedge v1.14它内部依赖的新版API在这个集群里根本不存在启动就会报资源类型找不到。下表是我整理这个资源包时的版本对照参考Kubernetes版本Kubeedge建议版本主要兼容说明v1.22.xv1.11API字段匹配充分v1.13.1完全覆盖v1.23.xv1.12需关注CRD版本迁移v1.24.xv1.13新版keadm适配良好推荐v1.13.x2.3 安装kubeadm、kubelet、kubectl并固定版本kubeadm、kubelet、kubectl三个二进制必须固定到v1.22.17不能直接yum install最新版。这三个组件版本不一致时最典型的问题是kubelet版本比API Server新初始化之后master节点状态异常查日志又看不出明显报错非常耗时间。yum install -y kubeadm-1.22.17 kubelet-1.22.17 kubectl-1.22.17 systemctl enable --now kubelet执行后kubelet会启动失败这是正常的因为还没有/etc/kubernetes/kubelet.conf等集群初始化完成后会自动恢复正常不用理会。容器运行时这里用的是DockerK8s v1.22还保留对dockershim的支持这个版本选Docker最省心到v1.24之后必须换containerd这跟你的资源包镜像格式也有关系。Docker的yum仓库按常规方式配置好然后安装Docker 20.10.24装完同样设置开机自启。yum install -y docker-ce-20.10.24 systemctl enable --now docker这里补充一个建议如果后续边缘节点数量多给每台节点都预留2核4G内存磁盘至少20G。CloudCore在云端大概占500MB内存边缘侧EdgeCore占用不高但镜像和日志会慢慢涨磁盘留太小说到底还是自己受罪。3. kubeadm初始化集群关键参数与离线镜像导入集群初始化是整个部署过程的分水岭。参数写对、镜像到位后面Calico和Kubeedge一路顺畅参数写错轻则Pod网络不通重则整个控制面起不来。这一章把kubeadm init的参数含义、离线镜像导入和Calico安装按执行顺序拆开讲。3.1 kubeadm init 参数逐个拆解先看完整命令看完再解释不要急着执行kubeadm init \ --kubernetes-versionv1.22.17 \ --control-plane-endpoint192.168.1.10 \ --apiserver-advertise-address192.168.1.10 \ --pod-network-cidr10.244.0.0/16 \ --service-cidr10.96.0.0/12 \ --v5--kubernetes-version固定为v1.22.17防止kubeadm联网探测最新版本在离线环境里这一步会卡在网络查询上。--control-plane-endpoint指向云端控制节点的内网IP单控制节点场景下这行跟advertise-address写同一个IP没有冲突但有了这个参数后续若扩展第二个master只需要把负载均衡端点地址改过来即可。--pod-network-cidr用10.244.0.0/16这是为了跟资源包里calico.yaml的默认IP池保持一致如果这里改成了别的网段后面Calico也要同步修改CALICO_IPV4POOL_CIDR否则两个网段不一致会导致Pod无法跨节点通信。--service-cidr保持默认10.96.0.0/12这个段在MetalLB场景下与外部VIP池不冲突但如果你的局域网正好用了10.96网段就要换掉。初始化输出里最后几行是kubeadm join命令务必保存好。执行完成后设置kubectl的配置文件mkdir -p $HOME/.kube cp /etc/kubernetes/admin.conf $HOME/.kube/config chown $(id -u):$(id -g) $HOME/.kube/config不拷贝admin.conf的话kubectl会提示connection refused因为默认kubeconfig路径下没有可用配置。3.2 离线镜像导入两种运行时下的load与tag矫正kubeadm init会在初始化时自动拉取控制面镜像包括kube-apiserver、kube-controller-manager、kube-scheduler、etcd、kube-proxy、pause等。在纯内网环境这一步会卡住所以要在init之前先把资源包里的镜像tar导入节点。Docker运行时下的导入顺序如下docker load -i image.tar docker load -i metrics-server.tar docker load -i nginx.tar docker load -i coreedge.tar docker load -i kubeedge.tar docker load -i metallb_image.tardocker load执行完用docker images确认镜像列表docker images | grep -E kube-apiserver|kube-controller|kube-scheduler|etcd|kube-proxy|pause这块有一个经常翻车的细节tar包里的镜像tag可能与kubeadm默认引用的tag不一致。比如tar里保存的是kube-apiserver:v1.22.17但kubeadm默认从registry.k8s.io拉取同名镜像如果本地镜像仓库名对不上kubeadm会认为本地不存在该镜像然后继续尝试联网拉取。解决办法是用docker tag把镜像名改成kubeadm期望的全名比如docker tag kube-apiserver:v1.22.17 registry.k8s.io/kube-apiserver:v1.22.17如果哪台节点用的是containerd导入命令要换成ctr注意命名空间参数ctr -n k8s.io image import image.tar crictl imagesctr -n k8s.io是关键。containerd里镜像默认放在default命名空间而kubelet通过CRI接口访问的是k8s.io命名空间漏掉这个参数load完之后kubelet依然报镜像找不到。这是新手在containerd环境里最常见的翻车原因没有之一。3.3 安装Calico网络插件并确认节点Ready控制面初始化成功后安装Calico网络插件kubectl apply -f calico.yamlCalico在这里承担Pod网络职责。Kubeedge要求云边之间的Pod网络互通控制节点和边缘节点都要有可用的CNI否则CloudCore与EdgeCore通过Pod IP通信会失败。apply之后观察状态kubectl get pods -n kube-system -l k8s-appcalico-node kubectl get node如果节点一直NotReady看calico-node日志定位kubectl logs -n kube-system calico-node-pod -n kube-system | grep -i error常见失败原因集中在三个方向pod-network-cidr与Calico默认IP池不一致、节点网卡MTU问题导致BGP握手失败、云主机环境禁用了IPIP隧道。我这个环境里用的就是默认IPIP模式没有额外操作。还有一个可选操作如果希望控制节点也能调度业务Pod执行kubectl taint nodes --all node-role.kubernetes.io/master-不执行也不影响Kubeedge部署CloudCore本身就是以Deployment方式跑在控制面上的keadm init会自动做标签匹配。4. 安装Kubeedge v1.13.1keadm云边协同全流程Kubeedge安装分为云端和边缘两段云端部署CloudCore边缘部署EdgeCore中间通过WebSocket通信。这一章按keadm的实际操作顺序把解压、init、token生成、节点join和调度验证完整串起来。4.1 解压keadm并完成云端组件部署资源包里keadm-v1.13.1-linux-amd64.tar.gz就是安装器本体先解压到控制节点tar -xzf keadm-v1.13.1-linux-amd64.tar.gz cd keadm-v1.13.1-linux-amd64 cp keadm /usr/local/bin/ keadm version确认版本输出为v1.13.1之后执行云端初始化keadm init --advertise-address192.168.1.10 --kube-config/root/.kube/configkeadm init会读取kubeconfig自动在kubeedge命名空间下创建CloudCore的Deployment、Service、ConfigMap和相关RBAC资源。--advertise-address必须填边缘节点能访问到的云端IP也就是控制节点的内网IP不能填127.0.0.1也不能填Pod IP。如果这个地址填错边缘节点join之后会一直显示connecting状态日志里反复报websocket连接失败。查看云端组件状态kubectl get pods -n kubeedge kubectl logs -n kubeedge cloudcore-pod -f正常情况下cloudcore-xxx状态为Running。如果看到CrashLoopBackOff优先排查advertise-address是否正确、kubeedge命名空间下ConfigMap里的配置是否被改动过。keadm在首次init时会自动生成一套自签证书放在/etc/kubeedge目录之后组件以root运行一般不会出现证书权限问题。4.2 生成token并让边缘节点加入云端组件就绪后生成边缘节点接入用的tokenkeadm gettoken --kube-config/root/.kube/config拿到一串UUID格式的token在边缘节点上执行joinkeadm join --cloudcore-ipport192.168.1.10:10000 --token上一步生成的tokenkeadm join会在边缘节点上安装EdgeCore并注册为systemd服务。join过程中会自动探测边缘节点的容器运行时如果你边缘节点上已经装好Docker或containerd它会把运行时参数一并写进EdgeCore配置。join之后查看服务状态systemctl status edgecore journalctl -u edgecore -f边缘节点初期最容易遇到三个问题cloudcore地址不通、token过期、云边时间不同步。地址不通就检查10000端口放通情况token无效就重新gettoken时间不同步则EdgeCore会拒绝对话现象是join命令成功但kubectl get nodes里看不到边缘节点。时间这个问题在虚拟机环境尤其常见Centos7.9默认时钟源可能没配置好边缘节点一定要先做chrony或ntp同步再join后面云边证书校验也依赖时间一致性。4.3 验证边缘节点接入与Pod调度边缘节点join完成后切换到控制节点查看kubectl get nodes正常情况下边缘节点会出现在列表中状态Ready角色列可能为空或显示edge。给边缘节点打上专用标签方便后面调度kubectl label node 边缘节点名 node-role.kubernetes.io/edge然后部署一个测试Pod资源包里的nginx.tar对应的就是这一步用的nginx镜像先用nginx.yaml验证边缘节点的容器能正常拉起来kubectl apply -f nginx.yaml kubectl get pods -o widenginx.yaml里要配置好调度规则通常是nodeSelector指定边缘节点标签或者直接指定nodeName。如果你不想把Pod写死在某个节点可以用标签选择器spec: nodeSelector: node-role.kubernetes.io/edge: 这一步的意义在于确认EdgeCore能正常接收云端下发的Pod创建请求并完成容器生命周期管理。Pod如果一直Pending在边缘节点上执行journalctl -u edgecore -f看具体日志多半是镜像拉取失败或者运行时配置不对。这里跑通的nginx到第5章还要继续作为LoadBalancer的服务后端来用所以不要跳过。5. 部署MetalLB负载均衡器配置、验证与常见问题排查Kubeedge部署完成之后边缘服务要暴露给外部访问就需要一个负载均衡器。云厂商环境可以直接绑定SLB但自建机房或内网环境没有这个条件MetalLB就是用来解决这个问题的。本章安装metallb-native.yaml配置IP地址池和L2通告最后用nginx-loadbalancer.yaml验证LoadBalancer类型Service并在5.4节集中排查最常见的故障。5.1 安装metallb-native.yamlkubectl apply -f metallb-native.yamlmetallb-native.yaml是MetalLB在v0.13及以上版本推荐的部署方式包含了完整命名空间、controller Deployment、speaker DaemonSet和全部RBAC资源。以前老版本用configmap定义配置这个版本则直接使用CRD配置方式更清晰也方便通过kubectl get命令查看状态。查看组件运行状态kubectl get pods -n metallb-system预期看到controller一个Podspeaker在每个节点上各一个Pod全部Running。只要有一个Pod没起来后面的IP分配都会异常。MetalLB有两种运行模式BGP和L2。这里用的是L2模式因为L2不依赖路由器支持只要二层网络能通就行。L2模式下speaker通过ARP应答VIP地址把外部流量吸引到本节点再经过kube-proxy转发到后端Pod。BGP模式需要路由器开启BGP协议普通内网环境不好满足所以这个资源包选择L2是合理的。5.2 创建IP地址池与L2通告创建IP地址池first-ipaddresspool.yamlapiVersion: metallb.io/v1beta1 kind: IPAddressPool metadata: name: first-pool namespace: metallb-system spec: addresses: - 192.168.1.200-192.168.1.250创建L2通告l2-forward.yamlapiVersion: metallb.io/v1beta1 kind: L2Advertisement metadata: name: l2-forward namespace: metallb-system spec: ipAddressPools: - first-pool应用这两个配置kubectl apply -f first-ipaddresspool.yaml kubectl apply -f l2-forward.yamlIP池的网段必须与控制节点所在局域网真实网段一致这很关键。如果IP池里写的地址和实际局域网不在同一二层域ARP广播发不出去VIP永远分配不出来。另外IP池范围要避开DHCP分配区间否则你的VIP和某个真实设备抢IP冲突起来非常难查。5.3 用nginx-loadbalancer.yaml测试LoadBalancer服务前面第4章部署的nginx正好拿来当测试后端现在创建LoadBalancer类型的Servicekubectl apply -f nginx-loadbalancer.yaml kubectl get svc nginx-lb -wnginx-loadbalancer.yaml里的关键定义是type: LoadBalancer它让K8s调用MetalLB的controller分配外部IPapiVersion: v1 kind: Service metadata: name: nginx-lb spec: type: LoadBalancer selector: app: nginx ports: - port: 80 targetPort: 80当EXTERNAL-IP从pending变成192.168.1.200开头的地址时说明IP池分配成功。这时在任意一台能访问该网段的机器上访问curl http://192.168.1.200能看到nginx默认欢迎页就说明整条链路已通。MetalLB的L2模式会把VIP绑定在某一个节点的网卡上你可以在控制节点执行ip addr看192.168.1.200是否出现在某块网卡上。5.4 常见问题排查现象、原因、解决这一节把我在这个环境里实际踩过的坑按现象整理出来每条都按原因和解决路径写清楚。现象一EXTERNAL-IP一直pending这种现象最常见的原因有三个方向IPAddressPool没有创建成功、地址池范围里没有可用地址、L2Advertisement没有匹配到目标地址池。先执行kubectl describe svc nginx-lb看Events再看metallb-controller日志kubectl logs -n metallb-system -l componentcontroller kubectl get ipaddresspool -n metallb-system如果是controller日志里报address pool exhaustion就去确认IP池里是否还有未分配地址如果是CRD对象查不到说明first-ipaddresspool.yaml没有apply成功重新apply后要确认资源类型是IPAddressPool而不是旧版的ConfigMap。现象二VIP能ping通但访问不了服务L2模式下流量到达VIP所在节点后节点需要把流量经kube-proxy转发到后端Pod。最常见的坑是Service配置了externalTrafficPolicy: Local这样kube-proxy只把流量转发到VIP所在节点上的Pod如果该节点没有nginx副本连接就被丢弃。解决办法是把externalTrafficPolicy改成Cluster或确保后端Pod覆盖所有节点。现象三边缘节点上的Pod访问不到VIP这个问题在Kubeedge环境里比纯云节点集群更常见。边缘节点访问VIP时ARP请求到达了speaker所在节点但由于边缘节点的网络路径和控制节点不完全一致返回流量路由可能不对称。一般先检查kube-proxy在边缘节点是否正常运行再看边缘节点到VIP所在节点三层是否icmp通。如果边缘节点本身也运行着speaker这会触发新一轮leader选举流量在一段时间内会抖动。让边缘节点上的应用通过ClusterIP或Service DNS访问后端可以绕开这个场景。现象四节点重启后VIP漂移到了其他节点L2模式本身就是单节点应答当持有VIP的节点宕机或网络断开其他节点的speaker会接管。这让很多第一次用MetalLB的人以为配置被重置了。其实这是设计行为不是故障。如果业务要求VIP固定在某个节点可以给speaker的DaemonSet加nodeSelector限定节点范围但这个操作会降低可用性一般不推荐除非你有明确的网络规划要求。6. 验证跑通负载均衡IP访问边缘应用的完整链路环境全部搭完不代表跑通最后这轮验证要覆盖云、边、外部访问三个维度缺一个都不算闭环。先把完整状态检查一遍kubectl get nodes kubectl get pods -A kubectl get svc -A输出里确认边缘节点Ready、CloudCore和EdgeCore相关Pod都处于Running状态、nginx-lb的EXTERNAL-IP是192.168.1.200网段的地址。然后做端到端访问验证在控制节点上执行curl http://192.168.1.200这一步确认外部流量能经过VIP转发到后端nginx。接着在边缘节点上执行同样的curl确认边缘侧也能访问到该VIP。如果边缘节点访问不通回到5.4节的第三个现象去查kube-proxy状态。再验证边缘应用的调度链路把nginx副本通过nodeSelector固定到边缘节点然后删除重建Pod观察调度结果kubectl delete pod nginx-pod kubectl get pods -o widePod重新调度到边缘节点后再次curl VIP确认后端Pod即使落在边缘节点上也能正常响应。这个测试能同时证明两件事EdgeCore的Pod调度链路是通的MetalLB的流量转发不依赖后端Pod所在位置。最后看一组关键日志确认云边会话健康kubectl logs -n kubeedge cloudcore-pod --tail50 journalctl -u edgecore -n 50 --no-pager两条命令输出的日志里不应该有连续的websocket断连或证书过期报错。看完这套流程整个环境才算真正验收完毕。从那以后我每次部署Kubeedge都会强制走一遍这条链路先看节点列表再看云边核心组件状态最后curl一次LoadBalancer的VIP三步全过才敢交付环境。这套验证习惯帮我挡掉了好几次升级和迁移翻车希望也能帮到你。本文还有配套的精品资源点击获取