作者 青鸟

随着Istio Ambient Beta的发布,一种新的流量拦截方案也随之问世。但是相关的文章还都是停留在对于Alpha的讨论,对于新版的探讨还很少。最近的工作中也在寻找一种理想的流量转发方案,在研究了Istio Ambient Beta的实现后,感觉具有一定的参考意义。于是乎便有了本篇博客,也就是从实验的角度,看看Beta版的实现流量拦截与转发的功能实现。

同时本文主要讨论的是istio Ambient中,四层流量的代理方案,即ztunnel代理pod进出流量,七层的方案为waypoint,方案较为复杂,之后再讨论。

四层代理架构

Istio Ambient Alpha 版的重点是在有限的配置和环境下证明 Ambient 数据平面模式的价值。 然而,当时的条件十分有限。Ambient 模式依赖于透明地重定向在工作负载 Pod 和 ztunnel 之间的流量, 然而,最初为此使用的机制与多种第三方容器网络接口(CNI)实现相冲突。现用户希望能够在 minikube 和 Docker Desktop 上使用 Ambient 模式, 希望使用 Cilium 和 Calico 等 CNI 实现, 还希望能够支持在 OpenShift 和 Amazon EKS 等使用内部 CNI 实现上运行的服务。 在各种场景下广泛支持 Kubernetes 已成为 Ambient 网格进阶至 Beta 的首要需求,也就是说人们期望 Istio 能够在任意 Kubernetes 平台和任何 CNI 实现中工作。 毕竟,如果 Ambient 不能随处可用,那么 Ambient 就不能被称为 Ambient!

Istio Ambient Alpha 版中的流量拦截方案依赖的是node主机的iptables。在主机网络命名空间中重定向流量的根本问题在于, 这正是集群的主流 CNI 实现必须配置流量路由/网络规则的地方。 这造成了不可避免的冲突,最关键的是:

  1. 主流 CNI 实现的基本主机级网络配置可能会干扰 Istio 的 CNI 扩展的主机级 Ambient 网络配置,从而导致流量中断和其他冲突。
  2. 如果用户部署了由主流 CNI 实现强制执行的网络策略, 则在部署 Istio CNI 扩展时可能不会强制执行网络策略(取决于主流 CNI 实现如何执行 NetworkPolicy)

因此社区提出了一种新的流量拦截方案,即in-Pod的流量拦截,也就是通过cni在pod创建的时候在pod内部实现流量拦截转发到主机上的ztunnel中。详细的解决方案可以参考社区的文章

关于四层流量代理的概览可以参考下面的图片,很清晰的描述了流量在istio网络中的流动

在开始之前,我们需要了解一下istio所使用的几项技术。

HBONE

HBONE(HTTP Based Overlay Network Encapsulation,基于 HTTP 的覆盖网络封装) 是 Istio 组件之间使用的安全隧道协议。 HBONE 是 Istio 特定的术语。它是一种通过单个 mTLS 加密网络连接(加密隧道) 透明地多路复用与许多不同应用程序连接相关的 TCP 流的机制。

HBONE 隧道的一个重要特性是可以透明地代理原始应用程序请求, 而无需以任何方式改变底层应用程序流量流。 这意味着有关连接的元数据可以传送到目标代理,而无需更改应用程序请求 - 例如, 无需将 Istio 特定的标头附加到应用程序流量中。

详见HBONE文档

tproxy

tproxy 是 Linux 内核自 2.2 版本以来支持的透明代理(Transparent proxy),其中的 t 代表 transparent,即透明。你需要在内核配置中启用 NETFILTER_TPROXY 和策略路由。通过 tproxy,Linux 内核就可以作为一个路由器,将数据包重定向到用户空间。

详见 tproxy 文档

实验

实验环境的设置使用的是kind,具体见下面

1
2
3
4
kubectl get nodes -owide
NAME                  STATUS   ROLES           AGE    VERSION   INTERNAL-IP   EXTERNAL-IP   OS-IMAGE                         KERNEL-VERSION    CONTAINER-RUNTIME
istio-control-plane   Ready    control-plane   4d2h   v1.30.0   172.26.0.2    <none>        Debian GNU/Linux 12 (bookworm)   6.3.13-linuxkit   containerd://1.7.15
istio-worker          Ready    <none>          4d2h   v1.30.0   172.26.0.3    <none>        Debian GNU/Linux 12 (bookworm)   6.3.13-linuxkit   containerd://1.7.15

