全网最细,深度解析 Istio Ambient Mesh 流量路径

简介: 本文旨在对 Istio Ambient Mesh 的流量路径进行详细解读,力求尽可能清晰地呈现细节,以帮助读者完全理解 Istio Ambient Mesh 中最为关键的部分。

1.前言

Istio Ambient Mesh 是 Istio 社区的推出的将 Sidecar 的能力抽离至 ztunnel 和 waypoint 的全新架构,同时基于 iptables 和策略路由实现了该架构下的流量规则,目前网络上已经有些资料对这部分的实现进行了一定程度的剖析(比如 Solo.io 推出的三篇系列文章),但仍然有很多细节尚没有任何文章提及。本文旨在对 Istio Ambient Mesh 的流量路径进行详细解读,力求尽可能清晰地呈现细节,以帮助读者完全理解 Istio Ambient Mesh 中最为关键的部分。阅读本文需要读者具备基本的 TCP/IP 协议知识和操作系统网络知识,包括 iptables 和路由等。鉴于篇幅限制,本文不会详细介绍这些基础概念,对于感兴趣的读者,建议自行查阅相关资料以深入了解。

1.1环境

我们使用一个最简化的场景来说明 Ambient Mesh 流量路径:从一个运行在 Node-A 上的 sleep Pod 中通过 curl 发起一个 HTTP 请求,请求的对象是集群中名为 httpbin 的服务。在该服务下有一个位于 Node-B 上的 httpbin Pod,由于服务(ClusterIP)在 K8s 中只是一个虚拟的 IP(它并不是任何真实存在的网络设备地址),所以在这个场景中,这个请求是直接发往 httpbin 应用 Pod 的,该场景的网络路径看起来会像是这样:

然而,在 Ambient Mesh 流量规则的影响下,实际上从 sleep Pod 发出的数据包需要经历一系列 iptables、策略路由被转发至 ztunnel 或 waypoint 之后才能到达对端的 httpbin 应用当中。这里为了在后续的篇幅中便于描述,我们需要先介绍一下在这一流程中的所有参与者,读者无需记住这些设备的地址和名称,此处列出只是为了方便查阅:

2.完整流量路径

sleep -> httpbin 的请求路径虽然看起来十分简单,然而在 Ambient Mesh 下,ztunnel 接管了所有的出入连接,从 sleep pod 发出的 ip 包都会被劫持到 ztunnel 的 15001 端口,经过 ztunnel 加密后再发出,所以事实上 sleep pod 中执行的 curl 发起的连接实际上是连接到了 ztunnel Pod 中的协议栈,同样地,httpbin Pod 接收到的连接实际上则是来自它所在 Node 上运行的 ztunnel,所以实际建立的 TCP 连接有 3 条:

  • sleep -> NodeA ztunnel
  • NodeA ztunnel -> NodeB ztunnel
  • NodeB ztunnel -> httpbin

由于 Ambient Mesh 是 4 层透明设计,虽然中间经过了 ztunnel 或是 waypoint proxy 的转发,但无论是 sleep Pod 中的 curl 应用还是 httpbin pod 中的 httpbin 应用都无法感知到中间代理的存在。为了达到这个目的,数据包在很多环节无法直接通过 DNAT 及 SNAT 进行转发,因为 DNAT 和 SNAT 动作会修改数据包的源/目标信息,Ambient Mesh 设计的这些规则很大程度是为了贯彻两端都透明的传输路径,为了能够对每一部分逐一讲解,我们需要再将上图进一步细化:

我们来解读一下这张图,笔者将整个路径分为三大部分,第一部分为 curl 到 ztunnelA,用红色连线表示;第二部分为 ztunnelA 到 ztunnelB,用蓝色线表示;第三部分为 ztunnelB 到 httpbin,用黄色线表示。此外,线的类型分为两种,两端没有箭头的连线表示这些设备是直接连接的,即数据包从这一头进入,就会从另一头出来,虽然两台宿主机 eth0 设备的连接方式与 Pod 和宿主机的连接方式不同,但这并不是本文的重点,我们也视为它是直连的即可,本文重点将解释有箭头的连线,对上面分出来的三部分路径逐一分析,阐明数据包在这部分路径上是如何被转发的。

