ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

KubeEdge边缘节点服务暴露:MetalLB负载均衡器部署实践

KubeEdge边缘节点服务暴露:MetalLB负载均衡器部署实践

简介:面向Kubeedge初学者的完整部署资源包,围绕CentOS 7.9、Kubernetes v1.22.17与Kubeedge v1.13.1交付全套集成安装素材。除了覆盖kubeadm建集群、Kubeedge v1.13.1安装,还详细演示了采用MetalLB负载均衡器将边缘服务以LoadBalancer类型对外暴露的方式,解决边缘节点网络接入与实际流量调度问题。压缩包共15个文件,约474.64MB:6个tar镜像包内置coreedge、kubeedge、nginx、calico、metallb等离线镜像,7个yaml配置文件对应网络插件、MetalLB组件、地址池和负载均衡测试实例,另有keadm工具gz包与一份完整部署md文档。已有242人学习下载。从环境准备、集群创建、边缘节点接入到MetalLB配置、实例验证,各环节均有对应素材,读者可按文档顺序核对执行,大幅缩短环境搭建和问题排查时间,尤其适合K8s初级运维、边缘计算研究者以及正在尝试Kubeedge+LoadBalancer方案的开发者。

1. KubeEdge装上以后,服务IP才是最后一公里

边缘节点上的服务到底怎么暴露 IP?这是很多人部署完 KubeEdge 后第一个卡住的地方。NodePort 端口不规整,hostNetwork 又没法在多副本间做负载均衡,内网设备的接入地址漂来漂去,运维想固定一个 VIP 都难。这篇记录的是 CentOS 7.9 + K8s v1.22.17(kubeadm 方式)+ KubeEdge v1.13.1 + MetalLB 负载均衡器的一条完整部署链路,目标就是让边缘服务拿到稳定、可预期的 LoadBalancer 地址。压缩包里带了镜像文件、全套 yaml 和一份部署文档,离线和内网环境都能照着重现。我完整走了一遍,确实能跑通,但中间几个坑很隐蔽,这篇把参数、验证方法和踩坑点都留出来。

2. 先把 K8s v1.22.17 集群装扎实:kubeadm 初始化与 Calico 网络

2.1 系统准备:CentOS 7.9 的内核与内核参数

CentOS 7.9 默认内核是 3.10,跑 K8s v1.22.17 能启动,但 iptables 的 nf_conntrack 模块经常报错,尤其是桥接流量过滤那一环,一不注意 Pod 之间的通信就时好时坏。我一般先把内核升到 5.4 以上,再关 swap,最后调内核参数。

swapoff -a sed -i '/ swap / s/^/#/' /etc/fstab
cat <<EOF > /etc/sysctl.d/k8s.conf net.bridge.bridge-nf-call-iptables = 1 net.bridge.bridge-nf-call-ip6tables = 1 net.ipv4.ip_forward = 1 vm.swappiness = 0 EOF sysctl --system

说明:br_netfilter 模块必须先加载,否则 iptables 规则不会作用到 bridge 流量,后续 Calico 的转发链会拦不住包。顺手执行modprobe br_netfilter更稳妥。sysctl 里ip_forward一定要开,MetalLB 做 L2 通告时宿主机要转发到达 VIP 的报文。

然后是 kubeadm、kubelet、kubectl,版本必须钉死在 1.22.17:

cat <<EOF > /etc/yum.repos.d/kubernetes.repo [kubernetes] name=Kubernetes baseurl=<你的镜像源>/kubernetes/yum/repos/kubernetes-el7-x86_64/ enabled=1 gpgcheck=0 EOF yum install -y kubelet-1.22.17 kubeadm-1.22.17 kubectl-1.22.17 systemctl enable --now kubelet

说明:baseurl 里的镜像源地址按实际可用的内网镜像站替换,gpgcheck 设 0 是为了跳过签名校验,离线环境更省事。三个 RPM 版本必须对齐,kubelet 版本低于 apiserver 会有一堆兼容告警,高版本又可能握手失败,踩过一回就长记性了。

2.2 镜像预载:离线包里的 image.tar

