Kubernetes
Kubernetes 是为运行分布式集群而建立的,分布式系统的本质使得网络成为 Kubernetes 的核心和必要组成部分,了解 Kubernetes 网络模型可以能够正确运行、监控和排查应用程序故障。

- 所有 Pod 都可以在不使用 NAT 的情况下与所有其他 Pod 进行通信
- 所有节点都可以在没有 NAT 的情况下与所有 Pod 进行通信
- Pod 自己的 IP 与其他 Pod 看到的 IP 是相同的
- 容器到容器的网络
- Pod 到 Pod 的网络
- Pod 到 Service 的网络
- 互联网到 Service 的网络
容器到容器网络
通常情况下将虚拟机中的网络通信视为直接与以太网设备进行交互,如图1所示。
命名空间创建后,会在 /var/run/netns 下面为其创建一个挂载点,即使没有附加任何进程,命名空间也是可以保留的。 可以通过列出 /var/run/netns 下的所有挂载点或使用 ip 命令来列出可用的命名空间。
$ ip netns add ns1
默认情况下,Linux 将为每个进程分配到 root network namespace,以提供访问外部的能力,如图2所示。
$ ls /var/run/netnsns1$ ip netnsns1


Pod 到 Pod 网络
在 Kubernetes 中,每个 Pod 都有一个真实的 IP 地址,每个 Pod 都使用该 IP 地址与其他 Pod 进行通信。接下来将来了解 Kubernetes 如何使用真实的 IP 来实现 Pod 与 Pod 之间的通信的。先来讨论同一节点上的 Pod 通信的方式。 从 Pod 的角度来看,它存在于自己的网络命名空间中,需要与同一节点上的其他网络命名空间进行通信。值得庆幸的时候,命名空间可以使用 Linux 虚拟以太网设备或由两个虚拟接口组成的 veth 对进行连接,这些虚拟接口可以分布在多个命名空间上。要连接 Pod 命名空间,可以将 veth 对的的一侧分配给 root network namespace,将另一侧分配给 Pod 的网络命名空间。每个 veth 对就像一根网线,连接两侧并允许流量在它们之间流动。这种设置可以复制到节点上的任意数量的 Pod。图4显示了连接虚拟机上每个 Pod 的 root network namespace 的 veth 对。

同节点 Pod 通信
网络命名空间将每个 Pod 隔离到自己的网络堆栈中,虚拟以太网设备将每个命名空间连接到根命名空间,以及一个将命名空间连接在一起的网桥,这样就准备好在同一节点上的 Pod 之间发送流量了,如下图6所示。
跨节点 Pod 通信
在研究了如何在同一节点上的 Pod 之间路由数据包之后,接下来看下不同节点上的 Pod 之间的通信。Kubernetes 网络模型要求 Pod 的 IP 是可以通过网络访问的,但它并没有规定必须如何来实现。 通常集群中的每个节点都分配有一个 CIDR,用来指定该节点上运行的 Pod 可用的 IP 地址。一旦以 CIDR 为目的地的流量到达节点,节点就会将流量转发到正确的 Pod。图7展示了两个节点之间的网络通信,假设网络可以将 CIDR 中的流量转发到正确的节点。
Pod 到 Service
上面已经介绍了如何在 Pod 和它们相关的 IP 地址之间的通信。但是 Pod 的 IP 地址并不是固定不变的,会随着应用的扩缩容、应用崩溃或节点重启而出现或消失,这些都可能导致 Pod IP 地址发生变化,Kubernetes 中可以通过 Service 对象来解决这个问题。 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 与 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 通信

Service 到 Pod 通信

