在公有云部署的kubernetes集群中,有公有云厂商提供LoadBalancer类型的Service。但是在基于本地环境部署的k8s集群是我们常用的测试环境和开发环境;需要通过NodePort和externalIPs方式将外部流量引入集群中,这就带来了很多的不便。

尤其是我们通过helm去部署一些服务时,尝尝会依赖于LoadBalancer的资源类型,导致创建的services中type: LoadBalancers会一直处于Pending状态;我们不得不进行仓库的fetch,然后手动进行values的修改。

Metallb 通过标准路由协议能解决该问题。MetalLB 也是 CNCF 的沙箱项目,最早发布在
https://github.com/google/metallb 开发,后来迁移到 https://github.com/metallb/metallb 中。

MetalLB 通过 MetalLB hooks 监听SVC的变化;然后通过Speaker组件采用对应的模式将外部流量引流到kubernetes集群node节点的可达路径。而具体到Pod中则是通过kuber-proxy依据转发模式(iptables或ipvs)将流量转发到Pod中。

MetaLB负责从主机维度实现负载均衡,而pod副本间的负载是通过kube-proxy实现。MetalLB负责IP地址分配、依据设定的广播模式进行广播、节点选举、节点失效切换等功能。而引流的过程则通过ARP、NDP和BGP标准路由协议实现。

 

主要的两大功能:

  1. 地址分配:用户需要在配置中提供一个地址池,Metallb 将会在其中选取地址分配给服务。
  2. 地址广播(IP外部声明):根据不同配置,Metallb 会以二层(ARP/NDP)或者 BGP 的方式进行地址的广播。

工作模式:

  1. BGP模式(Layer 3),使用BGP协议分配地址池;运行 BGP 的设备之间可以交换路由信息,我们可以将自己的 IP 段通过 BGP 协议告诉其他设备,这样其他设备就能正确的路由数据包到服务器上了。BGP 需要路由器的支持。如果Calico也是使用的BGP模式,有可能会有冲突从而导致metallb无法正常工作。     
  2. ARP(IPV4)/NDP(IPV6)工作模式(Layer2);使用 ARP/NDP 协议分配地址池;在服务器的内部子网里找未使用的 IP,然后等其他电脑访问这个 IP 的时候,我们回应一个 ARP 包,其他电脑就知道这个 IP 在哪里可以通信了,尽管这个 IP 其实没有绑定到任何网卡上,也有可能只是 iptables 里的一条记录。分配的 IP 只能和服务器其他 IP 位于同一子网,这就要求我们所有的节点必须在同一个二层网络内。

k8s 实现loadbalancer_k8s 实现loadbalancer

 

 

k8s 实现loadbalancer_nginx_02

 

 

更多详情请参考官方文档:https://metallb.universe.tf/。

架构

二层部署的架构图,参考红帽openshift官方文档。

k8s 实现loadbalancer_子网_03

 

 

上图显示了与 MetalLB 相关的以下概念:

  • 应用程序可以通过在 172.130.0.0/16 子网上具有集群 IP 的服务获取。该 IP 地址可以从集群内部访问。服务也有一个外部 IP 地址,用于分配给服务的 MetalLB,即 192.168.100.200。
  • 节点 1 和 3 具有应用程序的 pod。
  • speaker 守护进程集在每个节点上运行一个 pod。MetalLB Operator 启动这些 pod。
  • 每个 speaker pod 都是主机网络的 pod。容器集的 IP 地址与主机网络上节点的 IP 地址相同。
  • 节点 1 上的 speaker pod 使用 ARP 声明服务的外部 IP 地址 192.168.100.200。声明外部 IP 地址的 speaker pod 必须与服务的端点位于同一个节点上,端点必须为 Ready 条件。
  • 客户端流量路由到主机网络,并连接到 192.168.100.200 IP 地址。在流量进入节点后,服务代理会根据您为服务设置的外部流量策略,将流量发送到同一节点上的应用 pod 或其他节点。
  • 如果节点 1 不可用,则外部 IP 地址将故障转移到另一节点。在具有应用 pod 和服务端点实例的另一个节点上,speaker Pod 开始宣布外部 IP 地址 192.168.100.200,新节点接收客户端流量。在图中,唯一的候选项是节点 3。

部署

环境要求

集群版本信息如下:

[root@k8s-master01 metallb]# kubectl get nodes -o wide
NAME           STATUS   ROLES           AGE   VERSION   INTERNAL-IP      EXTERNAL-IP   OS-IMAGE                KERNEL-VERSION              CONTAINER-RUNTIME
k8s-master01   Ready    control-plane   33d   v1.25.3   192.168.30.119   <none>        CentOS Linux 7 (Core)   6.0.5-1.el7.elrepo.x86_64   docker://20.10.0
k8s-node01     Ready    <none>          33d   v1.25.3   192.168.30.120   <none>        CentOS Linux 7 (Core)   6.0.5-1.el7.elrepo.x86_64   docker://20.10.0

支持MetalLB的CNI如下:

Network addon

Compatible

Antrea

Yes (Tested on version 1.4 and 1.5)

Calico

Mostly (see known issues)

Canal

Yes

Cilium

Yes

Flannel

Yes

Kube-ovn

Yes

Kube-router

Mostly (see known issues)

Weave Net

Mostly (see known issues)

注意事项:

  • 参考 CLOUD COMPATIBILITY https://metallb.universe.tf/installation/clouds/ 查看你的环境是否支持 MetalLB
  • 使用 BGP 工作模式时,需要一台或多台支持 BGP 的路由器
  • 由于第 2 层模式依赖于 ARP 和 NDP,客户端必须位于没有中断服务的节点所在的同一子网,以便 MetalLB 正常工作。另外,分配给该服务的 IP 地址必须在客户端用来访问该服务的网络所在的同一子网中。
  • 使用 L2 工作模式时,所有的节点必须在同一个二层网络内;必须允许节点之间通过 7946 端口(TCP & UDP,可以配置其他端口)通信,memberlist服务监听在该端口;二层模式不需要将 IP 绑定到工作节点的网络接口上。它的工作原理是直接响应本地网络上的 ARP 请求,将本机的 MAC 地址提供给客户端

  • 从 Kubernetes v1.14.2 开始,若 kube-proxy 使用 IPVS 模式,需要开启 strict ARP (严格的ARP)模式,使用 kubectl edit configmap -n kube-system kube-proxy 修改如下:

 

[root@k8s-master01 k8s]# kubectl edit configmaps -n kube-system kube-proxy

k8s 实现loadbalancer_k8s 实现loadbalancer_04

 

[root@k8s-master01 ~]# kubectl get configmap kube-proxy -n kube-system -o yaml | \
> sed -e "s/strictARP: false/strictARP: true/" | \
> kubectl diff -f - -n kube-system
diff -u -N /tmp/LIVE-3742864266/v1.ConfigMap.kube-system.kube-proxy /tmp/MERGED-3444415735/v1.ConfigMap.kube-system.kube-proxy
--- /tmp/LIVE-3742864266/v1.ConfigMap.kube-system.kube-proxy    2022-12-07 15:59:12.659122095 +0800
+++ /tmp/MERGED-3444415735/v1.ConfigMap.kube-system.kube-proxy    2022-12-07 15:59:12.660122131 +0800
@@ -33,7 +33,7 @@
       excludeCIDRs: null
       minSyncPeriod: 0s
       scheduler: ""
-      strictARP: false
+      strictARP: true
       syncPeriod: 0s
       tcpFinTimeout: 0s
       tcpTimeout: 0s 
 
 
[root@k8s-master01 ~]# kubectl get configmap kube-proxy -n kube-system -o yaml | \
> sed -e "s/strictARP: false/strictARP: true/" | \
> kubectl apply -f - -n kube-system
Warning: resource configmaps/kube-proxy is missing the kubectl.kubernetes.io/last-applied-configuration annotation which is required by kubectl apply. kubectl apply should only
 be used on resources created declaratively by either kubectl create --save-config or kubectl apply. The missing annotation will be patched automatically.configmap/kube-proxy configured 
更新kube-proxy pod
[root@k8s-master01 k8s]#  kubectl get pod -n kube-system |grep kube-proxy | awk '{system("kubectl delete pod "$1" -n kube-system")}'
pod "kube-proxy-dqzfr" deleted
pod "kube-proxy-dssrs" deleted
pod "kube-proxy-gn8kn" deleted
pod "kube-proxy-jmxgl" deleted
或者 reboot集群wget https://raw.githubusercontent.com/metallb/metallb/v0.13.7/config/manifests/metallb-native.yaml
 
[root@k8s-master01 metallb]# cat metallb-native.yaml | grep image
        image: quay.io/metallb/controller:v0.13.7
        image: quay.io/metallb/speaker:v0.13.7docker pull quay.io/metallb/controller:v0.13.7
