k8s系列14-calico开启eBPF

本文最后更新于:January 17, 2023 pm

本文主要介绍如何在calico集群上开启eBPF加速网络数据转发,同时会对eBPF及其在calico中的一些优势特点进行介绍。

关于本次使用的calico集群的部署过程可以参考之前的文章k8s系列13-calico部署BGP模式的高可用k8s集群

此前写的一些关于k8s基础知识和集群搭建的一些方案,有需要的同学可以看一下。

1、eBPF

1.1 关于eBPF

eBPF是一项革命性的技术,起源于 Linux 内核,可以在操作系统内核中运行沙盒程序。它用于安全有效地扩展内核的功能,而无需更改内核源代码或加载内核模块。

它允许将小程序加载到内核中,并附加到钩子上,这些钩子在某些事件发生时被触发。这甚至允许大量定制内核的行为。虽然 eBPF 虚拟机对于每种类型的钩子都是相同的,但钩子的功能却有很大差异。因为将程序加载到内核中可能很危险,所以内核通过非常严格的静态验证器运行所有程序;静态验证器对程序进行沙盒处理,确保它只能访问允许的内存部分,并确保它必须迅速终止。

eBPF is a Linux kernel feature that allows fast yet safe mini-programs to be loaded into the kernel in order to customise its operation.

1.2 eBPF的优势

ebpf的几个优势:

  • 更高的吞吐IO
  • 更低的CPU资源占用
  • 无需kube-proxy即可对K8S服务实现原生支持
    • 更低的首个数据包延迟
    • 外部请求可保留客户端源IP(Preserves external client source IP)
    • 支持 DSR (Direct Server Return)
    • 相比kube-proxy,ebpf数据面同步转发规则时占用的资源更少

更多的信息可以查看calico官方的这篇介绍文章

2、calico配置eBPF

2.1 升级内核

我们使用的centos7系统,根据文档比较适合的内核版本需要大于5.8,因此我们直接使用elrepo源升级最新的6.1.4版本内核。

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
36
37
38
39
# 查看elrepo源中支持的内核版本
[root@k8s-calico-master-10-31-90-1 ~]# yum --disablerepo="*" --enablerepo="elrepo-kernel" list available
Loaded plugins: fastestmirror
Loading mirror speeds from cached hostfile
elrepo-kernel | 3.0 kB 00:00:00
elrepo-kernel/x86_64/primary_db | 2.1 MB 00:00:00
Available Packages
elrepo-release.noarch 7.0-6.el7.elrepo elrepo-kernel
kernel-lt.x86_64 5.4.228-1.el7.elrepo elrepo-kernel
kernel-lt-devel.x86_64 5.4.228-1.el7.elrepo elrepo-kernel
kernel-lt-doc.noarch 5.4.228-1.el7.elrepo elrepo-kernel
kernel-lt-headers.x86_64 5.4.228-1.el7.elrepo elrepo-kernel
kernel-lt-tools.x86_64 5.4.228-1.el7.elrepo elrepo-kernel
kernel-lt-tools-libs.x86_64 5.4.228-1.el7.elrepo elrepo-kernel
kernel-lt-tools-libs-devel.x86_64 5.4.228-1.el7.elrepo elrepo-kernel
kernel-ml.x86_64 6.1.4-1.el7.elrepo elrepo-kernel
kernel-ml-devel.x86_64 6.1.4-1.el7.elrepo elrepo-kernel
kernel-ml-doc.noarch 6.1.4-1.el7.elrepo elrepo-kernel
kernel-ml-headers.x86_64 6.1.4-1.el7.elrepo elrepo-kernel
kernel-ml-tools.x86_64 6.1.4-1.el7.elrepo elrepo-kernel
kernel-ml-tools-libs.x86_64 6.1.4-1.el7.elrepo elrepo-kernel
kernel-ml-tools-libs-devel.x86_64 6.1.4-1.el7.elrepo elrepo-kernel
perf.x86_64 5.4.228-1.el7.elrepo elrepo-kernel
python-perf.x86_64 5.4.228-1.el7.elrepo elrepo-kernel



