关于k8s api-server端口范围和node节点范围冲突会导致集群不正常故障总结

1.故障背景

  由于需要部署新环境,于是在阿里云新建一个ack集群,部署了业务,结果整晚上的存活探针告警,新集群接近30个业务pod,整晚上将近50多条存活探针告警,这个结果明显不正常。

  但是查看所有pod状态事件全部正常,阿里云托管的ack也没有事件异常,第一反应确实是集群某些参数不对导致这个问题,于是就有了以下过程。

 

2.环境信息

  这里首先交代一下环境版本信息。

软件
版本
系统版本
Alibaba Cloud Linux release 3 (Soaring Falcon)
ack集群
1.28.9
 

3.故障解决

  首先,在对比没有这方面告警的ack集群的环境下,该新集群本人仅仅只是手动优化了镜像的一些参数:比如limit限制,端口范围等等。

  (阿里云os镜像的默认范围为32768-60999)

  本人修改后为5000-65535。而阿里云ack APIServer提供了ServiceNodePortRange参数范围为30000~32767,以上明显冲突覆盖了。

  以下为阿里云官方解释:

  但是目前为止还不知道为什么api-server和node节点端口范围重合会导致探针频繁失败,修改node节点端口范围为32768-60999故障完美解决。

  (以上先解决问题,然后开始了漫长的调查出路。)

 

4.网络原理篇

  因为上一次使用版本是停留在1.20版本,没有对ack版本进行升级,都是凑合用。

  目前使用的1.28版本,于是把所有的网络原因重新复习了一遍。如果需要更加深入理解端口冲突的危害,并且在跟某云提工单的时候不被忽悠,建议看完以下原理篇。

 

4.1 K8s集群网络前言

  K8s是一个强大的平台,但它的网络比较复杂,涉及很多概念,例如Pod网络,Service网络,Cluster IPs,NodePort,LoadBalancer和Ingress等等,这么多概念足以让新手望而生畏。但是,只有深入理解K8s网络,才能为理解和用好K8s打下坚实基础。为了帮助大家理解,模仿TCP/IP协议栈,我把K8s的网络分解为四个抽象层,从0到3,除了第0层,每一层都是构建于前一层之上,如下图所示:

  第0层Node节点网络比较好理解,也就是保证K8s节点(物理或虚拟机)之间能够正常IP寻址和互通的网络,这个一般由底层(公有云或数据中心)网络基础设施支持。第0层我们假定已经存在,所以不展开。第1到3层网络,分别进行剖析。

  本文旨在帮助大家建立K8s网络的概念模型,而不是对底层技术的精确描述。实际我们学技术以应用为主,重要的是快速建立起直观易懂的概念模型,能够指导我们正常应用即可,当然理解一定的技术细节也是有帮助的。另外,本文假定读者对基本的网络技术,ip地址空间和容器技术等有一定的了解。

 

4.2 Pod网络概念模型

  Pod相当于是K8s云平台中的虚拟机,它是K8s的基本调度单位。所谓Pod网络,就是能够保证K8s集群中的所有Pods(包括同一节点上的,也包括不同节点上的Pods),逻辑上看起来都在同一个平面网络内,能够相互做IP寻址和通信的网络,下图是Pod网络的简化概念模型:

  Pod网络构建于Node节点网络之上,它又是上层Service网络的基础。为了进一步理解Pod网络,我将对同一节点上的Pod之间的网络,以及不同节点上的Pod之间网络,分别进行剖析。

 

