Ambient Mesh Works : 2024 Version
在 Ambient Mesh Works 中我们分析了最初的版本,当前版本又做了一些更新,我们今天就来看看这些更新。
CNI & Ztunnel
相较于之前的版本中,使用了标记路由的方式进行流量透传Pod
-> Host
-> Ztunnel
这样的路径,这样的路径对当前的其他的 CNI
比如 flannel
之类的冲击都比较的大,因此在当前的版本中,进行了较大的修改
这里采用了一个的新的设计模式
Pod
-> Ztunnel
减少了到 Host
绕一圈的情况。
这里实现了标准的 CNI
的接口,不熟悉的同学可以参考 Kubernetes Network Plugins
所以这里需要实现 2
个东西
CNI-CLI
: 给kube
来调用,istio
中的名称为istio-cni
CNI-Daemonset-POD
: 处理逻辑,istio
中的名称为install-cni
那我们就按顺序来看一下
CNI-CLI
程序的入口如下
1 | func runPlugin() error { |
那么主要的逻辑显然就在 Add
Delete
这些具体的逻辑中了。我们用 Add
逻辑看一下,代码在 plugin.go 这里进行拆解分析。
1 | if conf.AmbientEnabled { |
(*) 原 CNI 模式模式
这里就和我们 ambient
没什么关系了,主要是在比较早期的版本 CNI plugin 的逻辑,不基于 initContainer
是通过这里完成 iptables
的劫持逻辑的。
紧接着的逻辑,有一长串来判断是不是需要处理重定向,这里包含了
- 如果
POD
中包含了ISTIOINIT
的初始化容器 - 如果
POD
中包含了DISABLE_ENVOY
的环境变量 - 如果
POD
中包含了ISTIOPROXY
组件
等等逻辑就不去重定向请求,这部分就略过了,核心逻辑在下面
1 | log.Debugf("Setting up redirect") |
这里开始重定向 POD
的所有请求,这段逻辑的主体 redirect
就是之前 init
容器 iptables
规则修改,因此这里也没什么特别神奇的处理。
最终就会执行如下命令
1 | func (ipt *iptables) Program(podName, netns string, rdrct *Redirect) error { |
CNI-Install
另外一个组件,常驻在节点的 POD
这里使用的是另外一个 CMD
在 istio
中称之为 install-cni
这个程序的入口显然就是通过 UDS
获取到 PodAdd
事件然后进行处理。我们就从这里开始
Init
1 | // 启动一个 UDS Listener Server 来监听各种事件 |
但是 NewServer
中包含了大量的逻辑,这里就简单看看
1 | func NewServer(ctx context.Context, ready *atomic.Value, pluginSocket string, args AmbientArgs) (*Server, error) { |
handleAddEvent
看完了简要的初始化工作,我们来看函数的处理主体
1 | func (s *CniPluginServer) handleAddEvent(w http.ResponseWriter, req *http.Request) { |
1 | func (s *CniPluginServer) ReconcileCNIAddEvent(ctx context.Context, addCmd CNIPluginAddEvent) error { |
AddPodToMesh
AddPodToMesh
逻辑中构成了主要的核心流程
1 | // AddPodToMesh 增加一个 Pod 到 网格中 |
到这里,针对 POD 的逻辑我们已经做完了,下面就是 Ztunel 的逻辑了,我们简要的总结一下
PodAdded
经过简单的跳转就可以抵达这里。PodAdded
的主要工作是把当前需要处理的 Pod
信息发送给 ztunnel
1 | // 这里通过 GRPC 请求,把 uid 和 netns 发给 Ztunnel 就结束了,这部分看起来还是很简单,就不做详细介绍了 |
处理完这些 CNI
的工作就完成了。
Ztunnel
和 install-cni
的逻辑一样,这里我们应该也是从 zdsapi.WorkloadRequest
开始了解起来,这部分的逻辑在 process
就是一个标准的事件循环,读取 Request
然后处理 Response
1 | pub async fn process(&mut self, mut processor: WorkloadStreamProcessor) -> Result<(), Error> { |
实际上的逻辑通过一些简单的跳转,我们就可以找到
1 | async fn add_workload_inner( |
可以看出来 Proxy
的主体逻辑在 proxy.run()
中,但是在我们看 run
之前,我们先来阅读 new_proxies_from_factory
中的逻辑
1 | if self.config.proxy { |
我们当前关注我们可能使用最多的 inbound
和 outbound
1 | pub(super) async fn new(mut pi: ProxyInputs, drain: Watch) -> Result<Inbound, Error> { |
Outbound Run
接下来,我们分析其中一个场景 outbound
是如何流转流量的。
完整代码在 outbound.rs 非常的长,我们这里分段理解一下
最启示的部分在 outbound.rs
1 | loop { |
而下一步的 proxy
函数在
1 | async fn proxy(&mut self, stream: TcpStream, outer_conn_drain: Watch) -> Result<(), Error> { |
那下面就来到了最复杂的部分了,分解之,从outbound.rs 开始看起
Build Request
在理解这块代码之前需要理解下,当前系统的几个路径
- Pod -> Pod: 都在 ambient 中,只有 L4 的需求,就是 Pod –(outbound)–> Ztunnel —> Pod –(inbound)–> Ztunnel
- Pod -> Pod: 都在 ambient 中,有 L7 的需求,就是 Pod –(outbound)–> Ztunnel —-> Waypoint –> Pod –(inbound)–> Ztunnel
- Pod -> Pod(非ambient): Pod –(outbound)–> Ztunnel —> Pod
实际上并不存在 Ztunnel
-> Ztunnel
的直接路径,这个和之前的版本是不一样的,因此如果只从宿主机的角度看请求,是无法感知到这个请求的存在的,只有 Ztunnel
-> Pod
的路径
1 | // 构建目标的访问请求,这里还是比较复杂的 |
代码比较细碎,整体上的逻辑就是
- 根据
目标IP
判断是不是一个SVC
的ClusterIP
- 如果是访问
SVC
的地址,先去找waypoint
有没有,如果有就走waypoint
- 然后根据
IP
来找对应的workload
,找不到说明不在系统内,那就是Passthrough
的路径 - 如果访问的是
IP
的地址,并且来源不是Waypoint
的可以在尝试再找一下这个IP
是否有waypoint
有就走那去,这里解决了之前 IP 无法匹配 VS 的问题 - 剩下来的逻辑里面就看看对端是不是有
ztunnel
的逻辑,走HBONE
协议,不然就走默认的TCP
Proxy
接下来的处理在
1 | let res = match req.protocol { |
对于 proxy_to_hbone
的逻辑就是符合协议的,我们抛开一些细节上的点,大致上的逻辑上
1 |
|