本地 K8s 集群使用 OpenELB 实现 LoadBalancer 负载均衡

一、概述

  • 在本地 K8s 环境为 Ingress 控制器配置一个 LoadBalaner 类型的 Service,需要部署一个支持该服务类型的负载均衡器

1. OpenELB 简介

  • 在社区中目前最流行的应该是 MetalLB 这个项目,现在也属于 CNCF 沙箱项目,该项目在 2017 年底发起,经过 4 年的发展已经在社区被广泛采用
  • OpenELB 是国内青云开源的另外一个负载均衡器,之前叫 PorterLB,是为物理机(Bare-metal)、边缘(Edge)和私有化环境设计的负载均衡器插件,可作为 Kubernetes、K3s、KubeSphere 的 LB 插件对集群外暴露 LoadBalancer 类型的服务,现阶段是 CNCF 沙箱项目,核心功能包括:
    • 基于 BGP 与 Layer 2 模式的负载均衡
    • 基于路由器 ECMP 的负载均衡
    • IP 地址池管理
    • 使用 CRD 进行 BGP 配置

openelb

2. 与 MetaLB 对比

  • OpenELB 作为后起之秀,采用了更加 Kubernetes-native 的实现方式,可以直接通过 CRD 进行配置管理,下面是关于 OpenELB 与 MetaLB 的简单对比

(1) 云原生架构

  • 在 OpenELB 中,不管是地址管理,还是 BGP 配置管理,都可以使用 CRD 来配置。对于习惯了 Kubectl 的用户而言,OpenELB 十分友好,在 MetalLB 中,需通过 ConfigMap 来配置,感知它们的状态需要通过查看监控或者日志

(2) 灵活的地址管理

  • OpenELB 通过 EIP 这个自定义资源对象来管理地址,它定义子资源 Status 来存储地址分配状态,这样就不会存在分配地址时各副本发生冲突的情况

(3) 使用 gobgp 发布路由

  • 不同于 MetalLB 自己实现 BGP 协议,OpenELB 采用标准的 gobgp 来发布路由,这样做的好处如下:
    • 开发成本低,且有 gobgp 社区支持
    • 可以利用 gobgp 丰富特性
    • 通过 BgpConf/BgpPeer CRD 动态配置 gobgp,用户无需重启 OpenELB 即可动态加载最新的配置信息
    • gobgp 作为 lib 使用时,社区提供了基于 protobuf 的 API,OpenELB 在实现 BgpConf/BgpPeer CRD 时也是参照该 API,并保持兼容
    • OpenELB 也提供 status 用于查看 BGP neighbor 配置,状态信息丰富

(4) 架构简单,资源占用少

  • OpenELB 目前只用部署 Deployment 即可,通过多副本实现高可用,部分副本崩溃后并不会影响已建立的正常连接
  • BGP 模式下,Deployment 不同副本都会与路由器建立连接用于发布等价路由,所以正常情况下我们部署两个副本即可。在 Layer 2 模式下,不同副本之间通过 Kubernetes 提供的 Leader Election 机制选举 Leader,进而应答 ARP/NDP

二、安装

  • 在 Kubernetes 集群中,只需要安装一次 OpenELB,第一次安装的时候还会为 admission webhook 配置 https 证书
  • 安装完成后,集群中会安装一个 openelb-manager Deployment,其中包含一个 openelb-manager Pod,该 Pod 为整个 Kubernetes 集群实现了 OpenELB 的功能
  • 安装完成后,可以扩展 openelb-manager Deployment,将多个 OpenELB 副本(openelb-manager Pods)分配给多个集群节点,保证高可用。有关详细信息,请参阅配置多个 OpenELB 副本
  • 使用 Helm 安装 OpenELB:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
$ helm repo add kubesphere-stable https://charts.kubesphere.io/stable
$ helm repo update
$ kubectl create ns openelb-system
$ helm install openelb kubesphere-stable/openelb -n openelb-system

$ kubectl get po -n openelb-system
NAME READY STATUS RESTARTS AGE
openelb-admission-create-2v8mw 0/1 Completed 0 36m
openelb-admission-patch-g6p7p 0/1 Completed 2 36m
openelb-keepalive-vip-khp79 1/1 Running 0 24m
openelb-keepalive-vip-pw27b 1/1 Running 0 24m
openelb-manager-7cf895f6bd-7n7tg 1/1 Running 0 24m
$ kubectl get validatingwebhookconfiguration
NAME WEBHOOKS AGE
openelb-admission 1 37m
$ kubectl get crd | grep kubesphere
bgpconfs.network.kubesphere.io 2023-10-09T07:28:37Z
bgppeers.network.kubesphere.io 2023-10-09T07:28:37Z
eips.network.kubesphere.io 2023-10-09T07:28:37Z

三、配置

  • 要使用 layer2 模式的 OpenELB,首先需要保证所有 Kubernetes 集群节点必须在同一个二层网络(在同一个路由器下)。节点信息如下所示:
1
2
3
4
5
$ kubectl get nodes -o wide
NAME STATUS ROLES AGE VERSION INTERNAL-IP EXTERNAL-IP OS-IMAGE KERNEL-VERSION CONTAINER-RUNTIME
debian201 Ready control-plane 2d6h v1.28.2 10.4.7.201 <none> Debian GNU/Linux 11 (bullseye) 5.10.0-25-amd64 containerd://1.7.6
debian202 Ready <none> 2d6h v1.28.2 10.4.7.202 <none> Debian GNU/Linux 11 (bullseye) 5.10.0-25-amd64 containerd://1.7.6
debian203 Ready <none> 2d6h v1.28.2 10.4.7.203 <none> Debian GNU/Linux 11 (bullseye) 5.10.0-25-amd64 containerd://1.7.6
  • 首先需要为 kube-proxy 启用 strictARP,以便 Kubernetes 集群中的所有网卡停止响应其他网卡的 ARP 请求,而由 OpenELB 处理 ARP 请求