4.2.1 同一节点上的Pod网络

  前面提到,Pod相当于是K8s云平台中的虚拟机,实际一个Pod中可以住一个或者多个(大多数场景住一个)应用容器,这些容器共享Pod的网络栈和其它资源如Volume。那么什么是共享网络栈?同一节点上的Pod之间如何寻址和互通?我以下图样例来解释:

  上图节点上展示了Pod网络所依赖的3个网络设备,eth0是节点主机上的网卡,这个是支持该节点流量出入的设备,也是支持集群节点间IP寻址和互通的设备。docker0是一个虚拟网桥,可以简单理解为一个虚拟交换机,它是支持该节点上的Pod之间进行IP寻址和互通的设备。veth0则是Pod1的虚拟网卡,是支持该Pod内容器互通和对外访问的虚拟设备。docker0网桥和veth0网卡,都是linux支持和创建的虚拟网络设备。

  上图Pod1内部住了3个容器,它们都共享一个虚拟网卡veth0。内部的这些容器可以通过localhost相互访问,但是它们不能在同一端口上同时开启服务,否则会有端口冲突,这就是共享网络栈的意思。Pod1中还有一个比较特殊的叫pause的容器,这个容器运行的唯一目的是为Pod建立共享的veth0网络接口。如果你SSH到K8s集群中一个有Pod运行的节点上去,然后运行docker ps,可以看到通过pause命令运行的容器。

  Pod的IP是由docker0网桥分配的,例如上图docker0网桥的IP是172.17.0.1,它给第一个Pod1分配IP为172.17.0.2。如果该节点上再启一个Pod2,那么相应的分配IP为172.17.0.3,如果再启动Pod可依次类推。因为这些Pods都连在同一个网桥上,在同一个网段内,它们可以进行IP寻址和互通,如下图所示

  从上图我们可以看到,节点内Pod网络在172.17.0.0/24这个地址空间内,而节点主机在10.100.0.0/24这个地址空间内,也就是说Pod网络和节点网络不在同一个网络内,那么不同节点间的Pod该如何IP寻址和互通呢?下一节我们来分析这个问题

 

4.2.2 不同节点间的Pod网络

  现在假设我们有两个节点主机,host1(10.100.0.2)和host2(10.100.0.3),它们在10.100.0.0/24这个地址空间内。host1上有一个PodX(172.17.0.2),host2上有一个PodY(172.17.1.3),Pod网络在172.17.0.0/16这个地址空间内。注意,Pod网络的地址,是由K8s统一管理和分配的,保证集群内Pod的IP地址唯一。我们发现节点网络和Pod网络不在同一个网络地址空间内,那么host1上的PodX该如何与host2上的PodY进行互通?

  实际上不同节点间的Pod网络互通,有很多技术实现方案,底层的技术细节也很复杂。为了简化描述,我把这些方案大体分为两类,一类是路由方案,另外一类是覆盖(Overlay)网络方案。

  如果底层的网络是你可以控制的,比如说企业内部自建的数据中心,并且你和运维团队的关系比较好,可以采用路由方案,如下图所示:

  这个方案简单理解,就是通过路由设备为K8s集群的Pod网络单独划分网段,并配置路由器支持Pod网络的转发。例如上图中,对于目标为172.17.1.0/24这个范围内的包,转发到10.100.0.3这个主机上,同样,对于目标为172.17.0.0/24这个范围内的包,转发到10.100.0.2这个主机上。当主机的eth0接口接收到来自Pod网络的包,就会向内部网桥转发,这样不同节点间的Pod就可以相互IP寻址和通信。这种方案依赖于底层的网络设备,但是不引入额外性能开销。

  如果底层的网络是你无法控制的,比如说公有云网络,或者企业的运维团队不支持路由方案,可以采用覆盖(Overlay)网络方案,如下图所示:

  所谓覆盖网络,就是在现有网络之上再建立一个虚拟网络,实现技术有很多,例如flannel/weavenet等等,这些方案大都采用隧道封包技术。简单理解,Pod网络的数据包,在出节点之前,会先被封装成节点网络的数据包,当数据包到达目标节点,包内的Pod网络数据包会被解封出来,再转发给节点内部的Pod网络。这种方案对底层网络没有特别依赖,但是封包解包会引入额外性能开销。

 

4.2.3 CNI简介

  考虑到Pod网络实现技术众多,为了简化集成,K8s支持CNI(Container Network Interface)标准,不同的Pod网络技术可以通过CNI插件形式和K8s进行集成。节点上的Kubelet通过CNI标准接口操作Pod网路,例如添加或删除网络接口等,它不需要关心Pod网络的具体实现细节。

 

