0%

【Kubernetes】网络模型

Docker 容器网络模型中,我介绍了各种容器网络的实现原理,然而真正将容器技术发扬光大的是Kubernetes容器编排平台。Kubernetes通过整合规模庞大的容器实例形成集群,这些容器实例可能运行在异构的底层网络环境中,如何保证这些容器间的互通是实际生产环境中首要考虑的问题之一。本文将介绍在 k8s 环境下,网络模型的基本原理和主流 k8s 容器网络方案。

K8s 网络要求

Kubernetes对容器技术做了更多的抽象,其中最重要的一点是提出Pod的概念,Pod是Kubernetes资源调度的基本单元,我们可以简单地认为Pod是容器的一种延伸扩展,从网络的角度来看,Pod必须满足以下条件:

  • 每一个Pod都有一个独特的IP地址,所有Pod都在一个可以直接连通的、扁平的网络空间中
  • 同一个Pod内的所有容器共享同一个netns网络命名空间

基于这样的基本要求,我们可以知道:

  • 同一个Pod内的所有容器之间共享端口,可直接通过localhost+端口来访问
  • 由于每个Pod有单独的IP,所以不需要考虑容器端口与主机端口映射以及端口冲突问题

事实上,Kubernetes进一步确定了对一个合格集群网络的基本要求:

  • 任意两个Pod之间其实是可以直接通信的,无需显式地使用NAT进行地址的转换
  • 任意集群节点node与任意Pod之间是可以直接通信的,无需使用明显的地址转换,反之亦然
  • 任意Pod看到自己的IP跟别人看见它所用的IP是一样的,中间不能经过地址转换

也就是说,必须同时满足以上三点的网络模型才能适用于kubernetes,事实上,在早期的Kubernetes中,并没有什么网络标准,只是提出了以上基本要求,只有满足这些要求的网络才可以部署Kubernetes,基于这样的底层网络假设,Kubernetes设计了Pod-Deployment-Service的经典三层服务访问机制。直到1.1发布,Kubernetes才开始采用全新的 CNI 网络标准。

Pod 网络模型

要了解kubernetes网络模型的实现原理,我们就要从单个Pod入手,事实上,一旦熟悉了单个Pod的网络模型,就会发现kubernetes网络模型基本遵循和容器网络模型一样的原理。

Pod 启动的时候先创建pause容器生成对应的netns网络命名空间,然后其他容器共享pause容器创建的网络命名空间。而对于单个容器的网络模型我们之前也介绍过,主要就是通过docker0网桥设备与veth设备对连接不同的容器网络命名空间,由此,我们可以得到如下图所示的单个Pod网络模型的创建过程:

可以看到,同一个Pod里面的其他容器共享pause容器创建的网络命名空间,也就是说,所有的容器共享相同的网络设备,路由表设置,服务端口等信息,仿佛是在同一台机器上运行的不同进程,所以这些容器之间可以直接通过localhost与对应的端口通信;对于集群外部的请求,则通过docker0网桥设备充当的网关,同时通过iptables做地址转换。我们会发现,这其实就是对当个容器的bridge网络模型的扩展。

主流网络方案

上一小节我们知道单个Pod的网络模型是容器网络模型的扩展,但是Pod与Pod之间的是怎么相互通信的呢?这其实与容器之间相互通信非常类似,也分为同一个主机上的Pod之间与跨主机的Pod之间两种。

如容器网络模型一样,对于统一主机上的Pod之间,通过docker0网桥设备直接二层(数据链路层)网络上通过MAC地址直接通信:

而跨主机的Pod之间的相互通信也主要有以下两个思路:

  1. 修改底层网络设备配置,加入容器网络IP地址的管理,修改路由器网关等,该方式主要和SDN(Software define networking)结合。
  2. 完全不修改底层网络设备配置,复用原有的underlay平面网络,解决容器跨主机通信,主要有如下两种方式:
  • 隧道传输(Overlay): 将容器的数据包封装到原主机网络的三层或者四层数据包中,然后使用主机网络的IP或者TCP/UDP传输到目标主机,目标主机拆包后再转发给目标容器。Overlay隧道传输常见方案包括Vxlan、ipip等,目前使用Overlay隧道传输技术的主流容器网络有Flannel等;

  • 修改主机路由:把容器网络加到主机路由表中,把主机网络设备当作容器网关,通过路由规则转发到指定的主机,实现容器的三层互通。目前通过路由技术实现容器跨主机通信的网络如Flannel host-gw、Calico等;

下表是几种主流Kubernetes网络方案的对比:

A Overlay-Network Host-RouteTable NetworkPolicy Support Decentralized IP Allocation
Flannel UDP/VXLAN Host-GW N N
Weave UDP/VXLAN N/A Y Y
Calico IPIP BGP Y N

Flannel

Flannel 通过给每台宿主机分配一个子网的方式为容器提供虚拟网络,它基于Linux TUN/TAP,使用UDP封装IP包来创建overlay网络,并借助etcd维护网络的分配情况。

Flannel 网络是目前最主流的容器网络之一,同时支持overlay和修改主机路由两种模式。 Flannel和Docker原生Overlay网络不同的是,后者的所有Node节点共享一个子网,而Flannel初始化时通常指定一个16位的网络,然后每个Node单独分配一个独立的24位子网。由于Node都在不同的子网,跨节点通信本质为三层通信,也就不存在二层的ARP广播问题了。对于overlay的数据包封装,可以使用用户态的UDP,内核态的Vxlan(性能相对较好),甚至在集群规模不大,且处于同一个二层域时可以采用host-gw的方式修改主机路由表

另外,Flannel之所以被认为非常简单优雅的是,不像Docker原生overlay网络需要在容器内部再增加一个网桥设备专门用于overlay网络的通信,Flannel只需要使用Docker最原生的docker0网络,除了需要为每个Node配置subnet外,几乎不改变原有的Docker网络模型。

正如上面这张图看到的那样,Flannel在Docker最原生的docker0网桥设备和veth设备对的基础上,为了每个node单独分配了一个子网:

Node Name Node IP Node Subnet
Node 1 192.168.1.101 10.0.1.0/24
Node 2 192.168.1.102 10.0.2.0/24

我们也可以看到,容器对外流量的访问以及统一主机上的容器之前的访问完全与docker原生的bridge模式相同。那么,怎么实现跨主机间的容器相互访问呢?

前面说过,Flannel同时提供了overlay和修改主机路由两种方式,其中overlay方式主要通过vxlan隧道,而修改主机路由则是通过配置主机静态路由的方式。

vxlan 隧道

查看主机node1的本地静态路由:

1
2
3
4
5
6
7
8
9
10
# node1
# ip route show
...
10.0.1.0/24 dev docker0 proto kernel scope link src 10.0.1.1
10.0.2.0/24 via 10.0.2.0 dev flannel.1 onlink
# node2
# ip route show
...
10.0.2.0/24 dev docker0 proto kernel scope link src 10.0.2.1
10.0.1.0/24 via 10.0.1.0 dev flannel.1 onlink

可以看到,对于每个主机,当前子网都可以直接通过docker0网桥直接访问,而其他的子网则需要特殊的网络设备flannel.1来访问,它是一个Linux的vxlan网络设备,其中.1为VNI值,默认值为1。

因为同一主机的容器处于同一子网,因此直接通过ARP学习即可,而不同主机上的容器处于不同的子网,所以不涉及ARP广播泛洪的问题。但是,vxlan网络设备flannel.1如何知道对端VTEP地址呢?我们来看一下转发表fdb:

1
2
# bridge fdb | grep flannel
82:fa:1d:48:14:00 dev flannel.1 dst 192.168.1.102 self permanent

其中192.168.1.102是另外一个Node的IP地址,即VTEP地址,而82:fa:1d:48:14:00是对端网络设备flannel.1的MAC地址,从permanent可以看出转发表是由Flannel静态添加的,这些信息可以保存在etcd中。

