Kubernetes不为裸机集群提供网络负载均衡器(LoadBalancer服务)的实现。 裸机集群只能使用NodePort和externalIPs服务来将用户流量引入他们的集群,Metallb旨在通过提供负载均衡器以便裸机集群上的外部服务可以更好工作。
MetalLB简介
MetalLB是k8s生态中的AddOn之一,分为controller和speaker两个组件。
- Controller通过k8s api-server监听service变化,给与service一个服务IP或回收该IP。
- Speaker组件采用Layer2或BGP模式走api-server将流量引入集群,k8s再通过kube-proxy,走iptables或ipvs,将流量引入pod。此外speaker还会负责服务IP驻守节点的选举,当该节点宕机时,会举行重新选举,从而实现HA。
服务IP需要k8s维护者自己给定同k8s集群同网段的一个多个IP地址,作为服务IP池,以便创建service时,可以使用其中的某个IP作为会漂移的服务IP。
工作原理
具体的工作原理如下图所示,Controller负责监听Service变化,当Service配置为LoadBalancer模式时,从IP池分配给到相应的IP地址并对该IP的生命周期进行管理。Speaker则会依据选择的协议进行相应的广播或应答,实现IP地址的通信响应。当业务流量通过TCP/UDP协议到达指定的Node时,由Node上面运行的Kube-Proxy组件对流量进行处理,并分发到对应服务的Pod上面。