4.2.4 小结

  1. K8s的网络可以抽象成四层网络,第0层节点网络,第1层Pod网络,第2层Service网络,第3层外部接入网络。除了第0层,每一层都构建于上一层之上。
  2. 一个节点内的Pod网络依赖于虚拟网桥和虚拟网卡等linux虚拟设备,保证同一节点上的Pod之间可以正常IP寻址和互通。一个Pod内容器共享该Pod的网络栈,这个网络栈由pause容器创建。
  3. 不同节点间的Pod网络,可以采用路由方案实现,也可以采用覆盖网络方案。路由方案依赖于底层网络设备,但没有额外性能开销,覆盖网络方案不依赖于底层网络,但有额外封包解包开销。
  4. CNI是一个Pod网络集成标准,简化K8s和不同Pod网络实现技术的集成。

  有了Pod网络,K8s集群内的所有Pods在逻辑上都可以看作在一个平面网络内,可以正常IP寻址和互通。但是Pod仅仅是K8s云平台中的虚拟机抽象,最终,我们需要在K8s集群中运行的是应用或者说服务(Service),而一个Service背后一般由多个Pods组成集群,这时候就引入了服务发现(Service Discovery)和负载均衡(Load Balancing)等问题

 

4.3 Service 网络

  我们假定在K8s集群中部署了一个Account-App应用,这个应用由4个Pod(虚拟机)组成集群一起提供服务,每一个Pod都有自己的PodIP和端口。我们再假定集群内还部署了其它应用,这些应用中有些是Account-App的消费方,也就说有Client Pod要访问Account-App的Pod集群。这个时候自然引入了两个问题:

  1. 服务发现(Service Discovery): Client Pod如何发现定位Account-App集群中Pod的IP?况且Account-App集群中Pod的IP是有可能会变的(英文叫ephemeral),这种变化包括预期的,比如Account-App重新发布,或者非预期的,例如Account-App集群中有Pod挂了,K8s对Account-App进行重新调度部署。
  2. 负载均衡(Load Balancing):Client Pod如何以某种负载均衡策略去访问Account-App集群中的不同Pod实例?以实现负载分摊和HA高可用。

  实际上,K8s通过在Client和Account-App的Pod集群之间引入一层Account-Serivce抽象,来解决上述问题:

  1. 服务发现:Account-Service提供统一的ClusterIP来解决服务发现问题,Client只需通过ClusterIP就可以访问Account-App的Pod集群,不需要关心集群中的具体Pod数量和PodIP,即使是PodIP发生变化也会被ClusterIP所屏蔽。注意,这里的ClusterIP实际是个虚拟IP,也称Virtual IP(VIP)。
  2. 负载均衡:Account-Service抽象层具有负载均衡的能力,支持以不同策略去访问Account-App集群中的不同Pod实例,以实现负载分摊和HA高可用。K8s中默认的负载均衡策略是RoundRobin,也可以定制其它复杂策略。

  K8s中为何要引入Service抽象?背后的原理是什么?后面我将以技术演进视角来解释这些问题。

 

4.3.1 服务发现技术演进

  DNS域名服务是一种较老且成熟的标准技术,实际上DNS可以认为是最早的一种服务发现技术。

  在K8s中引入DNS实现服务发现其实并不复杂,实际K8s本身就支持Kube-DNS组件。假设K8s引入DNS做服务发现(如上图所示),运行时,K8s可以把Account-App的Pod集群信息(IP+Port等)自动注册到DNS,Client应用则通过域名查询DNS发现目标Pod,然后发起调用。这个方案不仅简单,而且对Client也无侵入(目前几乎所有的操作系统都自带DNS客户端)。但是基于DNS的服务发现也有如下问题

  1. 不同DNS客户端实现功能有差异,有些客户端每次调用都会去查询DNS服务,造成不必要的开销,而有些客户端则会缓存DNS信息,默认超时时间较长,当目标PodIP发生变化时(在容器云环境中是常态),存在缓存刷新不及时,会导致访问Pod失效
  2. DNS客户端实现的负载均衡策略一般都比较简单,大都是RoundRobin,有些则不支持负载均衡调用。

  考虑到上述不同DNS客户端实现的差异,不在K8s控制范围内,所以K8s没有直接采用DNS技术做服务发现。注意,实际K8s是引入Kube-DNS支持通过域名访问服务的,不过这是建立在CusterIP/Service网络之上,这个我后面会展开。