# 看起来ml版本的内核比较满足我们的需求,直接使用yum进行安装
sudo yum --enablerepo=elrepo-kernel install kernel-ml -y
# 使用grubby工具查看系统中已经安装的内核版本信息
sudo grubby --info=ALL
# 设置新安装的6.1.4版本内核为默认内核版本,此处的index=0要和上面查看的内核版本信息一致
sudo grubby --set-default-index=0
# 查看默认内核是否修改成功
sudo grubby --default-kernel
# 重启系统切换到新内核
init 6
# 重启后检查内核版本是否为新的6.1.4
uname -a

确认系统内核升级成功后我们继续下一步

1
2
3
4
5
6
7
8
9
10
11
12
13
$ ansible calico -m shell -a "uname -rv"
10.31.90.4 | CHANGED | rc=0 >>
6.1.4-1.el7.elrepo.x86_64 #1 SMP PREEMPT_DYNAMIC Wed Jan 4 18:17:10 EST 2023
10.31.90.5 | CHANGED | rc=0 >>
6.1.4-1.el7.elrepo.x86_64 #1 SMP PREEMPT_DYNAMIC Wed Jan 4 18:17:10 EST 2023
10.31.90.3 | CHANGED | rc=0 >>
6.1.4-1.el7.elrepo.x86_64 #1 SMP PREEMPT_DYNAMIC Wed Jan 4 18:17:10 EST 2023
10.31.90.2 | CHANGED | rc=0 >>
6.1.4-1.el7.elrepo.x86_64 #1 SMP PREEMPT_DYNAMIC Wed Jan 4 18:17:10 EST 2023
10.31.90.1 | CHANGED | rc=0 >>
6.1.4-1.el7.elrepo.x86_64 #1 SMP PREEMPT_DYNAMIC Wed Jan 4 18:17:10 EST 2023
10.31.90.6 | CHANGED | rc=0 >>
6.1.4-1.el7.elrepo.x86_64 #1 SMP PREEMPT_DYNAMIC Wed Jan 4 18:17:10 EST 2023

2.2 配置API Server

在默认情况下,calico是通过kube-proxy和集群内的apiserver进行通信的。当我们开启了ebpf之后,往往会关闭集群的kube-proxy功能,此时为了保证calico能够和apiserver进行通信,就需要我们先手动配置一个稳定可用的地址。一般来说,使用我们初始化集群的时候配置的VIP和端口即可。

1
2
3
4
5
6
7
8
9
10
11
12
$ cat kubernetes-services-endpoint.yaml
kind: ConfigMap
apiVersion: v1
metadata:
name: kubernetes-services-endpoint
namespace: tigera-operator
data:
KUBERNETES_SERVICE_HOST: "k8s-calico-apiserver.tinychen.io"
KUBERNETES_SERVICE_PORT: "8443"

$ kubectl create -f kubernetes-services-endpoint.yaml
configmap/kubernetes-services-endpoint created

更新配置后检查pod是否重启成功,以及集群是否正常

1
2
$ watch kubectl get pods -n calico-system
$ calicoctl node status

2.3 配置kube-proxy

因为ebpf会和kube-proxy冲突,比较好的方式是禁用掉kube-proxy。而禁用kube-proxy主要有两种方式,一种是直接将其对应的daemonset删除,另一种则是通过nodeSelector的方式将特定的node设置为不运行kube-proxy。从官方的文档来看两种方式各有优势,如果是初始化k8s集群的时候就直接使用ebpf的话可以考虑直接在初始化参数中将kube-proxy禁用,这里我们已经安装好了kube-proxy,则使用nodeSelector的方式控制会更为优雅。

1
$ kubectl patch ds -n kube-system kube-proxy -p '{"spec":{"template":{"spec":{"nodeSelector":{"non-calico": "true"}}}}}'

此时我们再查看集群的kube-proxy状态可以发现DESIREDCURRENT均为0

1
2
3
$ kubectl get ds -n kube-system kube-proxy
NAME DESIRED CURRENT READY UP-TO-DATE AVAILABLE NODE SELECTOR AGE
kube-proxy 0 0 0 0 0 kubernetes.io/os=linux,non-calico=true 4d9h

