作者:二哥聊云原生 2022-04-02 08:14:25
云计算
云原生 这篇文章我主要通过下面这张全景图来讲述K8S是如何利用VXLAN来实现K8s的容器通信方案的。
目前成都创新互联已为千余家的企业提供了网站建设、域名、虚拟空间、网站托管运营、企业网站设计、清徐网站维护等服务,公司将坚持客户导向、应用为本的策略,正道将秉承"和谐、参与、激情"的文化,与客户和合作伙伴齐心协力一起成长,共同发展。
一篇文章围绕一张图,讲述一个主题。不过这个主题偏大,我估计需要好几篇文章才能说得清楚。
云原生的代表技术包括容器、服务网格、微服务、不可变基础设施和声明式API。其中K8s是不可变基础设施的压舱石。典型的K8s集群由数十个Node, 成百个Pod,上千个Container组成。相互隔离的容器间需要协作才能完成更大规模的应用。而协作就需要网络通信。
这篇文章我主要通过下面这张全景图来讲述K8s是如何利用VXLAN来实现K8s的容器通信方案的。网络通信不是量子纠缠,网络流量是实打实地通过了各个虚拟的、实体的网络设备,途径每个设备节点时自然也会受到设备上的路由、iptables等策略控制。
图:VXLAN容器网络方案全景图
K8s的容器通信方案有很多种。譬如flannel实现的host-gw方案、calico基于三层转发实现的方案以及本文着重讲述的flannel.1 VXLAN方案。为什么我要挑flannel.1 VXLAN方案来细聊呢,因为它够复杂,涉及到了比较多的虚拟网络设备和组网技术。
这张图里面涉及到如下几种网络设备,有机会我们单独拿一篇出来过一下这些设备。
虽然容器间的网络方案多种多样,但所有的容器网络通信问题,其实都可以归结为以下几种场景。本篇我们专注容器间通信的场景,故略去了其它通信主体与容器通信的情形,比如本地Node里面的进程也会和容器通信。留个彩蛋,以后再聊。
这里需要强调的一个点是,虽然Pod是K8s编排调度的基本单位,但是通信的需求却发端于Pod里面的容器。
这张图里面,Node 1 和Node X位于同一个局域网17.168.0.0/24。Node 1的IP地址是17.168.0.2,Node X的IP是17.168.0.3。
K8s集群所使用的子网为10.244.0.0/16。对于网络17.168.0.0/24和它里面的交换机和路由器来说,K8s集群所使用的子网是无效的网络,交换机和路由器更是无从转发、路由任何源IP或目的IP为K8s子网的数据包。
非常明显的矛盾出现了:K8s集群要通过子网为10.244.0.0/16通信,而宿主机环境却根本不认识这个子网。我们接下来将看到"特洛伊木马"的故事在这里再次上演。
我们的目标是在这种矛盾的网络环境下,解释清楚pod a里面的container-1访问pod b里面的container-1时发生了哪些事情。图中蓝色的标线展示了数据流的方向。
图中的绿色标线和绿色的框图表示了与VXLAN相关的数据流和网络封包示意图。
出于简单,Node 1里面只画出了一个Pod, pod a,所有的Pod都连在了bridge cni0上,子网为10.244.0.1/24。Node X里面只画了两个Pod, pod b和pod c ,所有的Pod也一样都连在了bridge cni0上,子网为10.244.1.1/24。
每个Node上面的bridge都分配有IP地址。Pod a的IP地址是10.244.0.2,Pod b的IP地址是10.244.1.3。
这是最简单的情形,内核自带技能,不需额外的组网技术加持。
需要强调的一个知识点是Pod内部所有的容器是共享同一个网络栈、routes以及iptables的,因为它们属于同一个network namespace。
在一个k8s cluster内部,每个Pod拥有独一无二的IP地址,Pod内部所有的container共享分配Pod的地址。Pod内部的容器共享pod的IP地址,但各个容器的端口不能冲突。
由于Pod调度的原子性,一个Pod内部的所有container只会被调度到一台主机上运行。类似本地机器上两个应用程序通过localhost进行进程间通信一样,同一个Pod内部的容器间可以直接通过localhost来通信。此时的traffic直接通过loopback 网络设备在两个容器间流动。图中的bridge无法感知这样的traffic,主机上的网络栈和其它网络设备更不会感知到。
图中Node X上画出了多个Pod。当Pod b里面的container-1想要访问Pod c里面的container-1时属于这个场景。
Pod b里面的路由表决定了访问Pod c的traffic需要从自己的interface eth0出去。
从图中可以看到Pod b和Pod c都是插在了bridge上面。作为一个虚拟的二层交换机,它按照二层交换机的行为交换、转发数据包。
在这种场景下,这两个container之间的通信行为不会超出bridge的范围,包括Pod b的container-1通过ARP得知目的container的MAC地址也是在bridge内处理。也不会涉及NAT等地址转换操作。
这是最常用的通信场景。容器访问api server即是典型的例子。
下面开始最复杂的步骤,这些步骤发生在Node 1。Node X收到以太帧后的操作是一个逆过程,这里不做赘述。
我们按照traffic的流向,以它途径的各个网络设备(虚拟的、实体的)为分割节点,分段讲述每段发生了什么。
从Pod a的路由表可知,以太帧需要从它的NIC eth0离开。因为eth0是veth的其中一端,另外一端插在bridge cni0上面,于是以太帧进入cni0。此以太帧的目的MAC地址为bridge。
前面提到该网桥配置有IP地址,现在它收到一个目的MAC地址为自己的数据包,于是触发了 Linux Bridge 的特殊转发规则:网桥不会将这个数据包转发给任何设备,而是直接转交给主机的三层协议栈处理。
主机协议栈根据host的路由表,从而得知需要把IP包交给本机的flannel.1。
从这步以后就是三层路由了,已经不在网桥的工作范围之内,而是由 Linux 主机依靠 Netfilter 进行 IP 转发(IP Forward)去实现的。注意这里是IP包转发,接收者收到的是3层的package,因而它不包含二层的数据。
至此,越过千山万水,本机的flannel.1终于收到了IP包。
从这里开始,flannel.1需要想办法营造幻象:跨主机营造一个虚拟的网络10.244.0.0/16,好让Pod a看起来Pod b和它正处于一个完全合法的、信息交换自由无障碍的环境。天真的Pod们完全不知这个网络是一个虚拟的、私有的、宿主机网络里面的交换机和路由器根本不认识它这样一个事实。
前面提到flannel.1收到的是 IP 包,既然是IP包,那它就没有MAC地址,但flannel.1同时又要想办法把“原始 IP 包”加上一个目的 MAC 地址(当然也需要包含源flannel.1的MAC地址),封装成一个完整的二层数据帧,然后发送给位于Node X上的flannel.1。
而大家都知道要组装一个完整的二层数据帧,首先需要解决的问题是目标 flannel.1的MAC地址是什么呢?下面的提示给出了答案。
我们已经知道了Node X上的flannel.1的 IP 地址,它是数据包的目的地。要根据三层 IP 地址查询对应的二层 MAC 地址,这正是 ARP(Address Resolution Protocol )表的功能。这里要用到的 ARP 记录,也是 flanneld 进程在 Node 1 节点启动时,自动添加在 Node 1 上的。我们可以通过 ip 命令看到它,如下所示:
# 在Node 1上
$ ip neigh show dev flannel.1
10.244.1.0 lladdr 5e:f8:4f:00:e3:37 PERMANENT
通过ARP,我们知道了目的 flannel.1的MAC是 5e:f8:4f:00:e3:37。到此时,已经完整地产生了内部数据载荷(Inner payload), 内部IP头(Inner IP Header) 10.244.1.3和内部Ethernet头(Inner Ethernet Header)5e:f8:4f:00:e3:37了。
但是,因为上面提到的这些 VTEP 设备的 MAC 地址,对于宿主机网络来说并没有什么实际意义,所以上面封装出来的这个数据帧,并不能在我们的宿主机二层网络里传输。为了方便叙述,我们把它称为“内部数据帧”(Inner Ethernet Frame),或者叫"原始二层数据帧"(Original Layer 2 Frame)。
封装好的内部数据帧如全景图中蓝色的方框所示。
接下来,Linux 内核还需要再把“原始二层数据帧”进一步封装成为宿主机网络里的一个普通的外部数据帧,好让它载着“原始二层数据帧”,通过宿主机的 eth0 网卡进行传输。
如下图所示,原始二层数据帧加上VXLAN头,我们把它叫做“VXLAN数据帧”。在全景图中,我在蓝色的方框上面加了一个灰色的方框,用来表示VXLAN头。需要特别注意下灰色方框中VNI=1这个部分。VNI(Virtual Network Identifier)长24-bit,在这里flannel.1默认把它设置为1,这样Node X上面的flannel.1就知道这个数据帧是需要它处理的。
有了VXLAN数据帧,就可以开始演绎一个和“特洛伊木马”相同的故事。VXLAN数据帧如同希腊战士,但我们的目的不是攻打特洛伊城,而是把这个VXLAN数据帧完整地、神不知鬼不觉地送到城内的flannel.1手里。要达到这个目的,我们还需要一个木马。
图:VXLAN数据帧
好了,“希腊战士”有了,我们就差一个木马了。接下来要做的事情是,像把希腊战士藏到木马里一样,Linux 内核要把这个VXLAN数据帧塞进一个 UDP 包里发出去。上面的全景图中,我特意把VXLAN数据帧画得窄了一些,好让你感觉外围稍胖的UDP包确实像是个木马。
Node 1上的flannel.1 设备要扮演一个“网桥”的角色,在二层网络进行 UDP 包的封包和转发。在Node 1看来,它会以为自己的 flannel.1 设备只是在向另外一台宿主机的 flannel.1 设备,发起了一次普通的 UDP 链接,却全然不知它发送的是一个木马(不要紧张,此木马非木马病毒)。
但且慢,先回答一个问题:刚才在组装内部数据帧的时候,我们知道 flannel.1 设备已经知道了目的 flannel.1 设备的 MAC 地址,但这个 UDP 包该发给哪台宿主机呢?也就是说,木马有了,希腊战士也藏到木马肚子里了,但特洛伊城在哪里?
是时候轮到一个叫作转发数据库(FDB, Forwarding Database)上场帮忙了。这个 flannel.1“网桥”对应的 FDB 信息,也是 flanneld 进程负责维护的。它的内容可以通过 bridge fdb 命令查看到,如下所示:
# 在Node 1上,使用“目的VTEP设备”的MAC地址进行查询
$ bridge fdb show flannel.1 | grep 5e:f8:4f:00:e3:37
5e:f8:4f:00:e3:37 dev flannel.1 dst 17.168.0.3 self permanent
在上面这条 FDB 记录里,指定了这样一条规则:发往我们前面提到的“目的 flannel.1”(MAC 地址是 5e:f8:4f:00:e3:37)的二层数据帧,应该通过本机的flannel.1 设备,发往 IP 地址为 17.168.0.3 的主机。显然,这台主机正是 Node X,UDP 包要发往的目的地就找到了。
得到了目的IP地址,自然也会得知Node X的MAC地址。接下来的流程,就是一个正常的,宿主机网络上的封包工作,且最终从 Node 1 的 eth0 网卡发出去了。只不过这个过程发生在虚拟设备flannel.1上面罢了。
标题名称:特洛伊木马-图解VXLAN容器网络通信方案
标题路径:http://www.shufengxianlan.com/qtweb/news45/484445.html
网站建设、网络推广公司-创新互联,是专注品牌与效果的网站制作,网络营销seo公司;服务项目有等
声明:本网站发布的内容(图片、视频和文字)以用户投稿、用户转载内容为主,如果涉及侵权请尽快告知,我们将会在第一时间删除。文章观点不代表本网站立场,如需处理请联系客服。电话:028-86922220;邮箱:631063699@qq.com。内容未经允许不得转载,或转载时需注明来源: 创新互联