另外一种较新的服务发现技术,是引入Service Registry+Client配合实现,在当下微服务时代,这是一个比较流行的做法。目前主流的产品,如Netflix开源的Eureka + Ribbon,HashiCorp开源的Consul,还有阿里新开源Nacos等,都是这个方案的典型代表。

  在K8s中引入Service Registry实现服务发现也不复杂,K8s自身带分布式存储etcd就可以实现Service Registry。假设K8s引入Service Registry做服务发现(如上图所示),运行时K8s可以把Account-App和Pod集群信息(IP + Port等)自动注册到Service Registry,Client应用则通过Service Registry查询发现目标Pod,然后发起调用。这个方案也不复杂,而且客户端可以实现灵活的负载均衡策略,但是需要引入客户端配合,对客户应用有侵入性,所以K8s也没有直接采用这种方案。

  K8s虽然没有直接采用上述方案,但是它的Service网络实现是在上面两种技术的基础上扩展演进出来的。它融合了上述方案的优点,同时解决了上述方案的不足,下节我会详细剖析K8s的Service网络的实现原理。

 

4.3.2 K8s的Service网络原理

  前面提到,K8s的服务发现机制是在上节讲的Service Registry + DNS基础上发展演进出来的,下图展示K8s服务发现的简化原理:

  在K8s平台的每个Worker节点上,都部署有两个组件,一个叫Kubelet,另外一个叫Kube-Proxy,这两个组件+Master是K8s实现服务注册和发现的关键。下面我们看下简化的服务注册发现流程。

  • 首先,在服务Pod实例发布时(可以对应K8s发布中的Kind: Deployment),Kubelet会负责启动Pod实例,启动完成后,Kubelet会把服务的PodIP列表汇报注册到Master节点。
  • 其次,通过服务Service的发布(对应K8s发布中的Kind: Service),K8s会为服务分配ClusterIP,相关信息也记录在Master上。
  • 第三,在服务发现阶段,Kube-Proxy会监听Master并发现服务ClusterIP和PodIP列表映射关系,并且修改本地的linux iptables转发规则,指示iptables在接收到目标为某个ClusterIP请求时,进行负载均衡并转发到对应的PodIP上。
  • 运行时,当有消费者Pod需要访问某个目标服务实例的时候,它通过ClusterIP发起调用,这个ClusterIP会被本地iptables机制截获,然后通过负载均衡,转发到目标服务Pod实例上。

  实际消费者Pod也并不直接调服务的ClusterIP,而是先调用服务名,因为ClusterIP也会变(例如针对TEST/UAT/PROD等不同环境的发布,ClusterIP会不同),只有服务名一般不变。为了屏蔽ClusterIP的变化,K8s在每个Worker节点上还引入了一个KubeDNS组件,它也监听Master并发现服务名和ClusterIP之间映射关系,这样, 消费者Pod通过KubeDNS可以间接发现服务的ClusterIP。

  注意,K8s的服务发现机制和目前微服务主流的服务发现机制(如Eureka + Ribbon)总体原理类似,但是也有显著区别(这些区别主要体现在客户端):

  1. 首先,两者都是采用客户端代理(Proxy)机制。和Ribbon一样,K8s的代理转发和负载均衡也是在客户端实现的,但Ribbon是以Lib库的形式嵌入在客户应用中的,对客户应用有侵入性,而K8s的Kube-Proxy是独立的,每个Worker节点上有一个,它对客户应用无侵入。K8s的做法类似ServiceMesh中的边车(sidecar)做法。
  2. 第二,Ribbon的代理转发是穿透的,而K8s中的代理转发是iptables转发,虽然K8s中有Kube-Proxy,但它只是负责服务发现和修改iptables(或ipvs)规则,实际请求是不穿透Kube-Proxy的。注意早期K8s中的Kube-Proxy代理是穿透的,考虑到有性能损耗和单点问题,后续的版本就改成不穿透了。
  3. 第三,Ribbon实现服务名到服务实例IP地址的映射,它只有一层映射。而K8s中有两层映射,Kube-Proxy实现ClusterIP->PodIP的映射,Kube-DNS实现ServiceName->ClusterIP的映射。

  个人认为,对比目前微服务主流的服务发现机制,K8s的服务发现机制抽象得更好,它通过ClusterIP统一屏蔽服务发现和负载均衡,一个服务一个ClusterIP,这个模型和传统的IP网络模型更贴近和易于理解。ClusterIP也是一个IP,但这个IP后面跟的不是一个服务实例,而是一个服务集群,所以叫集群ClusterIP。同时,它对客户应用无侵入,且不穿透没有额外性能损耗。

 