2.1第一部分:curl 到 ztunnel-A

当我们运行 curl 发送请求时,curl 向 httpbin 服务发起连接,经过 DNS 解析后,我们的连接目标是 httpbin 服务的 ClusterIP(而不是 Pod),在 K8s 集群中,发往 Cluster IP 的数据包会被一系列 iptables 规则将目标地址转化为 PodIP 然后通过宿主机发出,然而 在Ambient Mesh 中,则需要先经过 ztunnel 加密后再发出,本节我们就先来看看数据包是怎样被路由到 ztunnel Pod 中的 ztunnel 进程的。

2.1.1 Sleep NS -> Node-A NS

由于 sleep Pod 中只有一个网络设备 eth0,它是 pod 的 veth pair 在 pod 网络命名空间内的一端,它的对端是宿主机网络命名空间里的 vethbda3de4b 设备,pod 与外部的所有网络通信无论是上下行都经由这个通道进行收发。在 Sidecar 模式下,为了将流量重定向到 Sidecar,pod 网络命名空间内有一系列 iptables 规则,然而在 Ambient 模式下,由于 Pod 内不再注入 Sidecar,所以 Pod 网络命名空间不再有任何 iptables 规则,所以这一阶段的流量路径与一般 K8s pod 无异,从该 Pod 网络协议栈发出的数据包,将都通过 Pod 唯一的网络设备 eth0 发出,然后从宿主机端的 veth 设备到达宿主机的网络命名空间。

2.1.2 Node-A NS -> ztunnelA NS

  • 首个上行数据包

数据包到达宿主机后需要被重定向至 ztunnel,然而这个路径上的首个数据包(TCP 的握手数据包)将和后面的数据包经由不同的路径被转发至 ztunnel,我们先从首个数据包开始。由于 sleep pod 中 curl 发起连接的对象是 httpbin 服务,经过 DNS 解析,其真正连接的目标是 httpbin 服务的 ClusterIP,所以,sleep pod 协议栈发出的三层数据包的源地址和目标地址将会是这样的:

K8s 为服务路由配置了 iptables 规则,以将服务地址 DNAT 为具体的 Endpoint 地址,为了跳过这个逻辑将数据包重定向到 ztunnel,Ambient Mesh CNI 为宿主机在 K8s 的规则之前添加了一些 iptables 规则和路由规则。数据包从 sleep pod 的 veth 到达宿主机后,开始执行 PREROUTING 链,我们来看一下 PREROUTING 链中的内容:

=== PREROUTING ===
    --- raw ---
    --- mangle ---
        -A PREROUTING -j ztunnel-PREROUTING
    --- nat ---
        -A PREROUTING -j ztunnel-PREROUTING
        -A PREROUTING -m comment --comment "kubernetes service portals" -j KUBE-SERVICES
        -A PREROUTING -d 172.18.0.1/32 -j DOCKER_OUTPUT

可以看到,Istio Ambient Mesh 在 mangle 表和 nat 表中加入了规则,强制跳转到 ztunnel-PREROUTING 链处理,由于是无条件跳转到 ztunnel-PREROUTING 链,所以非 Ambient Mesh 的 Pod 发出的数据包也会跳转到这个链处理,为了避免将网格外的 Pod 的流量错误地重定向到 ztunnel,在Pod 启动时,Ambient Mesh 的 CNI 会对其进行判定,若判定结果为启用 Ambient,则会将这个 Pod 的 IP 加入到宿主机网络命名空间下名为 ztunnel-pods-ips 的 ipset 中。在本文的场景中,sleep Pod 将被加入 Node-A 上名为 ztunnel-pods-ips 的 ipset,所以来自 sleep Pod 的数据包将命中 ztunnel-PREROUTING 链中的下列规则:(规则省略了与当前阶段无关的内容):

