本文主要在k8s原生集群上部署v0.12.1
版本的MetalLB
作为k8s的LoadBalancer
,主要涉及MetalLB的Layer2模式和BGP模式两种部署方案。由于BGP的相关原理和配置比较复杂,这里仅涉及简单的BGP配置。
文中使用的k8s集群是在CentOS7系统上基于docker
和flannel
组件部署v1.23.6
版本,此前写的一些关于k8s基础知识和集群搭建的一些方案,有需要的同学可以看一下。
1、工作原理
1.1 简介
在开始之前,我们需要了解一下MetalLB的工作原理。
MetalLB hooks into your Kubernetes cluster, and provides a network load-balancer implementation. In short, it allows you to create Kubernetes services of type
LoadBalancer
in clusters that don’t run on a cloud provider, and thus cannot simply hook into paid products to provide load balancers.It has two features that work together to provide this service: address allocation, and external announcement.
MetalLB是 Kubernetes 集群中关于LoadBalancer的一个具体实现,主要用于暴露k8s集群的服务到集群外部访问。MetalLB可以让我们在k8s集群中创建服务类型为LoadBalancer
的服务,并且无需依赖云厂商提供的LoadBalancer
。
它具有两个共同提供此服务的工作负载(workload):地址分配(address allocation)和外部公告(external announcement);对应的就是在k8s中部署的controller
和speaker
。
1.2 address allocation
地址分配(address allocation)这个功能比较好理解,首先我们需要给MetalLB分配一段IP,接着它会根据k8s的service中的相关配置来给LoadBalancer
的服务分配IP,从官网文档中我们可以得知LoadBalancer
的IP可以手动指定,也可以让MetalLB自动分配;同时还可以在MetalLB的configmap
中配置多个IP段,并且单独设置每个IP段是否开启自动分配。
地址分配(address allocation)主要就是由作为deployment
部署的controller
来实现,它负责监听集群中的service状态并且分配IP
1.3 external announcement
外部公告(external announcement)的主要功能就是要把服务类型为LoadBalancer
的服务的EXTERNAL-IP
公布到网络中去,确保客户端能够正常访问到这个IP。MetalLB对此的实现方式主要有三种:ARP/NDP和BGP;其中ARP/NDP分别对应IPv4/IPv6协议的Layer2模式,BGP路由协议则是对应BGP模式。外部公告(external announcement)主要就是由作为daemonset
部署的speaker
来实现,它负责在网络中发布ARP/NDP报文或者是和BGP路由器建立连接并发布BGP报文。
1.4 关于网络
不管是Layer2模式还是BGP模式,两者都不使用Linux的网络栈,也就是说我们没办法使用诸如ip
命令之类的操作准确的查看VIP所在的节点和相应的路由,相对应的是在每个节点上面都能看到一个kube-ipvs0
网卡接口上面的IP。同时,两种模式都只是负责把VIP的请求引到对应的节点上面,之后的请求怎么到达pod,按什么规则轮询等都是由kube-proxy实现的。
两种不同的模式各有优缺点和局限性,我们先把两者都部署起来再进行分析。
2、准备工作
2.1 系统要求
在开始部署MetalLB之前,我们需要确定部署环境能够满足最低要求:
- 一个k8s集群,要求版本不低于1.13.0,且没有负载均衡器相关插件
- k8s集群上的CNI组件和MetalLB兼容
- 预留一段IPv4地址给MetalLB作为LoadBalance的VIP使用
- 如果使用的是MetalLB的BGP模式,还需要路由器支持BGP协议
- 如果使用的是MetalLB的Layer2模式,因为使用了memberlist算法来实现选主,因此需要确保各个k8s节点之间的7946端口可达(包括TCP和UDP协议),当然也可以根据自己的需求配置为其他端口
2.2 cni插件的兼容性
MetalLB官方给出了对主流的一些CNI的兼容情况,考虑到MetalLB主要还是利用了k8s自带的kube-proxy组件做流量转发,因此对大多数的CNI兼容情况都相当不错。
CNI | 兼容性 | 主要问题 |
---|---|---|
Calico | Mostly (see known issues) | 主要在于BGP模式的兼容性,但是社区也提供了解决方案 |
Canal | Yes | - |
Cilium | Yes | - |
Flannel | Yes | - |
Kube-ovn | Yes | - |
Kube-router | Mostly (see known issues) | 无法支持 builtin external BGP peering mode |
Weave Net | Mostly (see known issues) | externalTrafficPolicy: Local 支持情况视版本而定 |
从兼容性上面我们不难看出,大多数情况是没问题的,出现兼容性问题的主要原因就是和BGP有冲突。实际上BGP相关的兼容性问题几乎存在于每个开源的k8s负载均衡器上面。
2.3 云厂商的兼容性
MetalLB官方给出的列表中,我们可以看到对大多数云厂商的兼容性都很差,原因也很简单,大多数的云环境上面都没办法运行BGP协议,而通用性更高的layer2模式则因为各个云厂商的网络环境不同而没办法确定是否能够兼容
The short version is: cloud providers expose proprietary APIs instead of standard protocols to control their network layer, and MetalLB doesn’t work with those APIs.
当然如果使用了云厂商的服务,最好的方案是直接使用云厂商提供的LoadBalance
服务。
3、Layer2 mode
3.1 部署环境
本次MetalLB
的部署环境为基于docker
和flannel
部署的1.23.6
版本的k8s集群
IP | Hostname |
---|---|
10.31.8.1 | tiny-flannel-master-8-1.k8s.tcinternal |
10.31.8.11 | tiny-flannel-worker-8-11.k8s.tcinternal |
10.31.8.12 | tiny-flannel-worker-8-12.k8s.tcinternal |
10.8.64.0/18 | podSubnet |
10.8.0.0/18 | serviceSubnet |
10.31.8.100-10.31.8.200 | MetalLB IPpool |
3.2 配置ARP参数
部署Layer2模式需要把k8s集群中的ipvs配置打开strictARP
,开启之后k8s集群中的kube-proxy
会停止响应kube-ipvs0
网卡之外的其他网卡的arp请求,而由MetalLB接手处理。
strict ARP
开启之后相当于把 将 arp_ignore
设置为 1 并将 arp_announce
设置为 2 启用严格的 ARP,这个原理和LVS中的DR模式对RS的配置一样,可以参考之前的文章中的解释。
strict ARP configure arp_ignore and arp_announce to avoid answering ARP queries from kube-ipvs0 interface
# 查看kube-proxy中的strictARP配置
$ kubectl get configmap -n kube-system kube-proxy -o yaml | grep strictARP
strictARP: false
# 手动修改strictARP配置为true
$ kubectl edit configmap -n kube-system kube-proxy
configmap/kube-proxy edited
# 使用命令直接修改并对比不同
$ kubectl get configmap kube-proxy -n kube-system -o yaml | sed -e "s/strictARP: false/strictARP: true/" | kubectl diff -f - -n kube-system
# 确认无误后使用命令直接修改并生效
$ kubectl get configmap kube-proxy -n kube-system -o yaml | sed -e "s/strictARP: false/strictARP: true/" | kubectl apply -f - -n kube-system
# 重启kube-proxy确保配置生效
$ kubectl rollout restart ds kube-proxy -n kube-system
# 确认配置生效
$ kubectl get configmap -n kube-system kube-proxy -o yaml | grep strictARP
strictARP: true
3.3 部署MetalLB
MetalLB的部署也十分简单,官方提供了manifest文件部署(yaml部署),helm3部署和Kustomize部署三种方式,这里我们还是使用manifest文件部署。
大多数的官方教程为了简化部署的步骤,都是写着直接用kubectl命令部署一个yaml的url,这样子的好处是部署简单快捷,但是坏处就是本地自己没有存档,不方便修改等操作,因此我个人更倾向于把yaml文件下载到本地保存再进行部署。
# 下载v0.12.1的两个部署文件
$ wget https://raw.githubusercontent.com/metallb/metallb/v0.12.1/manifests/namespace.yaml
$ wget https://raw.githubusercontent.com/metallb/metallb/v0.12.1/manifests/metallb.yaml
# 如果使用frr来进行BGP路由管理,则下载这两个部署文件
$ wget https://raw.githubusercontent.com/metallb/metallb/v0.12.1/manifests/namespace.yaml
$ wget https://raw.githubusercontent.com/metallb/metallb/v0.12.1/manifests/metallb-frr.yaml
下载官方提供的yaml文件之后,我们再提前准备好configmap
的配置,github上面有提供一个参考文件,layer2模式需要的配置并不多,这里我们只做最基础的一些参数配置定义即可:
protocol
这一项我们配置为layer2
addresses
这里我们可以使用CIDR来批量配置(198.51.100.0/24
),也可以指定首尾IP来配置(192.168.0.150-192.168.0.200
),这里我们指定一段和k8s节点在同一个子网的IP
$ cat > configmap-metallb.yaml <<EOF
apiVersion: v1
kind: ConfigMap
metadata:
namespace: metallb-system
name: config
data:
config: |
address-pools:
- name: default
protocol: layer2
addresses:
- 10.31.8.100-10.31.8.200
EOF
接下来就可以开始进行部署,整体可以分为三步:
- 部署
namespace
- 部署
deployment
和daemonset
- 配置
configmap
# 创建namespace
$ kubectl apply -f namespace.yaml
namespace/metallb-system created
$ kubectl get ns
NAME STATUS AGE
default Active 8d
kube-node-lease Active 8d
kube-public Active 8d
kube-system Active 8d
metallb-system Active 8s
nginx-quic Active 8d
# 部署deployment和daemonset,以及相关所需的其他资源
$ kubectl apply -f metallb.yaml
Warning: policy/v1beta1 PodSecurityPolicy is deprecated in v1.21+, unavailable in v1.25+
podsecuritypolicy.policy/controller created
podsecuritypolicy.policy/speaker created
serviceaccount/controller created
serviceaccount/speaker created
clusterrole.rbac.authorization.k8s.io/metallb-system:controller created
clusterrole.rbac.authorization.k8s.io/metallb-system:speaker created
role.rbac.authorization.k8s.io/config-watcher created
role.rbac.authorization.k8s.io/pod-lister created
role.rbac.authorization.k8s.io/controller created
clusterrolebinding.rbac.authorization.k8s.io/metallb-system:controller created
clusterrolebinding.rbac.authorization.k8s.io/metallb-system:speaker created
rolebinding.rbac.authorization.k8s.io/config-watcher created
rolebinding.rbac.authorization.k8s.io/pod-lister created
rolebinding.rbac.authorization.k8s.io/controller created
daemonset.apps/speaker created
deployment.apps/controller created
# 这里主要就是部署了controller这个deployment来检查service的状态
$ kubectl get deploy -n metallb-system
NAME READY UP-TO-DATE AVAILABLE AGE
controller 1/1 1 1 86s
# speaker则是使用ds部署到每个节点上面用来协商VIP、收发ARP、NDP等数据包
$ kubectl get ds -n metallb-system
NAME DESIRED CURRENT READY UP-TO-DATE AVAILABLE NODE SELECTOR AGE
speaker 3 3 3 3 3 kubernetes.io/os=linux 64s
$ kubectl get pod -n metallb-system -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
controller-57fd9c5bb-svtjw 1/1 Running 0 117s 10.8.65.4 tiny-flannel-worker-8-11.k8s.tcinternal <none> <none>
speaker-bf79q 1/1 Running 0 117s 10.31.8.11 tiny-flannel-worker-8-11.k8s.tcinternal <none> <none>
speaker-fl5l8 1/1 Running 0 117s 10.31.8.12 tiny-flannel-worker-8-12.k8s.tcinternal <none> <none>
speaker-nw2fm 1/1 Running 0 117s 10.31.8.1 tiny-flannel-master-8-1.k8s.tcinternal <none> <none>
$ kubectl apply -f configmap-layer2.yaml
configmap/config created
3.4 部署测试服务
我们还是自定义一个服务来进行测试,测试镜像使用nginx,默认情况下会返回请求客户端的IP和端口
$ cat > nginx-quic-lb.yaml <<EOF
apiVersion: v1
kind: Namespace
metadata:
name: nginx-quic
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-lb
namespace: nginx-quic
spec:
selector:
matchLabels:
app: nginx-lb
replicas: 4
template:
metadata:
labels:
app: nginx-lb
spec:
containers:
- name: nginx-lb
image: tinychen777/nginx-quic:latest
imagePullPolicy: IfNotPresent
ports:
- containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: nginx-lb-service
namespace: nginx-quic
spec:
externalTrafficPolicy: Cluster
internalTrafficPolicy: Cluster
selector:
app: nginx-lb
ports:
- protocol: TCP
port: 80 # match for service access port
targetPort: 80 # match for pod access port
type: LoadBalancer
loadBalancerIP: 10.31.8.100
EOF
注意上面的配置中我们把service配置中的type
字段指定为LoadBalancer
,并且指定了loadBalancerIP
为10.31.8.100
注意:并非所有的
LoadBalancer
都允许设置loadBalancerIP
。如果
LoadBalancer
支持该字段,那么将根据用户设置的loadBalancerIP
来创建负载均衡器。如果没有设置
loadBalancerIP
字段,将会给负载均衡器指派一个临时 IP。如果设置了
loadBalancerIP
,但LoadBalancer
并不支持这种特性,那么设置的loadBalancerIP
值将会被忽略掉。
# 创建一个测试服务检查效果
$ kubectl apply -f nginx-quic-lb.yaml
namespace/nginx-quic created
deployment.apps/nginx-lb created
service/nginx-lb-service created
查看服务状态,这时候TYPE已经变成LoadBalancer
,EXTERNAL-IP
显示为我们定义的10.31.8.100
# 查看服务状态,这时候TYPE已经变成LoadBalancer
$ kubectl get svc -n nginx-quic
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
nginx-lb-service LoadBalancer 10.8.32.221 10.31.8.100 80:30181/TCP 25h
此时我们再去查看k8s机器中的nginx-lb-service
状态,可以看到ClusetIP
、LoadBalancer-VIP
和nodeport
的相关信息以及流量策略TrafficPolicy
等配置
$ kubectl get svc -n nginx-quic nginx-lb-service -o yaml
apiVersion: v1
kind: Service
metadata:
annotations:
kubectl.kubernetes.io/last-applied-configuration: |
{
"apiVersion":"v1","kind":"Service","metadata":{
"annotations":{
},"name":"nginx-lb-service","namespace":"nginx-quic"},"spec":{
"externalTrafficPolicy":"Cluster","internalTrafficPolicy":"Cluster","loadBalancerIP":"10.31.8.100","ports":[{
"port":80,"protocol":"TCP","targetPort":80}],"selector":{
"app":"nginx-lb"},"type":"LoadBalancer"}