相对于docker原生的overlay来说,除了增加或者减少Node,需要Flannel配合配置静态路由以及fdb表,容器的创建与删除完全不需要Flannel干预,事实上Flannel也不需要知道有没有新的容器创建或者删除。

host-gw 路由

上面我们介绍了Flannel通过vxlan隧道搭建overlay实现容器间的跨主机通信,其实Flannel支持通过host-gw,也就是修改主机路由的方式实现容器间的跨主机通信,此时每个Node都相当于一个路由器,作为容器的网关,负责容器的路由转发。

对于Flannel的host-gw修改主机路由的方式,我们后面有机会在详细去剖析。需要说明的是host-gw的方式相对overlay由于少了vxlan的封包拆包过程,直接路由数据包到目的地址,因此性能相对要好。不过正是由于它是通过添加静态路由的方式实现,每个宿主机相当于是容器的网关,因此每个宿主机必须在同一个子网内,否则跨子网由于链路层不通导致无法实现路由。

Weave Net

Weave Net 是一个多主机容器网络方案,支持去中心化的控制平面,各个host上的wRouter间通过建立Full Mesh的TCP链接,并通过Gossip来同步控制信息。这种方式省去了集中式的K/V Store,能够在一定程度上减低部署的复杂性,Weave将其称为“data centric”,而非RAFT或者Paxos的“algorithm centric”。

数据平面上,Weave通过UDP封装实现L2 Overlay,封装支持两种模式:

  • 一种是运行在user space的sleeve mode。Sleeve mode通过pcap设备在Linux bridge上截获数据包并由wRouter完成UDP封装,支持对L2 traffic进行加密,还支持Partial Connection,但是性能损失明显。
  • 另一种是运行在kernal space的 fastpath mode。Fastpath mode即通过OVS的odp封装VxLAN并完成转发,wRouter不直接参与转发,而是通过下发odp 流表的方式控制转发,这种方式可以明显地提升吞吐量,但是不支持加密等高级功能。

Contiv

Contiv 是思科开源的容器网络方案,主要提供基于Policy的网络管理,并与主流容器编排系统集成。Contiv最主要的优势是直接提供了多租户网络,并支持L2(VLAN), L3(BGP), Overlay (VXLAN)以及思科自家的ACI。

Calico

Calico 是一个基于BGP的纯三层的数据中心网络方案(不需要Overlay),并且与OpenStack、Kubernetes、AWS、GCE等IaaS和容器平台都有良好的集成。

Calico在每一个计算节点利用Linux Kernel实现了一个高效的vRouter来负责数据转发,而每个vRouter通过BGP协议负责把自己上运行的workload的路由信息像整个Calico网络内传播——小规模部署可以直接互联,大规模下可通过指定的BGP route reflector来完成。 这样保证最终所有的workload之间的数据流量都是通过IP路由的方式完成互联的。Calico节点组网可以直接利用数据中心的网络结构(无论是L2或者L3),不需要额外的NAT,隧道或者Overlay Network。

此外,Calico基于iptables还提供了丰富而灵活的网络Policy,保证通过各个节点上的ACLs来提供Workload的多租户隔离、安全组以及其他可达性限制等功能。

Calico采用和Flannel host-gw类似的方式,即通过修改主机路由的方式实现容器间的跨主机通信,不同的地方在于Flannel通过flanneld进程逐一添加主机静态路由实现,而 Calico 则是通过 BGP 协议实现节点间路由规则的相互学习广播。

Calico也会像Flannel一样会为每个主机节点分配一个子网,只不过Calico默认分配的是26位子网,不同于Flannel默认分配的24位子网:

Node Name Node IP Node Subnet
Node 1 172.16.31.155 10.1.83.192 ⁄ 26
Node 2 172.16.32.117 10.1.32.192 ⁄ 26
Node 2 172.16.32.120 10.1.147.128 ⁄ 26

Calico通过BGP协议动态调价路由,那么我们就来看看主机的路由规则:

