大家好,我是二哥。今天这期是一篇关于VPC和三种K8s网络模型的汇总性文章。也是春节前最后一篇文章,发完二哥就准备进入过年模式了。提前祝大家虎年虎虎生威,万事如意。
本文所有的高清大图,我都放到github上去了:https://github.com/LanceHBZhang/LanceAndCloudnative。以后也是如此,就不再发预热预告了。图片版权归二哥所有,除个人学习外用于其它用途前请先通过公众号联系二哥授权。
我们在学习K8s的时候,总是会碰到一个痛点:K8s作为一个完整的商用解决方案,包含了很多零散的基础知识点。比如单网络模型方面就涉及到网络虚拟化、iptables、eBPF、SDN等等。单个知识点我们可能学过、见过甚至用过,但把K8s基本用法撸完一遍下来依旧觉得无法识得庐山真面目。我想一个可能的原因是我们过于关注在细节而忽略了它整体的样子。所以我总是尽量在一张图中画出与这个主题相关的全景全局图,比如K8s既然被尊称为云原生时代的“数据中心操作系统(DCOS)”,那从网络拓扑方面来说,它和数据中心是如何配合的呢?我将这三种K8s网络模型和VPC相遇时的样子分别画了出来。希望能给你一种鸟瞰的视角,而不是迷失在技术的细节丛林里。
Overlay模型下,网络包如同害羞的儿童,每次和别人说话都躲在妈妈背后,要妈妈帮他传话。host-gw模型下,网络包就像未成年的小伙子,既想走出去直面凶险的江湖,但又离不开妈妈的大力支持。而Underlay模型,网络包就是大人的模样了,有什么需求和想法,自己直接和对方谈。
但无论是哪种模式,都离不开一些基础概念。在二哥看来,理解好了这些基础,再去看K8s三种网络模式能起到事半功倍之效。所以本文二哥先花了一些篇幅把这个基础知识再梳理一遍。重要的话再强调一下:图1非常基础,也非常重要。图2-图5都与图1有关系。
来吧,进入正题。
1. 基础和基石
1.1 TCP/IP协议栈和网络栈
K8s“扁平网络”的三种典型实现方式是:Overlay、主机间路由(host-gw)以及Underlay。它们都会在容器内虚拟一张网卡,取决于实现方式的不同,这张虚拟网卡既可能是veth类型的,也可能直接对物理网卡的虚拟。
我们说Network namespace用来隔离包括网卡(Network Interface)、回环设备(Loopback Device)、网络栈、IP地址、端口等等在内的网络资源。它的特点是由可配置的数据组成。对于一个进程来说,这些要素,其实就构成了它发起和响应网络请求的基本环境。这里所谓“网络栈(Networking stack)”,包括了:路由表(Routing Table), network filter,iptables 规则等。
无论是一个 Linux 容器还是宿主机的进程所能看见的“网络栈”,实际上是被隔离在它们各自的 Network namespace 当中的,通信双方的网卡(如典型的veth pair)也自然被隔离到相应的namespace中。
另外还有一个栈也会被经常提起:TCP/IP协议栈。我们可以将TCP/IP协议栈看成是程序的代码部分,而网络栈看成是程序的数据部分。很显然TCP/IP栈应该是被这个OS上所有人共享的,无论是进程还是容器,甚至是基于qemu-kvm的虚拟机都共享着宿主机的协议栈,但网络栈却是各个network namespace独享的。
网络包无论是从容器内的网卡流出,还是离开容器后再与其它网络设备打交道,必然都会经过宿主机内核TCP/IP协议栈。容器内的网卡属于容器所在的network namespace,而网络包离开容器后的下一跳(个)网络设备同样也属于它自己的network namespace。
1.2 虚拟网卡数据接收流程
二哥在文章《看图写话:聊聊veth数据流》和《高清大图描绘辛苦的ksoftirqd》中结合图1,描述了网路包从veth一端流出到流入另一端过程中所涉及到的数据流以及在这个过程中内核线程ksoftirqd起到的至关重要的作用。这张图也适用普通虚拟网卡。
图1有一些关键的地方,我把它们列在这里,希望你看完后可以不自觉地拍下大腿,轻呼一声:哦,原来如此。
- 为了便于对比,它包含了两种类型的网络设备:一个物理网卡和veth虚拟网卡。数据流分为两条线路:线路1和线路2。 线路1从物理网卡的数据接收开始,涉及到中断处理流程和DMA。这一步的处理结果是网络包被放进了与每个网卡相关的queue(RingBuffer)里面。 线路2从容器内的进程在veth一端发送数据开始,它相对简单很多,主要是单纯的软件调用,没有硬件的介入。这一步的处理结果是skb被放入到了input_pkt_queue里。
- 无论是线路1还是线路2,它们都需要唤起ksoftirqd以便来处理skb,并提前将相应的网卡挂到per CPU的poll_list上。我们可以将poll_list想象成晾晒香肠的架子,而每个网络设备则如同香肠一样挂到架子上面等待ksoftirqd处理。
- ksoftirqd扛起了网络包处理的重担。它从pull_list里找到要处理的网卡并从该网卡的queue里面拿到skb,然后沿着网络设备子系统 -> IP协议层 -> TCP层一路调用内核里面的函数来分析和处理这个skb,最终将其放置到位于TCP层的socket接收队列里。 这个处理过程包括iptables的处理、路由查询、TCP协议处理等各种费时费力的工作。如图1所示,这里的iptables、路由查询等数据部分与每个network namespace相关,宿主机OS上会有若干个network ns,但TCP/IP协议栈作为代码部分却是大家共享的。
图 1:物理网卡和虚拟网卡数据接收完整数据处理流程
图2所示的K8s Overlay网络模型的实现有一个重要的技术点:veth pair。我们暂时将位于容器内的veth叫veth1,而插在bridge端口的那个对端veth称为veth-peer。当网络包从veth1流出时,其实是在图1的2.a处把skb所属的设备修改为veth-peer,先暂时放到了input_pkt_queue里,但这个时候skb还没有到设备veth-peer处。
步骤2.c所触发的软中断使得ksoftirqd/4开始消费input_pkt_queue里的skb。
当skb沿着图1右上部分的TCP/IP协议栈流到位于链路层的网络设备子系统的时候,也就来到了veth-peer的怀抱。对于veth-peer而言,这就开始了它接收网络包的协议栈相关的处理流程,这个流程包含了二层和三层的数据包过滤、数据包转换、路由查询等细节。对数据包的处理和路由查询所涉及到的规则和路由表都属于网络栈,而这个网络栈与veth-peer所在的network namespace有关。
图2-图6(除图4)都有一个公共的部分:VPC(Virtual Private Cloud)。它可以让我们在XX云上创建一个虚拟的私有网络。各个VPC之间的网络默认是完全的隔离的。VPC可以基于包括GRE、VXLAN在内的各种技术实现。二哥在文章《当vpc遇到K8s overlay》中介绍了基于VXLAN的实现方法。
通常VM被用来做K8s Work Node。这些图中的VM通过VPC被分成了两个隔离的网络,淡橙色部分的VM网段为172.20.6.35/16,VXLAN ID为1234;蓝色部分的网段为172.10.5.25/16,VXLAN ID为5678。
当然,一台物理服务器上会混合地运行属于不同VPC的VM,这几张图也强调了这点。
2. VPC与Overlay
这部分细节参考二哥的文章《当vpc遇到K8s overlay》。
图2的重点是在每台VM上,K8s CNI都会创建一个bridge和vtep。veth pair的一端安装在了Pod内部,而另一端则插到了网桥上。
bridge即为网桥,它的行为类似二层交换机。如果网络包的目的 MAC 地址为网桥本身,并且网桥设置了 IP 地址的话,那么bridge就认为该网络包应该是发往创建该网桥的那台主机,于是这个数据包将不会转发到任何设备,而是直接交给上层(三层)协议栈去处理。处理的过程会涉及到基于本机路由表的路由查询。
VTEP(VXLAN Tunnel Endpoints)是VXLAN网络中绝对的主角,它既可以是物理设备,也可以是虚拟化设备,主要负责 VXLAN 协议报文的封包和解包。
在本机路由规则的精心配合下,从Pod发出来的网络包先到达运行于这台VM上的VTEP设备,在那里进行 VXLAN 封包。类似地在对端VM上VTEP设备也会参与其中并进行解封包操作。两端VTEP的配合,给通信双方的Pod营造了一个假象:它们如同在同一个扁平的二层互通的局域网一样。
但这没有完,如果通信双方的Pod位于不同的VM里,还需要VPC的配合。这是另外一个VXLAN的封包和解包的故事,也就出现了另外一个隧道。所以你会在图2中看到两层VXLAN隧道:深青色VXLAN隧道和粉色VXLAN隧道。
图 2:vpc和K8s overlay网络模型
前文说到图1是基础,是基石。那么它的基础性在图2中是如何体现的呢?图2是veth pair bridge vtep的组合。那么当网络包从位于Pod内部的veth(发送端)流出的时候,就对应于图1中的线路2入口处。发送端veth将网络包递交给接收端veth的过程涉及到图1中的2.a、2.b、2.c。因为接收端veth插在网桥里面,这个过程涉及到网桥的处理,它体现在图1中TCP/IP协议栈里面,bridge部分的处理流程部分。
3. VPC与Underlay
这部分细节参考二哥的文章《广角-聊聊Underlay》和《一等公民,聊聊Underlay(微距篇)》。
和图2不同,图3里网络包从Pod流出后,像VM的eth0一样,直接进入了Open vSwith上。简单、粗暴、直接。但可惜,这种好事不是你想要就能有,得看K8s云产商是否提供这个功能。
图 3:vpc和K8s underlay网络模型
隔离特性使得流经这些network namespace里的,各自网络设备上面的流量是相互独立、平行的,尤其重要的是Pod里的流量进出这台虚拟机的时候,root network namespace无法感知到,所以在VM上对iptables、路由等设置对进出Pod的流量不起任何作用。traffic离开Pod和VM里的进程产生的traffic离开VM一样,都是离开各自的network namespace。
为了强调这样的平行关系,我在图3里画了两个蓝色的箭头和一个红色的箭头,它们的出发点分别是Pod和VM,终点是穿过Open vSwitch的远方。traffic从各自的network namespace离开,互不见面,互不问候,互不干扰。
不同的network ns有不同的实例。如前文所述,不同的实例包含了不同的网卡设备、IP地址等数据。TCP/IP协议栈只关心对于一个skb,要执行何种net filter规则、该做何种路由又该从哪个网卡将这个skb送走。至于这个网卡是在Pod内还是在宿主机VM上,重要吗?不重要。
什么?还是觉得抽象?没关系,换个姿势。二哥把内核中负责描述进程的数据结构task_struct和network namespace之间的结构关系画出来了。进程1和进程2共享宿主机root network namespace,它包含网卡eth0。Pod内的容器自然位于Pod自己的network ns中。但容器本质上也是进程而已,虽然在图中看起来Pod隔离了一个完全属于自己的eth1,但在内核看来,一样也是用相同的数据结构来描述它和network ns之间的关系。
好了,看完这个数据结构,我们再来想想,对于TCP/IP协议栈而言,是不是它面对的只是位于不同ns中的网卡而已呢?
图 4:network namespace与task_struct结构关系图
4. VPC与host-gw
可惜,这部分二哥之前没有专门撰文介绍。
Host-gw简单来讲就是将每个Node当成Pod的网关。所谓网关就像城门,是网络包离开当前局部区域的卡口。从当前位置动身出发至目的地,一路上会有若干个城池,若干个城门。沿途中每一个这样的卡口称为“下一跳”,也即下一次暂时落脚的地方。大家见过青蛙在荷叶上连续跳动的样子吧?青蛙每次都会选择下一个可以歇脚的荷叶,并把它作为下一次跳跃的起点。你可以把网络包想象成青蛙。
“下一跳”这个概念,我想唐僧一定感悟颇深:贫僧来自东土大唐(就是源 IP 地址),欲往西天拜佛求经(指的是目标 IP 地址)。路过宝地,借宿一晚,明日启程,请问接下来该怎么走啊?
Host-gw的实现方式有两种典型的代表:Flannel和Calico。它们的共同点都是以Node为网关,也都会查询宿主机上的路由表。但在宿主机的网络层看来,需要处理的网络包来自不同的设备。
4.1 VPC与host-gw(Flannel)
Flannel的实现方案里,由bridge来将离开Pod的网络包丢进宿主机TCP/IP协议栈进行路由的查询。最终网络包经由宿主机的eth0离开并进入对方宿主机的eth。当然这个过程中离不开OVS基于VXLAN所架设的隧道。
图中flanneld是一个daemonset,它负责维护每个Node(网关)的IP信息并更新宿主机的路由表。
图 5:vpc和K8s host-gw网络模型(Flannel实现方案)
图5和图1的关系在哪里呢?其实无论是网络包通过veth pair流动到网桥这一部分还是网络包从网桥出来后查询宿主机路由表部分都与图2“VPC与Overlay”非常相似。只不过差别在于经过路由查询后,二者的对网络包的处理不一样。图2部分,网络包被交给了vtep进行第一层的封包,而在这里,网络包则被直接从本机eth0发送出去了。可以看到这里少了一次VXLAN的封包和解封包操作,故而效率提高了一些。
4.2 VPC与host-gw(Calico)
Calico方案与Flannel方案最大的区别就是网桥不见了。图6中cali-xxx和Pod中的veth为veth pair。图6少了网桥,相应地也就不需要图1中bridge相关的处理过程,当cali-xxx收到了网络包后沿着图1所示的接收处理流程,直接将其丢进宿主机TCP/IP协议栈进行路由的查询。
图6中BGP Client用于在集群里分发路由规则信息,而Felix则负责更新宿主机的路由表。
其它类似,二哥就不再赘述了。
图 6:vpc和K8s host-gw网络模型(Calico实现方案)
以上就是本文的全部内容。谢谢!