详细的创建流程可以见文档,这里就放一张流程图

ipsets

通过 ipset 命令可以看到 worker 中创建了一个istio-inpod-probes-v4 ipset,该 ipset 是一个 ip 地址的集合,其中包含了该 node 上所有被 ambient 模式管理的应用 pod IP 地址。istio-cni 会 watch node 上的 pod 事件,更新该 ipset 中的 ip 地址。与实际的pod比较,可以发现是一致的

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
docker exec -it istio-worker ipset list
Name: istio-inpod-probes-v4
Type: hash:ip
Revision: 0
Header: family inet hashsize 1024 maxelem 65536
Size in memory: 918
References: 1
Number of entries: 6
Members:
10.244.1.19
10.244.1.23
10.244.1.20
10.244.1.21
10.244.1.22
10.244.1.24

kubectl get pods -o wide
NAME                                      READY   STATUS    RESTARTS      AGE    IP            NODE           
details-v1-65cfcf56f9-dptdh               1/1     Running   1 (66m ago)   4d2h   10.244.1.21   istio-worker  
productpage-v1-d5789fdfb-h5tkf            1/1     Running   1 (66m ago)   4d2h   10.244.1.19   istio-worker  
ratings-v1-7c9bd4b87f-kkl7p               1/1     Running   1 (66m ago)   4d2h   10.244.1.22   istio-worker   
reviews-v1-6584ddcf65-5g7lq               1/1     Running   1 (66m ago)   4d2h   10.244.1.23   istio-worker  
reviews-v2-6f85cb9b7c-6qm9v               1/1     Running   1 (66m ago)   4d2h   10.244.1.20   istio-worker  
reviews-v3-6f5b775685-pjddr               1/1     Running   1 (66m ago)   4d2h   10.244.1.24   istio-worker 

iptables创建

在监听到相关的pod创建事件后,istio-cni 节点代理进入 Pod 的网络命名空间, 并在 Pod 网络命名空间内建立网络重定向规则,以便拦截进入和离开 Pod 的数据包, 并将其透明地重定向到侦听已知端口(15008、15006、15001)的节点本地 ztunnel 代理实例。

0x111和0x539 通常用作对特定连接的标记,这样可以在 iptables 规则中识别和处理这些连接。如果一个连接的流量被标记为 0x111,通常意味着这个连接经历了特殊的处理,比如被 Istio 代理的流量,根据规则直接允许通过。规则中有对 0x539 的检查,只有非 0x539 绑定的流量才会受到重定向等处理。

在下面的规则中,在istio网络中为什么需要透明代理,也是我们首先需要搞清的问题。在拦截入站流量后,会使用127.0.0.1作为源地址,将流量转发给本地服务进程。本地服务进程看不到真实源IP地址。很多应用场景下,真实源IP地址是必须的,例如zookeeper的选举等。所以使用透明代理就可以有效规避这个问题,获取到真实的ip。

下面为创建的规则:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
kubectl debug $(kubectl get pod -l app=details -A -o jsonpath='{.items[0].metadata.name}') -it --image gcr.io/istio-release/base --profile=netadmin -- iptables-save
Defaulting debug container name to debugger-np99q.
# Generated by iptables-save v1.8.10 on Fri Oct  4 16:53:09 2024
*nat
:PREROUTING ACCEPT [1:60]                     # PREROUTING 链,允许所有数据包通过,初始状态接受 1 个数据包,总字节数为 60 。
:INPUT ACCEPT [1:60]                          # INPUT 链,允许所有来自网络的数据包,初始状态接受 1 个数据包,总字节数为 60 。
:OUTPUT ACCEPT [0:0]                          # OUTPUT 链,允许所有本地生成的数据包,初始状态没有接受数据包。
:POSTROUTING ACCEPT [1:60]                    # POSTROUTING 链,允许所有从网络接口发出的数据包,初始状态接受 1 个数据包,总字节数为 60 。
:ISTIO_OUTPUT - [0:0]                         # 定义一个名为 ISTIO_OUTPUT 的自定义链,初始包计数为 0 。

# 将所有经过 OUTPUT 链的数据包发送到 ISTIO_OUTPUT 链进行处理。
-A OUTPUT -j ISTIO_OUTPUT

