k8s 网络模型
容器网络通信模式
在Host模式中,各容器共享宿主机的根网络名称空间,它们使用同一个接口设备和网络协议栈,因此,用户必须精心管理共享同一网络端口空间容器的应用与宿主机应用,以避免端口冲突。
Bridge模式对host模式进行了一定程度的改进,在该模式中,容器从一个或多个专用网络(地址池)中获取IP地址,并将该IP地址配置在自己的网络名称空间中的网络端口设备上。于是,拥有独立、隔离的网络名称空间的各容器有自己独占的端口空间,而不必再担心各容器及宿主机间的端口冲突。
Bridge
Bridge是指Linux内核支持的虚拟网桥设备,它模拟的是物理网桥设备,工作于数据链路层,根据习得的MAC地址表向设备端口转发数据帧。虚拟以太网接口设备对(veth pair)是连接虚拟网桥和容器的网络媒介:一端插入到容器的网络栈中,表现为通信接口(例如eth0等),另一端则于宿主机上关联虚拟网桥并被降级为当前网桥的“从设备”,失去调用网络协议栈处理数据包的资格,从而表现为桥设备的一个端口
Linux网桥提供的是宿主机内部的网络,同一主机上的各容器可基于网桥和ARP协议完成本地通信。而在宿主机上,网桥表现为一个网络接口并可拥有IP地址,docker0会在docker daemon进程启动后被自动配置172.17.0.1/16的地址。于是,由宿主机发出的网络包可通过此桥接口送往连接至同一个桥上的其他容器,如图的Container-1或Container-2,这些容器通常需要由某种地址分配组件(IPAM)自动配置一个相关网络(例如72.17.0.0/16)中的IP地址。
但此私有网络中的容器却无法直接与宿主机之外的其他主机或容器进行通信,通常作为请求方,这些容器需要由宿主机上的iptables借助SNAT机制实现报文转发,而作为服务方时,它们的服务需要宿主机借助于iptables的DNAT规则进行服务暴露。
配置容器Bridge网络
1) 若不存在,则需要先在宿主机上添加一个虚拟网桥;
2)为每个容器配置一个独占的网络名称空间;
3)生成一对虚拟以太网接口(如veth pair),将一端插入容器网络名称空间,一端关联至宿主机上的网桥;
4)为容器分配IP地址,并按需生成必要的NAT规则。
跨主机bridge网络
尽管Bridge模型下各容器使用独立且隔离的网络名称空间,且彼此间能够互连互通,但跨主机的容器间通信时,请求报文会首先由源宿主机进行一次SNAT(源地址转换)处理,而后由目标宿主机进行一次DNAT(目标地址转换)处理方可送到目标容器,如图所示。这种复杂的NAT机制将会使得网络通信管理的复杂度随容器规模增呈成几何倍数上升,而且基于ipables实现的NAT规则,也限制了解决方案的规模和性能。
Kubernetes 网络模型
集群中每一个 Pod 都会获得自己的、 独一无二的 IP 地址, 这就意味着你不需要显式地在 Pod 之间创建链接,你几乎不需要处理容器端口到主机端口之间的映射。 这将形成一个干净的、向后兼容的模型;在这个模型里,从端口分配、命名、服务发现、 负载均衡、 应用配置和迁移的角度来看,Pod 可以被视作虚拟机或者物理主机。
Kubernetes 强制要求所有网络设施都满足以下基本要求(从而排除了有意隔离网络的策略):
Pod 能够与所有其他节点上的 Pod 通信, 且不需要网络地址转译(NAT)
节点上的代理(比如:系统守护进程、kubelet)可以和节点上的所有 Pod 通信
说明:对于支持在主机网络中运行 Pod 的平台(比如:Linux), 当 Pod 挂接到节点的宿主网络上时,它们仍可以不通过 NAT 和所有节点上的 Pod 通信。
这个模型不仅不复杂,而且还和 Kubernetes 的实现从虚拟机向容器平滑迁移的初衷相符, 如果你的任务开始是在虚拟机中运行的,你的虚拟机有一个 IP, 可以和项目中其他虚拟机通信。这里的模型是基本相同的。
Kubernetes 的 IP 地址存在于 Pod 范围内 —— 容器共享它们的网络命名空间 —— 包括它们的 IP 地址和 MAC 地址。 这就意味着 Pod 内的容器都可以通过 localhost 到达对方端口。 这也意味着 Pod 内的容器需要相互协调端口的使用,但是这和虚拟机中的进程似乎没有什么不同, 这也被称为“一个 Pod 一个 IP”模型。
Kubernetes 网络解决四方面的问题
Pod内容器间通信
Pod是Kubernetes调度的原子单元,其内部的各容器必须运行在同一节点之上。一个Pod资源内的各容器共享同一网络名称空间,它通常由构建Pod对象的基础架构容器pause所提供。因而,同一个Pod内运行的多个容器通过本地回路(loopback)接口即可在本地内核协议栈上完成交互。
不同Pod间通信
各Pod对象需要运行在同一个平面网络中,每个Pod对象拥有一个虚拟网络接口和集群全局唯一的地址,该IP地址可用于直接与其他Pod进行通信。另外,运行Pod的各节点也会通过桥接设备等持有此平面网络中的一个IP地址,如图中的cni0接口,这意味着Node到Pod间的通信也可直接在此网络进行。因此,Pod间的通信或Pod到Node间的通信类似于同一IP网络中的主机间进行的通信。
Kubernetes设计了Pod通信模型,却把相关功能及编排机制的实现通过kubenet或CNI插件API开放给第三方来实现。这些第三方插件要负责为各Pod设置虚拟网络接口、分配IP地址并将其接入到容器网络中等各种任务,以实现Pod间的直接通信。
Service与Pod间的通信
Kubernetes Service 管理一组 Pod,允许你跟踪一组随时间动态变化的 Pod IP 地址,Service 作为对 Pod 的抽象,为一组 Pod 分配一个虚拟的 VIP 地址,任何发往 Service VIP 的流量都会被路由到与其关联的一组 Pod。这就允许与 Service 相关的 Pod 集可以随时变更 - 客户端只需要知道 Service VIP 即可。
创建 Service 时候,会创建一个新的虚拟 IP(也称为 clusterIP),这集群中的任何地方,发往虚拟 IP 的流量都将负载均衡到与 Service 关联的一组 Pod。实际上,Kubernetes 会自动创建并维护一个分布式集群内的负载均衡器,将流量分配到 Service 相关联的健康 Pod 上。
netfilter and iptables
为了在集群中执行负载均衡,Kubernetes 会依赖于 Linux 内置的网络框架 - netfilter。Netfilter 是 Linux 提供的一个框架,它允许以自定义处理程序的形式实现各种与网络相关的操作,Netfilter 为数据包过滤、网络地址转换和端口转换提供了各种功能和操作,它们提供了引导数据包通过网络所需的功能,以及提供禁止数据包到达计算机网络中敏感位置的能力。
iptables 是一个用户空间程序,它提供了一个基于 table 的系统,用于定义使用 netfilter 框架操作和转换数据包的规则。在 Kubernetes 中,iptables 规则由 kube-proxy 控制器配置,该控制器会 watch kube-apiserver 的变更,当对 Service 或 Pod 的变化更新了 Service 的虚拟 IP 地址或 Pod 的 IP 地址时,iptables 规则会被自动更新,以便正确地将指向 Service 的流量路由到支持 Pod。iptables 规则会监听发往 Service VIP 的流量,并且在匹配时,从可用 Pod 集中选择一个随机 Pod IP 地址,并且 iptables 规则将数据包的目标 IP 地址从 Service 的 VIP 更改为所选的 Pod IP。当 Pod 启动或关闭时,iptables 规则集也会更新以反映集群的变化状态。换句话说,iptables 已经在节点上做了负载均衡,以将指向 Service VIP 的流量路由到实际的 Pod 的 IP 上。
在返回路径上,IP 地址来自目标 Pod,在这种情况下,iptables 再次重写 IP 头以将 Pod IP 替换为 Service 的 IP,以便 Pod 认为它一直只与 Service 的 IP 通信。
IPVS
Kubernetes 新版本已经提供了另外一个用于集群负载均衡的选项:IPVS, IPVS 也是构建在 netfilter 之上的,并作为 Linux 内核的一部分实现了传输层的负载均衡。IPVS 被合并到了 LVS(Linux 虚拟服务器)中,它在主机上运行并充当真实服务器集群前面的负载均衡器,IPVS 可以将基于 TCP 和 UDP 的服务请求定向到真实服务器,并使真实服务器的服务作为虚拟服务出现在一个 IP 地址上。这使得 IPVS 非常适合 Kubernetes 服务。
这部署 kube-proxy 时,可以指定使用 iptables 或 IPVS 来实现集群内的负载均衡。IPVS 专为负载均衡而设计,并使用更高效的数据结构(哈希表),与 iptables 相比允许更大的规模。在使用 IPVS 模式的 Service 时,会发生三件事:在 Node 节点上创建一个虚拟 IPVS 接口,将 Service 的 VIP 地址绑定到虚拟 IPVS 接口,并为每个 Service VIP 地址创建 IPVS 服务器。
Pod 到 Service 通信
1. 数据包首先通过 Pod 的 eth0 网卡发出
2. 数据包经过虚拟网卡 veth0 到达网桥 cbr0
3. 网桥上的 APR 协议查找不到该 Service,所以数据包被发送到 root namespace 中的默认路由 - eth0
4. 此时,在数据包被 eth0 接受之前,数据包将通过 iptables 过滤。iptables 使用其规则(由 kube-proxy 根据 Service、Pod 的变化在节点上创建的 iptables 规则)重写数据包的目标地址(从 Service 的 IP 地址修改为某一个具体 Pod 的 IP 地址)
5. 数据包现在的目标地址是 Pod 4,而不是 Service 的虚拟 IP 地址。iptables 使用 Linux 内核的 conntrack 工具包来记录具体选择了哪一个 Pod,以便可以将未来的数据包路由到同一个 Pod。简而言之,iptables 直接在节点上完成了集群内负载均衡的功能。数据包后续如何发送到 Pod 上,
Service 到 Pod 通信
1. 接收到此请求的 Pod 将会发送返回数据包,其中标记源 IP 为接收请求 Pod 自己的 IP,目标 IP 为最初发送对应请求的 Pod 的 IP
2. 当数据包进入节点后,数据包将经过 iptables 的过滤,此时记录在 conntrack 中的信息将被用来修改数据包的源地址(从接收请求的 Pod 的 IP 地址修改为 Service 的 IP 地址)
3. 然后,数据包将通过网桥、以及虚拟网卡 veth0
4. 最终到达 Pod 的网卡 eth0
集群外部客户端与Pod对象的通信
出站流量
1. 数据包从 Pod 的 network namespace 发出
2. 通过 veth0 到达虚拟机的 root network namespace
3. 由于网桥上找不到数据包目标地址对应的网段,数据包将被网桥转发到 root network namespace 的网卡 eth0。在数据包到达 eth0 之前,iptables 将过滤该数据包。
4. 在此处,数据包的源地址是一个 Pod,如果仍然使用此源地址,互联网网关将拒绝此数据包,因为其 NAT 只能识别与节点(虚拟机)相连的 IP 地址。因此,需要 iptables 执行源地址转换(source NAT),这样子,对互联网网关来说,该数据包就是从节点(虚拟机)发出的,而不是从 Pod 发出的
5. 数据包从节点(虚拟机)发送到互联网网关
6. 互联网网关再次执行源地址转换(source NAT),将数据包的源地址从节点(虚拟机)的内网地址修改为网关的外网地址,最终数据包被发送到互联网
在回路径上,数据包沿着相同的路径反向传递,源地址转换(source NAT)在对应的层级上被逆向执行。
入站流量
引入集群外部流量到达Pod对象有4种方式,有两种是基于本地节点的端口(nodePort)或根网络名称空间(hostNetwork),另外两种则是基于工作在集群级别的NodePort或LoadBalancer类型的Service对象。不过,即便是四层代理的模式也要经由两级转发才能到达目标Pod资源:请求流量首先到达外部负载均衡器,由其调度至某个工作节点之上,而后再由工作节点的netfilter(kube-proxy)组件上的规则(iptables或ipvs)调度至某个目标Pod对象。
参考文档
https://kubernetes.io/docs/concepts/services-networking/#the-kubernetes-network-model
https://sookocheff.com/post/kubernetes/understanding-kubernetes-networking-model/