Kubernetes — 网络模型
目录
文章目录
网络模型标准
容器常见的网络标准有两种,分别是:
- Docker 提出的 CNM(Container Network Model)
- CoreOS 提出的 CNI(Container Network Interface)
CNM
Libnetwork 是 CNM 标准的实现,Libnetwork 提供 Docker 守护程序和网络驱动程序之间的接口。网络控制器负责将驱动程序与网络配对。每个驱动程序负责管理其拥有的网络,包括提供给该网络的服务。每个网络有一个驱动程序,多个驱动程序可以与连接到多个网络的容器同时使用。
CNI
CNI 已经成为了 CNCF 的正式项目,用于编写插件以配置 Linux 容器中的网络接口。CNI 仅关注容器的网络连接并在删除容器的同时删除分配的资源。CNI 提供了广泛的支持,并且规范易于实现,支持第三方插件。
CNI 也是 Kubernetes 所采用的网络标准。常见的 CNI 网络插件有:
-
Calico:是一个基于 BGP 路由协议的纯 L3 的数据中心网络方案(不需要 Overlay),提供简单,可扩展的网络。除了可扩展的网络, Calico 还提供策略隔离。
-
Flannel:基于 Linux TUN/TAP,使用 UDP 封装 IP 数据包的方式来创建 Overlay 网络,并借助 etcd 来维护网络资源的分配情况,是一种简单易用的 Overlay 网络方案。
-
Weave Net:支持多主机容器网络,可以跨越不同的云网络配置。
-
Cilium:是一个开源软件,基于 Linux Kernel BPF 技术,可以在 Linux Kernel 内部动态地插入具有安全性、可见性的网络控制逻辑。
-
kopeio-networking:是专为 Kubernetes 而设计的网络方案,充分利用了 Kubernetes API,因此更简单,更可靠。
-
kube-router:也是专为 Kubernetes 打造的专用网络解决方案,旨在提供操作简单性和性能。
CNI 插件项目 Forks 数量比较:
CNI 插件项目 10Gbit 网络下的 CPU 消耗比较:
基础网络模型
Kubernetes 网络中涉及以下几种类型的地址:
- Node IP:宿主机 IP 地址。
- Pod IP:Pod 是 Kubernetes 的最小部署单元,Pod 下可以包含若干个 Containers,但是 Container 没有独立的 IP 地址,它们共享 Pod 的 IP 地址和 Ports 区间。
- Cluster IP:这里所述 Cluster 并非 Kubernetes Cluster,而是 Kubernetes Service 的 IP 地址。外部网络是无法访问该地址的,只有 Kubernetes Cluster 内部才能访问。因为 Cluster IP 是一个虚拟的 IP 地址,即:没有网络设备为这个 IP 地址负责。在 Kubernetes 内部使用了 IPtables 规则来重定向到其本地端口,再均衡到后端的 Pods;
- Public IP:因为 Cluster IP 能在 Kubernetes Cluster Internal 访问,属于应用程序内部的层级。如果希望将这个 Service 为 Kubernetes Cluster External 的客户端提供访问,就需要为这个 Service 提供一个 Public IP。
网络流量模型
Pod 内部的 Containers 间的通信(Container 模式)
Pod 内部的 Containers 通过 localhost 进行通信,它们使用了同一个 Network Namespace。对 Container 而言,hostname 就是 Pod 的名称。
Pod 内部的 Containers 共享同一个 IP 地址和端口区间,所以要为每个可以建立连接的 Container 分配不同的 Port 号。也就是说,Pod 中的 “应用” 需要自己协调端口号的分配和使用。
举个例子:创建一个 Pod ,包含两个 Containers。
可以看到同一个 Pod 下属的两个 Containers 共享了 IP 地址。
可以看见,同一个 Pod 下属的两个 Containers 不能占有同一个 Port 号,因为 Port 区间也是共享的。
所以,我们可以将 Pod 理解为一个小型的 “操作系统沙盒”,两个进程可以使用同一个操作系统 IP 地址,自然也就不可以使用同一个 Port 号了。
实现原理:同一个 Pod 内的 Containers 处于同一个 Network Namespace,因此使用了相同的 IP 地址和 Port 区间。该 Namespace 是由一个名为 Pause Container 实现的,每当一个 Pod 被创建,首先会创建一个 Pause Container。
后续所有新的普通 Containers 都通过过共享 Pause Container 的网络栈,实现与外部 Pod 进行通信。因此,对于同 Pod 下属的 Containers 而言,它们看到的网络视图是一样的。上述我们在 Container 中看的 IP 地址,实际就是 Pause Container 的 IP 地址,通过控制 Pause Container 的网络协议栈就可以影响所有同属 Pod 下的 Container 的网络协议栈了。
这种新创建的容器和已经存在的一个容器(Pause)共享一个 Network Namespace(而不是和宿主机共享)的模式,就是常说的 Container 模式。
同主机 Pod 间的通信(Host Virtual Network 模式)
每个 Node 上的每个 Pod 都有自己专属的 Network Namespace,由此实现 Container 模型网络的隔离。而两个 Pod 之间,即:两个 Network Namespace 之间希望进行通信的话,就需要使用到 Linux 操作系统的网络虚拟化技术 —— Veth Pair(虚拟网线)了。
但是,如果有多个 Pod 都需要两两建立 Veth Pair 的话,扩展性就会非常的差,假如:有 N 个 Pod,就需要创建 n(n-1)/2 个 Veth Pair。可见,除了 Veth Pair(虚拟网线)之外,我们还需要一个二层的 “集线” 设备 —— Linux Bridge(虚拟交换机)。
举个例子:创建位于同一个 Node 下属的 Pod1 和 Pod2,IP 地址分别为:10.244.1.16、10.244.1.18。
查看 Node 下的 Network Namespace:
分别查询两个 Network Namespace 下的 Network Interfaces:
可见,Pod1、Pod2 内部的 Veth Pair 的一端分别是 3: [email protected]
、3: [email protected]
;另一端则位于 default Network Namespace,分别为 7: [email protected]
、9: [email protected]
。注意:interface ID 和 ifID 刚刚好是两端对称的,以此来进行辨识。当然了,Bridge 也同样存在于 default Network Namespace。
这种借助于 Linux 操作系统原生的网络虚拟化技术实现的两个本地 Pods 之间的网络通信方式,称为 Host Virtual Network 模式。
跨主机 Pod 间的通信(SDN 模式)
总的来说,跨主机通信无非两种方式:
- Overlay 隧道互通:Nodes 之间通过 Over the Physical 网络互通,e.g. OvS、Flannel 和 Weave。
- Underlay 直接互通:Nodes 之间通过物理网络互通,e.g. Calico 的 Direct 模式、macvlan。
从网络角度对 Flannel 和 Calico 进行简单对比。可见,对性能敏感、策略需求较高时偏向于 Calico 方案。否则,采用 Flannel 会是更好的选择;
Flannel
地址分配
flanneld 守护进程第一次启动时,会从 etcd 获取配置的 Pod 网段信息,为本节点分配一个未使用的 IP 地址段,然后创建 flannedl.1 网络接口(也可能是其它名称),Flannel 将分配给自己的 Pod 网段信息写入 /run/flannel/docker 文件(不同 Kubernets 版本的文件名存在差异),Docker 后续使用这个文件中的环境变量设置 docker0 网桥,从而使这个地址段为本节点的所有。
查看 Flannel 为 Docker 分配的地址段:
表示该 Node 上所有 Pod 的 IP 地址都从 Flannel Subnet 10.244.1.1/24 中分配,比如:node1 下属的 2 个 Pod 的 IP 地址:
路由下发
每个 Node 中的 flanneld 守护进程,可以创建 Kernel 的路由表,查看 node1 的路由表如下:
其中 Destination 10.244.2.0 为 node2 的目的网段,出口为 flannel.1 interface。flannel.1 interface 是 flanneld 创建的一个隧道接口。并且 flanneld 中央存储了 Container-Node 之间的映射关系到 etcd 中,以此来作为建立最外层隧道封装的 Tunnel ID,将两个 Nodes 中的两个 Containers 互联起来。
数据面封装
Flannel 知道外层封装的隧道对端 IP 地址后,对数据报文进行封装,SourceIP 采用本节点的 NodeIP,DestIP 采用对端的 VXLAN 外层的 UDP Port 8472,隧道的对端只需要监听这个 Port 即可,当该 Port 收到报文后将报文送到 flannedld 进程,进程将报文送到 flanned interface 进行封装,然后查询本地路由表,可以看到目的地址的 interface 为 cni0:
Flannel 支持 2 种不同的后端实现,分别是:
- Host-gw:需要两个 Node 处于同一网段,不支持跨网,因此不适合大规模部署。
- VXLAN:使用 VXLAN 技术为各 Node 创建一个可以互通的 Pod 网络,使用的端口为 UDP 8472。
Calico
Calico 的组件包含:
- Felix(Calico agent):运行在每个 Node 上,为容器设置网络信息,例如:IP 地址、路由规则,IPtables 规则等。
- BIRD(BGP Client):运行在每个 Node 上,监听由 Felix 注入的路由信息,然后通过 BGP 协议广播告诉其他 Nodes,从而实现网络互通。
- BGP Route Reflector:BGP Peer 建立的方式多样,可以在 Node 之间两两建立 BPGP Peer(默认模式),和传统 iBGP Peer 问题类似,这会带来 n*(n-1)/2 的邻居量。因此也可以自建 RR 反射器,Node 和 RR 建立 Peer。当然 Node 也可以和 TOR 建立 Peer。具体选择哪种 Peer 方式没有固定标准,要适配总体网络规划,只要最终保证容器网络可正确发布到物理网络即可;
- Calicoctl:命令行管理工具。
Calico 支持 3 种路由模式:
- Direct 路由转发,报文不做封装;
- IP-in-IP(Default)封装;
- VXLAN 封装;
这里主要介绍 Direct 模式,使用 BGP 路由协议宣告容器网段,使得全网所有的 Nodes 和网络设备都有到彼此的路由的信息,然后直接通过 Underlay 转发。
数据通信的流程为:数据包先从 Veth Pair 的一端发出,到达 Node 上的以 Cali 为前缀的虚拟网卡上,也就到达了 Host 的内核网络协议栈。然后查询路由表转发;因为本地节点通过 Bird 和 RR 建立 BGP 邻居关系,会将本地的容器地址发送到 RR 从而反射到网络中的其它 Nodes 上,同样,其它 Nodes 的网络地址也会传送到本地,然后由 Felix 进程进行管理并下发到路由表中,报文匹配路由规则后正常进行转发即可(实际还有复杂的 iptables 规则,这里不做展开)。
Service 的 Cluster IP 和外部网络间的通信
Service 之于集群内部 Pods 之间的通信
Pod 间可以直接通过 IP 地址通信,但前提是 Pod 知道对方的 IP。在 Kubernetes Cluster 中,Pod 可能会频繁地销毁和创建,也就是说 Pod 的 IP 不是固定的。为了解决这个问题,Kubernetes Service 作为访问 Pod 的上层抽象。无论后端的 Pod 如何变化,Service 都作为稳定的前端对外提供服务。同时,Service 还提供了高可用和负载均衡功能,负责将请求转发给正确的 Pod。
Service 之于集群外部与 Pod 的通信
无论是 Pod IP 还是 Service 的 Cluster IP,它们都是只能在 Kubernetes Cluster 内部可见的私有 IP 地址。Kubernetes 提供了两种方式可以让外部网络访问 Service 的 Cluster IP,继而与 Pod 进行通信:
-
NodePort:Service 通过 Node 的静态端口对外提供服务,外部网络可以通过 NodeIP:NodePort 访问 Service,根据不同的 NodePort 可以访问不同的 Service。
-
LoadBalancer:Service 利用自建的负载均衡器(反向代理)将流量导向 Service,例如:Nginx、OpenStack Octavia、Cloud Provider(GCP、AWS、 Azure)等。