Skip to main content
  1. Docs/

Kubernetes 网络通信原理

·9 mins· ·
Owl Dawn
Author
Owl Dawn
Table of Contents

WIP
#

Kubernetes 作为容器编排系统,引入了容器虚拟化、pod、编排机制 等概念,网络通信在引入的同时也变得异常复杂。简单来讲,Kubernetes 的网络可以划分为 4 个维度

  • 同一个 pod 内容器间的网络通信
  • 同一个宿主机下,不同 pod 间的网络通信
  • 不同宿主机间,容器的网络通信
  • 外部与集群容器之间的网络通信

同一个 pod 内容器间的通信
#

Pod 为 Kubernetes 中的最小可部署对象,其中可以包含一组容器。正常情况下,同一个物理机上的两个进程是不能直接通信的,需要依赖网卡、回环设备、路由表等网络设施。

docker 容器主要基于 Linux 的 Namespace 技术和 Cgroup 技术进行逻辑层的虚拟化,分别实现了资源的隔离和限制。

容器启动时,通过对容器进程的 PID Namespace、Net Namespace、mnt Namespace 等配置,实现了对进程、网络、文件系统等资源的隔离。而对于同一个 Pod 内的不同容器来说,他们的 Namespace 进行了修改定制,共享同一个 Net Namespace,所以可以使用同一套网卡、回环设备、路由表等网络设施,因此容器间的通信也得到解决。

同一宿主机上的不同 pod 间的通信
#

由于网络和文件系统等 Namespace 的隔离,不同 Pod 之间的网络通信无法再依赖同一个 namespace 下的网络设施。实际上,我们可以将每一个容器看作一台物理机,其都有自己独立的网络栈,而实现网络通信最简单的方式,就是将两个容器连接到同一个网桥上。

**网桥(Bridge)**是 Linux 中能够起到虚拟交换机作用的网络设备,它是一个工作在数据链路层(Data Link)的设备,可以根据 MAC 地址学习来将数据包转发到网桥的不同端口(Port)上。而 Docker 项目会在宿主机上创建一个 docker0 网桥,将容器连接在 docker0 网桥上,就可以进行通信。

Veth Pair 设备常被用作连接不同 Net Namespace 的”网线“,当创建 Veth Pair 设备后,会出现两张(成对的)虚拟网卡(Veth Pair),从一张网卡上发出的数据包,会直接出现在另一张网卡上。当容器启动后,容器中 eth0 网卡正是 Veth Pair 设备在容器中的一端,而它的另一端,就可以在宿主机上查看到。当虚拟网卡插在宿主机上的网桥上时,就会降级为网桥的从设备,不再调用网络协议栈自行处理数据包,而是成为网桥的一个端口。所有数据包的处理全部交给对应的网桥。

当我们从一个容器访问宿主机上的另一个容器的时候,通过路由规则,经过虚拟网卡,数据包被发往对应的容器。首先容器通过 ARP 协议获取目标容器的 MAC 地址,将添加了 MAC 地址的数据包发往 docker0 网桥,随后网桥将数据包转发给目标容器,从而实现了容器间的通信。

不同宿主机上的容器间通信
#

Flannel 项目是一种容器网络解决方案,其支持三种后端实现来支持容器跨主网络

基于 UDP 的实现
#

UDP 模式是三种实现中最容易理解的方案,但也存在性能问题,目前已经被弃用。其实现基于在宿主机上部署的 flanneld 进程。

TUN(Tunnel) 设备:工作在三层的虚拟网络设备,用于在操作系统内核和应用程序间传递 IP 包。

Flannel 进程会创建一个 flannel0 设备。flannel0 就是一个 TUN 设备,当系统将 IP 包发送给 flannel0 设备后,flannal0 就会将这个数据包发送给创建该设备的进程,即 Flannel 进程。这是一个数据包从内核态发往用户态的过程。