docker pull quay.io/metallb/speaker:v0.13.7
[root@k8s-master01 metallb]# kubectl apply -f metallb-native.yaml 
namespace/metallb-system created
customresourcedefinition.apiextensions.k8s.io/addresspools.metallb.io 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
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
secret/webhook-server-cert created
service/webhook-service created
deployment.apps/controller created
daemonset.apps/speaker createdvalidatingwebhookconfiguration.admissionregistration.k8s.io/metallb-webhook-configuration created 
[root@k8s-master01 metallb]# kubectl get validatingwebhookconfigurations.admissionregistration.k8s.io 
NAME                            WEBHOOKS   AGE
metallb-webhook-configuration   7          11s
[root@k8s-master01 metallb]# 
[root@k8s-master01 metallb]# kubectl delete validatingwebhookconfigurations.admissionregistration.k8s.io metallb-webhook-configuration 
validatingwebhookconfiguration.admissionregistration.k8s.io "metallb-webhook-configuration" deleted

由于我们的集群工作在测试环境中,我们采用的是L2工作模式下。

第 2 层模式最容易上手,并且可以在任何环境中工作——不需要花哨的路由器。

定义要分给负载均衡服务的IP地址池。

新版本metallb使用了CR(Custom Resources),这里我们通过IPAddressPool的CR,进行地址池的定义。

如果实例中不设置IPAddressPool选择器L2Advertisement;那么L2Advertisement默认为该实例所有的IPAddressPool相关联。

 

[root@k8s-master01 metallb]# cat ip-pool-config.yaml
apiVersion: metallb.io/v1beta1
kind: IPAddressPool
metadata:
  name: first-pool
  namespace: metallb-system
spec:
  addresses:
  - 192.168.30.240-192.168.30.250

[root@k8s-master01 metallb]# kubectl apply -f ip-pool-config.yaml
ipaddresspool.metallb.io/first-pool created
[root@k8s-master01 metallb]# vim L2Advertisement.yaml

 

进行L2关联地址池的绑定。这里也可以使用标签选择器。

[root@k8s-master01 metallb]# cat L2Advertisement.yaml
apiVersion: metallb.io/v1beta1
kind: L2Advertisement
metadata:
  name: example
  namespace: metallb-system
spec:
  ipAddressPools:
  - first-pool

[root@k8s-master01 metallb]# kubectl apply -f L2Advertisement.yaml
l2advertisement.metallb.io/example created

 

测试

创建类型为LoadBalancer的SVC进行测试

[root@k8s-master01 metallb]# cat deploy-svc-loadbalance.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
 name: nginx
 namespace: default
spec:
 selector:
   matchLabels:
     app: nginx
 template:
   metadata:
     labels:
       app: nginx
   spec:
     containers:
     - name: nginx
       image: nginx:1
       ports:
       - name: http
         containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
 name: nginx
 namespace: default
spec:
 ports:
 - name: http
   port: 80
   protocol: TCP
   targetPort: 80
 selector:
   app: nginx
 type: LoadBalancer

 

 

[root@k8s-master01 metallb]# kubectl get svc
NAME         TYPE           CLUSTER-IP       EXTERNAL-IP      PORT(S)        AGE
kubernetes   ClusterIP      10.96.0.1        <none>           443/TCP        33d
nginx        LoadBalancer   10.103.105.227   192.168.30.240   80:31865/TCP   2m1s

 

在另外一台机器上尝试

[root@localhost ~]# curl -I 192.168.30.240
HTTP/1.1 200 OK
Server: nginx/1.21.5
Date: Wed, 07 Dec 2022 11:41:46 GMT
Content-Type: text/html
Content-Length: 615
Last-Modified: Tue, 28 Dec 2021 15:28:38 GMT
Connection: keep-alive
ETag: "61cb2d26-267"
Accept-Ranges: bytes

k8s 实现loadbalancer_k8s 实现loadbalancer_05

 

 

metallb +ingress  实验:

vim deploy.yaml

 将原来的type: ClusterIP修改为 type: LoadBalancer


k8s 实现loadbalancer_子网_06

 

 

service 类型修改为  LoadBalancer

 

k8s 实现loadbalancer_k8s 实现loadbalancer_07

 

 

kind: Deployment
apiVersion: apps/v1
metadata:
  name: web01
  namespace: test-ns
spec:
  replicas: 3
  selector:
    matchLabels:
      app: httpd01
  template:
    metadata:
      labels:
        app: httpd01
    spec:
      containers:
      - name: httpd
        image: httpd:latest
---
apiVersion: v1
kind: Service
metadata:
  name: httpd-svc
  namespace: test-ns
spec:
  selector:
    app: httpd01
  ports:
  - protocol: TCP
    port: 80
    targetPort: 80
  type: LoadBalancer