外网到 Service 通信
到这里已经了解了 Kubernetes 集群内的流量是如何路由的,但是更多的时候需要将服务暴露到外部去。这个时候会涉及到两个主要的问题:- 将流量从 Kubernetes 服务路由到互联网上去
- 将流量从互联网传到 Kubernetes 服务
出流量
从节点到公共 Internet 的路由流量也是和特定的网络有关系的,这取决于网络如何配置来发布流量的。这里以 AWS VPC 为例来进行说明。 在 AWS 中,Kubernetes 集群在 VPC 中运行,每个节点都分配有一个私有 IP 地址,该地址可从 Kubernetes 集群内访问。要从集群外部访问服务,可以在 VPC 上附加一个外网网关。外网网关有两个用途:在 VPC 路由表中为可路由到外网的流量提供目标,以及为已分配公共 IP 地址的实例执行网络地址转换 (NAT)。NAT 转换负责将集群节点的内部 IP 地址更改为公网中可用的外部 IP 地址。 有了外网网关,VM 就可以自由地将流量路由到外网。不过有一个小问题,Pod 有自己的 IP 地址,与运行 Pod 的节点 IP 地址不同,并且外网网关的 NAT 转换仅适用于 VM IP 地址,因为它不知道哪些 Pod 在哪些 VM 上运行 —— 网关不支持容器。来看看 Kubernetes 是如何使用 iptables 来解决这个问题的。 在下图中,数据包源自 Pod 的命名空间 (1),并经过连接到根命名空间 (2) 的 veth 对。一旦进入根命名空间,数据包就会从网桥移动到默认设备,因为数据包上的 IP 与连接到网桥的任何网段都不匹配。在到达根命名空间的网络设备 (3) 之前,iptables 会破坏数据包 (3)。在这种情况下,数据包的源 IP 地址是 Pod,如果将源保留为 Pod,外网网关将拒绝它,因为网关 NAT 只了解连接到 VM 的 IP 地址。解决方案是让 iptables 执行源 NAT —— 更改数据包源,使数据包看起来来自 VM 而不是 Pod。有了正确的源 IP,数据包现在可以离开 VM (4) 并到达外网网关 (5) 了。外网网关将执行另一个 NAT,将源 IP 从 VM 内部 IP 重写为公网IP。最后,数据包将到达互联网上 (6)。在返回的路上,数据包遵循相同的路径,并且任何源 IP 的修改都会被取消,这样系统的每一层都会接收到它理解的 IP 地址:节点或 VM 级别的 VM 内部,以及 Pod 内的 Pod IP命名空间。
入流量
让流量进入集群是一个非常难以解决的问题。同样这也和特定的网络环境有关系,但是一般来说入流量可以分为两种解决方案:- Service LoadBalancer
- Ingress 控制器
LoadBalancer
当创建一个 Kubernetes Service时,可以选择指定一个 LoadBalancer 来使用它。LoadBalancer 有提供服务的云供应商负责创建负载均衡器,创建服务后,它将暴露负载均衡器的 IP 地址。终端用户可以直接通过该 IP 地址与服务进行通信。LoadBalancer 到 Service
在部署了 Service 后,使用的云提供商将会创建一个新的 LoadBalancer(1)。因为 LoadBalancer 不支持容器,所以一旦流量到达 LoadBalancer,它就会分布在集群的各个节点上(2)。每个节点上的 iptables 规则会将来自 LoadBalancer 的传入流量路由到正确的 Pod 上(3)。从 Pod 到客户端的响应将返回 Pod 的 IP,但客户端需要有 LoadBalancer 的 IP 地址。正如之前看到的,iptables 和 conntrack 被用来在返回路径上正确重写 IP 地址。 下图展示的就是托管 Pod 的三个节点前面的负载均衡器。传入流量(1)指向 Service 的 LoadBalancer,一旦 LoadBalancer 接收到数据包(2),它就会随机选择一个节点。这里的示例中,选择了没有运行 Pod 的节点 VM2(3)。在这里,运行在节点上的 iptables 规则将使用 kube-proxy 安装到集群中的内部负载均衡规则,将数据包转发到正确的 Pod。iptables 执行正确的 NAT 并将数据包转发到正确的 Pod(4)。
Ingress 控制器
在七层网络上 Ingress 在 HTTP/HTTPS 协议范围内运行,并建立在 Service 之上。启用 Ingress 的第一步是使用 Kubernetes 中的 NodePort 类型的 Service,如果将 Service 设置成 NodePort 类型,Kubernetes master 将从指定的范围内分配一个端口,并且每个节点都会将该端口代理到 Service,也就是说,任何指向节点端口的流量都将使用 iptables 规则转发到 Service。 将节点的端口暴露在外网,可以使用一个 Ingress 对象,Ingress 是一个更高级别的 HTTP 负载均衡器,它将 HTTP 请求映射到 Kubernetes Service。根据控制器的实现方式,Ingress 的使用方式会有所不同。HTTP 负载均衡器,和四层网络负载均衡器一样,只了解节点 IP(而不是 Pod IP),因此流量路由同样利用由 kube-proxy 安装在每个节点上的 iptables 规则提供的内部负载均衡。 在 AWS 环境中,ALB Ingress 控制器使用 AWS 的七层应用程序负载均衡器提供 Kubernetes 入口。下图详细介绍了此控制器创建的 AWS 组件,它还演示了 Ingress 流量从 ALB 到 Kubernetes 集群的路由。
Ingress 到 Service
流经 Ingress 的数据包的生命周期与 LoadBalancer 的生命周期非常相似。主要区别在于 Ingress 知道 URL 的路径(可以根据路径将流量路由到 Service)Ingress 和节点之间的初始连接是通过节点上为每个服务暴露的端口。 部署 Service 后,使用的云提供商将创建一个新的 Ingress 负载均衡器 (1)。因为负载均衡器不支持容器,一旦流量到达负载均衡器,它就会通过为服务端口分布在组成集群 (2) 的整个节点中。每个节点上的 iptables 规则会将来自负载均衡器的传入流量路由到正确的 Pod (3)。Pod 到客户端的响应将返回 Pod 的 IP,但客户端需要有负载均衡器的 IP 地址。正如之前看到的,iptables 和 conntrack 用于在返回路径上正确重写 IP。
