前言
calico团队在3.13版本引入了eBPF,这个新的dataplane与传统的linux dataplane有哪些区别呢?
- 它可以扩展到更高的吞吐量。
- 每个GBit使用更少的CPU。
- 它具有对Kubernetes服务的本机支持(无需kube-proxy),该支持:
- 减少服务数据包的第一个数据包延迟。
- 一直保留到外部主机的外部客户端源IP地址。
- 支持DSR(直接服务器返回),以实现更高效的服务路由。
- 与kube-proxy相比,使用更少的CPU来保持数据平面同步。
此外,社区对新dataplane进行了性能测试,在短连接延时、服务访问时间、cpu使用率等方面,性能都有明显提升,详见:https://www.pianshen.com/article/28912006312/
本文主要目的是通过分析calico源码,弄明白eBPF是如何被引入到calico中的。
calico引入了多个eBPF hook点,本文重点分析connect_time_loadbalancer,即上面讲到的第一条支持:“减少服务数据包的第一个数据包延迟”
相关的工作是在calico的felix项目中实现的
工作流程
入口处:felix/dataplane/linux/int_dataplane.go 的NewIntDataplaneDriver()函数,进行dataplane的初始化
首先会判断是否开启了BPF,如果是开启状态,则进行以下操作:
1)注册 map manager,该manager的作用是负责管理ebpf的map(map用于userspace和kernel之间进行数据的共享)
2)注册endpoint manager,该manager的作用是负责各种ep的管理,包括host、workload等
3)创建各种map,比如nat的frontendMap、backendMap、routeMap、conntrackMap等
4)开启kube-proxy,注意此kube-proxy并非kubernetes的kube-proxy,而是proxy的一个封装,负责和kubernetes通信,维护各种map中的信息
5)若BPFConnTimeEnabled开启,则安装connect_time_loadbalancer,即加载相关的eBPF程序
6)启动dataplane(这部分暂不涉及connect_time_loadbalancer,本文暂不分析)
下面我们重点看第5)步代码是如何实现的
代码分析(加载eBPF程序)
入口 bpf/nat/connecttime.go的InstallConnectTimeLoadBalancer()函数
1 | func InstallConnectTimeLoadBalancer(frontendMap, backendMap, rtMap bpf.Map, cgroupv2 string, logLevel string) error { |
进入installProgram()函数,看如何加载程序
1 | func installProgram(name, ipver, bpfMount, cgroupPath, logLevel string, maps ...bpf.Map) error { |
下图是加载attach bpf程序后,用户进程调用socket api时的路径。在建立connect、recvms、sendmsg时都会经过这个程序进行处理。而这个ebpf程序所做的工作就是:判断是否要访问的是k8s service,如果是的话,直接将请求转发到后端的pod上,这样就不在需要做nat了,节省了所有的nat开销。即实现了:“减少服务数据包的第一个数据包延迟”
接下来看看bpf程序是如何实现的,共实现三个section bpf-gpl/connect_balancer.c
1 | __attribute__((section("calico_connect_v4"))) //section 1:在建立connection时,做nat转发,将请求转发至后端的pod |
实际的nat操作是do_nat_common()来做的
1 | static CALI_BPF_INLINE void do_nat_common(struct bpf_sock_addr *ctx, uint8_t proto) |