压缩包里 image.tar 包含 pause、etcd、kube-apiserver、kube-controller-manager、kube-scheduler、kube-proxy、coredns 这些基础镜像。在线环境可以直接kubeadm config images pull,内网环境就必须手动导入。

docker load < image.tar

说明:默认 CRD 用 docker 的情况下,docker load导入后镜像对 kubelet 直接可见。确认几个关键镜像在不在:

docker images | grep -E "kube-apiserver|etcd|coredns"

注意:如果控制面用 containerd,docker load导入的镜像对 containerd 不可见,得用ctr -n=k8s.io images import换一条路。我这里统一用 docker,包括 KubeEdge 边缘端也用 docker,避免两套运行时切换引入额外的排错变量。

2.3 kubeadm init:定好 Pod 网段与 API 地址

主节点(云端节点)初始化命令:

kubeadm init \ --apiserver-advertise-address=192.168.1.10 \ --kubernetes-version=v1.22.17 \ --pod-network-cidr=10.244.0.0/16 \ --service-cidr=10.96.0.0/12

参数说明:--apiserver-advertise-address必须写成云端节点内网 IP,千万别写成公网地址或者注释掉,否则 kubelet、kubectl、以及后面的 KubeEdge CloudCore 全会连不上 apiserver。--pod-network-cidr用 10.244.0.0/16,因为后面 Calico 的 yaml 默认按这个网段配置,两边不一致会让 Pod 一直 ContainerCreating。

初始化完成后配置 kubectl:

mkdir -p $HOME/.kube cp -i /etc/kubernetes/admin.conf $HOME/.kube/config kubectl get nodes

说明:admin.conf 如果权限不对,kubectl 会直接报 connection refused,最常见的是 chown 没做。复制完把属主改成当前用户,chown $(id -u):$(id -g) $HOME/.kube/config一步到位。

2.4 Calico 网络:为什么这里是必选项

压缩包里的 calico.yaml 是手工套件版,应用前先把 Pod 网段改成和 init 时一致的:

sed -i 's#192.168.0.0/16#10.244.0.0/16#' calico.yaml kubectl apply -f calico.yaml

说明:Calico 默认 IP 池是 192.168.0.0/16,和 kubeadm init 里的 10.244.0.0/16 对不上时,calico-node 的 IPIP 隧道路由会错乱。改成一致后,等 calico-node、coredns 都 Running 再继续:

kubectl get pods -n kube-system -o wide

为什么选 Calico 而不是 Flannel:KubeEdge 边缘端很多场景是跨网段组网,Flannel 的 VXLAN 在跨二层时需要额外维护 VTEP,麻烦。Calico 的 BGP + IPIP 对三层网络穿透更友好,边缘节点分布在不同内网段时,收敛也更快。这一步不 Ready,后面的 CloudCore 没法正常注册边缘节点。

3. KubeEdge v1.13.1 部署:CloudCore 与 EdgeCore 配对全过程

3.1 准备证书与 Token:keadm genCertScript 的用法

CloudCore 和 EdgeCore 之间走 HTTPS WebSocket,v1.13.1 默认端口是 10002,需要 CA 证书和 token。keadm 提供了证书生成脚本:

tar xf keadm-v1.13.1-linux-amd64.tar.gz cd keadm-v1.13.1-linux-amd64 ./keadm genCertScript --cloudcoreIP=192.168.1.10 ls /etc/kubeedge/certs/

说明:--cloudcoreIP必须写云端节点的内网 IP,证书里的 SAN 就是它。后续 EdgeCore 连接时校验证书域名,这里写错的话,WebSocket 握手会报 x509 错误,现象极其隐蔽,日志里只一行 certificate error。

Token 生成:

./keadm token create

说明:v1.13.1 的 token 是 CA 签发的临时凭证,EdgeCore 加入时需要带上。每个 token 默认有效期较短,如果隔天再 join,token 已经过期,直接 regenerate。旧版 keadm 生成的 token 在 v1.13.1 上不通用,版本对齐这条贯穿整个部署过程。

3.2 CloudCore 安装:components.yaml 与 coreedge.tar

压缩包里的 coreedge.tar 是 CloudCore 和边缘组件的镜像合集:

docker load < coreedge.tar kubectl create ns kubeedge kubectl apply -f components.yaml

说明:components.yaml 里定义 CloudCore 的 Deployment、Service 和 RBAC。关键参数在 Deployment 环境变量里的 advertiseAddress,必须改成 192.168.1.10,Service 默认 ClusterIP,端口 10002。v1.13.1 的 CloudCore 默认监听 HTTPS 10002,不是旧版的 HTTP 10000。

验证 CloudCore 起来没有:

kubectl get pods -n kubeedge kubectl logs -n kubeedge -l k8s-app=kubeedge

日志里看到 CloudCore 启动成功的标志性输出就算是活了。如果一直 CrashLoopBackOff,先看镜像是启动脚本缺参数,还是 advertiseAddress 配置没生效。

3.3 EdgeCore 安装:边缘端节点加入

边缘节点上导入 kubeedge.tar,然后执行 join:

tar xf kubeedge.tar ./keadm join \ --cloudcore-ipport=192.168.1.10:10002 \ --token=<token> \ --edgenode-name=edge-node-01

说明:--edgenode-name建议显式指定,不指定会用 hostname,内网机器 hostname 一旦重复,两个边缘节点会互相把对方踢下线。--skip-check-certificates这个参数不建议加,除非你完全确认证书没问题,否则等于让安全校验形同虚设。

EdgeCore 启动后确认服务状态:

systemctl status edgecore journalctl -u edgecore -f

日志能看到 WebSocket 连接建立成功,随后边缘节点信息上报到云端。如果连接一直在 retry,大概率是 token 过期或 10002 端口不通,第 5 章展开说。

3.4 云端确认节点状态

kubectl get nodes -o wide

看到边缘节点状态 Ready 后,打个标签方便管理:

kubectl label node edge-node-01 node-role.kubernetes.io/edge=

说明:这个标签让控制面把边缘节点和云端节点区分开。KubeEdge 调度器会优先考虑打了 edge 标签的节点,方便后续把工作负载放到边缘侧。手动打标签是为了面板更清晰,不打也不影响调度,但建议养成习惯。边缘节点如果一直 NotReady,先回头看 edgecore 的 WebSocket 日志,不要急着重置。

4. MetalLB 负载均衡器:给边缘服务分配稳定的 VIP

4.1 部署 MetalLB:metallb-native.yaml 与 IP 池

MetalLB 是裸机环境下常用的负载均衡器实现,通过 ARP 把 LoadBalancer IP 通告到二层网络。压缩包里 metallb-native.yaml 是 v0.13 之后的 CRD 版本:

docker load < metallb_image.tar kubectl apply -f metallb-native.yaml

说明:v0.13 之后 MetalLB 把 IP 池配置从 ConfigMap 换成了 CRD,所以压缩包里多了 first-ipaddresspool.yaml 和 l2-forward.yaml。先确认 controller 和 speaker 两个组件都起来了:

kubectl get pods -n metallb-system

如果 speaker pod CrashLoopBackOff,绝大多数是接口参数或 memberlist 密钥问题,具体在第 5 章。接着配置 IP 池:

kubectl apply -f first-ipaddresspool.yaml kubectl apply -f l2-forward.yaml

first-ipaddresspool.yaml 的基础内容:

apiVersion: metallb.io/v1beta1 kind: IPAddressPool metadata: name: edge-pool namespace: metallb-system spec: addresses: - 192.168.1.200-192.168.1.210

说明:地址段必须和边缘节点在同一个二层网络,MetalLB 的 L2 模式靠 ARP 应答来认领 VIP,跨三层是通告不通的。IP 范围要避开 DHCP 地址池,不然局域网里其他设备可能被误分配这个 IP,冲突之后流量就乱了。

l2-forward.yaml 是 L2Advertisement:

apiVersion: metallb.io/v1beta1 kind: L2Advertisement metadata: name: edge-l2 namespace: metallb-system spec: ipAddressPools: - edge-pool

说明:L2Advertisement 不指定 interfaces 时,会在所有网卡上应答 ARP。生产环境建议加上spec.interfaces: [ens33],避免多网卡机器上 VIP 被内网其他网段"截胡"。