=== ztunnel-PREROUTING ===
    --- raw ---
    --- mangle ---
        ......
        -A ztunnel-PREROUTING -p tcp -m set --match-set ztunnel-pods-ips src -j MARK --set-xmark 0x100/0x100
    --- nat ---
        -A ztunnel-PREROUTING -m mark --mark 0x100/0x100 -j ACCEPT
    --- filter ---

在执行 mangle 表阶段,由于数据包的源地址(sleep Pod 地址)在 ztunnel-pods-ips 内,所以这条规则匹配成功,数据包将被打上 0x100 标记,打上 0x100 标记的数据包将在随后的 nat 表阶段直接被接受,这就使得数据包跳过了 K8s iptables 规则的处理。到目前为止,规则只是为数据包打上了一个标记,数据包的目标地址没有发生任何改变,要改变数据包的路由,还需要借助路由表来实现。PREROUTING 结束后,数据包会进入路由阶段,由于数据包被打上了 0x100 标记,所以在策略路由阶段,将会命中如下规则(省略了无关规则):

......
101:    from all fwmark 0x100/0x100 lookup 101
......

命中这条策略路由规则后,将对该数据包使用路由表 101,我们再来看一下 101 路由表的内容:

default via 192.168.127.2 dev istioout 
10.244.2.3 dev veth51e3b96d scope link

由于数据包的目标地址是 httpbin 服务的 Cluster IP,在本文使用的演示环境中,httpbin 服务的 Cluster IP 为 10.96.0.105,所以将命中这个路由表中的默认规则,这条规则的意思是:通过 istioout 设备发往网关 192.168.127.2。而 istioout 设备是一个 bridge 设备,其另一端就是 ztunnel Pod 内的 pistioout 设备,其地址便是 192.168.127.2,数据包应用这条路由规则后,将通过 istioout 设备离开宿主机,然后从 pistioout 到达 ztunnel Pod 网络命名空间。

数据包通过 pistioout 到达 ztunnel Pod 网络命名空间后,由于数据包的目标地址并非 ztunnel Pod 中的任何网络设备,所以如果不进行任何干预,这个数据包将被 ztunnel Pod 的协议栈丢弃。ztunnel 进程的 outbound 代理监听在 15001 端口上,为了将数据包正确地送达该端口,在 ztunnel Pod 内也设置了一些 iptables 规则和路由规则,数据包到达 ztunnel Pod 网络命名空间后,将命中如下规则:

=== PREROUTING ===
    --- raw ---
    --- mangle ---
        ......
        -A PREROUTING -i pistioout -p tcp -j TPROXY --on-port 15001 --on-ip 127.0.0.1 --tproxy-mark 0x400/0xfff
        ......
    --- nat ---
    --- filter ---

这条规则将从 pistioout 设备进入的数据包的目标地址重定为 127.0.0.1 的 15001 端口上,并为数据包打上 0x400 的标识,需要注意的是,这里虽然重定向了目标地址,但是 tproxy 会保留数据包的原始目标地址,应用程序可以通过 getsockopt 的 SO_ORIGINAL_DST 选项获得数据包的原始目标地址。iptables 规则执行完毕后,将执行策略路由,数据包将命中如下策略路由规则:

20000:  from all fwmark 0x400/0xfff lookup 100

该策略路由指定使用 100 路由表进行路由,我们再看一下 100 路由表的内容:

local default dev lo scope host

100 路由表只有一条规则,即通过 lo 设备发往本机协议栈,至此数据包进入 ztunnel Pod 本机协议栈,由于在 iptables 中将数据包重定向到 127.0.0.1:15001,所以数据包最终到达 ztunnel 进程监听的 15001 端口,从 curl 到 ztunnel 进程的首个数据包的路径已经梳理完毕:

  • 下行数据包

