MetalLB 使用随记

MetalLB 是使用标准路由协议(ARP/BGP)为 Bare Metal Kubernetes 集群实现的负载平衡器。

MetalLB 的两种模式

Layer2 模式 (ARP)

L2 模式下,MetalLB 会通过 memberlist 选举出一个 Leader 节点,此节点负责向本地网络宣告 LoadBalancerIP。 从网络的角度来看,这台机器似乎有多个 IP 地址,它会响应来自 LoadBancerIP 的 ARP 请求。 L2 模式最大的优势是它不需要依赖譬如路由器等硬件的依赖便可工作。

  • 优势:通用型,不需要额外的硬件支持
  • 缺点:单节点的带宽限制、稍缓慢的故障转移(10s 左右)

Layer3 模式 (BGP)

在 BGP 模式下,集群中的每个节点都会与路由器建立 BGP Peer,并使用该会话向集群外部宣告集群服务的 LoadBalanceIP。 BGP Router 基于每个不同的连接选择一个下一跳(即集群某个节点,这不同于 L2 模式下所有流量先到达某个 Leader 节点)。

Layer2 模式

参考文档:https://metallb.io/installation/

1
2
3
helm repo add metallb https://metallb.github.io/metallb
helm repo update
helm -n metallb-system upgrade --install metallb metallb/metallb --create-namespace
1
2
3
4
5
6
root@test-0:~# kubectl get pod -n metallb-system
NAME READY STATUS RESTARTS AGE
metallb-controller-9bf78f497-twzcz 1/1 Running 0 4m56s
metallb-speaker-db2vv 4/4 Running 0 4m56s
metallb-speaker-kt45g 4/4 Running 0 4m56s
metallb-speaker-pjxm9 4/4 Running 0 4m56s

创建 IP Address Pool

顾名思义,池子中的 IP 就是分配给 Service 使用的 VIP。

默认情况下,MetalLB 会自动分配池子中的 IP,这可能会造成浪费,可以在通过 autoAssign: false 避免这个问题,同时为了避免 .0 和 .255 这样的 IP 被分配,可以将 avoidBuggyIPs 设置为 true

1
2
3
4
5
6
7
8
9
10
11
12
cat <<EOF | kubectl apply -f -
apiVersion: metallb.io/v1beta1
kind: IPAddressPool
metadata:
name: first-pool
namespace: metallb-system
spec:
addresses:
- 172.16.16.0/24
autoAssign: false
avoidBuggyIPs: true
EOF

IP 池也支持指定特定的 Namespace 和 Service 使用:

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
cat <<EOF | kubectl apply -f -
apiVersion: metallb.io/v1beta1
kind: IPAddressPool
metadata:
name: first-pool
namespace: metallb-system
spec:
addresses:
- 172.16.16.0/24
autoAssign: false
avoidBuggyIPs: true
serviceAllocation:
# 权重为 50,value 越小代表权重越高,1 代表最高,0 /没设置 priority 代表最低,优先级相同则随机选择
priority: 50
# IP 可以分配到下面的 Namespace 中,或者包含下面 label 的 Namespace 中
namespaces:
- default
namespaceSelectors:
- matchLabels:
foo: bar
# 在上述匹配的 Namespace 中,匹配标签为 app=nginx 的 Service 才能够分配 IP
serviceSelectors:
- matchExpressions:
- {key: app, operator: In, values: [nginx]}
EOF

创建 L2Advertisement

默认情况下,只有一个节点会被选出来宣告 VIP,这个节点被称为 Leader 节点。

正常情况下所有节点的 Speaker 都可以宣告 VIP,但在某些特殊情况下,VIP 只有在部分节点上能够使用,这时候可以通过使用 L2Advertisement CR 中的 nodeSelector 来实现。

在下面的例子中,first-pool 中的 VIP 只会被 test-1 节点宣告:

1
2
3
4
5
6
7
8
9
10
11
12
13
cat <<EOF | kubectl apply -f -
apiVersion: metallb.io/v1beta1
kind: L2Advertisement
metadata:
name: example
namespace: metallb-system
spec:
ipAddressPools:
- first-pool
nodeSelectors:
- matchLabels:
kubernetes.io/hostname: test-1
EOF

也可以指定网卡:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
cat <<EOF | kubectl apply -f -
apiVersion: metallb.io/v1beta1
kind: L2Advertisement
metadata:
name: example
namespace: metallb-system
spec:
ipAddressPools:
- first-pool
nodeSelectors:
- matchLabels:
kubernetes.io/hostname: test-1
interfaces:
- eth0
EOF

