没有云厂商,Kubernetes 的 LoadBalancer 怎么办?四种裸金属方案横向对比

原文: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
# service-loadbalancer.yaml
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、是否顺带做控制面高可用、生态成熟度如何。下面四种方案逐一拆解。

三、方案 1:MetalLB(最主流)

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
# metallb-config-l2.yaml
---
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
# metallb-config-bgp.yaml
---
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

验证 MetalLB

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
# 期望输出:EXTERNAL-IP 分配为 192.168.1.240
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
# kube-vip-cloud-controller.yaml
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
# kube-vip-daemonset.yaml
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
# purelb-config.yaml
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
# openelb-config.yaml
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
# metallb-multi-pool.yaml
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
# service-specific-ip.yaml
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
# podmonitor-metallb.yaml
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_upmetallb_allocator_addresses_in_use_totalmetallb_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 耗尽和意外中断。

把这套搞明白,你的裸金属集群在对外暴露服务这件事上,就和云上没什么两样了。


没有云厂商,Kubernetes 的 LoadBalancer 怎么办?四种裸金属方案横向对比
https://www.boer.xyz/posts/k8s-loadbalancer-without-cloud/
作者
boer
发布于
2026年7月29日
许可协议