原文:How to Set Up Kubernetes LoadBalancer Services Without Cloud Provider · 作者 Nawaz Dhandala · 2026-01-19
一、问题是什么
在云上的 Kubernetes(EKS / GKE / AKS)里,把 Service 类型设成 type: LoadBalancer 是件无脑的事:云控制器会自动给你建一个外部负载均衡器,再分配一个公网 IP,Service 几秒内就能拿到 EXTERNAL-IP。
可一旦你跑在裸金属、私有数据中心、或自家 homelab 上——没有云控制器管理器(Cloud Controller Manager),这套机制就失效了。你写一个 type: LoadBalancer 的 Service,它会永远卡在 Pending 状态:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| apiVersion: v1 kind: Service metadata: name: my-app namespace: default spec: type: LoadBalancer ports: - port: 80 targetPort: 8080 protocol: TCP selector: app: my-app
|
1 2 3
| # kubectl get svc my-app # NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE # my-app LoadBalancer 10.96.45.123 <pending> 80:31234/TCP 5m
|
<pending> 永远不会变成 IP。这篇文章就来解决它——在没有云厂商的环境下,怎么把 LoadBalancer Service 真正跑起来。
二、整体架构
所有”裸金属 LoadBalancer”方案,本质上都在干同一件事:给你一个虚 IP(VIP)池,再用某种协议把这个 VIP”广播”到网络里,让外部流量能找到它,再转给集群里的 Pod。
1 2 3 4 5 6 7 8 9 10 11 12 13
| 外部客户端 │ ▼ ┌──────────────┐ 管理 ┌─────────────────┐ │ 虚 IP (VIP) │◀───────────│ LB 控制器 │ │ │ │ (MetalLB/kube-vip)│ └──────────────┘ └────────┬────────┘ │ │ 分配 IP ▼ ▼ ┌──────────────────────────────────────────┐ │ Worker 节点(Speaker / Agent) │ │ 把 VIP 流量按 Service 规则转发给 Pod │ └──────────────────────────────────────────┘
|
差别只在于:用 ARP/NDP(二层) 还是 BGP(三层) 去广播 VIP、是否顺带做控制面高可用、生态成熟度如何。下面四种方案逐一拆解。
MetalLB 是裸金属 LoadBalancer 场景里采用最广的方案,支持两种模式:Layer 2(ARP/NDP)和 BGP。
安装
1 2 3 4 5
| kubectl apply -f https://raw.githubusercontent.com/metallb/metallb/v0.14.5/config/manifests/metallb-native.yaml kubectl wait --namespace metallb-system \ --for=condition=ready pod \ --selector=app=metallb \ --timeout=90s
|
Layer 2 模式配置
适合本地二层网络:用 ARP(IPv4)或 NDP(IPv6)来宣告 VIP。每个 VIP 在某一时刻只有一个节点当”leader”负责响应。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| --- apiVersion: metallb.io/v1beta1 kind: IPAddressPool metadata: name: default-pool namespace: metallb-system spec: addresses: - 192.168.1.240-192.168.1.250 --- apiVersion: metallb.io/v1beta1 kind: L2Advertisement metadata: name: default-l2-advertisement namespace: metallb-system spec: ipAddressPools: - default-pool
|
BGP 模式配置
规模更大时用 BGP 模式:把 VIP 宣告给一台支持 BGP 的路由器,由路由器做流量分发,负载分布更均匀。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| --- apiVersion: metallb.io/v1beta1 kind: IPAddressPool metadata: name: bgp-pool namespace: metallb-system spec: addresses: - 10.100.0.0/24 --- apiVersion: metallb.io/v1beta1 kind: BGPAdvertisement metadata: name: bgp-advertisement namespace: metallb-system spec: ipAddressPools: - bgp-pool --- apiVersion: metallb.io/v1beta2 kind: BGPPeer metadata: name: router-peer namespace: metallb-system spec: peerAddress: 192.168.1.1 peerASN: 64512 myASN: 64513
|
1 2 3 4 5
| kubectl create deployment nginx --image=nginx --replicas=3 kubectl expose deployment nginx --port=80 --type=LoadBalancer kubectl get svc nginx
curl http://192.168.1.240
|
四、方案 2:kube-vip
kube-vip 同时提供控制面高可用和 LoadBalancer Service 两种能力,因此在 kubeadm 自建集群里很受欢迎——一个组件把控制面 VIP 和 Service VIP 都管了。
安装
1 2 3 4 5 6 7 8 9 10 11 12
| kubectl apply -f https://kube-vip.io/manifests/rbac.yaml kubectl apply -f https://raw.githubusercontent.com/kube-vip/kube-vip-cloud-provider/main/manifest/kube-vip-cloud-controller.yaml
cat <<EOF | kubectl apply -f - apiVersion: v1 kind: ConfigMap metadata: name: kubevip namespace: kube-system data: range-global: 192.168.1.200-192.168.1.210 EOF
|
Cloud Provider 与 DaemonSet 清单
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| apiVersion: apps/v1 kind: Deployment metadata: name: kube-vip-cloud-provider namespace: kube-system spec: replicas: 1 selector: matchLabels: app: kube-vip-cloud-provider template: metadata: labels: app: kube-vip-cloud-provider spec: serviceAccountName: kube-vip-cloud-controller containers: - name: kube-vip-cloud-provider image: ghcr.io/kube-vip/kube-vip-cloud-provider:v0.0.12 imagePullPolicy: Always tolerations: - key: node-role.kubernetes.io/control-plane effect: NoSchedule
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| apiVersion: apps/v1 kind: DaemonSet metadata: name: kube-vip-ds namespace: kube-system spec: selector: matchLabels: app: kube-vip-ds template: metadata: labels: app: kube-vip-ds spec: serviceAccountName: kube-vip hostNetwork: true containers: - name: kube-vip image: ghcr.io/kube-vip/kube-vip:v1.0.4 args: - manager env: - name: vip_servicesinterface value: "eth0" - name: svc_enable value: "true" - name: svc_leasename value: plndr-svcs-lock - name: vip_arp value: "true" - name: vip_leaderelection value: "true" securityContext: capabilities: add: - NET_ADMIN - NET_RAW
|
kube-vip 接 BGP
只需修改 DaemonSet 的环境变量:关掉 ARP(vip_arp=false),打开 BGP 相关配置(router ID、AS 号、peer 地址、peer AS)即可。
五、方案 3:PureLB
PureLB 是个较新的替代品,主打简单和原生 Kubernetes 集成。
1 2 3
| helm repo add purelb https://purelb.io/charts helm repo update helm install purelb purelb/purelb --namespace purelb-system --create-namespace
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| apiVersion: purelb.io/v2 kind: ServiceGroup metadata: name: default namespace: purelb-system spec: local: v4pool: subnet: 192.168.1.0/24 pool: 192.168.1.230-192.168.1.250 aggregation: default --- apiVersion: purelb.io/v2 kind: LBNodeAgent metadata: name: default namespace: purelb-system spec: local: localInterface: default dummyInterface: kube-lb0
|
六、方案 4:OpenELB(面向裸金属和私有环境)
OpenELB(前身 PorterLB)专门为裸金属、边缘、私有环境设计,中文社区文档相对友好。
1 2
| wget https://raw.githubusercontent.com/openelb/openelb/release-0.6/deploy/openelb.yaml kubectl apply -f openelb.yaml
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| apiVersion: network.kubesphere.io/v1alpha2 kind: Eip metadata: name: default-pool spec: address: 192.168.1.200-192.168.1.220 protocol: layer2 interface: eth0 disable: false --- apiVersion: network.kubesphere.io/v1alpha2 kind: BgpConf metadata: name: default spec: as: 64513 routerId: 192.168.1.100 listenPort: 17900 --- apiVersion: network.kubesphere.io/v1alpha2 kind: BgpPeer metadata: name: router spec: conf: neighborAddress: 192.168.1.1 peerAs: 64512
|
七、怎么选:一张表 + 一棵决策树
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| 要搞裸金属 LoadBalancer? │ ▼ 小集群 / 简单场景? │ ┌────┴────┐ ▼ ▼ 需要控制面高可用? 不需要 │ │ ▼ ▼ kube-vip MetalLB │ ▼ 大规模 + 有 BGP 路由器? │ ┌────┴────────┐ ▼ ▼ 有 没有 │ │ ▼ ▼ MetalLB BGP OpenELB / PureLB(按特性挑)
|
| 特性 |
MetalLB |
kube-vip |
PureLB |
OpenELB |
| 二层模式 |
✅ |
✅ |
✅ |
✅ |
| BGP 模式 |
✅ |
✅ |
✅ |
✅ |
| 控制面高可用 |
❌ |
✅ |
❌ |
❌ |
| 成熟度 |
高 |
中 |
中 |
中 |
| 社区规模 |
大 |
增长中 |
小 |
中 |
| 配置方式 |
CRD |
环境变量 / CRD |
CRD |
CRD |
| 最适合 |
通用场景 |
kubeadm 高可用 |
简单部署 |
裸金属/边缘/私有 |
一句话:通用首选 MetalLB;需要控制面 HA 选 kube-vip;中文环境/边缘场景看 OpenELB;追求极简看 PureLB。
八、生产环境最佳实践
1. 合理预留 IP。 把 IP 池落在文档里,确保不和 DHCP 范围、静态 IP、基础设施 IP 冲突。
2. 用多个 IP 池。 为不同环境(生产 / 预发)建独立池,配合 autoAssign 和 annotation 指定池:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| apiVersion: metallb.io/v1beta1 kind: IPAddressPool metadata: name: production-pool namespace: metallb-system spec: addresses: - 10.0.1.0/24 autoAssign: true --- apiVersion: v1 kind: Service metadata: name: my-app annotations: metallb.io/address-pool: production-pool spec: type: LoadBalancer
|
3. 指定固定 IP。 为了 DNS、防火墙规则可预期,用 annotation 请求具体 IP:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| apiVersion: v1 kind: Service metadata: name: api-gateway annotations: metallb.io/loadBalancerIPs: 192.168.1.240 spec: type: LoadBalancer ports: - port: 443 targetPort: 8443 selector: app: api-gateway
|
4. 监控负载均衡器。 用 Prometheus + PodMonitor 抓取关键指标:
1 2 3 4 5 6 7 8 9 10 11 12 13
| apiVersion: monitoring.coreos.com/v1 kind: PodMonitor metadata: name: metallb namespace: metallb-system spec: selector: matchLabels: app: metallb podMetricsEndpoints: - port: monitoring interval: 30s
|
重点关注的指标:metallb_bgp_session_up、metallb_allocator_addresses_in_use_total、metallb_allocator_addresses_total。
5. 处理好故障转移。 Layer 2 模式下,leader 节点挂掉会有短暂中断。要最小化这个抖动,就用 BGP + ECMP——从所有节点同时宣告(需要路由器支持 ECMP)。
九、故障排查
Service 一直 Pending: 检查 MetalLB Pod 健康状态、controller 日志、IPAddressPool 容量;用 arping 查是不是有 IP 冲突。
访问不到 LoadBalancer IP: 看 speaker 日志确认是哪个节点在宣告这个 IP;检查 EndpointSlices、Pod 是否就绪;注意二层模式下客户端必须在同一网段。
BGP 会话建不起来: 检查 BgpPeer 资源和 speaker 日志、确认到路由器的网络连通性。常见原因:防火墙挡了 TCP 179 端口、AS 号写错、路由器根本没配 BGP 邻居。
十、结语
没有云厂商,照样能跑出健壮的 Kubernetes LoadBalancer。
- 同网段、图省事,从 MetalLB Layer 2 起步;
- 要真正的负载分发和更大规模,升级到 BGP;
- 规划好 IP 分配,避免冲突;
- 持续监控,防止 IP 耗尽和意外中断。
把这套搞明白,你的裸金属集群在对外暴露服务这件事上,就和云上没什么两样了。