KubeCon 2021|使用 eBPF 代替 iptables 优化服务网格数据面性能

2022年01月14日 阅读数:3
这篇文章主要向大家介绍KubeCon 2021|使用 eBPF 代替 iptables 优化服务网格数据面性能,主要内容包括基础应用、实用技巧、原理机制等方面,希望对大家有所帮助。

做者

刘旭,腾讯云高级工程师,专一容器云原生领域,有多年大规模 Kubernetes 集群管理及微服务治理经验,现负责腾讯云服务网格 TCM 数据面产品架构设计和研发工做。linux

引言

目前以 Istio[1] 为表明的服务网格广泛使用 Sidecar 架构,并使用 iptables 将流量劫持到 Sidecar 代理,优势是对应用程序无侵入,可是 Sidecar 代理会增长请求时延和资源占用。git

性能一直是用户十分关心的一个点,也是用户评估是否使用服务网格产品的关键因素,腾讯云 TCM 团队一直致力于优化服务网格性能,上周咱们在 KubeCon 分享了使用 eBPF 代替 iptables 优化服务网格数据面性能的方案。github

iptables 实现流量劫持

首先看一下当前社区使用的基于 iptables 的流量劫持方案,下图是一个 Pod 的建立过程,sidecar injector 会向 Pod 中注入两个容器,istio-init 和 istio-proxy后端

  • istio-init 是一个 init container,负责建立流量劫持相关的 iptables 规则,在建立完成后会退出安全

  • istio-proxy 中运行着 envoy,负责代理 Pod 的网络流量,iptables 会将请求劫持到 istio-proxy 处理性能优化

下图展现了 iptables 完成流量劫持的整个过程,这里简单说明下,感兴趣的同窗能够查看[2]cookie

  • Inbound iptables 将入流量重定向到 15006 端口,也就是 envoy 的 VirtualInboundListener,envoy 会根据请求的原始目的地址转发到应用程序的指定端口网络

  • Outbound iptables 将出流量重定向到 15001 端口,也就是 envoy 的 VirtualOutboundListener,envoy 会根据请求的原始目的地址以及 Host URL 等信息路由到指定后端架构

eBPF 实现流量劫持

eBPF(extended Berkeley Packet Filter) 是一种能够在 Linux 内核中运行用户编写的程序,而不须要修改内核代码或加载内核模块的技术,目前被普遍用于网络、安全、监控等领域。在 Kubernetes 社区最先也是最有影响的基于 eBPF 项目是 Cilium[4],Cilium 使用 eBPF 代替 iptables 优化 Service 性能。socket

Inbound

首先来看一下对入流量的劫持,对入流量的劫持主要使用 eBPF 程序 hook bind 系统调用完成。

eBPF 程序会劫持 bind 系统调用并修改地址,例如应用程序 bind 0.0.0.0:80 会被修改成 127.0.0.1:80,应用程序还有可能 bind ipv6 的地址,因此这里有两个 eBPF 程序分别处理 ipv4 和 ipv6 的 bind。

和 iptables 不一样,iptables 能够针对每一个 netns 单独设置规则,eBPF 程序 attach 到指定 hook 点后,会对整个系统都生效,例如 attach 到 bind 系统调用后,全部 Pod 内以及节点上进程调用 bind 都会触发 eBPF 程序,咱们须要区分哪些调用是来自须要由 eBPF 完成流量劫持的 Pod。

在 K8s 中,除了 hostnetwork 的状况,每一个 Pod 都有独立的 netns,而每一个 netns 都有惟一的 cookie,所以咱们将须要使用 eBPF 完成流量劫持的 Pod 对应的 netns cookie 保存在 cookie_map 中,eBPF 程序经过判断当前 socket 的 netns cookie 是否在 cookie_map 中来决定是否修改 bind 地址。

修改应用程序的 bind 地址后,还须要下发 pod_ip:80 listener 配置到 envoy,pod_ip:80 listener 会将请求转发到 127.0.0.1:80 也就是应用程序监听的地址,这样就实现了对入流量的劫持。可是这里有一个问题,因为 istio 使用 istio-proxy 用户启动 envoy,默认状况下非 root 用户不能 bind 1024 如下的特权端口,咱们经过 istio-init 修改内核参数 sysctl net.ipv4.ip_unprivileged_port_start=0 解决了这个问题。

对比 iptables 和 eBPF 对入流量的劫持,iptables 方案每一个包都须要 conntrack 处理,而 eBPF 方案只有在应用程序调用 bind 时执行一次,以后不会再执行,减小了性能开销。

Outbound

再来看一下对出流量的劫持,对出流量的劫持比较复杂,根据协议分为 TCP 和 UDP 两种状况。

TCP 流量劫持

对 TCP 的出流量劫持过程:

  • _coonect4 经过劫持 connect 系统调用将目的地址修改成127.0.0.1:15001,也就是 envoy 的 VirtualOutboundListerer,同时将链接的原始目的地址保存在 sk_storage_map

  • 在 TCP 链接创建完成后,sockops 会读取 sk_storage_map 中的数据,并以四元组(源IP、目的IP、源端口、目的端口)为 key 将原始目的地址保存在 origin_dst_map

  • _getsockopt经过劫持 getsockopt 系统调用,读取 origin_dst_map 中的数据将原始目的地址返回给 envoy