这一阶段的回程数据包也有一些特别的规则处理,当回程数据包通过 ztunnel 在宿主机上的 veth 进入宿主机时,它将命中如下规则:

-A ztunnel-PREROUTING ! -s 10.244.2.3/32 -i veth51e3b96d -j MARK --set-xmark 0x210/0x210
-A ztunnel-PREROUTING -m mark --mark 0x200/0x200 -j RETURN

这条规则的意义是:数据包不是来自 ztunnel Pod IP,但是却是从 ztunnel Pod 的 veth 进入宿主机的,就打上 0x210 标记。

为什么是这样的条件呢?因为 ztunnel 事实上是一个透明代理,也就是说 ztunnel 是在扮演对端的目标服务回复数据包,所以数据包中的源地址是对端服务地址,而不是 ztunnel Pod 的地址。

随后,由于数据包被打上了 0x210 标记,在路由阶段将命中宿主机上的如下策略路由规则。

100:    from all fwmark 0x200/0x200 goto 32766
......
32766:  from all lookup main

该路由规则指示通过 main 路由表进行路由,回程数据包的地址是 sleep Pod 地址,即 10.244.2.8,将命中 main 路由表中由 K8s 网络添加的规则:

10.244.2.8 dev vethbda3de4b scope host

该规则将数据包路由到 vethbda3de4b 设备,vethbda3de4b 是 sleep Pod 在宿主机端的 veth 设备,进入该设备的数据包将通过 sleep Pod 内的 eth0 设备进入 sleep Pod。

到这里还没有结束,确定路由后,由于该数据包的目标地址并非本机,所以将再执行 iptables FORWARD 链,在 FOWARD 链的 mangle 表中有如下规则:

-A FORWARD -j ztunnel-FORWARD

同样是无条件跳转到 ztunnel-FORWARD 链,在 ztunnel-FORWARD 链中,由于数据包带有 0x210 标识,将命中如下规则:

-A ztunnel-FORWARD -m mark --mark 0x210/0x210 -j CONNMARK --save-mark --nfmask 0x210 --ctmask 0x210

该规则将数据包上的标记保存到连接上,至此,这条连接上同样保存了 0x210 标记,属于该连接的数据包经过宿主机时,通过连接标记 0x210 将可以匹配到这条连接上的后续所有数据包。

  • 后续上行数据包

由于第一个回复数据包会使得这条连接被打上 0x210 标记,后续再从 sleep Pod veth 进入宿主机的上行数据包将在 PREROUTING 阶段命中如下规则:

-A ztunnel-PREROUTING ! -i veth51e3b96d -m connmark --mark 0x210/0x210 -j MARK --set-xmark 0x40/0x40

可以看到,这里通过 iptables 的 connmark 模块匹配连接标记,然后为该数据包再打上 0x40 标记,打上 0x40 标记的数据包将在随后的路由阶段命中如下规则:

102:    from all fwmark 0x40/0x40 lookup 102

随后使用 102 路由表进行路由:

default via 10.244.2.3 dev veth51e3b96d onlink
10.244.2.3 dev veth51e3b96d scope link

该路由表是 Ambient Mesh 创建的向 ztunnel Pod 路由的规则,由于数据包的目标地址是 httpbin 服务的 ClusterIP,所以这一阶段将命中 102 路由表中的默认路由规则,通过 veth51e3b96d 设备发往 ztunnel Pod。数据包进入 veth51e3b96d 设备后,会从 ztunnel Pod 的 eth0 设备进入 ztunnel Pod 网络命名空间,并命中如下 iptables 规则:

-A PREROUTING ! -d 10.244.2.3/32 -i eth0 -p tcp -j MARK --set-xmark 0x4d3/0xfff

该规则的条件是,如果数据包的目标地址不是 ztunnel Pod 地址,且从 eth0 设备进入,且协议为 TCP,则为数据包打上 0x4d3 标识,打上该标识后,数据包将在路由阶段命中如下规则:

20003:  from all fwmark 0x4d3/0xfff lookup 100

命中该策略路由后,将使用 100 路由表:

local default dev lo scope host

该路由表将数据包路由至 lo 设备,强制送入本机协议栈。

  • 总结

我们来简单总结一下,在从 Node-A 到 ztunnel 的这一阶段,首个数据包是从 istioout 接口离开宿主机,pistioout 接口进入 ztunnel Pod,而后续数据包由于连接打上了 0x210 标记的缘故,将通过 ztunnel Pod 的 veth 设备离开宿主机,再从 ztunnel Pod 的 eth0 设备进入 ztunnel Pod。

2.2第二部分:ztunnel-A 到 ztunnel-B

数据包到达 ztunnel 后,会进行 4 层负载均衡,关于这一部分可以参考我的这篇文章[1]。经过 4 层负载均衡之后,ztunnel 将目标服务 IP 转换为 Pod IP,由于零信任安全设计的缘故,ztunnel 需要先将数据转发给这个 Pod IP 所在节点的 ztunnel 而不是目标 Pod。Ambient Mesh 关于这一点的设计是,ztunnel 发出的数据包的目标地址是目标 Pod(也就是 httpbin Pod)的地址,这样以来就可以利用 K8s 的路由规则,使得这个数据包到达目标 Pod 所在的宿主机,在对端宿主机上,再通过一系列规则,将这个数据包路由到 ztunnel Pod,那么接下来这一节我们就来看看这条路径是如何实现的。

2.2.1 ztunnel-A Networking Namespace -> Node-A Networking Namespace

经过 4 层负载均衡,ztunnel 将向对端 Pod 的 15008 端口发起连接,之所以使用 15008 端口(而不是服务端口或者后端 Pod 端口)的原因是在对端 ztunnel 的 iptables 规则匹配时这个端口被用作了匹配条件,细节在第三部分将会讨论。ztunnel 发出的数据包首先需要从 ztunnel Pod 到达宿主机以便通过宿主机转发到对端 Node,ztunnel Pod 与 sleep Pod 一样,也有一对 veth pair 与宿主机连接,在 ztunnel Pod 网络命名空间下的设备名为 eth0,进入该设备的数据包会从宿主机端的 veth 设备进入宿主机网络命名空间,ztunnel 发出的数据包目标地址为 httpbin Pod 的地址,并且不会命中任何 ztunnel Pod 内的 iptables 规则,在路由阶段,该数据包将命中默认路由规则,将数据包通过 eth0 发往宿主机。

2.2.2 Node-A -> Node-B

数据包到达 NodeA 后,由于其目标地址是 httpbin Pod 的 IP 地址(10.244.1.17),所以将命中 K8s 在 main 路由表中添加的针对 pod 网段路由规则:

......
10.244.1.0/24 via 172.18.0.4 dev eth0
......

该规则表明去往 10.244.1.0/24 网段的数据包通过 eth0 设备发往网关 172.18.0.4(Node-B 地址),ztunnel Pod 发出的数据包为 httpbin Pod 地址 10.244.1.7,所以将命中这条规则,通过 eth0 设备离开 Node-A 去往 Node-B,最后通过 NodeB 的 eth0 设备进入 NodeB 网络命名空间:

2.2.3 Node-B Networking Namespace -> ztunnel-B Networking Namespace

  • 首个上行数据包

在 Inbound 阶段的节点到 ztunnel 的路径上,与 Outbound 阶段类似地,首个数据包(TCP 的握手数据包)将和后面的数据包经由不同的路径被转发至 ztunnel。同样先从首包开始分析,我们来看看 Node-B 的策略路由规则,由于这个数据包未携带任何标记,也不在 NodeB 的 ztunnel-pod-ips ipset 中,同时,数据包的目标地址是 Pod IP,也不会命中 K8s 网络的 iptables 规则,那么我们直接来看策略路由阶段:

0:      from all lookup local
100:    from all fwmark 0x200/0x200 goto 32766
101:    from all fwmark 0x100/0x100 lookup 101
102:    from all fwmark 0x40/0x40 lookup 102
103:    from all lookup 100
32766:  from all lookup main
32767:  from all lookup default

由于 local 路由表中都是本地路由规则所以无法命中,数据包没有任何标记,也不会命中 100、101、102,所以数据包将命中 103 规则,使用 100 路由表,接下来我们看看这个路由表的内容:

10.244.1.3 dev vethbcda8cd4 scope link 
10.244.1.5 via 192.168.126.2 dev istioin src 10.244.1.1 
10.244.1.6 via 192.168.126.2 dev istioin src 10.244.1.1
10.244.1.7 via 192.168.126.2 dev istioin src 10.244.1.1

100 路由表中为每一个在本节点上运行的 Pod 的 IP 地址都配置了一条路由规则,每一条规则都指向节点上的 istioin 设备,也就是说,使用该路由表进行路由时,只要数据包的目标地址是本机上的 Pod,则路由到 istioin 设备。本数据包的目标地址是 httpbin Pod 地址 10.244.1.7,则将命中第 4 行的路由规则:10.244.1.7 via 192.168.126.2 dev istioin src 10.244.1.1,从而进入 istioin 设备。我们在前面的讨论中提到过 istioout 设备,与之类似地,istioin 设备则用于处理 inbound 流量,其另一端为 ztunnel B Pod 中的 pistioin 设备。

数据包通过 pistioin 设备进入 ztunnel-B 后,同样还是因为目标地址是 httpbin Pod 的缘故,为了不被 ztunnel Pod 丢弃,也需要经过 iptables 规则的重定向,由于数据包的目标地址为 15008,所以该数据包将命中如下规则:

-A PREROUTING -i pistioin -p tcp -m tcp --dport 15008 -j TPROXY --on-port 15008 --on-ip 127.0.0.1 --tproxy-mark 0x400/0xfff

该规则匹配来自 pistioin 设备,协议为 tcp,且目标端口为 15008 的数据包,并将其通过 TPROXY 重定向到 127.0.0.1 的 15008 端口,同时为数据包打上 0x400 标记,数据包被打上标记后,将在策略路由阶段命中如下规则:

20000:  from all fwmark 0x400/0xfff lookup 100

该策略路由规则指示使用 100 路由表来路由数据包:

local default dev lo scope host

与 ztunnel-A 中类似,100 路由表只有一条路由规则,即将数据包路由到 lo 设备,强制使数据包进入本机协议栈,由于数据包的目标地址被 TPROXY 修改为 15008,所以数据包将最终到达 ztunnel 进程在 15008 端口上监听的 socket。至此,来自 ztunnel-A 的数据包成功到达 ztunnel 进程:

  • 下行数据包

当 ztunnel Pod 协议栈发出下行数据包后,数据包将通过 eth0 设备进入宿主机,在宿主机上将命中如下 iptables 规则:

-A ztunnel-PREROUTING ! -s 10.244.2.3/32 -i veth51e3b96d -j MARK --set-xmark 0x210/0x210

这条规则的匹配条件为,如果数据包的源地址不是 ztunnel Pod IP,但是来自 ztunnel Pod veth 设备,则打上 0x210 标记,与 Outbound 阶段一样,打上 0x210 标记的数据包都会使用 main 路由表进行路由:

100:    from all fwmark 0x200/0x200 goto 32766
......
32766:  from all lookup main

由于下行数据包的目标地址是 sleep Pod 地址(10.244.2.8),所以在 main 路由表中借助 K8s 网络的路由规则,发往 10.244.2.0/24 地址段的数据包将通过宿主机的 eth0 设备离开,被宿主机网络中的路由器路由到对端 pod 所在的宿主机上。

10.244.2.0/24 via 172.18.0.3 dev eth0