# 允许目标为 169.254.7.127 的 TCP 流量直接通过 ISTIO_OUTPUT 链。
-A ISTIO_OUTPUT -d 169.254.7.127/32 -p tcp -m tcp -j ACCEPT

# 允许所有标记为 0x111 的 TCP 数据包直接通过 ISTIO_OUTPUT 链。
-A ISTIO_OUTPUT -p tcp -m mark --mark 0x111/0xfff -j ACCEPT

# 允许所有非环回地址(不指向 127.0.0.1)的流量通过 ISTIO_OUTPUT 链。
-A ISTIO_OUTPUT ! -d 127.0.0.1/32 -o lo -j ACCEPT

# 对于不指向环回地址的 TCP 数据包,如果未标记为 0x539,则将其重定向到本地 port 15001 。
-A ISTIO_OUTPUT ! -d 127.0.0.1/32 -p tcp -m mark ! --mark 0x539/0xfff -j REDIRECT --to-ports 15001

COMMIT                                         # 提交以上配置的更改到 iptables 。
# Completed on Fri Oct  4 16:53:09 2024
# Generated by iptables-save v1.8.10 on Fri Oct  4 16:53:09 2024
*mangle
:PREROUTING ACCEPT [0:0]                     # PREROUTING 链,默许所有数据包通过。
:INPUT ACCEPT [132:11656]                     # INPUT 链,允许本地的输入数据包。
:FORWARD ACCEPT [0:0]                          # FORWARD 链,允许转发的数据包。
:OUTPUT ACCEPT [137:12233]                     # OUTPUT 链,允许本地生成的数据包。
:POSTROUTING ACCEPT [137:12233]                # POSTROUTING 链,允许所有从接口发出的数据包。
:ISTIO_OUTPUT - [0:0]                          # 自定义链 ISTIO_OUTPUT,初始计数为 0。
:ISTIO_PRERT - [0:0]                           # 自定义链 ISTIO_PRERT,初始计数为 0。

# 将所有经过 PREROUTING 链的数据包发送到 ISTIO_PRERT 链进行处理。
-A PREROUTING -j ISTIO_PRERT

# 将所有经过 OUTPUT 链的数据包发送到 ISTIO_OUTPUT 链进行处理。
-A OUTPUT -j ISTIO_OUTPUT

# 如果连接标记为 0x111,则恢复连接的标记。
-A ISTIO_OUTPUT -m connmark --mark 0x111/0xfff -j CONNMARK --restore-mark --nfmask 0xffffffff --ctmask 0xffffffff

# 如果数据包被标记为 0x539,记录该连接的标记并将其改为 0x111 。
-A ISTIO_PRERT -m mark --mark 0x539/0xfff -j CONNMARK --set-xmark 0x111/0xfff

# 允许源 IP 地址为 169.254.7.127 的 TCP 流量直接通过。
-A ISTIO_PRERT -s 169.254.7.127/32 -p tcp -m tcp -j ACCEPT

# 允许来自回环接口的所有 TCP 数据包,但不允许目标为环回地址(127.0.0.1)的流量。
-A ISTIO_PRERT ! -d 127.0.0.1/32 -i lo -p tcp -j ACCEPT

# 如果目标端口是 15008 且未标记为 0x539,则使用 TPROXY 将流量重定向到该端口。
-A ISTIO_PRERT -p tcp -m tcp --dport 15008 -m mark ! --mark 0x539/0xfff -j TPROXY --on-port 15008 --on-ip 0.0.0.0 --tproxy-mark 0x111/0xfff

# 允许与已建立的连接相关的 TCP 流量。
-A ISTIO_PRERT -p tcp -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT

# 对于不指向环回地址且未标记为 0x539 的 TCP 流量,将其重定向到 TPROXY 的端口 15006 。
-A ISTIO_PRERT ! -d 127.0.0.1/32 -p tcp -m mark ! --mark 0x539/0xfff -j TPROXY --on-port 15006 --on-ip 0.0.0.0 --tproxy-mark 0x111/0xfff

Unix套接字创建