提示:这个 l2-forward.yaml 虽然名字像转发策略,其实是 L2Advertisement。只建 IP 池不应用通告配置,LoadBalancer 会一直 Pending。

4.2 用 nginx 测试 LoadBalancer

压缩包里的 nginx.yaml 和 nginx-loadbalancer.yaml 是现成的测试样例:

kubectl apply -f nginx.yaml kubectl apply -f nginx-loadbalancer.yaml

nginx-loadbalancer.yaml 里的 Service 定义:

apiVersion: v1 kind: Service metadata: name: nginx-lb spec: type: LoadBalancer selector: app: nginx ports: - port: 80 targetPort: 80

应用后查看分配结果:

kubectl get svc nginx-lb

看到 EXTERNAL-IP 出现 192.168.1.200,说明 MetalLB 分配成功。在局域网内任意一台机器上:

curl http://192.168.1.200

能返回 nginx 默认页,整条链路就通了。这一步验证的不只是 MetalLB,还包括 Service 到 Endpoint 的转发、以及边缘节点上的 kube-proxy 规则是否生效。

4.3 为什么选 LoadBalancer:NodePort 与 hostNetwork 的边界

给边缘服务选暴露方式,我的判断依据:

方式端口副本扩展VIP 稳定性
NodePort30000-32767 固定范围同节点端口冲突端口不标准
hostNetwork宿主机端口同 IP 无法多副本依赖宿主机 IP
LoadBalancer+MetalLB标准 80/443无端口冲突固定 VIP

NodePort 在 v1.22.17 下默认范围是 30000-32767,客户端要记一个随机高位端口,防火墙上漏放一条规则就全部访问失败。hostNetwork 把 Pod 和宿主机网络混在一起,多副本时端口必然冲突,边缘节点 IP 变动之后服务地址也跟着漂。MetalLB 给的 VIP 是稳定的,对边缘网关、数据采集服务这种需要固定回调地址的场景很重要。这个项目用 nginx 做验证很合理,因为它同时验证了流量从 VIP 到 Service 再到 Pod 的完整转发链。

5. 避坑排查:五条血泪记录

5.1 kubeadm init 卡在 waiting for kubelet

现象:kubeadm init 执行后一直卡在 waiting for kubelet,几分钟后报错退出。

原因:CentOS 7.9 默认内核 3.10 缺 br_netfilter,docker 的 iptables 规则没生效;也可能是 kubelet 的 cgroup 驱动与 docker 不一致。

解决:先modprobe br_netfilter,再检查 kubelet 配置里 cgroup 驱动:

echo 'KUBELET_KUBEADM_ARGS="--cgroup-driver=systemd"' > /etc/sysconfig/kubelet systemctl daemon-reload && systemctl restart kubelet kubeadm reset

重新 init 就能过。这个坑几乎每个新环境都会踩一次,先把内核模块和 cgroup 对齐,后面省一半排错时间。

5.2 EdgeCore 连接 CloudCore 报 10002 拒绝

现象:keadm join 后,edgecore 日志反复出现 failed to connect to wss://192.168.1.10:10002。

原因:CloudCore 的 advertiseAddress 还是默认值,或者 kubeedge 命名空间下没有对应的 Service,10002 端口根本没有服务在监听。

解决:检查 CloudCore 的 Service 和 Deployment 配置:

kubectl get svc -n kubeedge kubectl edit -n kubeedge deployments.apps cloudcore

把 advertiseAddress 改成 192.168.1.10 后重新 apply。同时确认云端节点防火墙放行 10002 端口。注意 v1.13.1 的 CloudCore 是 HTTPS 10002,别在别的地方开着 10000 凑合用,协议和端口都得对上。

5.3 MetalLB speaker Pod CrashLoopBackOff:memberlist 加密密钥

现象:metallb-speaker 日志报 cannot join memberlist,反复重启。

原因:v0.13+ 默认启用 memberlist 加密,而 metallb-native.yaml 如果是从旧 ConfigMap 版本拼凑过来的,memberlist secret 缺失或不完整。

解决:删掉现存的 memberlist secret 重新应用:

kubectl delete secret memberlist -n metallb-system kubectl apply -f metallb-native.yaml

