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 的技术发展与研究应用,重新定义操作系统的底层逻辑。

在研究和应用 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 根据从控制平面下发的规则进行处理,处理完成后,会发送请求给实际的容器端口。

当应用想要访问其它版权声明:本文遵循 CC 4.0 BY-SA 版权协议,若要转载请务必附上原文出处链接及本声明,谢谢合作!服务时,会在 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 能识别原始的目的地址,以能够识别流量。

对于第一点,可以使用 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)。

如上图所示,在应用向外发起请求时,会经过如下阶段版权声明:本文遵循 CC 4.0 BY-SA 版权协议,若要转载请务必附上原文出处链接及本声明,谢谢合作!: 

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 获取当前连接的目的地址,get_sockopt 函数会根据四元组信息从 pair_original_dst 取出原始目的地址并返回,由此完全建立连接。

4. 在发送数据阶段,redir 程序会根据四元组信息,从 sock版权声明:本文遵循 CC 4.0 BY-SA 版权协议,若要转载请务必附上原文出处链接及本声明,谢谢合作!_pair_map 中读取 sock,然后通过 bpf_msg_redirect_hash 进行直接转发,加速请求。

其中,之所以在 connect 时,修改目的地址为 127.x.y.z 而不是 127.0.0.1,是因为在不同的 Pod 中,可能产生冲突的四元组,使用此方式即可巧妙地避开冲突 (每个 Pod 间的目的 IP 版权声明:本文遵循 CC 4.0 BY-SA 版权协议,若要转载请务必附上原文出处链接及本声明,谢谢合作!;不同,不会出现冲突的情况)。

四、入口流量处理

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

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

所以版权声明:本文遵循 CC 4.0 BY-SA 版权协议,若要转载请务必附上原文出处链接及本声明,谢谢合作!这里设计了一个小的控制平面 (以 DaemonSet 方式部署) Watch 所有的 Pod,类似于 kubelet 那样获取当前节点的 Pod 列表,将已经注入 Sidecar 的 Pod IP 地址写入 local_pod_ips 这个 map。

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

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

五、同节点加速

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

在 Istio 中,Envoy 访问应用的方式是使用当前 PodIP 加服务端口。经过上述入口流量处理后,我们会发现由于 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 条回应