Service 使用 VIP

在 Service 指定 IP Address Pool,会从 Pool 中随机获取 IP 使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
kubectl create deployment nginx --image harbor.warnerchen.com/library/nginx:mainline

cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Service
metadata:
annotations:
metallb.io/ip-allocated-from-pool: first-pool
metallb.universe.tf/address-pool: first-pool
labels:
app: nginx
name: nginx
namespace: default
spec:
ports:
- name: port-80
port: 80
protocol: TCP
targetPort: 80
selector:
app: nginx
type: LoadBalancer
EOF

即可使用 VIP 访问服务:

同时也可以看到在对应节点进行的宣告:

如果要指定 IP,可以通过下面的方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Service
metadata:
annotations:
metallb.io/ip-allocated-from-pool: first-pool
metallb.universe.tf/address-pool: first-pool
metallb.universe.tf/loadBalancerIPs: 172.16.16.201
labels:
app: nginx
name: nginx
namespace: default
spec:
ports:
- name: port-80
port: 80
protocol: TCP
targetPort: 80
selector:
app: nginx
type: LoadBalancer
EOF

IP 也可以被多个 Service 共享使用,通过端口进行区分:

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
39
40
41
42
43
44
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Service
metadata:
annotations:
metallb.io/ip-allocated-from-pool: first-pool
metallb.universe.tf/address-pool: first-pool
metallb.universe.tf/allow-shared-ip: "nginx"
labels:
app: nginx
name: nginx-1
namespace: default
spec:
ports:
- name: port-80
port: 80
protocol: TCP
targetPort: 80
selector:
app: nginx
type: LoadBalancer

---
apiVersion: v1
kind: Service
metadata:
annotations:
metallb.io/ip-allocated-from-pool: first-pool
metallb.universe.tf/address-pool: first-pool
metallb.universe.tf/allow-shared-ip: "nginx"
labels:
app: nginx
name: nginx-2
namespace: default
spec:
ports:
- name: port-81
port: 81
protocol: TCP
targetPort: 80
selector:
app: nginx
type: LoadBalancer
EOF

BGP 模式

Layer2 模式局限在一个二层网络中,流向 Service 的流量都会先转发到某一个特定的节点,这并不算真正意义上的负载均衡。

BGP 模式不局限于一个二层网络,集群中每个节点都会跟 BGP Router 建立 BGP 会话,宣告 Service 的 ExternalIP 的下一跳为集群节点本身。这样外部流量就可以通过 BGP Router 接入到集群内部,BGP Router 每次接收到目的是 LoadBalancer IP 地址的新流量时,它都会创建一个到节点的新连接。但选择哪一个节点,每个路由器厂商都有一个特定的算法来实现。所以从这个角度来看,这具有良好的负载均衡性。

安装配置 FRR

此处使用 FRR 作为 BGP Router,具体操作参考:https://warnerchen.github.io/2025/09/05/%E4%BD%BF%E7%94%A8-FRR-%E6%B5%8B%E8%AF%95-Calico-BGP-%E6%A8%A1%E5%BC%8F/

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
root@test-1:~# vtysh

Hello, this is FRRouting (version 8.1).
Copyright 1996-2005 Kunihiro Ishiguro, et al.

test-1# configure terminal
test-1(config)# router bgp 7675
test-1(config-router)# bgp router-id 172.16.16.141
test-1(config-router)# no bgp ebgp-requires-policy
test-1(config-router)# neighbor 172.16.16.142 remote-as 7776
test-1(config-router)# neighbor 172.16.16.142 description controlplane1
test-1(config-router)# neighbor 172.16.16.143 remote-as 7776
test-1(config-router)# neighbor 172.16.16.143 description worker1
test-1(config-router)# exit
test-1(config)# exit
test-1# exit

root@test-1:~# vtysh -c "show ip bgp summary"

IPv4 Unicast Summary (VRF default):
BGP router identifier 172.16.16.141, local AS number 7675 vrf-id 0
BGP table version 0
RIB entries 0, using 0 bytes of memory
Peers 2, using 1446 KiB of memory

Neighbor V AS MsgRcvd MsgSent TblVer InQ OutQ Up/Down State/PfxRcd PfxSnt Desc
172.16.16.142 4 7776 0 0 0 0 0 never Active 0 controlplane1
172.16.16.143 4 7776 0 0 0 0 0 never Active 0 worker1

