本文主要在k8s原生集群上部署v0.4.4
版本的OpenELB
作为k8s的LoadBalancer
,主要涉及OpenELB的Layer2模式和BGP模式两种部署方案。由于BGP的相关原理和配置比较复杂,这里仅涉及简单的BGP配置。
文中使用的k8s集群是在CentOS7系统上基于docker
和calico
组件部署v1.23.6
版本,此前写的一些关于k8s基础知识和集群搭建的一些方案,有需要的同学可以看一下。
1、工作原理
1.1 简介
OpenELB 是一个开源的云原生负载均衡器实现,可以在基于裸金属服务器、边缘以及虚拟化的 Kubernetes 环境中使用 LoadBalancer 类型的 Service 对外暴露服务。OpenELB 项目最初由 KubeSphere 社区 发起,目前已作为 CNCF 沙箱项目 加入 CNCF 基金会,由 OpenELB 开源社区维护与支持。
与MetalLB类似,OpenELB也拥有两种主要工作模式:Layer2模式和BGP模式。OpenELB的BGP模式目前暂不支持IPv6。
无论是Layer2模式还是BGP模式,核心思路都是通过某种方式将特定VIP的流量引到k8s集群中,然后再通过kube-proxy将流量转发到后面的特定服务。
1.2 Layer2模式
Layer2模式需要我们的k8s集群基础环境支持发送anonymous ARP/NDP packets。因为OpenELB是针对裸金属服务器设计的,因此如果是在云环境中部署,需要注意是否满足条件。
下图来自OpenELB官方,这里简单阐述一下Layer2模式的工作原理:
- 图中有一个类型为LoadBalancer的Service,其VIP为192.168.0.91(和k8s的节点相同网段),后端有两个pod(分别为pod1和pod2)
- 安装在 Kubernetes 集群中的 OpenELB 随机选择一个节点(图中为 worker 1)来处理 Service 请求。当局域网中出现arp request数据包来查询192.168.0.91的mac地址的时候,OpenELB会进行回应(使用 worker 1 的 MAC 地址),此时路由器(也可能是交换机)将 Service 的VIP 192.168.0.91和 worker 1 的 MAC 地址绑定,之后所有请求到192.168.0.91的数据包都会被转发到worker1上
- Service 流量到达 worker 1 后, worker 1 上的 kube-proxy 将流量转发到后端的两个pod进行负载均衡,这些pod不一定在work1上
主要的工作流程就如同上面描述的一般,但是还有几个需要额外注意的点:
- 如果 worker 1 出现故障,OpenELB 会重新向路由器发送 APR/NDP 数据包,将 Service IP 地址映射到 worker 2 的 MAC 地址,Service 流量切换到 worker 2
- 主备切换过程并不是瞬间完成的,中间会产生一定时间的服务中断(具体多久官方也没说,实际上应该是却决于检测到节点宕机的时间加上重新选主的时间)
- 如果集群中已经部署了多个 openelb-manager 副本,OpenELB 使用 Kubernetes 的领导者选举特性算法来进行选主,从而确保只有一个副本响应 ARP/NDP 请求
1.3 BGP模式
OpenELB的BGP模式使用的是gobgp实现的BGP协议,通过使用BGP协议和路由器建立BGP连接并实现ECMP负载均衡,从而实现高可用的LoadBalancer。
我们还是借用官网的图来解释一下这个流程,注意BGP模式暂不支持IPv6。
- 图中有一个类型为LoadBalancer的Service,其VIP为172.22.0.2(和k8s的节点不同网段),后端有两个pod(分别为pod1和pod2)
- 安装在 Kubernetes 集群中的 OpenELB 与 BGP 路由器建立 BGP 连接,并将去往 172.22.0.2 的路由发布到 BGP 路由器,在配置得当的情况下,路由器上面的路由表可以看到 172.22.0.2 这个VIP的下一条有多个节点(均为k8s的宿主机节点)
- 当外部客户端机器尝试访问 Service 时,BGP 路由器根据从 OpenELB 获取的路由,在 master、worker 1 和 worker 2 节点之间进行流量负载均衡。Service 流量到达一个节点后,该节点上的 kube-proxy 将流量转发到后端的两个pod进行负载均衡,这些pod不一定在该节点上
2、Layer2 Mode
2.1 配置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
2.2 部署openelb
这里我们还是使用yaml进行部署,官方把所有部署的资源整合到了一个文件中,我们还是老规矩先下载到本地再进行部署
$ wget https://raw.githubusercontent.com/openelb/openelb/master/deploy/openelb.yaml
$ kubectl apply -f openelb.yaml
namespace/openelb-system created
customresourcedefinition.apiextensions.k8s.io/bgpconfs.network.kubesphere.io created
customresourcedefinition.apiextensions.k8s.io/bgppeers.network.kubesphere.io created
customresourcedefinition.apiextensions.k8s.io/eips.network.kubesphere.io created
serviceaccount/kube-keepalived-vip created
serviceaccount/openelb-admission created
role.rbac.authorization.k8s.io/leader-election-role created
role.rbac.authorization.k8s.io/openelb-admission created
clusterrole.rbac.authorization.k8s.io/kube-keepalived-vip created
clusterrole.rbac.authorization.k8s.io/openelb-admission created
clusterrole.rbac.authorization.k8s.io/openelb-manager-role created
rolebinding.rbac.authorization.k8s.io/leader-election-rolebinding created
rolebinding.rbac.authorization.k8s.io/openelb-admission created
clusterrolebinding.rbac.authorization.k8s.io/kube-keepalived-vip created
clusterrolebinding.rbac.authorization.k8s.io/openelb-admission created
clusterrolebinding.rbac.authorization.k8s.io/openelb-manager-rolebinding created
service/openelb-admission created
deployment.apps/openelb-manager created
job.batch/openelb-admission-create created
job.batch/openelb-admission-patch created
mutatingwebhookconfiguration.admissionregistration.k8s.io/openelb-admission created
validatingwebhookconfiguration.admissionregistration.k8s.io/openelb-admission created
接下来我们看看都部署了什么CRD资源,这几个CRD资源主要就是方便我们管理openelb
,这也是OpenELB相对MetalLB的优势。
$ kubectl get crd
NAME CREATED AT
bgpconfs.network.kubesphere.io 2022-05-19T06:37:19Z
bgppeers.network.kubesphere.io 2022-05-19T06:37:19Z
eips.network.kubesphere.io 2022-05-19T06:37:19Z
$ kubectl get ns openelb-system -o wide
NAME STATUS AGE
openelb-system Active 2m27s
实际上主要工作的负载就是这两个jobs.batch
和这一个deployment
$ kubectl get pods -n openelb-system
NAME READY STATUS RESTARTS AGE
openelb-admission-create-57tzm 0/1 Completed 0 5m11s
openelb-admission-patch-j5pl4 0/1 Completed 0 5m11s
openelb-manager-5cdc8697f9-h2wd6 1/1 Running 0 5m11s
$ kubectl get deploy -n openelb-system
NAME READY UP-TO-DATE AVAILABLE AGE
openelb-manager 1/1 1 1 5m38s
$ kubectl get jobs.batch -n openelb-system
NAME COMPLETIONS DURATION AGE
openelb-admission-create 1/1 11s 11m
openelb-admission-patch 1/1 12s 11m
2.3 创建EIP
接下来我们需要配置loadbalancerIP所在的网段资源,这里我们创建一个Eip对象来进行定义,后面对IP段的管理也是在这里进行。
apiVersion: network.kubesphere.io/v1alpha2
kind: Eip
metadata:
# Eip 对象的名称。
name: eip-layer2-pool
spec:
# Eip 对象的地址池
address: 10.31.88.101-10.31.88.200
# openELB的运行模式,默认为bgp
protocol: layer2
# OpenELB 在其上侦听 ARP/NDP 请求的网卡。该字段仅在protocol设置为时有效layer2。
interface: eth0
# 指定是否禁用 Eip 对象
# false表示可以继续分配
# true表示不再继续分配
disable: false
status:
# 指定 Eip 对象中的IP地址是否已用完。
occupied: false
# 指定 Eip 对象中有多少个 IP 地址已分配给服务。
# 直接留空,系统会自动生成
usage:
# Eip 对象中的 IP 地址总数。
poolSize: 100
# 指定使用的 IP 地址和使用 IP 地址的服务。服务以Namespace/Service name格式显示(例如,default/test-svc)。
# 直接留空,系统会自动生成
used:
# Eip 对象中的第一个 IP 地址。
firstIP: 10.31.88.101
# Eip 对象中的最后一个 IP 地址。
lastIP: 10.31.88.200
ready: true
# 指定IP协议栈是否为 IPv4。目前,OpenELB 仅支持 IPv4,其值只能是true.
v4: true
配置完成后我们直接部署即可
$ kubectl apply -f openelb/openelb-eip.yaml
eip.network.kubesphere.io/eip-layer2-pool created
部署完成后检查eip的状态
$ kubectl get eip
NAME CIDR USAGE TOTAL
eip-layer2-pool 10.31.88.101-10.31.88.200 100
2.4 创建测试服务
然后我们创建对应的服务
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:
allocateLoadBalancerNodePorts: false
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
---
apiVersion