V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
macrokefeng
V2EX  ›  推广

Merbridge 项目开源上线!一行代码:开启 eBPF,代替 iptables,加速 Istio

  •  
  •   macrokefeng · 2022-02-10 18:53:52 +08:00 · 3408 次点击
    这是一个创建于 1020 天前的主题,其中的信息可能已经有所发展或是发生改变。

    图片

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


    01\

    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 。

    02

    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 能识别原始的目的地址,以能够识别流量。

    对于第一点,可以使用 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 获取当前连接的目的地址,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 加服务端口。经过上述入口流量处理后,我们会发现由于 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 内核处理流量的流程,提升服务之间的通讯质量。

    03

    Merbridge  的加速效果

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

    图片

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

    图片

    以上数据使用 wrk 测试得出。

    04

    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

    微信社群:

    图片

    扫码添加微信

    备注 [ Merbridge ] ,加入讨论群


    本文作者 

    图片

    刘齐均

    「 DaoCloud 道客」资深工程师

    Istio 社区成员,Kubernetes 的贡献者,CKA 认证


    参考文档:

    2 条回复    2022-02-11 14:59:03 +08:00
    lyhiving
        1
    lyhiving  
       2022-02-11 04:31:46 +08:00 via Android
    单向加速吗?如果是应用场景大
    vakara
        2
    vakara  
       2022-02-11 14:59:03 +08:00
    @lyhiving 是的。
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   4532 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 23ms · UTC 10:11 · PVG 18:11 · LAX 02:11 · JFK 05:11
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.