然后,istio-cni 节点代理通过 Unix 域套接字通知节点 ztunnel, 它应该在 Pod 的网络命名空间内建立本地代理侦听端口 (在 15008、15006 和 15001 上),并为 ztunnel 提供低等级 Linux 文件描述符用来表示 Pod 的网络命名空间。下面为创建的套接字:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
kubectl debug $(kubectl get pod -l app=details -A -o jsonpath='{.items[0].metadata.name}') -it  --image nicolaka/netshoot  -- ss -ntlp
Defaulting debug container name to debugger-nw4pb.
State  Recv-Q Send-Q Local Address:Port  Peer Address:PortProcess
LISTEN 0      128        127.0.0.1:15053      0.0.0.0:*
LISTEN 0      4096         0.0.0.0:9080       0.0.0.0:*
LISTEN 0      128                *:15008            *:*
LISTEN 0      128                *:15006            *:*
LISTEN 0      128                *:15001            *:*
LISTEN 0      128            [::1]:15053         [::]:*
LISTEN 0      4096            [::]:9080          [::]:*

ztunnel

节点本地 ztunnel 在内部启动一个新的代理实例和侦听端口集,专用于新添加的 Pod。

查看端口后,发现实际的端口只有几个处于listen状态,估计是在ztunnel中做了些基于账户的处理。直接查看ztunnel相应的负载比较麻烦,所以直接使用istioctl来查看。istioctl ztunnel-config 命令允许您查看 ztunnel 代理所看到的和发现的工作负载。你会看到 ztunnel 当前正在跟踪的所有工作负载和控制平面组件, 包括有关连接到该组件时要使用的 IP 地址和协议的信息,以及是否存在与该工作负载关联的 waypoint 代理。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
istioctl ztunnel-config workloads
NAMESPACE          POD NAME                                IP          NODE                  WAYPOINT PROTOCOL
default            bookinfo-gateway-istio-59dd7c96db-q9k6v 10.244.1.11 ambient-worker        None     TCP
default            details-v1-cf74bb974-5sqkp              10.244.1.5  ambient-worker        None     HBONE
default            notsleep-5c785bc478-zpg7j               10.244.2.7  ambient-worker2       None     HBONE
default            productpage-v1-87d54dd59-fn6vw          10.244.1.10 ambient-worker        None     HBONE
default            ratings-v1-7c4bbf97db-zvkdw             10.244.1.6  ambient-worker        None     HBONE
default            reviews-v1-5fd6d4f8f8-knbht             10.244.1.16 ambient-worker        None     HBONE
default            reviews-v2-6f9b55c5db-c94m2             10.244.1.17 ambient-worker        None     HBONE
default            reviews-v3-7d99fd7978-7rgtd             10.244.1.18 ambient-worker        None     HBONE
default            sleep-7656cf8794-r7zb9                  10.244.1.12 ambient-worker        None     HBONE
istio-system       istiod-7ff4959459-qcpvp                 10.244.2.5  ambient-worker2       None     TCP
istio-system       ztunnel-6hvcw                           10.244.1.4  ambient-worker        None     TCP
istio-system       ztunnel-mf476                           10.244.2.6  ambient-worker2       None     TCP
istio-system       ztunnel-vqzf9                           10.244.0.6  ambient-control-plane None     TCP
kube-system        coredns-76f75df574-2sms2                10.244.0.3  ambient-control-plane None     TCP
kube-system        coredns-76f75df574-5bf9c                10.244.0.2  ambient-control-plane None     TCP
local-path-storage local-path-provisioner-7577fdbbfb-pslg6 10.244.0.4  ambient-control-plane None     TCP

总结

上面的命令输出显示,额外的 Istio 特定链已被添加到应用程序 Pod 网络命名空间内的 netfilter/iptables 中的 NAT 和 Mangle 表中。 所有进入 Pod 的 TCP 流量都会被重定向到 ztunnel 代理进行入口处理。 如果流量是明文(源端口不等于 15008),会被重定向到 in-Pod ztunnel 明文侦听端口 15006。 如果流量是 HBONE(源端口等于 15008),会被重定向到 in-Pod ztunnel HBONE 侦听端口 15008。 任何离开 Pod 的 TCP 流量都会被重定向到 ztunnel 的端口 15001 进行出口处理, 然后由 ztunnel 使用 HBONE 封装发送出去。

我们也可以看一下Alpha 版的实现 可以看出,相比较为之前的Istio Ambient的流量拦截方案,新 Ambient 捕获模式的最终结果是所有流量捕获和重定向都发生在 Pod 的网络命名空间内。 对于节点、CNI 和其他所有内容来说,Pod 内似乎有一个 Sidecar 代理, 即使 Pod 中根本没有运行任何 Sidecar 代理。Beta版本也简化了之前的流程,更便于我们理解。

参考文章