1
2
3
4
5
6
$ kubectl edit configmap kube-proxy -n kube-system
ipvs:
strictARP: true

# 重启kube-proxy组件
$ kubectl rollout restart daemonset kube-proxy -n kube-system
  • 如果安装 OpenELB 的节点有多个网卡,则需要指定 OpenELB 在二层模式下使用的网卡,如果节点只有一个网卡,则可以跳过此步骤
  • 假设安装了 OpenELB 的 master1 节点有两个网卡(eth0 192.168.0.2 和 ens33 192.168.0.111),并且 eth0 192.168.0.2 将用于 OpenELB,那么需要为 master1 节点添加一个 annotation 来指定网卡:
1
$ kubectl annotate nodes master1 layer2.openelb.kubesphere.io/v1alpha1="192.168.0.2"
  • 接下来就可以创建一个 Eip 对象来充当 OpenELB 的 IP 地址池了,创建一个如下所示的资源对象:
1
2
3
4
5
6
7
8
9
apiVersion: network.kubesphere.io/v1alpha2
kind: Eip
metadata:
name: eip-pool
spec:
address: 10.4.7.210-10.4.7.219
protocol: layer2
disable: false
interface: ens32
  • address 属性用来指定 IP 地址池,可以填写一个或多个 IP 地址(要注意不同 Eip 对象中的 IP 段不能重叠),将被 OpenELB 使用。值格式可以是:

    • IP 地址,例如 192.168.0.100
    • IP 地址/子网掩码,例如 192.168.0.0/24
    • IP 地址 1-IP 地址 2,例如 192.168.0.91-192.168.0.100
  • protocol 属性用来指定 Eip 对象用于哪种 OpenELB 模式,可以配置为 layer2 或 bgp,默认为 bgp 模式。这里使用 layer2 模式,所以需要显示指定 interface 是用来指定 OpenELB 监听 ARP 或 NDP 请求的网卡,该字段仅在协议设置为 layer2 时有效

  • disable 表示是否禁用 Eip 对象

  • 创建完成 Eip 对象后可以通过 Status 来查看该 IP 池的具体状态:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
$ kubectl get eip
NAME CIDR USAGE TOTAL
eip-pool 10.4.7.210-10.4.7.219 10
$ kubectl get eip eip-pool -oyaml
apiVersion: network.kubesphere.io/v1alpha2
kind: Eip
metadata:
annotations:
kubectl.kubernetes.io/last-applied-configuration: |
{"apiVersion":"network.kubesphere.io/v1alpha2","kind":"Eip","metadata":{"annotations":{},"name":"eip-pool"},"spec":{"address":"10.4.7.210-10.4.7.219","disable":false,"interface":"ens32","protocol":"layer2"}}
creationTimestamp: "2023-10-09T08:16:47Z"
finalizers:
- finalizer.ipam.kubesphere.io/v1alpha1
generation: 2
name: eip-pool
resourceVersion: "182002"
uid: 15817d3f-742b-42bd-b9c1-8ca36c38a515
spec:
address: 10.4.7.210-10.4.7.219
interface: ens32
protocol: layer2
status:
firstIP: 10.4.7.210
lastIP: 10.4.7.219
poolSize: 10
ready: true
v4: true
  • 到这里 LB 的地址池就准备好了,接下来创建一个 LoadBalancer 类型的服务,通过 LB 来进行暴露,如下所示:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
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:
- containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: nginx
annotations:
lb.kubesphere.io/v1alpha1: openelb
protocol.openelb.kubesphere.io/v1alpha1: layer2
eip.openelb.kubesphere.io/v1alpha2: eip-pool
spec:
selector:
app: nginx
type: LoadBalancer
ports:
- name: http
port: 80
targetPort: 80
  • 注意这里为 Service 添加了几个 annotations 注解:
    • lb.kubesphere.io/v1alpha1: openelb 用来指定该 Service 使用 OpenELB
    • protocol.openelb.kubesphere.io/v1alpha1: layer2 表示指定 OpenELB 用于 Layer2 模式
    • eip.openelb.kubesphere.io/v1alpha2: eip-pool 用来指定了 OpenELB 使用的 Eip 对象,如果未配置此注解,OpenELB 会自动使用与协议匹配的第一个可用 Eip 对象,此外也可以删除此注解并添加 spec:loadBalancerIP 字段(例如 spec:loadBalancerIP: 10.4.7.211)以将特定 IP 地址分配给 Service
  • 创建完成后可以看到 Service 服务被分配了一个 EXTERNAL-IP,然后就可以通过该地址来访问 nginx 服务了
1
2
3
4
5
6
$ kubectl get svc nginx
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
nginx LoadBalancer 10.97.141.215 10.4.7.210 80:32608/TCP 55s

$ curl 10.4.7.210
<title>Welcome to nginx!</title>
  • 此外 OpenElb 还支持 BGP 模式以及集群多路由的场景,更新使用方法可以查看官方文档 https://openelb.github.io/docs/ 了解更多相关信息

参考