UDP 流量劫持

istio 在 1.8 版本支持了智能 DNS 代理[5],开启后 iptables 会将 DNS 请求劫持到 Sidecar 处理,咱们也须要用 eBPF 实现相同逻辑,对于 TCP DNS 的劫持和上面相似,对 UDP DNS 的劫持见下图

对 UDP 的出流量劫持过程:

  • _connect4_sendmsg4 都是负责修改 UDP 的目的地址为 127.0.0.1:15053 并保存原始的目的地址到 sk_storage_map,由于 Linux 提供两种发送 UDP 数据的方式
    • 先调用 connect 再调用 send,这种状况由 _connect4 处理
    • 直接调用 sendto,这种状况由 _sendmsg4 处理
  • recvmsg4 经过读取 sk_storage_map 将回包的源地址改成原始的目的地址,这是由于有些应用程序,例如 nslookup 会校验回包的源地址。

对于 TCP 和 connected UDP,iptables 方案每一个包都须要 conntrack 处理,而eBPF 方案的开销是一次性的,只须要在 socket 创建时执行一次,下降了性能开销。

Sockmap

使用 sockmap 优化服务网格性能的方案最先由 cilium 提出,咱们的方案也参考了 cilium,这里借用 cilium 的两张图来讲明下优化效果

优化前 Sidecar 代理与应用程序间的网络通讯都须要通过 TCP/IP 协议栈处理

优化后 Sidecar 代理与应用程序间的网络通讯绕过了 TCP/IP 协议栈,若是两个 Pod 在同一节点上,两个 Pod 间的网络通讯也能够被优化。这里简单说明下 sockmap 的优化原理,感兴趣的同窗能够查看[6][7]。

  • sock_hash 是一个存储 socket 信息的 eBPF map,key 是四元组(源IP、目的IP、源端口、目的端口)
  • _sockops 负责监听 socket 事件,并将 socket 信息保存在 sock_hash
  • _sk_msg 会拦截 sendmsg 系统调用,而后到 sock_hash 中查找对端 socket,若是找到会调用 bpf_msg_redirect_hash直接将数据发送给对端 socket

问题

可是用四元组作为 key 可能会存在冲突的问题,例如在同一节点上的两个 Pod 中,envoy 使用同一源端口 50000 请求应用程序的 80 端口。

为了解决这个问题,咱们在 key 中添加了 netns cookie,同时对于非 localhost 的请求将 cookie 设置为 0,这样既保证了 key 不会冲突,又能够加速同一节点上两个 Pod 间的网络通讯。

可是以前版本的内核不支持在 sockopssk_msg 这两种 eBPF 程序中获取 netns cookie 信息,所以咱们提交了两个 patch [8 ][9]到内核社区,目前已合入 5.15 版本。

架构

整个方案的架构如图所示,istio-ebpf 以 DaemonSet 的形式运行在节点上,负责 load/attach eBPF 程序和建立 eBPF map。istio-init 容器仍然保留,可是再也不建立 iptables 规则,而是更新 eBPF map,istio-init 会将 Pod 的 netns cookie 保存在 cookie_map 中。同时咱们也修改了 istiod,istiod 会根据 Pod 的流量劫持模式(iptables/eBPF)下发不一样的 xDS 配置。

性能对比

测试环境:Ubuntu 21.04 5.15.7

  • 同等条件下,使用 eBPF 可减小 20% 的 System CPU 占用
  • 同等条件下,使用 eBPF 可提升 20% QPS
  • 同等条件下,使用 eBPF 可下降请求时延

总结

服务网格的 Sidecar 架构不可避免的会增长请求时延和资源占用,咱们经过使用 eBPF 代替 iptables 实现流量劫持,同时使用 sockmap 加速 Sidecar 代理和应用程序间的网络通讯,在必定程度上下降了请求时延和资源开销,因为内核版本等限制这一方案预计会在明年初上线,TCM 团队将持续探索新的性能优化方向。

Reference

[1] https://istio.io

[2] https://jimmysong.io/blog/sidecar-injection-iptables-and-traffic-routing

[3] https://ebpf.io

[4] https://cilium.io

[5] https://istio.io/latest/blog/2020/dns-proxy

[6] https://arthurchiao.art/blog/socket-acceleration-with-ebpf-zh

[7] https://github.com/cilium/cilium/tree/v1.11.0/bpf/sockops

[8] https://git.kernel.org/pub/scm/linux/kernel/git/bpf/bpf-next.git/commit/?id=6cf1770d

[9] https://git.kernel.org/pub/scm/linux/kernel/git/bpf/bpf-next.git/commit/?id=fab60e29f

关于咱们

更多关于云原生的案例和知识,可关注同名【腾讯云原生】公众号~

福利:

①公众号后台回复【手册】,可得到《腾讯云原生路线图手册》&《腾讯云原生最佳实践》~

②公众号后台回复【系列】,可得到《15个系列100+篇超实用云原生原创干货合集》,包含Kubernetes 降本增效、K8s 性能优化实践、最佳实践等系列。

③公众号后台回复【白皮书】,可得到《腾讯云容器安全白皮书》&《降本之源-云原生成本管理白皮书v1.0》

【腾讯云原生】云说新品、云研新术、云游新活、云赏资讯,扫码关注同名公众号,及时获取更多干货!!