4.3.3 总结

  1. K8s的Service网络构建于Pod网络之上,它主要目的是解决服务发现(Service Discovery)和负载均衡(Load Balancing)问题。
  2. K8s通过一个ServiceName+ClusterIP统一屏蔽服务发现和负载均衡,底层技术是在DNS+Service Registry基础上发展演进出来。
  3. K8s的服务发现和负载均衡是在客户端通过Kube-Proxy + iptables转发实现,它对应用无侵入,且不穿透Proxy,没有额外性能损耗。
  4. K8s服务发现机制,可以认为是现代微服务发现机制和传统Linux内核机制的优雅结合。

  有了Service抽象,K8s中部署的应用都可以通过一个抽象的ClusterIP进行寻址访问,并且消费方不需要关心这个ClusterIP后面究竟有多少个Pod实例,它们的PodIP是什么,会不会变化,如何以负载均衡方式去访问等问题。但是,K8s的Service网络只是一个集群内可见的内部网络,集群外部是看不到Service网络的,也无法直接访问。而我们发布应用,有些是需要暴露出去,要让外网甚至公网能够访问的,这样才能对外提供服务。K8s如何将内部服务暴露出去?

 

4.4 外部接入网络

  在讲到K8s如何接入外部流量的时候,大家常常会听到NodePort,LoadBalancer和Ingress等概念,这些概念都是和K8s外部流量接入相关的,它们既是不同概念,同时又有关联性。下面我们分别解释这些概念和它们之间的关系。

 

4.4.1 NodePort

  先提前强调一下,NodePort是K8s将内部服务对外暴露的基础,后面的LoadBalancer底层有赖于NodePort。

  之前我们讲了K8s网络的4层抽象,Service网络在第2层,节点网络在第0层。实际上,只有节点网络是可以直接对外暴露的,具体暴露方式要看数据中心或公有云的底层网络部署,但不管采用何种部署,节点网络对外暴露是完全没有问题的。那么现在的问题是,第2层的Service网络如何通过第0层的节点网络暴露出去?我们可以回看一下K8s服务发现的原理图,如下图所示,然后不妨思考一下,K8s集群中有哪一个角色,即掌握Service网络的所有信息,可以和Service网络以及Pod网络互通互联,同时又可以和节点网络打通?

  答案是Kube-Proxy。上一篇我们提到Kube-Proxy是K8s内部服务发现的一个关键组件,事实上,它还是K8s将内部服务暴露出去的关键组件。Kube-Proxy在K8s集群中所有Worker节点上都部署有一个,它掌握Service网络的所有信息,知道怎么和Service网络以及Pod网络互通互联。如果要将Kube-Proxy和节点网络打通(从而将某个服务通过Kube-Proxy暴露出去),只需要让Kube-Proxy在节点上暴露一个监听端口即可。这种通过Kube-Proxy在节点上暴露一个监听端口,将K8s内部服务通过Kube-Proxy暴露出去的方式,术语就叫NodePort(顾名思义,端口暴露在节点上)。下图是通过NodePort暴露服务的简化概念模型

  如果我们要将K8s内部的一个服务通过NodePort方式暴露出去,可以将服务发布(kind: Service)的type设定为NodePort,同时指定一个30000~32767范围内的端口。服务发布以后,K8s在每个Worker节点上都会开启这个监听端口。这个端口的背后是Kube-Proxy,当K8s外部有Client要访问K8s集群内的某个服务,它通过这个服务的NodePort端口发起调用,这个调用通过Kube-Proxy转发到内部的Servcie抽象层,然后再转发到目标Pod上。Kube-Proxy转发以及之后的环节,可以和上面的《Service网络》的内容对接起来。注意,为了直观形象,上图的Service在K8s集群中被画成一个独立组件,实际是没有独立Service这样一个组件的,只是一个抽象概念,如果要理解这个抽象的底层实现细节,可以回头看上一图的K8s服务发现原理,或者回到上面的《Service网络》。

 