2.4 配置eBPF

开启eBPF只需要修改linuxDataplane参数即可,注意eBPF模式不支持配置hostPorts,因此需要同时将其设置为空。

1
2
$ kubectl patch installation.operator.tigera.io default --type merge -p '{"spec":{"calicoNetwork":{"linuxDataplane":"BPF", "hostPorts":null}}}'
installation.operator.tigera.io/default patched

等待calico滚动重启完成,即成功开启eBPF。注意在滚动重启的过程中,会存在部分node使用eBPF而部分node使用iptables的情况。

2.5 配置DSR

eBPF还有一项不错的功能称之为DSR(Direct Server Return),即pod在接收到外部的请求之后,直接由pod本身对客户端进行回包,而不是再经由原来的路径回包,这样可以有效缩短回包路径从而提升性能。

DSR模式的工作效果和LVS的DR模式十分相似,但是一般来说,DSR需要pod和客户端之间的网络本身是正常联通的,所以默认情况下该项功能并没有开启。

我们可以使用calicoctl来修改felixconfiguration中的bpfExternalServiceMode参数,将其从默认的Tunnel修改为DSR即可启用。

1
2
3
# 开启DSR模式
$ calicoctl patch felixconfiguration default --patch='{"spec": {"bpfExternalServiceMode": "DSR"}}'
Successfully patched 1 'FelixConfiguration' resource

如果需要关闭DSR模式只需要逆向修改回去即可

1
2
# 关闭DSR模式
$ calicoctl patch felixconfiguration default --patch='{"spec": {"bpfExternalServiceMode": "Tunnel"}}'

3、检验eBPF

3.1 calico-bpf

官方在calico-node中内置了calico-bpf来帮助我们排查定位eBPF模式下的一些问题,我们也可以用它来检测集群的eBPF是否工作正常。

对应的ds/calico-node也可以替换为某个特定node上面的calico-node的pod名称

1
2
3
4
5
6
7
8
# 查看calico-bpf工具使用说明
$ kubectl exec -n calico-system ds/calico-node -- calico-node -bpf
# 查看eth0网卡计数器
$ kubectl exec -n calico-system ds/calico-node -- calico-node -bpf counters dump --iface=eth0
# 查看conntrack情况
$ kubectl exec -n calico-system ds/calico-node -- calico-node -bpf conntrack dump
# 查看路由表
$ kubectl exec -n calico-system ds/calico-node -- calico-node -bpf routes dump

3.2 访问服务

我们在集群外的机器进行测试,分别访问podIP 、clusterIP和loadbalancerIP,查看是否能够正确返回客户端的IP地址10.31.100.100

1
2
3
4
5
6
7
8
9
10
11
# 访问pod IP
root@tiny-unraid:~# curl 10.33.26.2
10.31.100.100:43240

# 访问clusterIP
root@tiny-unraid:~# curl 10.33.151.137
10.31.100.100:52758

# 访问loadbalancerIP
root@tiny-unraid:~# curl 10.33.192.0
10.31.100.100:7319

在配置了eBPF的情况下,从集群外访问clusterIP和loadbalancerIP都是能够正常返回客户端的IP地址的。

4、关闭eBPF

关闭eBPF的操作也是非常简单,只需要逆向操作上面的过程即可,这里直接关闭calico的eBPF功能(DSR只在eBPF下生效),再开启k8s集群的kube-proxy即可。

1
2
3
4
# 设置linuxDataplane为iptables模式
$ kubectl patch installation.operator.tigera.io default --type merge -p '{"spec":{"calicoNetwork":{"linuxDataplane":"Iptables"}}}'
# 关闭nodeSelector,开启k8s集群的kube-proxy
$ kubectl patch ds -n kube-system kube-proxy --type merge -p '{"spec":{"template":{"spec":{"nodeSelector":{"non-calico": null}}}}}'

配置完成后,等待全部节点的calico重启完成并且kube-proxy启动完成即可。