K8S网络模型设计:扁平的可连通的网络
K8S的网络是一个极其复杂的网络,如果想要用两个简单的词来描述K8S网络,那么我觉得扁平和可连通是K8S网络最大的特点(不懂隔离性)。
何为连通呢?
二层网络的连通:如果能够直接通过MAC帧直接通信的网络便是二层连通的网络,LAN就是这种网络
比如无限WIFI网络,比如以太网
三层网络的连通:如果能够通过IP报直接通信的网络便是三层连通的网络,便是三层连通
三层网络的连通分为两个部分,第一个部分是三层网络中的每一个LAN都是二层连通的,其次需要存在能够连通的路由来保证;这里可以简单回顾下三层网络通信的流程
通过路由表确定目标ip是否在链路上
如果在链路上,通过arp协议获取对应主机的mac地址,发送mac帧到链路上;
如果不在同一个链路上,通过本地路由表发送mac帧给下一跳,然后下一跳解析mac帧,分析ip报,继续路由直到最终跳到目标网络再次通过mac帧发送到目标主机或者到达ttl消失。
假如其中任何一个步骤不满足或者出问题,三层网络就无法连通
何为扁平呢?
就是希望可以在pod内直接通过IP进行互相通信而不需要在pod内部使用vpn之类的东西来连接其他pod(基础架构化),具体的可以看下k8s对网络的设计与要求。
k8s在设计其网络时,就希望网络对运行在其中的pod是透明的,因此提出了以下的一些要求与原则
k8s组网要求
所有的Pods之间可以在不使用NAT网络地址转换的情况下相互通信
所有的Nodes之间可以在不使用NAT网络地址转换的情况下相互通信
每个Pod自己看到的自己的ip和其他Pod看到的一致
k8s网络模型设计原则
每个Pod都拥有一个独立的 IP地址,而且 假定所有 Pod 都在一个可以直接连通的、扁平的网络空间中 。
不管它们是否运行在同 一 个 Node (宿主机)中,都要求它们可以直接通过对方的 IP 进行访问。
设计这个原则的原因 是,用户不需要额外考虑如何建立 Pod 之间的连接,也不需要考虑将容器端口映射到主机端口等问题。
而要想深入了解K8S的网络,就不得不去了解Linux操作系统中的网络以及计算机网络协议栈和一些网络技术
其中关于计算机网络协议栈道部分上次分享已经分享过了,所以本次的主题更多是Linux操作系统的网络以及一些网络技术
Linux操作系统中的网络
首先,我们来看下基本的linux网络,如下图所示
一个APP生成socket数据,然后经过网络协议栈包装IP报文,然后封装成MAC帧,在经过网络协议栈的过程中,会存在netfilters对数据进行一定的处理,同时也会存在路由的过程,
如果在同一个物理链路内,将直接通过ARP协议获取目标IP地址的MAC地址最终发送出去;
如果不在同一个物理链路则通过路由表确定下一跳的MAC地址,封装成MAC帧发送到目标地址。
在这个过程中,会根据路由表选择对应的端口,如果是lo端口,则会将帧原封不动的返回计算机网络协议栈,然后回到监听对应端口的SOCKET里。
如果是以太网端口则走以太网端口,如果是蓝牙或者无线网端口同理。
iptables与netfilters
iptables是一个用户空间的应用程序,通过该程序可以修改一些配置文件,这些文件定义了防火墙的一些行为,netfilters是操作系统内核的一部分,netfilters里有5个回调钩子会触发iptables里的规则;iptables只是Linux防火墙的管理工具而已,位于/sbin/iptables。真正实现防火墙功能的是
netfilter,它是Linux内核中实现包过滤的内部结构。
这里不具体讲述其实现的原理,仅仅列出netfilters的一些功能:
1)filter表——三个链:INPUT、FORWARD、OUTPUT
作用:过滤数据包 内核模块:iptables_filter.
2)Nat表——三个链:PREROUTING、POSTROUTING、OUTPUT
作用:用于网络地址转换(IP、端口) 内核模块:iptable_nat
3)Mangle表——五个链:PREROUTING、POSTROUTING、INPUT、OUTPUT、FORWARD
作用:修改数据包的服务类型、TTL、并且可以配置路由实现QOS内核模块:iptable_mangle(别看这个表这么麻烦,咱们设置策略时几乎都不会用到它)
4)Raw表——两个链:OUTPUT、PREROUTING
作用:决定数据包是否被状态跟踪机制处理 内核模块:iptable_raw
虚拟网络设备 tap/tun
TUN 和 TAP 设备是 Linux 内核虚拟网络设备,纯软件实现。TUN(TUNnel)设备模拟网络层设备,处理三层报文如 IP
报文。TAP 设备模拟链路层设备,处理二层报文,比如以太网帧。TUN 用于路由,而 TAP 用于创建网桥。OS 向连接到 TUN/TAP
设备的用户空间程序发送报文;用户空间程序可像往物理口发送报文那样向 TUN/TAP 口发送报文,在这种情况下,TUN/TAP
设备发送(或注入)报文到 OS 协议栈,就像报文是从物理口收到一样。
虚拟网络设备 veth-pairs
虚拟以太网电缆。使用双向有名管道实现。常用于不同 namespace 之间的通信,即 namespace 数据穿越或容器数据穿越。
虚拟网络设备 bridge
bridge是linux自带的虚拟交换机(网桥),其可以连接多个以太网设备,拥有智能处理MAC帧的能力,流向交换机的MAC帧将智能的被传输到相应的二层链路
网络命名空间
在 Linux 中,网络名字空间可以被认为是隔离的拥有单独网络栈(网卡、路由转发表、iptables)的环境。网络名字空间经常用来隔离网络设备和服务,只有拥有同样网络名字空间的设备,才能看到彼此。
从逻辑上说,网络命名空间是网络栈的副本,有自己的网络设备、路由选择表、邻接表、Netfilter表、网络套接字、网络procfs条目、网络sysfs条目和其他网络资源。
从系统的角度来看,当通过clone()系统调用创建新进程时,传递标志CLONE_NEWNET将在新进程中创建一个全新的网络命名空间。
从用户的角度来看,我们只需使用工具ip(package is iproute2)来创建一个新的持久网络命名空间。
从系统实现来说,就是原本一个数据结构是static公共的,后来变成进程私有的,在PCB里存在一个命名空间的结构,命名空间里有着网络命名空间,网络命名空间拥有着所有跟网络相关的配置数据
默认空的网络命名空间可能只有一个未启动的lo回环网卡。
两个网络命名空间可以通过以太网揽直接连着两个网络命名空间的网卡,也可以通过以太网网桥连接。
通过以太网网桥或者以太网揽连接的两个网络命名空间只能说是在二层连通的,如果希望在三层连通,还需要给每个网络命名空间配置相应的路由表规则以及分配IP地址。
SingleHost容器网络
none模式
本质上就是创建一个网络命名空间,里面没有路由表,也没有通过veths-pair连接任何链路,外部无法访问这个容器,容器也无法访问外部
host模式
本质上就是使用宿主机的默认网络命名空间
container模式
本质上就是将当前容器部署在另一个容器所在的网络命名空间,这样发给本地的报文最终通过回环网卡回到了本机,这是同一个网络命名空间可以互通的原因
bridge模式
桥接模式就是在这些网络命名空间通过veth-pairs连接到同一个虚拟交换机上(二层连通),同时在对应的命名空间配置对应的路由表规则,但是从图片中可以看到交换机另一端连的上网络协议栈。
也就是那些MAC帧都会被宿主机接收,但是宿主机接收并不一定会处理,比如并没有开启ip转发功能(工作于路由器模式还是主机模式),那么不是本地ip的报文都会被丢弃;或者说netfilters拒绝处理
这些奇怪的报文。
理论上,这些容器发送给其他容器的mac报文是会被虚拟交换机智能转发到对应的容器的,这是同一主机不同容器互相连通的原因
假如宿主机配备了相应的路由规则和防火墙规则,那么容器的报文说能够通过路由最终转发出去的,这也是容器访问互联网的原理
但是这种模式是没法运用在多主机的情况下,因为宿主机不知道其他宿主机里的虚拟网络的路由,当相关ip报到达宿主机时,这些ip报将会被宿主机交给默认路由(下一跳:路由器)
最终路由器会把相关的ip报丢失或者到达ttl最终丢失
MultiHost容器网络
路由方案
回顾docker的单机网络模型,我们发现多主机不能通行的原因就在于你只能给当前主机配置路由规则和防火墙规则,而其他主机并不知道这些ip在你的虚拟网络中,假如能够将这些路由信息同步到其他
宿主机,那么网络便会打通。比较直接的想法就是给每台宿主机配置路由规则。而路由规则要求下一跳必须在当前网络,所以假如宿主机是二层互联的,那么通过给这些宿主机同步这些路由规则便能够
实现一个扁平的连通的网络。
其中布置在每一台宿主机可以通过k8s的daemonSet实现,而这种数据的管理可以交给etcd来实现。
这类方案便是基于路由,基于这个方案的实现有基于静态路由的flannel的host-gateway,以及基于动态路由的calico(使用边际路由协议以及一堆深奥的名词的实现)。
下面来看看Flannel的host-gateway原理(每一台宿主机都相当于本机容器网络的路由器):
通过路由方案构建的网络,宿主机也能访问这些虚拟网络里的Pod
询问基德大佬得知国际化sit环境的k8s网络接口实现就是Flannel的Host-gateway,而我们的办公网络和集群网络之间的路由是搭建好的,所以我们应该可以直接通过podId访问pod里的服务
下面是sit环境的两个服务
跟踪路由发现符合猜想
其中10.1.9.56和10.1.1.24就是宿主机的ip,这些宿主机在一个LAN里,这些宿主机相当于虚拟网络中的路由器;
猜测我们办公网和qunhe集群在一个VLAN里(二层可达)
隧道方案
隧道方案比较典型的就是UDP和XVLAN,两者都是使用Overlay网络(覆盖网络,所谓的大二层技术);其实用隧道技术最多的是VPN应用
其中UDP是XVLAN的替代品(早期Linux没有支持XVLAN协议,通过tun/tap技术将流量引到用户空间然后解包生成包再发,因为发生在用户空间而且多次copy导致性能较差,所以一般不推荐,除非你的linux版本比较低没法用xvlan)
下面就简单介绍下XVLAN技术的大概原理,下图是XVLAN的报文格式,可以发现就是在高层协议的报文里塞了二层报文
其中XVLAN头里有一个关键的字段,VNID这是个24位的字段,每个虚拟的网络主机都有一个自身的VNID作为标识,理论上支持2的24次方个虚拟网络。
在docker的桥接网络里,是使用docker0网桥,在Flannel的xvlan方案里则是使用cni0作为网桥(和docker0没啥区别),主要的不同是cni网桥后面连接的是flannel.1这个网络设备,应该是一个虚拟网卡
这个网卡将原始报文包装成XVLAN报文(linux高版本支持xvlan报文)
这时需要的信息有 源nodeId,目标nodeId,源vnid,源macId,目标macId,源podId,目标podId
其中目标nodeId,目标macId这两个信息是不存在的;因此需要有个方式根据目标podId获取目标nodeId以及目标macId
因此需要记录如何根据目标podId获取目标macId以及目标nodeId即可
这些数据是可以托管在某个地方的,Flannel就是将这些信息记录在etcd上
在每个node上的flannel.1网络设备通过etcd来通过对方的podId获取nodeId和macId
这样最终报文就变成了一个源ip是源nodeIp,目标ip是目标nodeIp的IP报文了(两台宿主机三层可达)
原本经过虚拟网桥是直接连接网络协议栈,但在xvlan模式下,则改为连接一个flannel1,在flannel1中将对原始报文封装成overlay报文转发
udp模式类似,只是udp转发报文说通过tap连通到用户空间,用户空间对报文进行处理然后发送(因为多次内核态用户态切换且数据copy问题,性能较差,仅在不支持xvlan的低版本linux中使用)
当然xvlan是一个技术,上面只是简单介绍最简单的形式
参考:
K8S知识图谱:https://zhaohuabing.com/post/2020-02-22-k8s-mindmap/
VXLAN协议原理简介:https://cizixs.com/2017/09/25/vxlan-protocol-introduction/