4.4.2 LoadBalancer

  上面我们提到,将K8s内部的服务通过NodePort方式暴露出去,K8s会在每个Worker节点上都开启对应的NodePort端口。逻辑上看,K8s集群中的所有节点都会暴露这个服务,或者说这个服务是以集群方式暴露的(实际支持这个服务的Pod可能就分布在其中有限几个节点上,但是因为所有节点上都有Kube-Proxy,所以所有节点都知道该如何转发)。既然是集群,就会涉及负载均衡问题,谁负责对这个服务的负载均衡访问?答案是需要引入负载均衡器(Load Balancer)。下图是通过LoadBalancer,将服务对外暴露的概念模型。

  假设我们有一套阿里云K8s环境,要将K8s内部的一个服务通过LoadBalancer方式暴露出去,可以将服务发布(Kind: Service)的type设定为LoadBalancer。服务发布后,阿里云K8s不仅会自动创建服务的NodePort端口转发,同时会自动帮我们申请一个SLB,有独立公网IP,并且阿里云K8s会帮我们自动把SLB映射到后台K8s集群的对应NodePort上。这样,通过SLB的公网IP,我们就可以访问到K8s内部服务,阿里云SLB负载均衡器会在背后做负载均衡。

  值得一提的是,如果是在本地开发测试环境里头搭建的K8s,一般不支持Load Balancer也没必要,因为通过NodePort做测试访问就够了。但是在生产环境或者公有云上的K8s,例如GCP或者阿里云K8s,基本都支持自动创建Load Balancer。

 

4.4.3 Ingress

  有了前面的NodePort + LoadBalancer,将K8s内部服务暴露到外网甚至公网的需求就已经实现了,那么为啥还要引入Ingress这样一个概念呢?它起什么作用?

  我们知道在公有云(阿里云/AWS/GCP)上,公网LB+IP是需要花钱买的。我们回看上图的通过LoadBalancer(简称LB)暴露服务的方式,发现要暴露一个服务就需要购买一个独立的LB+IP,如果要暴露十个服务就需要购买十个LB+IP,显然,从成本考虑这是不划算也不可扩展的。那么,有没有办法只需购买一个(或者少量)的LB+IP,但是却可以按需暴露更多服务出去呢?答案其实不复杂,就是想办法在K8内部部署一个独立的反向代理服务,让它做代理转发。谷歌给这个内部独立部署的反向代理服务起了一个奇怪的名字,就叫  Ingress,它的简化概念模型如下图所示:

  Ingress就是一个特殊的Service,通过节点的HostPort(80/443)暴露出去,前置一般也有LB做负载均衡。Ingress转发到内部的其它服务,是通过集群内的Service抽象层/ClusterIP进行转发,最终转发到目标服务Pod上。Ingress的转发可以基于Path转发,也可以基于域名转发等方式,基本上你只需给它设置好转发路由表即可,功能和Nginx无本质差别。

  注意,上图的Ingress概念模型是一种更抽象的画法,隐去了K8s集群中的节点,实际HostPort是暴露在节点上的。

  所以,Ingress并不是什么神奇的东西,首先,它本质上就是K8s集群中的一个比较特殊的Service(发布Kind: Ingress)。其次,这个Service提供的功能主要就是7层反向代理(也可以提供安全认证,监控,限流和SSL证书等高级功能),功能类似Nginx。第三,这个Service对外暴露出去是通过HostPort(80/443),可以和上面LoadBalancer对接起来。有了这个Ingress Service,我们可以做到只需购买一个LB+IP,就可以通过Ingress将内部多个(甚至全部)服务暴露出去,Ingress会帮忙做代理转发。

  那么哪些软件可以做这个Ingress?传统的Nginx/Haproxy可以,现代的微服务网关Zuul/SpringCloudGateway/Kong/Envoy/Traefik等等都可以。当然,谷歌别出心裁给这个东东起名叫Ingress,它还是做了一些包装,以简化对Ingress的操作。如果你理解了原理,那么完全可以用Zuul或者SpringCloudGateway,或者自己定制开发一个反向代理,来替代这个Ingress。部署的时候以普通Service部署,将type设定为LoadBalancer即可,如下图所示:

  注意,Ingress是一个7层反向代理,如果你要暴露的是4层服务,还是需要走独立LB+IP方式。

 