在确定路由后,命中 ztunnel-FORWARD 链中的如下规则:

-A ztunnel-FORWARD -m mark --mark 0x210/0x210 -j CONNMARK --save-mark --nfmask 0x210 --ctmask 0x210

与 Outbound 阶段在 Node-A 上一样,通过回复数据包为该连接打上了 0x210 标识,连接上的标识将使得后续数据包命中其他规则。

  • 后续上行数据包

由于连接打上了 0x210 标识,所以后续数据包进入 Node-B 后将命中如下 iptables 规则:

-A ztunnel-PREROUTING ! -i vethbcda8cd4 -m connmark --mark 0x210/0x210 -j MARK --set-xmark 0x40/0x40
-A ztunnel-PREROUTING -m mark --mark 0x200/0x200 -j RETURN

与 Node-A 上类似,如果数据包不是来自 ztunnel veth,且带有 0x210 连接标记,则为数据包打上 0x40,目的是让数据包在接下来的路由阶段使用 102 路由表进行路由:

102:    from all fwmark 0x40/0x40 lookup 102

102 路由表是通往 ztunnel-B 的路由表,数据包将命中默认路由规则,被路由到 vethbcda8cd4(即 ztunnel-B 在宿主机上的网络设备),从而进入 ztunnel Pod。

default via 10.244.1.3 dev vethbcda8cd4 onlink 
10.244.1.3 dev vethbcda8cd4 scope link

进入 ztunnel Pod 后的流程与 Inbound 阶段一致,这里不再赘述。

  • 总结

Node-B 上的 Inbound 阶段与 Node-A 上的 Outbound 阶段类似,在从 Node-B 到 ztunnel 的这一阶段,首个数据包是从 istioin 接口离开宿主机,pistioin 接口进入 ztunnel Pod,而后续数据包由于连接打上了 0x210 标记的缘故,将通过 ztunnel Pod 的 veth 设备离开宿主机,再从 ztunnel Pod 的 eth0 设备进入 ztunnel Pod。

2.3第三部分:ztunnel-B 到 httpbin

ztunnel-B 收到来自对端 ztunnel 的连接后,将立即建立一条通往目标 Pod 的 TCP 连接,以便将解密的数据通过这个连接发往目标 Pod,ztunnel-B 通过调用 getsockopt 使用 SO_ORIGINAL_DST 选项从连接获得到原始目标地址,并通过 ztunnel-A 发来的 HTTP CONNECT 握手消息获知真实目标端口,同时,为了让 httpbin Pod 认为数据包来自 sleep Pod,ztunnel 需要在用于连接到 httpbin 的 sockt 上通过 IP_TRANSPARENT 选项将连接的源地址强制设置为 sleep Pod 的地址(ztunnel 可以通过连接的源地址来得知 sleep Pod 的地址)。这样一来,从 ztunnelB 发出的数据包的源地址是 sleep Pod 地址,目标地址则是 httpbin Pod 地址,就像数据包真的是从 sleep Pod 发来的一样。接下来,我们看看这个数据包如何从 ztunnel Pod 离开,并最终到达 httpbin Pod。

2.3.1 ztunnel-B Netwoking Namespace -> Node-B Netwoking Namespace

由于发往 httpbin Pod 的数据包不会命中任何 ztunnel B Pod 内的 iptables 规则,所以 Pod 不会携带任何标签,该数据包将命中 ztunnel Pod 内的以下策略路由,使用 main 路由表。

0:      from all lookup local
20000:  from all fwmark 0x400/0xfff lookup 100
20003:  from all fwmark 0x4d3/0xfff lookup 100
32766:  from all lookup main
32767:  from all lookup default

main 路由表的内容如下:

default via 10.244.1.1 dev eth0 
10.244.1.0/24 via 10.244.1.1 dev eth0 src 10.244.1.3 
10.244.1.1 dev eth0 scope link src 10.244.1.3 
192.168.126.0/30 dev pistioin proto kernel scope link src 192.168.126.2 
192.168.127.0/30 dev pistioout proto kernel scope link src 192.168.127.2