udp 模式网络通信流程

  1. 容器中发起的 IP 包,由于目标 IP 地址不在 docker0 网桥的网段内,匹配到默认的路有规则,通过容器网关进入 docker0 网桥,流向宿主机。(用户态 -> 内核态
  2. 经过路由规则,进入 flannel0 设备
  3. flannel0 设备将数据包转交给宿主机上的 Flanneld 进程(内核态 -> 用户态
  4. Flanneld 根据目标 IP 地址,匹配到对应目标的子网,通过 ETCD 查询到子网对应的宿主机地址
  5. Flanneld 将数据包封装成 UDP 包,然后通过 UDP 协议目标的宿主机。(用户态 -> 内核态
  6. 目标宿主机和容器通过相反的流程进行数据包接受和解析

UDP 模式实际是一种三层的 Overlay 网络,其首先对发出端的 IP 包进行 UDP 封装,然后在接收端进行解封装拿到原始的 IP 包,进而把这个 IP 包转发给目标容器。但是因为存在三次用户态和内核态之间的数据拷贝,所以存在比较严重的性能问题。

基于 VXLAN 的实现
#

VXLAN,即 Virtual Extensible LAN虚拟可扩展局域网),是 Linux 内核本身就支持的一种网络虚似化技术,从而可以在内核实现 UDP 的封装和解封装工作,减少了用户态内核态数据拷贝的开销

VXLAN 覆盖网络的设计思想:在现有的三层网络之上,“覆盖”一层虚拟的、由内核 VXLAN 模块负责维护的二层网络,使得连接在这个 VXLAN 二层网络上的“主机”(虚拟机或者容器都可以)之间,可以像在同一个局域网(LAN)里那样自由通信。当然,实际上,这些“主机”可能分布在不同的宿主机上,甚至是分布在不同的物理机房里。

基于 VXLAN 的跨主通信网络链路

flannel.1 设备,就是 VXLAN 所需的 VTEP 设备,处于内核态,它既有 IP 地址,也有 MAC 地址。

目标地址的设备信息,同样由位于宿主机的 flanneld 进程维护。当新的 Node 启动并加入到集群网络时,flanneld 进程就会添加一条路有规则,如下,同时也会在 ARP 表中添加对应的 mac 地址

$ route -n
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
...
10.1.16.0       10.1.16.0       255.255.255.0   UG    0      0        0 flannel.1

其表示发往 10.1.16.0/24 网段的 IP 包,都需要经过 flannel.1 设备发出,并且,它最后被发往的网关地址是:10.1.16.0。而 10.1.16.0 即新 Node 的 VTEP(即 flannel.1)设备的 ip 地址

整体链路如下:

  1. 数据包经过 docker0 网桥,路由至 flannel.1 设备进行处理
  2. 内核根据 flanneld 进程得到目标地址的 mac 地址信息,将数据封装成二层数据帧,并添加一个特殊的 VXLAN 头
    VXLAN 头里有一个重要的标志叫作 VNI,VTEP 设备用这个标志识别某个数据帧是不是应该归自己处理。而在 Flannel 中,VNI 的默认值是 1,这也是为何,宿主机上的 VTEP 设备都叫作 flannel.1 的原因,这里的“1”,其实就是 VNI 的值。
  3. linux 内核将这个二层数据帧封装成 udp 包发出去(一般 4789 作为 VTEP 设备的 UDP 端口)
  4. 正常的数据封包流程,内核在二层数据帧外再添加上 UDP header,再加上 IP 头,组成四层数据包,再添加二层数据帧头,最终发往目标主机。最终组成的包格式如下图: 注意,flannel.1 的二层数据帧只有目标 VXLAN 设备的 MAC 地址,不能直接发出去,还得封装成 UDP 包在四层网络传输,所以必须要知道目标主机的 IP。目标 VTEP 设备的MAC 地址和目标主机 IP 的映射关系保存在 FDB(Forwarding Database)转发数据库里,由 flanneld 维护

基于 host-gw 的实现
#

host-gw 模式工作原理,即将每个 Flannel 子网(Flannel Subnet)的下一跳,设置为该子网对应的宿主机的 IP 地址。host 即为通信链路的网关,Flannel 子网和主机的信息全部储存在 ETCD 中,flanneld watch 这些数据变化并对路由表进行更新。

Flanenel 使用该模式时,会在宿主机上添加一条路由规则,如下

$ ip route
...
10.244.1.0/24 via 10.168.0.3 dev eth0

表示,目的 IP 属于 10.244.1.0/24 网段的 IP 包,都需要经过本机的 eth0 设备发出,且下一跳(next-hop)为 10.168.0.3,即目的宿主机的地址。之后 IP 包由网络层进入链路层,封装成帧,eht0 设备就会使用下一跳地址对应的 mac 地址作为数据帧的目的 mac 地址,从而发往目标宿主机。

目的宿主机拿到 IP 包后,根据 IP 包的目的 IP 地址,匹配到对应路由规则,将数据包发往 cni0 网桥,从而进入容器中。

这种模式不需要额外的封包和解包带来的性能损耗,性能损失很小,但是也要求集群的宿主机之间是二层相通的。但是实际上,集群大部分是三层相通,即 IP 互通,但是宿主机可能分布在了不同的子网,造成了二层不通的情况。

Calico
#

Calico 的网络方案类似 Flannel 的 host-gw 模式,同样是在每台宿主机上添加一个下一跳到目标宿主机的路由规则。但是路由信息的维护不同于 flanneld 的 etcd,calico 采用 BGP(边界网关协议,Border Gateway Protocol)。

BGP 是 linux 内核原生支持的,专门用在大规模数据中心维护不同的“自治系统”之间路由信息的、无中心的复杂路由协议。简单来说,BGP 可以实现在大规模网络中的节点路由信息共享。

Calico 项目有三部分组成

  • Calico 的 CNI 插件
  • Felix。DaemonSet,负责在宿主机上插入路由规则(写入 linux 内核的 FIB 转发信息库),以及维护 Calico 所需的网络设备
  • BIRD。即 BGP 的客户端,负责在集群里分发路由规则信息。

Calico 的“下一跳”路由规则,就是由 Calico 的 Felix 进程负责维护。这些路由规则信息,由 BIRD 组件(BGP Client)通过 BGP 协议传输来。Calico 项目将集群上所有的节点当做边界路由器来处理,组成了一个全联通的网络,节点之间通过 BGP 交换路由规则。这些节点,我们称为 BGP Peer

Calico 的 CNI 插件会为每个容器设置一个 Veth Pair 设备,并将其一端放置在宿主机上(cali 前缀),这一点不同于 Flannel 在宿主机上创建网桥设备,Calico 的 CNI 插件需要在宿主机上为每个容器的 Veth Pair 设备配置一条路由规则用于接收传入的 IP 包。如 Node2 上的 Container4 对应的路由规则:

10.233.2.3 dev cali5863f3 scope link # 发往 10.233.2.3 的 IP 包应该进入 cali5863f3 设备
  1. 容器发出 IP 包,经过 Veth Pair 设备,发到宿主机。
  2. 宿主机根据路由规则的下一跳 IP 地址,转发到正确网关
  3. 后续同 Flannel 的 host-gw 模式

Calico 默认配置下,是 Node-to-Node Mesh 模式,每个宿主机的 BGP Client 都需要和其他所有节点的 BGP Client 通信交换路由信息,如果节点增加,连接数量会以 N^2 规模增长。一般推荐默认模式用于少于 100 个节点的集群。

而更大规模的集群,推荐使用 Route Reflector 模式。该模式下 Calico 会指定一个或几个专门的节点用于与所有的节点建立 BGP 连接,学习到全局的路由规则,同时充当中间代理,而其他节点只需跟这几个专门的节点交换路由信息,就可以获取全局的路由信息。此时 BGP 的连接规模增长控制在了 N。

IPIP 模式
#

基于以上,同样,Calico 也是建立在二层连通的规则上。如果 node 在不同的子网中,则需要使用 IPIP 模式。

ipip 模式下,Felix 进程在 Node1 上添加的路由规则如下:

10.233.2.0/24 via 192.168.2.2 tunl0 

与之前不同的是,负责将 IP 包发出的设备变味了 tunl0,这是一个 IP 隧道(IP tunnel)设备,IP 包进入该设备后,会被 linux 内核的 IPIP 驱动接管,将这个 IP 包直接封装在一个宿主机网络的 IP 包中。如图,原 IP 包被封装成了新的 IP 包的 Payload,而新 IP 包的目的地址则为 Node2 的 IP 地址,即 192.168.2.2,离开 Node1 之后经过路由器到达 Node2。

IPIP 模式下由于多了额外的封包、解包工作,性能相较默认有所下降,可能与 Flannel VXLAN 模式性能相当。实际使用中,可以根据实际情况选择合适的网络方案,尽量避免使用 IPIP。

私有云网络方案
#

私有云环境在,宿主机之间的网关存在允许干预和定制的情况,这种情况下,将宿主机网关加入到 BGP Mash,从而避免使用 IPIP,是一种迫切的需求。Calico 项目提供了两种将宿主机网关设置为 BGP Peer 的方案:

  • 所有宿主机都主动和宿主机网关建立 BGP Peer 关系,将路由信息同步到网管上。这种方案下,Calico 要求宿主机网关支持 Dynamic Neighbors 的 BGP 配置方式,允许你给路由器配置一个网段,然后路由器就会自动跟该网段里的主机建立起 BGP Peer 关系。

  • 使用一个或多个独立组件负责搜集整个集群里的所有路由信息,然后通过 BGP 协议同步给网关。而我们前面提到,在大规模集群中,Calico 本身就推荐使用 Route Reflector 节点的方式进行组网。所以,这里负责跟宿主机网关进行沟通的独立组件,直接由 Route Reflector 兼任即可。这种方案网关的 BGP Peer 个数有限且固定,可以直接将这些独立组件配置成路由器的 BGP Peer,无需 Dynamic Neighbors。独立组件只需 watch ETCD 中宿主机和对应网段的变化,通过 BGP 协议分发给网关。

Related

Kubenetes 搭建(使用 debian12)
·4 mins
OpenTelemetry
·2 mins
ElasticSearch 基础使用
·14 mins
2020
·1 min
2024
·1 min
Kubernetes Operator
·11 mins