Kubernetes / Linux Note / 运维笔记

Merbridge 开启 eBPF 加速 Istio

Einic Yeo · 2月4日 · 2022年 · · · · ·

Merbridge 项目开源上线,只需要在 Istio 集群执行一条命令,即可直接使用 eBPF 代替 iptables,实现网络加速!


一、Merbridge

以 Istio 为首的服务网格技术,正在被越来越多的企业所瞩目。Istio 使用 Sidecar 借助 iptables 技术实现流量拦截,可以处理所有应用的出入口流量,以实现流量治理、观测、加密等能力。

然而,使用 iptables 的技术,需要对出入口都拦截,会让原本只需在内核态处理两次的链路,变成四次,造成大量的性能损失,这对一些性能要求高的场景有明显的影响。

但是正如 “一切问题都是时间问题” 所说,随着时间推移,新技术不断兴起,解决方案也随之而来。

近两年,由于 eBPF 技术的兴起,不少围绕 eBPF 的项目应运而成。eBPF 在可观测性和网络包的处理方面也出现了不少优秀的案例,如 Cilium、px.dev 等项目。「DaoCloud 道客」作为第一家正式获准加入 eBPF 基金会的中国公司,非常重视操作系统底层技术 eBPF 带来的革命性改变,深度参与 eBPF 的技术发展与研究应用,重新定义操作系统的底层逻辑。

在研究和应用 版权声明:本文遵循 CC 4.0 BY-SA 版权协议,若要转载请务必附上原文出处链接及本声明,谢谢合作!eBPF 技术过程中,「DaoCloud 道客」云原生技术工程师发现借助 eBPF 的 sockops 和 redir 能力,可以高效地处理数据包,通过结合实际生产场景,实现了用 eBPF 代替 iptables 为 Istio 进行加速,Merbridge 项目由此诞生。

现在,我们开源了 Merbridge 项目,只需要在 Istio 集群执行以下一条命令,即可直接使用 eBPF 代替 iptables 实现网络加速!

kubectl apply -f https://raw.githubusercontent.com/merbridge/merbridge/main/deploy/all-in-one.yaml

注意:当前仅支持在 5.7 版本及以上的内核下运行,请事先升级您的内核版本。

利用 eBPF 的 sockops 进行性能优化