由于 httpbin Pod 的地址为 10.244.1.7,所以将使用路由表中的默认规则 default via 10.244.1.1 dev eth0 进行路由,数据包将被送往 eth0 网络接口离开 ztunnel Pod,进入宿主机。

2.3.2 Node-B Netwoking Namespace -> httpbin Netwoking Namespace

数据包到达宿主机后,由于其目标地址是本机上的 Pod 的地址,所以需要避免其命中在前一阶段命中的将数据包重定向到 ztunnel Pod 的路由规则,Ambient Mesh 为此在 ztunnel-PREROUTING 链中添加了如下规则(如何跳转到 ztunnel-PREROUTING 链参考前文,这里不再赘述):

......
-A ztunnel-PREROUTING ! -s 10.244.1.3/32 -i vethbcda8cd4 -j MARK --set-xmark 0x210/0x210
......

vethbcda8cd4 即是 ztunnel B Pod 的 veth pair 在宿主机一端的设备,数据包进入 ztunnel Pod 的 eth0 设备后,正是通过这个设备到达宿主机,这条规则匹配的正是来自 ztunnel Pod veth 的数据包,命中这条规则的数据包将打上 0x210 的标识,在第二部分中,来自 ztunnel-A 的数据包命中了编号为 103 的策略路由规则,使用了 100 路由表从而被路由至 ztunnel Pod,但当前数据包由于存在 0x210 标记,所以会先命中编号为 100 的策略路由规则,最终使用 main 路由表进行路由:

0:      from all lookup local
100:    from all fwmark 0x200/0x200 goto 32766
101:    from all fwmark 0x100/0x100 lookup 101
102:    from all fwmark 0x40/0x40 lookup 102
103:    from all lookup 100
32766:  from all lookup main
32767:  from all lookup default

我们来看一下 main 路由表的内容:

default via 172.18.0.1 dev eth0 
10.244.0.0/24 via 172.18.0.2 dev eth0 
10.244.1.2 dev veth1eb71e57 scope host 
10.244.1.3 dev vethbcda8cd4 scope host 
10.244.1.5 dev veth6cba8664 scope host 
10.244.1.6 dev vetheee0622e scope host
10.244.1.7 dev vethfc1b555e scope host
10.244.2.0/24 via 172.18.0.3 dev eth0 
172.18.0.0/16 dev eth0 proto kernel scope link src 172.18.0.4 
192.168.126.0/30 dev istioin proto kernel scope link src 192.168.126.1

K8s 网络会在 main 路由表中插入当前节点上所有 pod 路由的条目,发往 Pod IP 的数据包将被路由到连接到 Pod 网络命名空间内的 veth 设备,这个发往 httpbin Pod 的数据包将命中第 7 行的路由规则,从而被路由到 vethfc1b555e 设备,然后数据包将从该设备的另一端 -- 也就是位于 httpbin Pod 内的 eth0 设备进入 httpbin Pod。至此,我们已经走完了数据包的完整路径。

3.结语

本文中笔者剖析了 Ambient Mesh 中从源 Pod 到目标 Pod 的完整路径,但限于篇幅,实际上还有一些细节在本文中尚未提及,非 Pod 间通讯的数据包路由等,笔者将在未来的分享中逐步分享这些内容。阿里云服务网格 ASM[2]在刚刚发布的 1.18 版本中也正式支持了 Ambient 模式,作为业界首个支持 Ambient 模式的全托管服务网格,ASM 对部分常见网络插件进行了适配,并提供了 Ambient 模式下路由规则、安全规则等相应的配套文档,欢迎各位读者前来体验。

相关链接:

[1] Istio Ambient Mesh 四层负载均衡实现剖析

[2] 阿里云服务网格 ASM

作者介绍
目录