1。背景计算、存储和网络是云时代的三大基础服务,作为新一代基础设施的Kubernetes也不例外。三者之中,网络是最难掌握、最容易出问题的服务;本文简单梳理一下Kubernetes网络流量模型,希望能为初学者提供一些思路。首先看一下kubernetes的整体模型:容器网络中涉及到的几个地址:NodeIp:物理机地址。PODIp:Kubernetes的最小部署单元是Pod。一个pod可能包含一个或多个容器。简单来说,容器没有自己独立的地址。它们共享POD的地址和端口范围。ClusterIp:服务的IP地址。外网无法ping通该地址,因为是虚拟IP地址,没有网络设备负责这个地址。内部实现是使用Iptables规则重定向到其本地端口,然后平衡到后端Pod;它仅用于Kubernetes集群的内部访问。PublicIp:ClusterIP范围池中Service对象分配的IP,只能在内部访问,适合作为应用的内部层。如果这个Service作为前端服务,要为集群外的客户提供服务,我们需要为这个服务提供一个公网IP。2、容器网络流量模型容器网络至少需要解决以下几种场景下的通信:①一个POD内的容器之间的通信②同一主机上的POD之间的通信③跨主机的POD之间的通信④集群中的ServiceClusterIp和外部访问下面介绍实现方法2.1POD内容器间通信Pod内的容器之间可以通过“localhost”进行通信。它们使用相同的网络命名空间。对于容器,主机名是Pod的名称。一个Pod中的所有容器共享相同的IP地址和端口空间,您需要为每个需要接收连接的容器分配不同的端口。也就是说,Pod中的应用需要自己协调端口的使用。实验如下:首先,我们创建一个Pod,其中包含两个容器。容器参数如下:检查:可以看到容器共享Pod的地址,所以它们是否使用相同的端口资源,我们可以简单实验一下:先监听容器1中的一个容器端口:然后检查是否端口在容器2中被占用:可见端口也是共享的;所以简单理解Pod可以看成是一个小系统,而容器可以看成是系统中的一个不同的进程;内部实现:其实是和POD中的容器共享同一个Namespace,所以使用同一个Ip和Port空间。Namespace由一个名为Pause的小容器实现。每当创建Pod时,都会先创建一个pause容器,然后pod中的其他容器共享这个pause容器的网络栈,使外部pod能够进行通信。因此,对于同一个Pod中的所有容器,它们看到的是同一个网络视图。我们在容器中看到的地址,即Pod地址,其实就是Pause容器。IP地址。整体模型如下:我们在node节点查看之前创建的POD,可以看到pause容器:这个新创建的容器与现有容器(pause)共享一个NetworkNamespace(而不是与宿主机共享)就是我们常说的容器模式。2.2同一主机上的POD之间的通信每个节点上的每个Pod都有自己的命名空间。如何与同一主机上的POD通信?我们可以在两个POD之间建立一个VetPair进行通信,但是如果有多个Container,成对建立Veth会很麻烦。如果有N个POD,那么我们需要创建n(n-1)/2个VethPairs,可扩展性很差。如果我们能把这些VethPairs连接到一个集中的转发点,统一转发就很方便了。这个集中转发点就是我们常说的网桥;如下图(为简单起见,此处忽略暂停):仍然以我们的测试环境为例,创建pod1和pod2的地址分别为:10.244.1.16、10.244.1.18,位于node1节点。查看节点下的命名空间:这两个NS就是上面两个POD对应的命名空间,查询对应命名空间下的接口:可以看到红色标记的地址,其实就是POD的ip地址;NS和对应的POD地址已经找到了,那么如何确认这两个ns下的虚拟接口的另一端呢?比较直观的确认方式是:上面的接口如3:eth0@if7,表示本端接口id为3,对端接口id为7,我们看veth端口defaultnamespace(我们一般在default下看default):7:veth3b416eb5@if3,这个接口的id正是我们要找的,id为7的接口就是veth对的另一端;2.3POD间跨主机通信简单来说,网络上两个端点之间的互通方案不外乎两种。存在相互路由信息,路由信息存在于underlay路径上。一种是overlay方案,通过隧道实现互通,underlay层只需要保证主机可达即可。前者的代表方案有Calico(直接模式)和Macvlan,后者的方案有Overlay、OVS、Flannel和Weave。我们拿有代表性的Flannel和calico插件来介绍;2.3.1Flannel的整体通信流程如下:通信流程2.3.1.1地址分配flanneld第一次启动时,会从etcd获取配置的Pod网段信息,并分配一个Unused地址段,然后创建flannedl。1网络接口(或其他名称,如flannel1等),flannel会将分配给自己的Pod网段信息写入/run/flannel/docker文件(不同k8s版本文件名差异),docker再使用环境该文件中的变量设置docker0网桥,使该地址段归该节点所有;查看flannel为docker分配的地址段:表示该节点创建的POD地址均来自10.244.1.1/24,例如node1节点的以下两个pod。2.3.1.2路由下发在每台主机上,flannel都运行一个名为flanneld的守护进程,它可以在内核中创建路由表。查看node1节点的路由表如下:可以看到node2节点的路由规则匹配10.244.2.0,出接口为flannel.1端口(接口名flannel后面的数字可能不同)flannel.1是一个由flanneld程序创建的隧道端口;这里有个问题,如何判断隧道打到哪里,很明显,flannld存储了类似容器和物理节点的映射关系,存储在etcd中,flannld进程通过读取映射关系信息来判断隧道的外层封装在etcd中。2.3.1.3数据面封装Flannel在知道外层封装地址后对报文进行封装,源端使用自己的物理ip地址,目的端使用对端,vxlan外层udp端口8472(如果是UDP封装,则使用8285作为defaultdestinationport,下面会提到),peer只需要监听端口,当端口收到报文后,将报文发送给flannedld进程,flannedld进程将报文发送给flanned接口进行封装,然后查询本地路由表:可以看出目的地址是cni0;Flannel功能内部支持三种不同的后端实现,即:Host-gw:要求两台主机在同一网段,不支持跨网,因此不适合大规模部署UDP:不推荐使用,除非内核不支持vxlan或者调试时不使用,目前已经废弃;vxlan:vxlan封装,flannel利用vxlan技术创建一个Pod网络,可以和各个节点通信,使用的端口是UDP8472(这个端口需要开放,比如公有云AWS等)。下面在node节点抓包验证一下:(注:因为在linux环境下,Flannel的vxlan包中的UDP目的端口是8472,而标准的Vxlan报文是根据目的端口4789来识别的,所以需要手动指定按照vxlan解析,否则无法识别内层信息)2.3.2CalicoCalico支持3种路由模式:Direct:路由转发,不封装数据包;Ip-In-Ip:Calico默认路由方式,数据面采用ipip封装;vxlan:vxlan封装;这里主要介绍Direct模式,采用软路由建立BGP来宣告容器网段,让全网所有Node和网络设备都相互有路由信息,然后通过underlay直接转发。Calico实现的整体结构如下:组件包括:Felix:Calicoagent:运行在各个节点上,为容器设置网络信息:IP、路由规则、iptable规则等BIRD:BGPClient:监听路由信息由Felix在Host上注入,然后通过BGP协议广播给其他Host节点,实现网络互通邻居数来自n*(n-1)/2,所以也可以自己搭建RR反射器(上图中的结构),node节点和RR建立peer,当然node也可以和Tor建立peer,详细组网讨论可以参考官网:https://docs.projectcalico。org/reference/architecture/design/l3-interconnect-fabricCalicoctl:calico命令行管理工具。选择哪种对等模式没有固定的标准。适应整体网络规划,保证容器网络能够正确发布到物理网络即可。数据通信过程是:数据包先从veth设备发送到对方端口,然后到达目的地。在主机上以Cali开头的虚拟网卡上,到达这一端会到达主机上的网络协议栈,然后查询路由表进行转发;因为机器通过bird和RR建立了bgp邻居关系,所以会把本地的容器地址发送给RR,反映给网络中的其他节点。同样,其他节点的网络地址也会传送到本地,然后由Felix进程进行管理,发送到路由表中。数据包匹配路由规则后,可以正常转发(其实iptables规则有很复杂,这里就不展开了)通过简单的实验学习一下:具体的安装过程就不说了,大家可以参考官网:https://www.projectcalico.org/安装部署;Node节点bgp配置如下:为了简化实验,我们启用另一台机器运行FRR作为RR(Frr参考官网https://frrouting.org/),RR配置如下:这样所有节点都与RR建立了bgp邻居,通过以下方式查看邻居状态:我们新建两个pod,分别位于两个node节点上:默认情况下,当第一个容器出现在网络中时,calico会分配一个子网(子网掩码/26)到容器,此节点上的后续pod将出现Pod从该子网分配IP地址。这样做的好处是可以减小节点上路由表的大小。当我们进入容器查看路由时,发现网关地址为169.254.1.1。其实在calico网络中,容器网关始终是169.254.1.1,这个地址在实际网络中是不存在的,它是一个直接的ARP代理(ee:ee:ee:ee:ee:ee),当我们创建一个Pod,系统会在对应的节点上添加一个新的cali开头的虚拟网卡是vethpair的另一端(这一端是容器本地的eth0端口),它的mac是对应的mac地址到上面的169.254.1.1。此时消息已经进入了默认的命名空间。到这里,开始查看路由表:其中192.168.23.128/26是node2上的地址空间。路由由node2鸟发送给RR,RR反射给node1鸟,然后由felix管理,发送到路由表。我们可以使用node1节点抓包进一步确认:同时由于calico的代理方式,同一节点的不同POD之间的通信也比较特殊。也是通过三层转发来实现的。例如node2节点的两个地址存在于路由表的/32位,下一跳接口是veth-pair的一端。另一端是对应的pod内部接口;这与通过bridge实现的flannel不同;2.3.3小结这里我们从网络的角度对flannel和calico做一个简单的对比:总体来说,对性能敏感,策略要求高,我倾向于Calio方案,否则Flannel会是更好的选择;2.4Service与对外通信的实现Service与对外通信场景涉及较多的iptables转发原理,这里限于篇幅不再展开。简单介绍如下:Pod与服务通信:Pod可以直接通过IP地址进行通信,但前提是Pod知道对方的IP在Kubernetes集群中,Pod可能会频繁销毁和创建,也就是说Pod的IP不固定。为了解决这个问题,Service提供了一个访问Pod的抽象层。无论后端Pod如何变化,Service作为稳定的前端对外提供服务。同时Service还提供了高可用和负载均衡的功能。Service负责将请求转发到正确的Pod;对外通信:无论是Pod的IP,还是Service的ClusterIP,都只能在Kubernetes集群内看到,在集群外的世界里,这些IP都是私有的。Kubernetes为外界与Pod通信提供了两种方式:NodePort:Service通过Cluster节点的静态端口对外提供服务,外界可以通过:访问Service。LoadBalancer:Service使用云提供商提供的负载均衡器对外提供服务,云提供商负责将负载均衡器的流量导向Service。目前支持的云提供商包括GCP、AWS、Azur等。3.结论容器网络场景复杂,涉及面广。希望能给大家一些经验作为参考。如果我错了,请纠正我。