Total number of neighbors 2

创建 BGPAdvertisement

BGPAdvertisement 用于指定需要通过 BGP 宣告的地址池,同 L2 模式,可通过池名称或者 labelSelector 筛选。同时可配置 BGP 的一些属性:

1
2
3
4
5
6
7
8
9
10
11
12
cat <<EOF | kubectl apply -f -
apiVersion: metallb.io/v1beta1
kind: BGPAdvertisement
metadata:
name: test
namespace: metallb-system
spec:
# 选择要宣告的地址池
ipAddressPools:
- bgp-pool
aggregationLength: 32
EOF

创建 BGPPeer

BGPPeer 用于与外部 BGP Router 建立 BGP 连接:

1
2
3
4
5
6
7
8
9
10
11
12
cat <<EOF | kubectl apply -f -
apiVersion: metallb.io/v1beta2
kind: BGPPeer
metadata:
name: test
namespace: metallb-system
spec:
myASN: 7776
peerASN: 7675
peerAddress: 172.16.16.141
routerID: 172.16.16.142
EOF

创建完成后,在 BGP Router 查看状态,可以看到此时 State/PfxRcd 还是 0,这是因为还没有 VIP 被宣告:

1
2
3
4
5
6
7
8
9
10
11
12
13
root@test-1:~# vtysh -c "show ip bgp summary"

IPv4 Unicast Summary (VRF default):
BGP router identifier 172.16.16.141, local AS number 7675 vrf-id 0
BGP table version 0
RIB entries 0, using 0 bytes of memory
Peers 2, using 1446 KiB of memory

Neighbor V AS MsgRcvd MsgSent TblVer InQ OutQ Up/Down State/PfxRcd PfxSnt Desc
172.16.16.142 4 7776 4 3 0 0 0 00:00:16 0 0 controlplane1
172.16.16.143 4 7776 4 3 0 0 0 00:00:17 0 0 worker1

Total number of neighbors 2

Service 使用 VIP

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Service
metadata:
annotations:
metallb.io/ip-allocated-from-pool: bgp-pool
metallb.universe.tf/address-pool: bgp-pool
labels:
app: nginx
name: nginx
namespace: default
spec:
ports:
- name: port-80
port: 80
protocol: TCP
targetPort: 80
selector:
app: nginx
type: LoadBalancer
EOF

创建完成后,再去看 BGP Router 状态,可以看到已经建立连接:

1
2
3
4
5
6
7
8
9
10
11
12
13
root@test-1:~# vtysh -c "show ip bgp summary"

IPv4 Unicast Summary (VRF default):
BGP router identifier 172.16.16.141, local AS number 7675 vrf-id 0
BGP table version 4
RIB entries 1, using 184 bytes of memory
Peers 2, using 1446 KiB of memory

Neighbor V AS MsgRcvd MsgSent TblVer InQ OutQ Up/Down State/PfxRcd PfxSnt Desc
172.16.16.142 4 7776 11 11 0 0 0 00:04:49 1 1 controlplane1
172.16.16.143 4 7776 11 11 0 0 0 00:04:50 1 1 worker1

Total number of neighbors 2

查看 BGP 路由,VIP 会均衡的分配给集群的两个节点:

1
2
3
4
5
6
7
8
9
10
11
12
root@test-1:~# vtysh -c "show ip route"
Codes: K - kernel route, C - connected, S - static, R - RIP,
O - OSPF, I - IS-IS, B - BGP, E - EIGRP, N - NHRP,
T - Table, v - VNC, V - VNC-Direct, A - Babel, F - PBR,
f - OpenFabric,
> - selected route, * - FIB route, q - queued, r - rejected, b - backup
t - trapped, o - offload failure

K>* 0.0.0.0/0 [0/0] via 172.16.16.1, ens34, 01:25:13
C>* 172.16.16.0/24 is directly connected, ens34, 01:25:13
B>* 172.16.16.50/32 [20/0] via 172.16.16.142, ens34, weight 1, 00:01:38
* via 172.16.16.143, ens34, weight 1, 00:01:38

查看 Service 具体信息,可以看到两个节点都有宣告,且协议使用的是 BGP:

Author

Warner Chen

Posted on

2025-04-22

Updated on

2025-11-11

Licensed under

You need to set install_url to use ShareThis. Please set it in _config.yml.
You forgot to set the business or currency_code for Paypal. Please set it in _config.yml.

Comments

You forgot to set the shortname for Disqus. Please set it in _config.yml.