1
2
3
4
5
6
7
8
# node1
# ip route show
...
blackhole 10.1.83.192/26 proto bird
10.1.83.193 dev cali8632426305e scope link
10.1.83.194 dev cali87c063cc2db scope link
10.1.32.192/26 via 172.16.32.117 dev tunl0 proto bird onlink
10.1.147.128/26 via 172.16.32.120 dev tunl0 proto bird onlink

可以看到,Calico修改主机路由来实现容器间的跨主机通信方式和Flannel host-gw一样,下一跳直接指向对应主机的IP,把主机当作容器的网关。但Calico会增加一条blackhole的规则,将非法的容器IP直接丢弃;另外一个不一样的地方是数据包到达宿主机后,Flannel会通过路由转发流量到bridge网络设备中,再由bridge设备转发给容器,而Calico则为每个容器IP生成一条路由规则,直接指向容器的网卡对端。当容器数量达到一定规模之后,主机路由规则数量也会越来越多,性能会是个问题。Calico给出的解决方案是路由反射器(route reflector)

此外,需要注意的是,我们知道Flannel host-gw的方式只支持每个宿主机在同一个子网内,才可以添加静态路由规则,不能支持跨网段的主机node,那么Calico支持吗?

答案是肯定的,Calico在安装的时候可以指定是否打开ipip模式,也就是ipip隧道。在我的实验环境中,ipip模式已经打开,如果你观察得够仔细的话,你会发现主机路由中发往其他主机上的容器的路由要经过tunl0网络设备,我们来看一下tunl0网络设备的类型:

1
2
3
# ip link show tunl0
5: tunl0@NONE: <NOARP,UP,LOWER_UP> mtu 1430 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1
link/ipip 0.0.0.0 brd 0.0.0.0

可以看到,tunl0其实是Linux的TUN网络设备,它会在不同主机节点间开启IPIP隧道,也就是说,即使主机节点处于三层(IP)网络中,数据包还是可以通过隧道发送到目标主机,在由目标主机转发给对应的容器。这时Calico其实采用的是overlay的方式。

OVN

OVN (Open Virtual Network) 是OVS提供的原生虚拟化网络方案,旨在解决传统SDN架构(比如Neutron DVR)的性能问题。

OVN为Kubernetes提供了两种网络方案:

  • Overaly: 通过ovs overlay连接容器
  • Underlay: 将VM内的容器连到VM所在的相同网络

其中,容器网络的配置是通过OVN的CNI插件来实现

SR-IOV

Intel 维护了一个 SR-IOV的 CNI插件,fork自hustcat/sriov-cni,并扩展了DPDK的支持。

Romana

Romana是 Panic Networks 在2016年提出的开源项目,旨在借鉴 route aggregation 的思路来解决Overlay方案给网络带来的开销。

OpenContrail

OpenContrail 是 Juniper 推出的开源网络虚拟化平台,其商业版本为Contrail。其主要由控制器和vRouter组成:

  • 控制器提供虚拟网络的配置、控制和分析功能
  • vRouter提供分布式路由,负责虚拟路由器、虚拟网络的建立以及数据转发

其中,vRouter支持三种模式

  • Kernel vRouter:类似于ovs内核模块
  • DPDK vRouter:类似于ovs-dpdk
  • Netronome Agilio Solution (商业产品):支持DPDK, SR-IOV and Express Virtio (XVIO)

这个项目 提供了一个OpenContrail的CNI插件。

Canal

Canal是Flannel和Calico联合发布的一个统一网络插件,提供CNI网络插件,并支持network policy。

kuryr-kubernetes

kuryr-kubernetes 是OpenStack推出的集成Neutron网络插件,主要包括Controller和CNI插件两部分,并且也提供基于Neutron LBaaS的Service集成。

Cilium

Cilium是一个基于eBPF和XDP的高性能容器网络方案,提供了CNI和CNM插件。

CNI-Genie

CNI-Genie是华为PaaS团队推出的同时支持多种网络插件(支持calico, canal, romana, weave等)的CNI插件

参考资料