网络连接本质上是 socket 之间的通讯,eBPF 提供了一个 bpf_msg_redirect_hash (https://man7.org/linux/man-pages/man7/bpf-helpers.7.html)函数,用来将应用发出的包直接转发到对端的 socket,可以极大地加速包在内核中的处理流程。

这里 sock_map 是记录 socket 规则的关键部分,即根据当前的数据包信息,从 sock_map 中挑选一个存在的 socket 连接来转发请求。所以需要先在 sockops 的 hook 处或者其它地方,将 socket 信息保存到 sock_map,并提供一个规则 (一般为四元组) 根据 key 查找到 socket。

二、Merbridge的实现原理

下文将按照实际的场景,逐步介绍 Merbridge 详细的设计和实现原理,这将让您对 Merbridge 和 eBPF 有一个初步的了解。

Istio 基于 iptables 的原理

如图所示,当外部流量访问应用的端口时,会在 iptables 中被 PREROUTING 拦截,最后转发到 Sidecar 容器的 15006 端口,然后交给 Envoy 来进行处理 (图中 1-2-3-4 的红色路径)。

Envoy 根据从控制平面下发的规则进行处理,处理完成后,会发送请求给实际的容器端口。

当应用想要访问其它服务时,会在 iptables 中被 OUTPUT 拦截,然后转发给 Sidecar 容器的 15001 端口由 Envoy 监听 (图中 9-10-11-12 的红色路径),与入口流量的处理差不多。

由此可以看到,原本流量可以直接到应用端口,但是通过 iptables 转发到 Sidecar,然后又让 Sidecar 发送给应用,这种方式无疑增加了开销。虽然 iptables 在很多情况下是通用的,但是它的通用性决定了它的性能并不总是很理想,因此它不可避免地会在不同的过滤规则下,给整个链路增加延迟

如果使用 sockops 将 Sidecar 直接连接到应用的 Socket,就可以使流量不经过 iptables,加速处理流程,明显提高性能。

三、出口流量处理

如上所述,我们希望使用 eBPF 的 sockops 来绕过 iptables 以加速网络请求,同时希望能够完全适配社区版 Istio,所以需要先模拟 iptables 所做的操作。

iptables 本身使用 DNAT 功能做流量转发,想要用 eBPF 模拟 iptables 的能力,就需要使用 eBPF 实现类似 iptables DNAT 的能力。

这里主要有两个要点:

1. 修改连接发起时的目的地址,让流量能够发送到新的接口;

2. 让 Envoy 能识别原始的目的地址,以能够版权声明:本文遵循 CC 4.0 BY-SA 版权协议,若要转载请务必附上原文出处链接及本声明,谢谢合作!识别流量。

版权声明:本文遵循 CC 4.0 BY-SA 版权协议,若要转载请务必附上原文出处链接及本声明,谢谢合作!

对于第一点,可以使用 eBPF 的 connect 程序修改 user_ip 和 user_port 实现。

对于第二点,需要用到 ORIGINAL_DST 的概念,这在 Linux 内核中是 netfilter 模块专属的。

其原理为:应用程序 (包括 Envoy) 在收到连接之后调用 get_sockopts 函数,获取 ORIGINAL_DST。如果经过了 iptables 的 DNAT,那么 iptables 就会给当前的 socket 设置 ORIGINAL_DST 这个值,并把原有的 IP + 端口写入这个值,应用程序就可以根据连接拿到原有的目的地址。

那么我们就需要通过eBPF 的 get_sockopt函数来修改这个调用 (不用bpf_setsockopt的原因是目前这个参数并不支持 SO_ORIGINAL_DST 的 optname)。

如上图所示,在应用向外发起请求时,会经过如下阶段: 

1. 在应用向外发起连接时,connect 程序会将目标地址修改为 127.x.y.z:15001,并用 cookie_original_dst 保存原始目的地址。

2. 在 sockops 程序中,将当前 sock 和四元组保存在 sock_pair_map 中。同时,将四元组信息和对应的原始目的地址写入 pair_original_dst 中 (之所以不用 cookie,是因为 get_sockopt 函数无法获取当前 cookie)。

3. Envoy 收到连接之后会调用 getsockopt 获取当版权声明:本文遵循 CC 4.0 BY-SA 版权协议,若要转载请务必附上原文出处链接及本声明,谢谢合作!前连接的目的地址,get_sockopt 函数会根据四元组信息从 pair_original_dst 取出原始目的地址并返回,由此完全建立连接。

4. 在发送数据阶段,redir 程序会根据四元组信息,从 sock_pair_map 中读取 sock,然后通过 bpf_msg_redirect_hash 进行直接转发,加速请求。

其中,之所以在 connect 时,修改目的地址为 127.x.y.z 而不是 127.0.0.1,是因为在不同的 Pod 中,可能产生冲突的四元组,使用此方式即可巧妙地避开冲突 (每个 Pod 间的目的 IP 不同,不会出现冲突的情况)。

四、入口流量处理

入口流量处理基本和出口流量类似,唯一的区别是需要将目的地址端口改成 15006。 

但是需要注意,由于 eBPF 不像 iptables 能在指定命名空间生效,它是全局的,这就造成如果针对一个本来不是 Istio 管理的 Pod 或者一个外部的 IP 地址,也进行了修改端口的操作,那就会引起严重问题,会让请求无法建立连接。

所以这里设计了一个小的控制平面 (以 DaemonSet 方式部署) Watch 所有的 Pod,类似于 kubelet 那样获取当前节点的 Pod 列表,将已经注入 Sidecar 的 Pod IP 地址写入 local_pod_ips 这个 map。

当我们在做入口流量处理时,如果目的地址不在这个列表之中,就不做处理,让它走原来的逻辑,这样就可以比较灵活且简单地处理入口流量。 

其他流程和出口流量流程一样。

五、同节点加速

通过入口流量处理,理论上可以直接加速同节点的 Envoy 到 Envoy 速度。但这个场景存在一个问题,Envoy 访问当前 Pod 的应用时会出错。 

在 Istio 中,Envoy 访问应用的方式是使用当前 PodIP 加服务端口。经版权声明:本文遵循 CC 4.0 BY-SA 版权协议,若要转载请务必附上原文出处链接及本声明,谢谢合作!过上述入口流量处理后,我们会发现由于 PodIP 也存在于 local_pod_ips 中,那么这个请求会被转发到 PodIP + 15006 端口,这显然是不行的,会造成无限递归。

这样我们就无法在 eBPF 中获取当前 ns 的 IP 地址信息,怎么办?

为此,我们设计了一套反馈机制:

即在 Envoy 尝试建立连接时,还是会走重定向到 15006 端口,但是在 sockops 阶段会判断源 IP 和目的地址 IP 是否一致。如果一致,代表发送了错误的请求,那么我们会在 sockops 丢弃这个连接,并将当前的 ProcessID 和 IP 地址信息写入 process_ip 这个 map,让 eBPF 支持进程与 IP 的对应关系。当下次发送请求时,直接从 process_ip 表检查目的地址是否与当前 IP 地址一致。

Envoy 会在请求失败时重试,且这个错误只会发生一次,后续的连接会非常快。

六、连接关系

在没有使用 Merbridge (eBPF)优化之前,Pod 到 Pod 间的访问如下图所示:

在使用 Merbridge (eBPF)优化之后,出入口流量会直接跳过很多内核模块,明显提高性能

同时如果两个 Pod 在同一台机器上,那么 Pod 之间的通讯将更加高效:

如上所述,通过使用 eBPF 在主机上对相应的连接进行处理,可以大幅度地减少 Linux 内核处理流量的流程,提升服务之间的通讯质量。

七、Merbridge的加速效果

使用 eBPF 代替 iptables 之后整体延迟的情况 (越低越好):

使用 eBPF 代替 iptables 之后整体 QPS 的情况 (越高越好):

以上数据使用 wrk 测试得出

八、Merbridge 项目广邀各路豪杰

以上介绍的都是 Merbridge 项目的核心能力,其通过使用 eBPF 代替 iptables,可以在服务网格场景下,完全无感知地对流量通路进行加速。同时,不会对现有的 Istio 做任何修改,原有的逻辑依然畅通。这意味着,如果以后不再使用 eBPF,那么可以直接删除掉 DaemonSet,改为传统的 iptables 方式后,也不会出现任何问题。Merbridge 是一个完全独立的开源项目,目前还处于早期阶段。我们希望有更多的用户或开发者参与其中,优化各组件的技术能力,推动服务网格发展壮大。

项目地址:https://github.com/merbridge/merbridge

社区交流:https://join.slack.com/t/merbridge/shared_invite/zt-11uc3z0w7-DMyv42eQ6s5YUxO5mZ5hwQ

0 条回应