4.4.4 Kubectl Proxy & Port Forward

  上面提到的服务暴露方案,包括NodePort/LoadBalancer/Ingress,主要针对正式生产环境。如果在本地开发测试环境,需要对本地部署的K8s环境中的服务或者Pod进行快速调试或测试,还有几种简易办法,这边一并简单介绍下,如下图所示:

  • 办法一,通过kubectl proxy命令,在本机上开启一个代理服务,通过这个代理服务,可以访问K8s集群内的任意服务。背后,这个Kubectl代理服务通过Master上的API Server间接访问K8s集群内服务,因为Master知道集群内所有服务信息。这种方式只限于7层HTTP转发。
  • 办法二,通过kubectl port-forward命令,它可以在本机上开启一个转发端口,间接转发到K8s内部的某个Pod的端口上。这样我们通过本机端口就可以访问K8s集群内的某个Pod。这种方式是TCP转发,不限于HTTP。
  • 办法三,通过kubectl exec命令直接连到Pod上去执行linux命令,功能类似docker exec。

 

4.4.5 总结

  1. NodePort是K8s内部服务对外暴露的基础,LoadBalancer底层有赖于NodePort。NodePort背后是Kube-Proxy,Kube-Proxy是沟通Service网络、Pod网络和节点网络的桥梁。
  2. 将K8s服务通过NodePort对外暴露是以集群方式暴露的,每个节点上都会暴露相应的NodePort,通过LoadBalancer可以实现负载均衡访问。公有云(如阿里云/AWS/GCP)提供的K8s,都支持自动部署LB,且提供公网可访问IP,LB背后对接NodePort。
  3. Ingress扮演的角色是对K8s内部服务进行集中反向代理,通过Ingress,我们可以同时对外暴露K8s内部的多个服务,但是只需要购买1个(或者少量)LB。Ingress本质也是一种K8s的特殊Service,它也通过HostPort(80/443)对外暴露。
  4. 通过Kubectl Proxy或者Port Forward,可以在本地环境快速调试访问K8s中的服务或Pod。
  5. K8s的Service发布主要有3种type,type=ClusterIP,表示仅内部可访问服务,type=NodePort,表示通过NodePort对外暴露服务,type=LoadBalancer,表示通过LoadBalancer对外暴露服务(底层对接NodePort,一般公有云才支持)。

 

