前言
OpenELB 是由青云科技开源的云原生负载均衡器,可以在基于裸金属服务器、边缘以及虚拟化的 Kubernetes 环境中使用 LoadBalancer 类型的 Service 对外暴露服务。核心功能包括:
- BGP 模式和二层网络模式下的负载均衡
- 基于路由器 ECMP 的负载均衡
- IP 地址池管理
- 使用 CRD 进行 BGP 配置
工作原理
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 请求
BGP 模式(暂未实验,需要支持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 不一定在该节点上
因为 BGP 需要BGP路由器的支持,就暂时没有搭建,后续会考虑改用另外模式对外访问。
Layer2 Mode实验
配置 ARP 参数
在Layer2模式下,需要为kube-proxy启用strictARP,开启之后 k8s 集群中的 kube-proxy 会停止响应 kube-ipvs0 网卡之外的其他网卡的 arp 请求,让OpenELB处理ARP请求。
strict ARP 开启之后相当于把 将 arp_ignore 设置为 1 并将 arp_announce 设置为 2 启用严格的 ARP,这个原理和 LVS 中的 DR 模式对 RS 的配置一样,可以参考之前的文章中的解释[8]。
❝
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 strictARPstrictARP: 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 strictARPstrictARP: true
部署 openelb
这里我们还是使用 yaml 进行部署,官方把所有部署的资源整合到了一个文件中,我们还是老规矩先下载到本地再进行部署。
$ wget https://raw.githubusercontent.com/openelb/openelb/master/deploy/openelb.yaml
#已下载openelb源码包,进入deploy目录应用 https://github.com/openelb/openelb
$ cd /openelb-master/deploy
##镜像需要自己拉取,上传到haror
$ vi openelb.yamlimage: registry.k8s.io/ingress-nginx/kube-webhook-certgen:v1.1.1修改为示例:image: 10.8.59.173:17443/cloud/eastcom.com/openelb/kube-webhook-certgen:v1.1.1
$ 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
root@cloud-173:/home/xuwt/openelb-master/deploy# kubectl get pods -n openelb-system
NAME READY STATUS RESTARTS AGE
openelb-admission-create-755w5 0/1 ImagePullBackOff 0 116s
openelb-admission-patch-tkh8w 0/1 ErrImagePull 0 116s
openelb-manager-794999f796-pwlm5 0/1 ContainerCreating 0 116s
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
指定用于OpenELB的网卡
如果安装了OpenELB的节点有多个NIC,则需要在第2层模式下指定用于OpenELB的NIC。如果节点只有一个NIC,则可以跳过此步骤。
在本例中,安装了OpenELB的master 1节点有两个NIC(eno1 10.8.59.173和eno2 192.168.1.2),我们指定 eno1 用于OpenELB。
运行以下命令注释cloud-173以指定NIC:
kubectl annotate nodes master-1 layer2.openelb.kubesphere.io/v1alpha1="10.200.0.31"
kubectl annotate nodes master-2 layer2.openelb.kubesphere.io/v1alpha1="10.200.0.32"
kubectl annotate nodes master-3 layer2.openelb.kubesphere.io/v1alpha1="10.200.0.33"
创建 EIP(EXTERNAL-IP)
EXTERNAL-IP 就是可以在公网访问的 IP 。在局域网中使用EIP供集群外部访问。这里的EIP对象充当OpenELB的IP地址池,如果你有多个IP,则填写起始IP-结束IP,如果你只有一个IP,则填写一个就好。
接下来我们需要配置 loadbalancerIP 所在的网段资源,这里我们创建一个 Eip 对象来进行定义,后面对 IP 段的管理也是在这里进行。
apiVersion: network.kubesphere.io/v1alpha2
kind: Eip
metadata:# Eip 对象的名称。name: eip-layer2-pool
spec:# Eip 对象的地址池address: 10.200.0.231-10.200.0.232# openELB的运行模式,默认为bgpprotocol: layer2# OpenELB 在其上侦听 ARP/NDP 请求的网卡。该字段仅在protocol设置为时有效layer2。interface: bond0# 指定是否禁用 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.200ready: 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
创建测试服务
然后我们创建对应的服务
apiVersion: apps/v1
kind: Deployment
metadata:labels:name: nginx-31-openelbname: nginx-31-openelb
spec:replicas: 2selector:matchLabels:app: nginx-31-openelbtemplate:metadata:annotations:labels:app: nginx-31-openelbspec:affinity:nodeAffinity:requiredDuringSchedulingIgnoredDuringExecution:nodeSelectorTerms:- matchExpressions:- key: kubernetes.io/hostnameoperator: Invalues:- master-1containers:- name: nginx-openelb-containerimage: 10.200.0.31:17443/cloud/eastcom.com/nginx:latestports:- containerPort: 80
---
apiVersion: v1
kind: Service
metadata:name: nginx-service-openelbannotations:lb.kubesphere.io/v1alpha1: openelbprotocol.openelb.kubesphere.io/v1alpha1: layer2eip.openelb.kubesphere.io/v1alpha2: eip-pool-231
spec:ports:- port: 80protocol: TCPtargetPort: 80selector:app: nginx-31-openelbtype: LoadBalancer
然后我们检查部署状态:
$ kubectl apply -f nginx-fix173-openelb.yaml
deployment.apps/nginx-31-openelb configured
service/nginx-service-openelb unchanged
在集群节点的执行ip neigh命令,可以查看MAC地址表和IP邻居表中,10.200.0.231的mac地址如下:
同时在集群外,不同网段物理机(接入同一个交换机),使用arp广播实验,可以查找到mac地址:
Layer2 工作原理日志检查
我们查看 pod 的日志,可以看到更多的详细信息:
$ kubectl logs -f -n openelb-system openelb-manager-5cdc8697f9-h2wd6
...省略一堆日志输出...
{"level":"info","ts":1705563860.7568872,"msg":"setup openelb service","service":"default/nginx-service-openelb"}
{"level":"info","ts":1705563860.7570846,"logger":"IPAM","msg":"unAssignIP","args":{"Key":"default/nginx-service-openelb","Addr":"","Eip":"eip-pool-231","Protocol":"layer2","Unalloc":false},"peek":true,"result":{"Addr":"10.200.0.231","Eip":"eip-pool-231","Protocol":"layer2","Sp":{}},"err":null}
{"level":"info","ts":1705563860.8391945,"logger":"arpSpeaker","msg":"map ingress ip","ingress":"10.200.0.231","nodeIP":"10.200.0.32","nodeMac":"ca:09:ad:fb:c7:aa"}
{"level":"info","ts":1705563860.839278,"logger":"arpSpeaker","msg":"send gratuitous arp packet","eip":"10.200.0.231","nodeIP":"10.200.0.32","hwAddr":"ca:09:ad:fb:c7:aa"}
{"level":"info","ts":1705563860.8393674,"logger":"arpSpeaker","msg":"send gratuitous arp packet","eip":"10.200.0.231","nodeIP":"10.200.0.32","hwAddr":"ca:09:ad:fb:c7:aa"}
可以看到 openelb-manager 会持续的监听局域网中的 ARP request 请求,当遇到请求的 IP 是自己 IP 池里面已经使用的 VIP 时会主动响应。如果 openelb-manager 存在多个副本的时候,它们会先使用 k8s 的选主算法来进行选主,然后再由选举出来的主节点进行 ARP 报文的响应。
❝
In Layer 2 mode, OpenELB uses the leader election feature of Kubernetes to ensure that only one replica responds to ARP/NDP requests.
查看 arp 表来确定 MAC 地址从而确定 VIP 所在的节点,
root@master-1:/home/xuwt/openelb# kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 4h44m
nginx-service-openelb LoadBalancer 10.97.40.158 10.200.0.231 80:30267/TCP 54m
root@master-1:/home/xuwt/openelb# ip neigh | grep 10.200.0
10.200.0.231 dev bond0 lladdr ca:09:ad:fb:c7:aa STALE
OpenELB-manager 高可用
默认情况下,openelb-manager 只会部署一个副本,对于可用性要求较高的生产环境可能无法满足需求,官方也给出了部署多个副本[9]的教程。
官方教程的方式是推荐通过给节点添加 label 的方式来控制副本的部署数量和位置,这里我们将其配置为每个节点都运行一个服务(类似于 daemonset)。首先我们给需要部署的节点打上 labels。
# 我们给集群内的三个节点都打上label
$ kubectl label --overwrite nodes tiny-calico-master-88-1.k8s.tcinternal tiny-calico-worker-88-11.k8s.tcinternal tiny-calico-worker-88-12.k8s.tcinternal lb.kubesphere.io/v1alpha1=openelb
# 查看当前节点的labels
$ kubectl get nodes -o wide --show-labels=true | grep openelb
tiny-calico-master-88-1.k8s.tcinternal Ready control-plane,master 16d v1.23.6 10.31.88.1 <none> CentOS Linux 7 (Core) 3.10.0-1160.62.1.el7.x86_64 docker://20.10.14 beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,kubernetes.io/arch=amd64,kubernetes.io/hostname=tiny-calico-master-88-1.k8s.tcinternal,kubernetes.io/os=linux,lb.kubesphere.io/v1alpha1=openelb,node-role.kubernetes.io/control-plane=,node-role.kubernetes.io/master=,node.kubernetes.io/exclude-from-external-load-balancers=
tiny-calico-worker-88-11.k8s.tcinternal Ready <none> 16d v1.23.6 10.31.88.11 <none> CentOS Linux 7 (Core) 3.10.0-1160.62.1.el7.x86_64 docker://20.10.14 beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,kubernetes.io/arch=amd64,kubernetes.io/hostname=tiny-calico-worker-88-11.k8s.tcinternal,kubernetes.io/os=linux,lb.kubesphere.io/v1alpha1=openelb
tiny-calico-worker-88-12.k8s.tcinternal Ready <none> 16d v1.23.6 10.31.88.12 <none> CentOS Linux 7 (Core) 3.10.0-1160.62.1.el7.x86_64 docker://20.10.14 beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,kubernetes.io/arch=amd64,kubernetes.io/hostname=tiny-calico-worker-88-12.k8s.tcinternal,kubernetes.io/os=linux,lb.kubesphere.io/v1alpha1=openelb
然后我们先把副本的数量缩容到 0。
$ kubectl scale deployment openelb-manager --replicas=0 -n openelb-system
接着修改配置,在部署节点的 nodeSelector 字段中增加我们前面新加的 labels
复制
$ kubectl get deployment openelb-manager -n openelb-system -o yaml
...略去一堆输出...nodeSelector:kubernetes.io/os: linuxlb.kubesphere.io/v1alpha1: openelb
...略去一堆输出...
扩容副本数量到 3。
复制
$ kubectl scale deployment openelb-manager --replicas=3 -n openelb-system
检查 deployment 状态
$ kubectl get po -n openelb-system -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
openelb-admission-create-fvfzk 0/1 Completed 0 78m 10.88.84.89 tiny-calico-worker-88-11.k8s.tcinternal <none> <none>
openelb-admission-patch-tlmns 0/1 Completed 1 78m 10.88.84.90 tiny-calico-worker-88-11.k8s.tcinternal <none> <none>
openelb-manager-6457bdd569-6n9zr 1/1 Running 0 62m 10.31.88.1 tiny-calico-master-88-1.k8s.tcinternal <none> <none>
openelb-manager-6457bdd569-c6qfd 1/1 Running 0 62m 10.31.88.12 tiny-calico-worker-88-12.k8s.tcinternal <none> <none>
openelb-manager-6457bdd569-gh995 1/1 Running 0 62m 10.31.88.11 tiny-calico-worker-88-11.k8s.tcinternal <none> <none>
至此就完成了 openelb-manager 的高可用部署改造。
参考资料:
https://tinychen.com/20220523-k8s-07-loadbalancer-openelb/
https://openelb.io/docs/overview/
https://blog.csdn.net/weixin_39246554/article/details/129386533
在删除 OpenELB 之前,必须确保openelb-system
命名空间下没有任何服务,特别是后面配置到eip时,必须先删除Eip
,再删除 OpenELB ,否则你会遇到无法删除Eip
的Bug,引用来源:https://kubesphere.com.cn/forum/d/2379-porter/8
https://blog.csdn.net/weixin_64334766/article/details/134794870
[root@k8s-master service]# cat service1.yaml
apiVersion: apps/v1
kind: Deployment
metadata:labels:name: my-nginxname: my-nginxnamespace: myns
spec:replicas: 3selector:matchLabels:name: my-nginx-deploytemplate:metadata:labels:name: my-nginx-deployspec:containers:- name: my-nginx-podimage: nginxports:- containerPort: 80---apiVersion: v1
kind: Service
metadata:name: my-nginx-servicenamespace: mynsannotations: #这三行详情也要添加,尤为重要lb.kubesphere.io/v1alpha1: openelbprotocol.openelb.kubesphere.io/v1alpha1: layer2 eip.openelb.kubesphere.io/v1alpha2: my-eip-pool #指定创建地址池时指定的名称
spec:ports:- port: 80protocol: TCPtargetPort: 80selector:app: my-nginx-deploytype: LoadBalancer #指定type为LoadBalancerLoadBalancerIP: number 这段执行错误 #是可以指定IP的