作者主页(文火冰糖的硅基工坊):文火冰糖(王文兵)的博客_文火冰糖的硅基工坊_CSDN博客
本文网址:https://blog.csdn.net/HiWangWenBing/article/details/122806829
目录
第1章 K8S集群的网络模型概述
1.1 什么是K8S集群的网络模型
1.2 K8S为什么需要网络模型
1.3 K8S集群网络设计的基本原则
1.4 K8S集群的网络类型
1.5 四层模型
第2章 Docker容器和Docker容器之间的网络与通信
第3章 Pod与Pod之间的网络与通信
3.1 Linux虚拟网桥
3.2 同一个Node中的Pod之间的通信
3.3 不同Node的Pods之间的通信
3.4 具体的网络规划与实现
3.5 pod网络的IP地址空间的配置
第4章 Pod与Service之间的网络与通信
4.1 Pod通信的缺点与Service的出现
4.2 Service的IP地址
4.3 服务请求在Service和Pod之间的转发与路由
第5章 Internet与Service之间的网络与通信
5.1 从k8s service访问Internet的访问
5.2 从Internet访问k8s的service的访问
5.2.1 NodePort Service
5.2.2 LoadBalancer Service (负载均衡器)
5.2.3 Ingress controller(ingress 控制器)
计算机网络是指由通信线路互相连接的许多自主工作的计算机构成的集合体,各个部件之间以何种规则进行通信。
K8S的网络模型是指确定K8S集群内部有哪些节点、模块组成,节点、模块间是如何分层的、如何组网, 集群内的Pod是如何通信的。
K8S的网络模型分为集群内部网络模型和集群作为一个整体的外部网络模型。
(1)通信单元数量众多
集群内由无数个服务单元Pod组成。
(2)统一的IP通信模型
为了使得服务单元之间解耦,把每个服务单元设计成一个独立基于网络通信的应用程序,每个Pod拥有独立的IP地址,不同的服务之间通过IP网络进行通信,而不是单主机的进程间通信。
(3)对外,统一的接口
对外,集群只有一个或少力量接口,蔽集群内部的细节,所有的外部服务请求在外部接口汇总。
(4)对内,负载均衡
对外部业务请求的处理,内部采用负载均衡策略。
综上所述,就需要合适的网络模型,来实现集群的上述特征。
(1)每个Pod、Sevice拥有独立的IP地址
每个pod自身就有一个独立的、集群内部的虚拟IP地址。当然,这个IP地址是虚拟IP地址,是私有IP地址,集群外部是看不见的。
(2)偏平的Pod IP地址空间
假定所有 Pod 都在一个可以直接连通的、扁平的网络空间中,所以不管它们是否运行在同 一 个 Node (宿主机)中,都要求它们可以直接通过对方的 IP 进行访问。
设计这个原则的原因 是,用户不需要额外考虑如何建立 Pod 之间的连接,也不需要考虑将容器端口映射到主机端口等问题。
(3)IP地址的一致性
由于 Kubemetes 的网络模型假设 Pod 之间访问时使用的是对方 Pod 的实际地址,所以一个 Pod 内部的应用程序看到的自己的 IP 地址和端口与集群内其他 Pod 看到的一样。它们都是 Pod 实际分配的IP地址 (从dockerO上分配的)。将IP地址和端口在Pod内部和外部都保持一致, 我们可以不使用 NAT 来进行转换。
Docker容器和Docker容器之间的网络与通信
Pod与Pod之间的网络与通信
Pod与Service之间的网络与通信
Internet与Service之间的网络与通信
第1层(最底层):微服务实例层 =》 Pod层
第2层:服务抽象层 =》 Service层
第3层:服务统一入口层:Ingress层
第4层:负载均衡层:LB层
Pod是多个紧密关联的容器的抽象,是K8S最小的部署单元。
Docker容器和Docker容器是解决Pod内部的容器与容器之间的通信。
每个Pod中管理着一组Docker容器,这些Docker容器共享同一个网络命名空间。
Pod内部的容器通常部署在同一个Node节点上。
Pod中的每个Docker容器拥有与Pod相同的IP地址空间和port空间。每个容器和每个Pod都有各自独立的虚拟IP地址 (不同于主机的私有IP地址和共有IP地址)
Pod内部的容器共享相同的网络命名空间,他们之间可以通过localhost:127.0.0.1进行网络通信。相互访问。
什么机制让同一个Pod内的多个docker容器相互通信那?其实是使用Docker的一种网络模型:–net=container。
container模式指定新创建的Docker容器和已经存在的一个容器共享一个网络命名空间,而不是和宿主机共享。新创建的Docker容器不会创建自己的网卡,配置自己的 IP,而是和一个指定的容器共享 IP、端口范围等。
每个Pod容器有一个pause容器其有独立的网络命名空间,在Pod内启动Docker容器时候使用 –net=container就可以让当前Docker容器加入到Pod容器拥有的网络命名空间(pause容器)
Linux以太网桥(Linux Ethernet bridge)是一个虚拟的2层网络设备(说白了,就是一个虚拟的以太网交换机),目的是把多个以太网段链接起来,网桥维护了一个转发表,通过检查转发表和待传输的数据包的目的地址,决定是否将数据包传递到连接到网桥的其他网段,网桥代码通过查看网络中每个以太网设备特有的MAC地址来决定是传输数据还是丢弃数据。
虚拟网桥实现了ARP协议用来根据给定的ip地址找到对应机器的数据链路层的mac地址,一开始转发表为空,当一个数据帧被网桥接受后,网桥会广播该帧到所有的链接设备(除了发送方设备),并且把响应这个广播的设备记录到转发表;随后发往相同ip地址的流量会直接从转发表查找正确的mac地址,然后转发包到对应的设备。
如上图显示,两个Pod通过veth对链接到root netns命名空间,并且通过网桥进行通信。
每个Pod都是都有一个虚拟网卡。
鉴于每个Pod有自己独立的网络命名空间,我们使用虚拟以太网设备把多个Pod的命名空间链接到了root命名空间,并且使用网桥让多个Pod之间进行通信。
k8s中,每个Pod拥有一个ip地址,不同的Pod之间可以直接使用该IP地址与彼此进行通讯。
在同一个Node上的不同Pod之间,每个Pod有自己的网络命名空间中,而不是共享相同的名字空间,因此在同一个Node上的不同Pod之间通信,必须使用各自的IP地址进行通信,而不能使用localhost:127.0.0.1。
不同Node上的Pod之间,各自拥有自己的名字空间,因此使用各自的IP地址进行通信。
集群内部的所有Pod使用相同的IP地址空间,每个Pod的虚拟IP地址在集群内部必须是唯一的、统一分配的。
k8s网络模型需要每个pod必须通过ip地址可以进行访问,每个pod的ip地址总是对网络中的其他pod可见,并且每个pod看待自己的ip与别的pod看待的是一样的(虽然他没规定如何实现),下面我们来看看:不同Node间Pod如何交互的?
K8s中集群中的每个Node(虚拟机)都会被分配了一个CIDR块,CIDR称为无类别域间路由选择,把网络前缀都相同的连续地址组成的地址组(块)称为CIDR地址块,集群中的不同Node有不同的CIDR块,每个Node负责给本Node上的Pod分配IP地址,其保证集群中不同Node上的Pod的IP不会冲突,同时还能够保证不同Node上的Pod的IP地址在同一个地址空间内。
如上图所示,Node1(vm1)上的Pod1与Node2(vm2)上Pod4之间进行交互。
首先,pod1通过自己的以太网设备eth0把数据包发送到关联到root命名空间的veth0上
然后,数据包被Node1上的网桥设备cbr0接收到,网桥查找转发表,发现找不到pod4的Mac地址,则会把包转发到默认路由(root命名空间的eth0设备,也是Node的外部以太网设备),然后数据包经过eth0就离开了Node1,被发送到网络。
最后,数据包到达Node2后,首先会被root命名空间的eth0设备,然后通过网桥cbr0把数据路由到虚拟设备veth1,最终数据表会被流转到与veth1配对的另外一端(pod4的eth0)
每个Node都知道如何把数据包转发到其内部运行的Pod,当一个数据包到达Node后,其内部数据流就和Node内Pod之间的流转类似了。
这样,就是实现了不同Node节点上Pod之间的通信。
对于如何来配置网络,如何实现上述的虚拟网络功能,k8s没有实现网络规划的具体逻辑,而是制定了一套CNI(Container Network Interface)接口规范,开放给社区来实现。
不同厂家,有不同的解决方案,这里有大量的解决方案,比如:
这些网络实现,并不是K8S原生软件的一部分,它属于第三方网络插件,在K8S安装好后,需要单独按照部署。
更多的信息如下:
Installing Addons | Kubernetes
pod网络的IP地址的IP地址空间,在创建集群的时候指定,如下图示例:
kubeadm init --apiserver-advertise-address 172.24.130.172 --control-plane-endpoint cluster-endpoint --image-repository registry.aliyuncs.com/google_containers --service-cidr 10.1.0.0/16 --pod-network-cidr 192.168.0.0/16
--pod-network-cidr 192.168.0.0/16
上面展示了Pod之间如何通过他们自己的IP地址进行通信,但是pod的IP地址是动态分配的,不是固定的,当集群中pod的规模缩减或者pod故障或者node故障重启后,新的pod的IP就可能与之前的不一样的, 这就给通过IP地址使用Pod中的服务的应用程序带来麻烦。所以k8s中衍生出来Service来解决这个问题。
k8s中 Sevice是一组提供相同服务实例的抽象,Service管理了一系列的Pods,每个Service有一个自己的虚拟的IP地址,要访问service管理的Pod上的服务,只需要访问你这个虚拟的IP就可以了,这个虚拟IP在虚拟服务创建后是固定的,当service下的pod规模改变、故障重启、node重启时候,虚拟Service的IP没有变,上述的变化,对使用service的用户来说是无感知的。
service通过服务发现机制,来动态的管理Pod规模的变化、状态的变化、pod在不同节点部署的变化。
当服务请求的数据包到达Service虚拟IP地址后,Service使用自动创建的负载均衡器和Pod映射表,把服务请求的数据包转发到特定的Pod上。
这里有四个关键的地方:
本文重点关注Service的虚拟IP地址与服务请求在Service和Pod之间的转发与路由
Service的功能的复杂度与Service的类型相关,不同类型的Service,其实现的功能不同,K8S支持4种不同了类型的Service:
Service与Pod一样,都有自己独立的IP地址,但Service与Pod拥有不同的地址空间。
该地址空间在创建集群的时候指定,如下图示例:
kubeadm init --apiserver-advertise-address 172.24.130.172 --control-plane-endpoint cluster-endpoint --image-repository registry.aliyuncs.com/google_containers --service-cidr 10.1.0.0/16 --pod-network-cidr 192.168.0.0/16
--service-cidr:10.1.0.0/16
每个虚拟Service创建的时候,都会获得一个在集群内唯一的私有IP地址。
(1)Linux netfilter
为了实现对输入到Service对应的IP接口的数据包的处理,k8s依赖linux内建的网络框架-netfilter。
Netfilter是Linux提供的内核态框架,允许使用者自定义处理接口实现各种与网络相关的操作。
Netfilter为包过滤,网络地址转换和端口转换提供各种功能和操作,以及提供禁止数据包到达计算机网络内敏感位置的功能。在linux内核协议栈中,有5个跟netfilter有关的钩子函数,数据包经过每个钩子时,都会检查上面是否注册有函数,如果有的话,就会调用相应的函数处理该数据包。这5个钩子函数的功能报包括:
K8S不直接注册Linux netfilter的钩子函数,而是通过Linux IP table机制,间接的操作Linux netfilter。
(2)Linux IP table
Iptables是运行在用户态的用户程序,其基于表来管理对数据保的处理规则,用于定义使用netfilter框架操作和转换数据包的规则。
根据规则rule表的作用分成了好几个表,不同功能的规则放在不同的表中,如用来过滤数据包的rule就会放到filter表中,用于处理地址转换的rule就会放到nat表中,其中rule就是应用在netfilter钩子上的函数,用来修改数据包的内容或过滤数据包。
IP table大致有五类规则表:
在k8s中,iptables规则由kube-proxy控制器进行配置的,该控制器监视K8s API服务器的更改。当对Service或Pod的虚拟IP地址进行修改时,iptables规则也会更新以便让service能够正确的把数据包路由到后端Pod。
iptables规则表监视发往Service虚拟IP的流量,并且在匹配时,从可用Pod集合中选择随机Pod IP地址,iptables规则将数据包的目标IP地址从Service的虚拟IP更改为选定的Pod的ip。
总的来说iptables已在机器上完成负载平衡,并将指向Servcie的虚拟IP的流量转移到实际的pod的IP。
在从pod到service的方向上,源IP地址来Pod。 在这种情况下,iptables再次重写IP头以将Pod IP替换为Service的IP,以便外部认为它一直与Service的虚拟IP通信。
Service与Node之间是IP通信,而不是MAC层通信,因此Service和Node通常不在同一个IP私网的网段 。
到目前为止,我们已经了解了如何在Kubernetes集群内部,实现Service与Pod,Pod与Pod的通信。很多时候,我们希望将服务暴露给外部使用(互联网)。这里就涉及到两个相关的问题:(1)从k8s service访问Internet(2)从Internet访问k8s的service。
如果期望讲服务暴露给外部用户,则需要集群有一个公网的IP地址,这个公网IP地址,可以是集群的网关的IP地址,也可以是Node节点的公网IP地址。
所谓从k8s service访问Internet的访问,就是来自Pod的数据,如何进入到internet。
在上图中,每个虚拟机Node有一个私有IP地址,该IP地址不同于service的虚拟IP地址,也不同于pod的虚拟IP地址,而是一个私有的实地址。
要实现集群与internet访问,就必须有一个internet网关,该网关实现公网IP和节点Node私网IP的NAT转换。基本过程如下(以pod1为例)
(1)pod1数据包的源IP地址为pod1-虚拟IP地址,目的IP地址为internet Client的公网IP地址。
(2)pod1的数据经过eth0,到达root netns的cbr0。
(3)root netns cbr0把数据包转发到iptable。
(4)iptable会把数据包的源IP地址(pod1-虚拟IP)变更为虚拟机的私有IP地址,然后通过eth0发送给网关。
(5)网关认为该数据包来自于虚拟机网络,则进行NAT转换,转换成虚拟机或集群的公网IP地址。
(6)网格把转换后的数据(源IP为虚拟机的公有IP, 目的IP为Client的公有IP)发送到internet上。
这个过程正好与5.1过程相反,如果说5.1的过程是IP地址映射是多对1的关系,那么5.2的过程就是IP地址映射是1对多的关系,因此更复杂。
外部不直接访问pod,而是先访问service,由service实现负载均衡与数据包在内部pod中的分发,因此这里的关键是:外部公网IP地址客户端如何访问集群内部私有IP地址的抽象服务。这个方向上功能的实现与service的类型密切相关,K8S支持三种能够提供公网服务请求的service类型:
这种方法的前提条件是:集群内部的每个节点都有一个公网IP地址。
(1)在每个节点Node的公网IP接口上,为虚拟服务Serivce生成一个端口映射,如上图中的30123
(2)发送到每个节点Node的公网IP+30123端口上的服务请求,经过IP Talbe扥机制,都会被映射到Service的私网IP+内部端口号上。
这一步是关键。NodePort是靠kube-proxy服务通过iptables的nat转换功能实现的,kube-proxy会在运行过程中动态创建与Service相关的iptables规则,这些规则实现了NodePort的请求流量重定向到kube-proxy进程上对应的Service的代理端口上。
(3)Service接收到来自私网(Service的私网IP+内部端口号)的服务请求后,会根据内部策略,选择一个目标Pod,作为最终的服务请求的处理端,实现service内部的负载均衡。
(4)其中一个Pod接收到来自service的服务处理请求。
这样就完成了Internet具有公网IP地址的Client到集群内部Pod的业务请求。
这里有一个关键性的问题,每个节点Node的公网IP接口上的端口号哪里来的?
这个端口号是在创建一个新的虚拟service的时候分配/指派的,如下图所示:
由于集群中的每个节点都暴露了一个用于标识service的端口号,且端口号完全相同,因此,从集群外部来看,可以通过任何一个节点的公网IP地址,都可以访问集群内部的该service。
这种方法的前提条件是,每个节点都有公网IP地址,如果集群的节点很多,还是挺浪费公网IP地址的。
该种类型的服务,不需要每个节点提供一个公网IP地址,它处于集群和internet,因此LoadBalancer service,不仅仅具备集群内部的私有IP地址,同时还具备internet共有IP地址。
所有发送到集群内部的服务器请求,首先发送到该LoadBalancer service,由该LoadBalancer service实现公网IP地址与集群内部私网地址的转换,然后转发到集群内部。如下图所示:
(1)首先流量被传到的Service的负载均衡器。
(2)一旦负载均衡器收到数据包,它就会随机选择一个VM(虚拟机节点)。
这里我们故意选择了没有Pod运行的node:node 2。
也就是说,LoadBalancer类型Service,其均衡的是最小单位是节点,而不是pod。
(3)node上运行的iptables规则将使用kube-proxy安装在集群中的内部负载平衡规则将数据包定向到正确的Node 中的Pod。
(4)iptables执行正确的NAT并将数据包转发到正确的Pod(4)。
这种方法的最大缺点是:
每种服务都需要创建自己独有的负载均衡器(LoadBalancer Service ),当集群内部有大量Service的时候,就需要创建大量的负载均衡器,每个负载均衡器都需要有一个独立的IP地址,每个负载均衡器相互独立运行。如下是LoadBalancer Service的创建方法
创建k8s service时,可以选择指定LoadBalancer。 LoadBalancer的实现由云控制器提供,该控制器知道如何为您的service创建负载均衡器。 创建service后,它将公布负载均衡器的IP地址。 作为最终用户,可以开始将流量定向到负载均衡器以开始与提供的service进行通信。
有没有一种方法或服务类型,为集群内所有的各种类型的服务提供负载均衡呢?
这就是 Ingress controller。
这是一个与上面提到的两种方式完全不同的机制,它不是与K8S的LoadBalancer service和NodePort Service并行的service类型,而是架构在Service之上的一种新的机制,Ingress工作在Http层。
它通过一个公开的ip地址,就能够为多个公开的服务提供统一的访问入口。
它通过HTTP / HTTPS的域名来标识不同的Service,如下图所示:
当客户端向Ingress发送http请求时候,ingress会根据请求的主机名和路径,决定请求转发到集群内部的哪个服务。
此种情形,由于集群内部的服务不需要提供共有IP地址,因此,服务的类型通常只需要是ClusterIP类型的Sevice即可。
然后由ClusterIP Sevice实现在不同pod间的负载均衡。
Ingress资源的创建方法:
如上定义了一个单一规则的Ingress,确保Ingress控制器接受的所有请求主机kubia.example.com的http请求被发送到端口80上的kubia-nodeport服务上。
工作原理:
(1)客户端,对kubia.example.com执行DNS查找,DNS服务器返回Ingress控制器的IP。
(2)客户端,拿到IP后,向Ingress控制器发送Http请求,并在http协议的Host中指定kubia.example.com。
(3)Ingress控制器,接收到请求后,从Host头部就知道该访问那个服务,通过与该Service关联的endpoint对象查询Pod ip,并将请求转发到某一个Pod,这里Ingress并没把请求转发给Service,而是自己选择一个一个Pod来访问。
(4)当然,Ingress控制器也可以把请求转发到某个某个虚拟Service中,再由Service在不同的Pod中进行负载均衡。
Ingress优缺点:
Ingress工作在OSI的第7层(HTTP层), 这种负载均衡器的一个好处是:它们具有HTTP感知能力,因此它们了解URL和路径,允许按URL路径细分服务流量,这是LoadBalancer service与NodePort Service所不具备的能力,LoadBalancer service和NodePort Service只能识别到IP地址。
Ingress的缺点也是明显的,由于Ingress工作在HTTP层,因此对于非HTTP服务请求,就无能为力了,且相对于工作在IP层(第3层)的负载均衡,工作在第7层的负载均衡的性能也会受到一定的影响。
作者主页(文火冰糖的硅基工坊):文火冰糖(王文兵)的博客_文火冰糖的硅基工坊_CSDN博客
本文网址: https://blog.csdn.net/HiWangWenBing/article/details/122806829