MetalLB支持两种模式,一种是Layer2模式,一种是BGP模式。
本次演示环境,我们采用Layer2模式
- Layer2模式
在2层模式下,Metallb会在Node节点中选出一台作为Leader,与服务IP相关的所有流量都会流向该节点。在该节点上, kube-proxy将接收到的流量传播到对应服务的Pod。当leader节点出现故障时,会由另一个节点接管。从这个角度来看,2层模式更像是高可用,而不是负载均衡,因为同时只能在一个节点负责接收数据。
在二层模式中会存在以下两种局限性:单节点瓶颈和故障转移慢的情况。
由于Layer 2 模式会使用单个选举出来的Leader来接收服务IP的所有流量,这就意味着服务的入口带宽被限制为单个节点的带宽,单节点的流量处理能力将成为整个集群的接收外部流量的瓶颈。
在故障转移方面,目前的机制是MetalLB通过发送2层数据包来通知各个节点,并重新选举Leader,这通常能在几秒内完成。但如果是计划外的事故导致的,此时在有故障的客户端刷新其缓存条目之前,将无法访问服务IP。
BGP模式
BGP模式是真正的负载均衡,该模式需要路由器支持BGP协议 ,群集中的每个节点会与网络路由器建议基于BGP的对等会话,并使用该会话来通告负载均衡的IP。MetalLB发布的路由彼此等效,这意味着路由器将使用所有的目标节点,并在它们之间进行负载平衡。数据包到达节点后,kube-proxy负责流量路由的最后一跳,将数据包发送到对应服务的Pod。
负载平衡的方式取决于您特定的路由器型号和配置,常见的有基于数据包哈希对每个连接进行均衡,这意味着单个TCP或UDP会话的所有数据包都将定向到群集中的单个计算机。
BGP模式也存在着自身的局限性,该模式通过对数据包头中的某些字段进行哈希处理,并将该哈希值用作后端数组的索引,将给定的数据包分配给特定的下一跳。但路由器中使用的哈希通常不稳定,因此只要后端节点数量发生变化时,现有连接就会被随机地重新哈希,这意味着大多数现有连接将被转发到另一后端,而该后端并不清楚原有的连接状态。为了减少这种麻烦,建议使用更加稳定的BGP算法,如:ECMP散列算法。
安装配置MetalLB
如果我们只使用Ingress-NGINX,不创建LoadBalancer,访问则需要Ingress svc port访问
不安装LoadBalancer 则ADDRESS将没有分配IP
root@k8s-master-01:~/metallb# kubectl get ingress
NAME CLASS HOSTS ADDRESS PORTS AGE
ingress-nginx-demo nginx nginx.frps.cn 80 86m
kube-proxy节点配置
kubectl edit configmap -n kube-system kube-proxy
kind: KubeProxyConfiguration
logging:
flushFrequency: 0
options:
json:
infoBufferSize: "0"
verbosity: 0
metricsBindAddress: 0.0.0.0:10249
mode: ipvs
### 以下为新增配置
ipvs:
strictARP: true
下载部署文件
kubectl apply -f https://raw.githubusercontent.com/metallb/metallb/v0.15.2/config/manifests/metallb-native.yaml
#符合国内网络下载
kubectl apply -f https://d.frps.cn/file/kubernetes/metallb/metallb-native.yaml
如果有镜像镜像无法下载可以使用我本地的镜像导入
wget https://d.frps.cn/file/kubernetes/metallb/metallb_0.15.2.tar
#docker load -i 导入即可
执行结果如下
root@k8s-master-01:~/metallb# kubectl apply -f metallb-native.yaml
namespace/metallb-system created
customresourcedefinition.apiextensions.k8s.io/bfdprofiles.metallb.io created
customresourcedefinition.apiextensions.k8s.io/bgpadvertisements.metallb.io created
customresourcedefinition.apiextensions.k8s.io/bgppeers.metallb.io created
customresourcedefinition.apiextensions.k8s.io/communities.metallb.io created
customresourcedefinition.apiextensions.k8s.io/ipaddresspools.metallb.io created
customresourcedefinition.apiextensions.k8s.io/l2advertisements.metallb.io created
customresourcedefinition.apiextensions.k8s.io/servicebgpstatuses.metallb.io created
customresourcedefinition.apiextensions.k8s.io/servicel2statuses.metallb.io created
serviceaccount/controller created
serviceaccount/speaker created
role.rbac.authorization.k8s.io/controller created
role.rbac.authorization.k8s.io/pod-lister created
clusterrole.rbac.authorization.k8s.io/metallb-system:controller created
clusterrole.rbac.authorization.k8s.io/metallb-system:speaker created
rolebinding.rbac.authorization.k8s.io/controller created
rolebinding.rbac.authorization.k8s.io/pod-lister created
clusterrolebinding.rbac.authorization.k8s.io/metallb-system:controller created
clusterrolebinding.rbac.authorization.k8s.io/metallb-system:speaker created
configmap/metallb-excludel2 created
secret/metallb-webhook-cert created
service/metallb-webhook-service created
deployment.apps/controller created
daemonset.apps/speaker created
validatingwebhookconfiguration.admissionregistration.k8s.io/metallb-webhook-configuration created
这里会为我们额外创建一个metallb-system命名空间
root@k8s-master-01:/tmp# kubectl get ns
NAME STATUS AGE
default Active 33d
extension-openpitrix Active 29d
extension-whizard-telemetry Active 29d
ingress-nginx Active 12h
kube-node-lease Active 33d
kube-public Active 33d
kube-system Active 33d
kubesphere-controls-system Active 29d
kubesphere-monitoring-system Active 29d
kubesphere-system Active 29d
metallb-system Active 35s
middle Active 31d
tools Active 31d
wordpress Active 31d
检查metallb状态
root@k8s-master-01:~/metallb# kubectl get all -n metallb-system
NAME READY STATUS RESTARTS AGE
pod/controller-8666ddd68b-47qm5 1/1 Running 0 3m9s
pod/speaker-5cg4j 1/1 Running 0 3m9s
pod/speaker-cxvsm 1/1 Running 0 3m9s
pod/speaker-h9px8 1/1 Running 0 3m9s
pod/speaker-k2q2m 1/1 Running 0 3m9s
pod/speaker-lr2v2 1/1 Running 0 3m9s
pod/speaker-tbvmc 1/1 Running 0 3m9s
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/metallb-webhook-service ClusterIP 10.96.0.81 <none> 443/TCP 3m9s
NAME DESIRED CURRENT READY UP-TO-DATE AVAILABLE NODE SELECTOR AGE
daemonset.apps/speaker 6 6 6 6 6 kubernetes.io/os=linux 3m9s
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/controller 1/1 1 1 3m9s
NAME DESIRED CURRENT READY AGE
replicaset.apps/controller-8666ddd68b 1 1 1 3m9s
要使用·Layer2模式进行配置,需要创建一个IPAddressPool资源对象,用来指定用于分配的IP池
root@k8s-master-01:~/metallb# cat ip-pool.yaml
apiVersion: metallb.io/v1beta1
kind: IPAddressPool
metadata:
name: ip-pool
namespace: metallb-system
spec:
addresses:
- 192.168.21.60-192.168.21.70
然后需要创建一个广播声明,可以关联上面的 IP 池对象,这样会使用关联的 IP 池地址。为了通告来自IPAddressPool的IP,L2Advertisement实例必须关联到 IPAddressPool。
比如上面的配置可以让 MetalLB控制从192.168.21.60-192.168.21.70 IP地址。
root@k8s-master-01:~/metallb# cat advertise.yaml
apiVersion: metallb.io/v1beta1
kind: L2Advertisement
metadata:
name: l2adver
namespace: metallb-system
spec:
ipAddressPools: # 如果不配置则会通告所有的IP池地址
- ip-pool
创建测试NGINX Pod及SVC
root@k8s-master-01:~/metallb# cat test-nginx.yaml
# test-lb.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx
spec:
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx
ports:
- name: http
containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: nginx
spec:
ports:
- name: http
port: 80
protocol: TCP
targetPort: 80
selector:
app: nginx
type: LoadBalancer
查看Ingress IP
root@k8s-master-01:~/metallb# kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 25d
nginx LoadBalancer 10.96.1.139 192.168.21.60 80:32473/TCP 18m
root@k8s-master-01:~/metallb#
root@k8s-master-01:~/metallb#
root@k8s-master-01:~/metallb# curl 192.168.21.60
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
html { color-scheme: light dark; }
body { width: 35em; margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif; }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>
<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>
<p><em>Thank you for using nginx.</em></p>
</body>
</html>
在每台 K8s 节点机器上面的 kube-ipvs0 网卡上面都能看到这个 LoadBalancer 的 VIP:
root@k8s-master-01:~# ip a|grep 192
inet 192.168.21.10/24 brd 192.168.21.255 scope global ens34
inet 192.168.21.60/32 scope global kube-ipvs0
结合Ingress-nginx-controller配置使用
测试文件如下
在测试文件中,我们使用了ingressClassName: nginx 这里的配置就是使用我们前面部署的Ingress-nginx控制器,结合ingress-nginx控制器进行访问
# lb-demo.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: lb-demo
namespace: default
spec:
selector:
matchLabels:
app: lb-demo
template: # pod 模板
metadata:
labels:
app: lb-demo
spec:
containers:
- name: app
image: nginx
ports:
- containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: lb-demo
namespace: default
spec:
selector:
app: lb-demo
ports:
- name: http
port: 80 # 这个是Service的端口
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: lb-demo
namespace: default
spec:
ingressClassName: nginx
rules:
- host: nginx1.frps.cn
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: lb-demo
port:
number: 80
接下来查看svc以及Ingress就已经生效了
root@k8s-master-01:/tmp# kubectl get ingress,svc
NAME CLASS HOSTS ADDRESS PORTS AGE
ingress.networking.k8s.io/lb-demo nginx nginx1.frps.cn 192.168.21.60 80 34s
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 33d
service/lb-demo ClusterIP 10.96.3.241 <none> 80/TCP 34s
结合我们上一篇文章,配置Ingress-nginx-controller 上面创建的Ingress会自动关联到 LoadBalancer MetalLB
root@k8s-master-01:/tmp# kubectl get ingress
NAME CLASS HOSTS ADDRESS PORTS AGE
ingress-nginx-demo nginx nginx.frps.cn 80 152m
#等待几分钟后,节点自动绑到LoadBalancer上
root@k8s-master-01:/tmp# kubectl get ingress
NAME CLASS HOSTS ADDRESS PORTS AGE
ingress-nginx-demo nginx nginx.frps.cn 192.168.21.60 80 152m
绑定浏览器访问
绑定到我们声明的192.168.21.60IP
192.168.21.60 #IP在我们每一台节点都运行