5.故障排查过程

  经过一整天和阿里云小哥的battle,阿里云小哥给出的理由是我的pod使用了nodeport端口范围覆盖了api-server导致冲突,等等多个原因,但是追溯到具体原因,阿里小哥无法解答,下午得空的时候去简单复习了一下网络原理,不管怎么样,阿里云小哥给我的解释都和原理对不上。

  于是晚上再提工单,得到另一个小哥的回复。

  以上大概意思就是,1.22版本以后的k8s,kube-proxy不会监听nodeport,就是不管你已经开始了什么端口,是没有任何检查的,以上是一个很重要的前提,对应前文开头为什么之前公司1.20版本却不会出现这种问题,现在1.28有这个问题。

  然后阿里云小哥又说我通过svc开启了nodeport所以导致探针冲突。

  所以以上2个阿里云小哥聊到这里都给了一个答案,端口冲突会探针失败,至于追根溯源的去勒戒,他们无法给出答案。

  在晚上的时候突然想到,一直思想目的端口的问题,想想探针呢?想到探针是有kubectl发起的,发起的源IP是节点ip,于是马上去确认路由走向,如下:

  在 Kubernetes 中,pod是由 kubelet 负责执行的。具体来说,pod的请求是通过以下过程进行的:

  1.kubelet

  • 每个节点上都有一个 kubelet 进程负责,管理该节点上的 Pod 和容器。节点请求是由 kubelet 发起的。

  2.Pod IP

  • kubelet 会使用 Pod 的 IP 地址来发起请求。每个 Pod 在创建时都会分配一个独立的 IP 地址。

  3.探针类型

  • HTTP 描述符:kubelet 通过发起 HTTP 请求来检查容器的健康状态,使用的是 Pod 的 IP 和指定的端口(例如 8080)。
  • TCP 节点:kubelet 会尝试通过指定的端口进行 TCP 连接,以判断容器是否正常。
  • 命令命令:kubelet 执行指定的命令,并根据命令的退出状态来判断容器的健康状态。

  那么kubectl探针如何去请求探测pod?在 Kubernetes 中,节点请求的源 IP 地址是 kubelet 所在节点的 IP 地址。以下是更详细的说明:

  具体问题

  1.探针请求的发起者:

  • 节点请求是由每个节点上的 kubelet 发起的。kubelet 是负责管理该节点上所有 Pod 的组件。

  2.源IP地址:

  • 当 kubelet 发起请求时,它使用 kubelet 所在节点的 IP 地址作为源 IP。这个 IP 地址是节点的网络接口 IP 地址。
  • 例如,如果 kubelet 在一个 IP 为192.168.1.10的节点上运行,而该节点上的某个 Pod 的 IP 地址为10.244.1.5,那么 kubelet 发起节点请求时的源 IP 将是192.168.1.10。

  节点请求的源 IP 是 kubelet 所在节点的 IP 地址,而目标 IP 是正在检查的 Pod 的 IP 地址。这种机制保证了 kubelet 能够有效地监测每个 Pod 的健康状态。

  总结就是说,如果kubectl 开启的源端口和和api-server 的端口复用了, API Server 使用了 31000-32000 端口范围。如果节点上某个进程(如 kubelet 发出的探针请求)随机选择的源端口落在 31000-32000 范围内,这可能与 API Server 端口产生冲突,导致端口争用。

  这种冲突会导致探针失败,因为源端口已被占用,探针请求无法正常发出。探针失败的原因并不是 Pod 的端口,而是 kubelet 发出的探针请求使用的 随机源端口 与 API Server 的端口产生了冲突。

  在回到一晚上50多次的探针告警,30个pod,每10秒就要探测一次,那就是10秒30次,一晚上时间就会探测108000次,端口范围仅有50000万不到,所以冲突的可能性就很大了。

 

6.总结

  集群整晚上探针频繁失败的主要原因总结如下:

  1. 1.22版本以后的k8s,kube-proxy不会监听nodeport, 如果 net.ipv4.ip_local_port_range 的范围和 ServiceNodePortRange 存在重叠,由于去掉了监听 NodePort 的逻辑,应用程序在选用随机端口的时候就可能选中重叠部分。
  2. Pod的ip以及端口是独立的,但是发起探针的kubectl的端口是随机的,这样就会造成冲突,从而造成探针失败。

  目前还未遇到集群节点离线等故障,但是也是概率性的问题,在调整2方面端口时,

  需要注意该配置项,在阿里云等保镜像中,保持默认即可避免这个问题。

 

7.参考链接

https://www.jianshu.com/p/80eb2e9e32db

https://help.aliyun.com/zh/ack/ack-managed-and-ack-dedicated/user-guide/comparison-between-terway-and-flannel?spm=a2c4g.11186623.0.0.45f31c08mHZ1vv

https://help.aliyun.com/zh/ack/ack-managed-and-ack-dedicated/user-guide/service-faq?spm=5176.2020520104.console-base_help.dexternal.3fb843ec1SbrsW#023c3ce009sv0

 

posted @   小家电维修  阅读(189)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
点击右上角即可分享
微信分享提示
目 录 X

1.故障背景

2.环境信息

3.故障解决

4.网络原理篇

4.1 K8s集群网络前言

4.2 Pod网络概念模型

4.2.1 同一节点上的Pod网络

4.2.2 不同节点间的Pod网络

4.2.3 CNI简介

4.2.4 小结

4.3 Service 网络

4.3.1 服务发现技术演进

4.3.2 K8s的Service网络原理

4.3.3 总结

4.4 外部接入网络

4.4.1 NodePort

4.4.2 LoadBalancer

4.4.3 Ingress

4.4.4 Kubectl Proxy & Port Forward

4.4.5 总结

5.故障排查过程

6.总结

7.参考链接