这个坑经常在复用旧 yaml 时触发,直接删 secret 让 MetalLB 重新生成,是最快的后悔药。

5.4 LoadBalancer 一直 Pending

现象:svc 状态停在 ExternalIP= ,describe 也看不到明显报错。

原因:IPAddressPool 没建、L2Advertisement 没应用,或者地址段和节点根本不在同一二层网络。

解决:

kubectl get ipaddresspool -n metallb-system kubectl get l2advertisement -n metallb-system

确认池子和通告配置都在。如果池子存在但始终不分配,看看这个 IP 是不是已经被另一个 Service 占用,MetalLB 同一 VIP 不会重复分配。另外地址段和节点网段不一致时,L2 通告是发不出去的。

5.5 CloudCore 与 EdgeCore 版本不一致导致的握手失败

现象:边缘节点一直 NotReady,edgecore 日志报证书相关错误,或者握手成功后状态又掉线。

原因:混用了 v1.12 和 v1.13.1 的 keadm,证书签名算法和协议细节不匹配。

解决:keadm、CloudCore、EdgeCore 全部统一到 v1.13.1,证书重新生成:

rm -rf /etc/kubeedge/ ./keadm reset ./keadm genCertScript --cloudcoreIP=192.168.1.10

重新 join。版本对齐是 KubeEdge 部署里最容易被忽略的一环,压缩包里既然全套都是 v1.13.1,就别从历史目录翻旧 keadm 出来混用。

补充一条:边缘端系统时间不同步也会导致证书校验失败。装完先同步一次时间,再看要不要排查其他问题,省得绕着证书转半天。

6. 验证矩阵与一键脚本:把 e2e 链路固化

6.1 验证矩阵

链路检查命令期望结果
K8s 集群kubectl get nodes云端节点 Ready
KubeEdgekubectl get nodes -l node-role.kubernetes.io/edge边缘节点 Ready
MetalLBkubectl get pods -n metallb-systemcontroller 和 speaker Running
LoadBalancerkubectl get svc nginx-lbEXTERNAL-IP 是 192.168.1.200
端到端curl http://192.168.1.200返回 nginx 默认页

6.2 一键验证脚本

我习惯把这套检查合并成脚本,每次部署完先跑一遍再交付:

#!/bin/bash # e2e-check.sh echo "== Node Status ==" kubectl get nodes -o wide | awk '{print $1, $2}' echo "== Edge Node ==" kubectl get nodes | grep -i edge || echo "edge node not ready" echo "== MetalLB ==" kubectl get pods -n metallb-system --field-selector=status.phase!=Running echo "== Service VIP ==" kubectl get svc nginx-lb -o jsonpath='{.status.loadBalancer.ingress[0].ip}' echo "" curl -s -o /dev/null -w "HTTP %{http_code}\n" http://$(kubectl get svc nginx-lb -o jsonpath='{.status.loadBalancer.ingress[0].ip}')

逻辑说明:前三段分别检查节点状态、边缘节点是否 Ready、MetalLB 组件是否有非 Running 的 Pod,最后一段从 Service 里取出 EXTERNAL-IP 并做 HTTP 探活。awk 取节点状态用的是第二列,NotReady 会直接显示出来,不用再开一个终端敲命令。

6.3 端到端验收的关键技巧

验证不要只在本机 curl。我会在另一台与边缘节点同网段的机器上,分别测 VIP、NodePort、CloudCore 10002 三条连通性,再回到边缘节点上直接 ping VIP。这种交叉验证能暴露 MetalLB 的 ARP 通告边界,也能看出防火墙规则到底挡没挡 10002。

另一个技巧:多副本场景下,在 Pod 里抓包看源 IP。KubeEdge 边缘 Pod 的流量路径和普通 K8s 集群略有差异,必要时在 nginx Pod 里 tcpdump,确认 SYN 包到达的是 VIP 还是 NodePort 地址,这决定了负载均衡是否真的生效。

从那以后我每次部署完都强制走一遍这套验证流程,版本号这种最基础的参数先用kubectl version和keadm version对齐,再往链路里探。希望帮到你。

本文还有配套的精品资源,点击获取

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进