在本次案例中,京东云的中台技术工程师遇到了来自客户提出的突破K8s产品性能限度的非凡需要,面对这个极具挑战的工作,攻城狮最终是否克服了重重困难,帮忙客户完满实现了需要?且看本期K8s技术案例分享!(情谊提醒:文章篇幅较长,倡议各位看官先珍藏再浏览,同时在浏览过程中留神劳逸结合,放弃身心健康!)

第一局部:“颇有共性”的需要

某日,咱们京东云的技术中台工程师接到了客户的求助。客户在云上环境应用了托管K8s集群产品部署测试集群。因业务须要,研发共事须要在办公网环境能间接拜访K8s集群的clueterIP类型的service和后端的pod。通常K8s的pod只能在集群内通过其余pod或者集群node拜访,不能间接在集群外进行拜访。而pod对集群内外提供服务时须要通过service对外裸露拜访地址和端口,service除了起到pod利用拜访入口的作用,还会对pod的相应端口进行探活,实现健康检查。同时当后端有多个Pod时,service还将依据调度算法将客户端申请转发至不同的pod,实现负载平衡的作用。罕用的service类型有如下几种:

service类型简介

1、 clusterIP类型,创立service时如果不指定类型的话的默认会创立该类型service,clusterIP类型的service只能在集群内通过cluster IP被pod和node拜访,集群外无法访问。通常像K8s集群零碎服务kubernetes等不须要对集群外提供服务,只须要在集群外部进行拜访的service会应用这种类型;

2、 nodeport类型,为了解决集群内部对service的拜访需要,设计了nodeport类型,将service的端口映射至集群每个节点的端口上。当集群外拜访service时,通过对节点IP和指定端口的拜访,将申请转发至后端pod;

3、 loadbalancer类型,该类型通常须要调用云厂商的API接口,在云平台上创立负载平衡产品,并依据设置创立监听器。在K8s外部,loadbalancer类型服务实际上还是和nodeport类型一样将服务端口映射至每个节点的固定端口上。而后将节点设置为负载平衡的后端,监听器将客户端申请转发至后端节点上的服务映射端口,申请达到节点端口后,再转发至后端pod。Loadbalancer类型的service补救了nodeport类型有多个节点时客户端须要拜访多个节点IP地址的有余,只有对立拜访LB的IP即可。同时应用LB类型的service对外提供服务,K8s节点无需绑定公网IP,只须要给LB绑定公网IP即可,晋升了节点安全性,也节约了公网IP资源。利用LB对后端节点的健康检查性能,可实现服务高可用。防止某个K8s节点故障导致服务无法访问。

Part1小结

通过对K8s集群service类型的理解,咱们能够晓得客户想在集群外对service进行拜访,首先举荐应用的是LB类型的service。因为目前K8s集群产品的节点还不反对绑定公网IP,因而应用nodeport类型的service无奈实现通过公网拜访,除非客户应用专线连贯或者IPSEC将本人的办公网与云上网络买通,能力拜访nodeport类型的service。而对于pod,只能在集群外部应用其余pod或者集群节点进行拜访。同时K8s集群的clusterIP和pod设计为不容许集群内部拜访,也是出于进步安全性的思考。如果将拜访限度突破,可能会导致平安问题产生。所以咱们的倡议客户还是应用LB类型的service对外裸露服务,或者从办公网连贯K8s集群的NAT主机,而后通过NAT主机能够连贯至K8s节点,再拜访clusterIP类型的service,或者拜访后端pod。

客户示意目前测试集群的clusterIP类型服务有上百个,如果都革新成LB类型的service就要创立上百个LB实例,绑定上百个公网IP,这显然是不事实的,而都革新成Nodeport类型的service的工作量也非常微小。同时如果通过NAT主机跳转登录至集群节点,就须要给研发共事提供NAT主机和集群节点的零碎明码,不利于运维治理,从操作便利性上也不如研发能够间接通过网络拜访service和pod简便。

第二局部:办法总比艰难多?

尽管客户的拜访形式违反了K8s集群的设计逻辑,显得有些“非主流”,然而对于客户的应用场景来说也是无可奈何的强需要。作为技术中台的攻城狮,咱们要尽最大致力帮忙客户解决技术问题!因而咱们依据客户的需要和场景架构,来布局实现计划。

既然是网络买通,首先要从客户的办公网和云上K8s集群网络架构剖析。客户办公网有对立的公网进口设施,而云上K8s集群的网络架构如下,K8s集群master节点对用户不可见,用户创立K8s集群后,会在用户选定的VPC网络下创立三个子网。别离是用于K8s节点通信的node子网,用于部署NAT主机和LB类型serivce创立的负载平衡实例的NAT与LB子网,以及用于pod通信的pod子网。K8s集群的节点搭建在云主机上,node子网拜访公网地址的路由下一跳指向NAT主机,也就是说集群节点不能绑定公网IP,应用NAT主机作为对立的公网拜访进口,做SNAT,实现公网拜访。因为NAT主机只有SNAT性能,没有DNAT性能,因而也就无奈从集群外通过NAT主机拜访node节点。

对于pod子网的布局目标,首先要介绍下pod在节点上的网络架构。如下图所示:

在节点上,pod中的容器通过veth对与docker0设施连通,而docker0与节点的网卡之间通过自研CNI网络插件连通。为了实现集群管制流量与数据流量的拆散,进步网络性能,集群在每个节点上独自绑定弹性网卡,专门供pod通信应用。创立pod时,会在弹性网卡上为Pod调配IP地址。每个弹性网卡最多能够调配21个IP,当一张弹性网卡上的IP调配满后,会再绑定一张新的网卡供后续新建的pod应用。弹性网卡所属的子网就是pod子网,基于这样的架构,能够升高节点eth0主网卡的负载压力,实现管制流量与数据流量拆散,同时pod的IP在VPC网络中有理论对应的网络接口和IP,可实现VPC网络内对pod地址的路由。

你须要理解的买通形式

理解完两端的网络架构后咱们来抉择买通形式。通常将云下网络和云上网络买通,有专线产品连贯形式,或者用户自建VPN连贯形式。专线产品连贯须要布设从客户办公网到云上机房的网络专线,而后在客户办公网侧的网络进口设施和云上网络侧的bgw边界网关配置到彼此对端的路由。如下图所示:

基于现有专线产品BGW的性能限度,云上一侧的路由只能指向K8s集群所在的VPC,无奈指向具体的某个K8s节点。而想要拜访clusterIP类型service和pod,必须在集群内的节点和pod拜访。因而拜访service和pod的路由下一跳,必须是某个集群节点。所以应用专线产品显然是无奈满足需要的。

咱们来看自建VPN形式,自建VPN在客户办公网和云上网络各有一个有公网IP的端点设施,两个设施之间建设加密通信隧道,理论底层还是基于公网通信。如果应用该计划,云上的端点咱们能够抉择和集群节点在同一VPC的不同子网下的有公网IP的云主机。办公网侧对service和pod的拜访数据包通过VPN隧道发送至云主机后,能够通过配置云主机所在子网路由,将数据包路由至某个集群节点,而后在集群节点所在子网配置到客户端的路由下一跳指向端点云主机,同时须要在pod子网也做雷同的路由配置。至于VPN的实现形式,通过和客户沟通,咱们选取ipsec隧道形式。

确定了计划,咱们须要在测试环境实施方案验证可行性。因为咱们没有云下环境,因而选取和K8s集群不同地区的云主机代替客户的办公网端点设施。在华东上海地区创立云主机office-ipsec-sh模仿客户办公网客户端,在华北北京地区的K8s集群K8s-BJTEST01所在VPC的NAT/LB子网创立一个有公网IP的云主机K8s-ipsec-bj,模仿客户场景下的ipsec云上端点,与华东上海云主机office-ipsec-sh建设ipsec隧道。设置NAT/LB子网的路由表,增加到service网段的路由下一跳指向K8s集群节点K8s-node-vmlppp-bs9jq8pua,以下简称node A。因为pod子网和NAT/LB子网同属于一个VPC,所以无需配置到pod网段的路由,拜访pod时会间接匹配local路由,转发至对应的弹性网卡上。为了实现数据包的返回,在node子网和pod子网别离配置到上海云主机office-ipsec-sh的路由,下一跳指向K8s-ipsec-bj。残缺架构如下图所示:

第三局部:实际出“问题”

既然确定了计划,咱们就开始搭建环境了。首先在K8s集群的NAT/LB子网创立K8s-ipsec-bj云主机,并绑定公网IP。而后与上海云主机office-ipsec-sh建设ipsec隧道。对于ipsec局部的配置办法网络上有很多文档,在此不做具体叙述,有趣味的童鞋能够参照文档本人实际下。隧道建设后,在两端互ping对端的内网IP,如果能够ping通的话,证实ipsec工作失常。依照布局配置好NAT/LB子网和node子网以及pod子网的路由。咱们在K8s集群的serivce中,抉择一个名为nginx的serivce,clusterIP为10.0.58.158,如图所示:

该服务后端的pod是10.0.0.13,部署nginx默认页面,并监听80端口。在上海云主机上测试ping service的IP 10.0.58.158,能够ping通,同时应用paping工具ping服务的80端口,也能够ping通!

应用curl http://10.0.58.158进行http申请,也能够胜利!

再测试间接拜访后端pod,也没有问题:)

正当攻城狮心里美滋滋,认为所有都功败垂成的时候,测试拜访另一个service的后果犹如一盆冷水泼来。咱们接着选取了mysql这个service,测试拜访3306端口。该serivce的clusterIP是10.0.60.80,后端pod的IP是10.0.0.14。

在上海云主机间接ping service的clusterIP,没有问题。然而paping 3306端口的时候,竟然不通了!

而后咱们测试间接拜访serivce的后端pod,诡异的是,后端pod无论是ping IP还是paping 3306端口,都是能够连通的!

肿么回事?

这是肿么回事?通过攻城狮一番比照剖析,发现两个serivce惟一的不同是,能够连通nginx服务的后端pod 10.0.0.13就部署在客户端申请转发到的node A上。而不能连通的mysql服务的后端pod不在node A上,在另一个节点上。为了验证问题起因是否就在于此,咱们独自批改NAT/LB子网路由,到mysql服务的下一跳指向后端pod所在的节点。而后再次测试。果然!当初能够拜访mysql服务的3306端口了!

第四局部:三个为什么?

此时此刻,攻城狮的心中有三个疑难:

(1)为什么申请转发至service后端pod所在的节点时能够连通?

(2)为什么申请转发至service后端pod不在的节点时不能连通?

(3)为什么不论转发至哪个节点,service的IP都能够ping通?

深入分析,打消问号

为了打消咱们心中的小问号,咱们就要深入分析,理解导致问题的起因,而后再隔靴搔痒。既然要排查网络问题,当然还是要祭出经典法宝——tcpdump抓包工具。为了把焦点集中,咱们对测试环境的架构进行了调整。上海到北京的ipsec局部维持现有架构不变,咱们对K8s集群节点进行扩容,新建一个没有任何pod的空节点K8s-node-vmcrm9-bst9jq8pua,以下简称node B,该节点只做申请转发。批改NAT/LB子网路由,拜访service地址的路由下一跳指向该节点。测试的service咱们选取之前应用的nginx服务10.0.58.158和后端pod 10.0.0.13,如下图所示:

当须要测试申请转发至pod所在节点的场景时,咱们将service路由下一跳批改为K8s-node-A即可。

万事俱备,让咱们开启解惑之旅!Go Go Go!

首先探索疑难1场景,咱们在K8s-node-A上执行命令抓取与上海云主机172.16.0.50的包,命令如下:

tcpdump -i any host 172.16.0.50 -w /tmp/dst-node-client.cap

各位童鞋是否还记得咱们之前提到过,在托管K8s集群中,所有pod的数据流量均通过节点的弹性网卡收发?在K8s-node-A上pod应用的弹性网卡是eth1。咱们首先在上海云主机上应用curl命令申请http://10.0.58.158,同时执行命令抓取K8s-node-A的eth1上是否有pod 10.0.0.13的包收发,命令如下:

tcpdump –i eth1 host 10.0.0.13

后果如下图:

并没有任何10.0.0.13的包从eth1收发,但此时上海云主机上的curl操作是能够申请胜利的,阐明10.0.0.13必然给客户端回包了,然而并没有通过eth1回包。那么咱们将抓包范畴扩充至全副接口,命令如下:

tcpdump -i any host 10.0.0.13

后果如下图:

能够看到这次的确抓到了10.0.0.13和172.16.0.50交互的数据包,为了便于剖析,咱们应用命令tcpdump -i any host 10.0.0.13 -w /tmp/dst-node-pod.cap将包输入为cap文件。

同时咱们再执行tcpdump -i any host 10.0.58.158,对service IP进行抓包。

能够看到172.16.0.50执行curl申请时能够抓到数据包,且只有10.0.58.158与172.16.0.50交互的数据包,不执行申请时没有数据包。因为这一部分数据包会蕴含在对172.16.0.50的抓包中,因而咱们不再独自剖析。

将针对172.16.0.50和10.0.0.13的抓包文件取出,应用wireshark工具进行剖析,首先剖析对客户端172.16.0.50的抓包,详情如下图所示:

能够发现客户端172.16.0.50先给service IP 10.0.58.158发了一个包,而后又给pod IP 10.0.0.13发了一个包,两个包的ID,内容等完全一致。而最初回包时,pod 10.0.0.13给客户端回了一个包,而后service IP 10.0.58.158也给客户端回了一个ID和内容完全相同的包。这是什么起因导致的呢?

通过之前的介绍,咱们晓得service将客户端申请转发至后端pod,在这个过程中客户端申请的是service的IP,而后service会做DNAT(依据目标IP做NAT转发),将申请转发至后端的pod IP。尽管咱们抓包看到的是客户端发了两次包,别离发给service和pod,实际上客户端并没有从新发包,而是由service实现了目标地址转换。而pod回包时,也是将包回给service,而后再由service转发给客户端。因为是雷同节点内申请,这一过程应该是在节点的外部虚构网络中实现,所以咱们在pod应用的eth1网卡上并没有抓到和客户端交互的任何数据包。再联合pod维度的抓包,咱们能够看到针对client抓包时抓到的http get申请包在对pod的抓包中也能抓到,也验证了咱们的剖析。

那么pod是通过哪个网络接口进行收发包的呢?执行命令netstat -rn查看node A上的网络路由,咱们有了如下发现:

在节点内,所有拜访10.0.0.13的路由都指向了cni34f0b149874这个网络接口。很显然这个接口是CNI网络插件创立的虚构网络设备。为了验证pod所有的流量是否都通过该接口收发,咱们再次在客户端申请service地址,在node A以客户端维度和pod维度抓包,然而这次以pod维度抓包时,咱们不再应用-i any参数,而是替换为-i cni34f0b149874。抓包后剖析比照,发现如咱们所料,客户端对pod的所有申请包都能在对cni34f0b149874的抓包中找到,同时对系统中除了cni34f0b149874之外的其余网络接口抓包,均没有抓到与客户端交互的任何数据包。因而能够证实咱们的推断正确。

综上所述,在客户端申请转发至pod所在节点时,数据通路如下图所示:

接下来咱们探索最为关怀的问题2场景,批改NAT/LB子网路由到service的下一跳指向新建节点node B,如图所示

这次咱们须要在node B和node A上同时抓包。在客户端还是应用curl形式申请service地址。在转发节点node B上,咱们先执行命令tcpdump -i eth0 host 10.0.58.158抓取service维度的数据包,发现抓取到了客户端到service的申请包,然而service没有任何回包,如图所示:

各位童鞋可能会有纳闷,为什么抓取的是10.0.58.158,但抓包中显示的目标端是该节点名?实际上这与service的实现机制无关。在集群中创立service后,集群网络组件会在各个节点上都选取一个随机端口进行监听,而后在节点的iptables中配置转发规定,但凡在节点内申请service IP均转发至该随机端口,而后由集群网络组件进行解决。所以在节点内拜访service时,理论拜访的是节点上的某个端口。如果将抓包导出为cap文件,能够看到申请的目标IP依然是10.0.58.158,如图所示:

这也解释了为什么clusterIP只能在集群内的节点或者pod拜访,因为集群外的设施没有K8s网络组件创立的iptables规定,不能将申请service地址转为申请节点的端口,即便数据包发送至集群,因为service的clusterIP在节点的网络中理论是不存在的,因而会被抛弃。(奇怪的姿态又增长了呢)

回到问题自身,在转发节点上抓取service相干包,发现service没有像转发到pod所在节点时给客户端回包。咱们再执行命令tcpdump -i any host 172.16.0.50 -w /tmp/fwd-node-client.cap以客户端维度抓包,包内容如下:

咱们发现客户端申请转发节点node B上的service后,service同样做了DNAT,将申请转发到node A上的10.0.0.13。然而在转发节点上没有收到10.0.0.13回给客户端的任何数据包,之后客户端重传了几次申请包,均没有回应。

那么node A是否收到了客户端的申请包呢?pod又有没有给客户端回包呢?咱们移步node A进行抓包。在node B上的抓包咱们能够获悉node A上应该只有客户端IP和pod IP的交互,因而咱们就从这两个维度抓包。依据之前抓包的剖析后果,数据包进入节点内之后,应该通过虚构设施cni34f0b149874与pod交互。而node B节点拜访pod应该从node A的弹性网卡eth1进入节点,而不是eth0,为了验证,首先执行命令tcpdump -i eth0 host 172.16.0.50和tcpdump -i eth0 host 10.0.0.13,没有抓到任何数据包。

阐明数据包没有走eth0。再别离执行tcpdump -i eth1 host 172.16.0.50 -w /tmp/dst-node-client-eth1.cap和tcpdump -i cni34f0b149874 host 172.16.0.50 -w /tmp/dst-node-client-cni.cap抓取客户端维度数据包,比照发现数据包内容完全一致,阐明数据包从eth1进入Node A后,通过零碎内路由转发至cni34f0b149874。数据包内容如下:

能够看到客户端给pod发包后,pod给客户端回了包。执行tcpdump -i eth1 host 10.0.0.13 -w /tmp/dst-node-pod-eth1.cap和tcpdump -i host 10.0.0.13 -w /tmp/dst-node-pod-cni.cap抓取pod维度数据包,比照发现数据包内容完全一致,阐明pod给客户端的回包通过cni34f0b149874收回,而后从eth1网卡来到node A节点。数据包内容也能够看到pod给客户端返回了包,但没有收到客户端对于返回包的回应,触发了重传。

那么既然pod的回包曾经收回,为什么node B上没有收到回包,客户端也没有收到回包呢?查看eth1网卡所属的pod子网路由表,咱们豁然开朗!

因为pod给客户端回包是从node A的eth1网卡收回的,所以尽管依照失常DNAT规定,数据包应该发回给node B上的service端口,然而受eth1子网路由表影响,数据包间接被“劫持”到了K8s-ipsec-bj这个主机上。而数据包到了这个主机上之后,因为没有通过service的转换,回包的源地址是pod地址10.0.0.13,目标地址是172.16.0.50,这个数据包回复的是源地址172.16.0.50,目标地址10.0.58.158这个数据包。相当于申请包的目标地址和回复包的源地址不统一,对于K8s-ipsec-bj来说,只看到了10.0.0.13给172.16.0.50的reply包,然而没有收到过172.16.0.50给10.0.0.13的request包,云平台虚构网络的机制是遇到只有reply包,没有request包的状况会将request包抛弃,防止利用地址坑骗发动网络攻击。所以客户端不会收到10.0.0.13的回包,也就无奈实现对service的申请。在这个场景下,数据包的通路如下图所示:

此时客户端能够胜利申请pod的起因也高深莫测 ,申请pod的数据通路如下:

申请包和返回包的门路统一,都通过K8s-ipsec-bj节点且源目IP没有产生扭转,因而pod能够连通。

看到这里,机智的童鞋可能曾经想到,那批改eth1所属的pod子网路由,让去往172.16.0.50的数据包下一跳不发送到K8s-ipsec-bj,而是返回给K8s-node-B,不就能够让回包沿着去路原路返回,不会被抛弃吗?是的,通过咱们的测试验证,这样的确能够使客户端胜利申请服务。然而别忘了,用户还有一个需要是客户端能够间接拜访后端pod,如果pod回包返回给node B,那么客户端申请pod时的数据通路是怎么的呢?

如图所示,能够看到客户端对Pod的申请达到K8s-ipsec-bj后,因为是同一vpc内的地址拜访,所以遵循local路由规定间接转发到node A eth1网卡,而pod给客户端回包时,受eth1网卡路由管制,发送到了node B上。node B之前没有收到过客户端对pod的request包,同样会遇到只有reply包没有request包的问题,所以回包被抛弃,客户端无奈申请pod。

至此,咱们搞清楚了为什么客户端申请转发至service后端pod不在的节点上时无奈胜利拜访service的起因。那么为什么在此时尽管申请service的端口失败,然而能够ping通service地址呢?攻城狮推断,既然service对后端的pod起到DNAT和负载平衡的作用,那么当客户端ping service地址时,ICMP包应该是由service间接应答客户端的,即service代替后端pod回答客户端的ping包。为了验证咱们的推断是否正确,咱们在集群中新建一个没有关联任何后端的空服务,如图所示:

而后在客户端ping 10.0.62.200,后果如下:

果不其然,即便service后端没有任何pod,也能够ping通,因而证实ICMP包均为service代答,不存在理论申请后端pod时的问题,因而能够ping通。

第五局部:天无绝人之路

既然费尽周折找到了拜访失败的起因,接下来咱们就要想方法解决这个问题。事实上只有想方法让pod跨节点给客户端回包时暗藏本人的IP,对外显示的是service的IP,就能够防止包被抛弃。原理上相似于SNAT(基于源IP的地址转换)。能够类比为没有公网IP的局域网设施有本人的内网IP,当拜访公网时须要通过对立的公网进口,而此时内部看到的客户端IP是公网进口的IP,并不是局域网设施的内网IP。实现SNAT,咱们首先会想到通过节点操作系统上的iptables规定。咱们在pod所在节点node A上执行iptables-save命令,查看零碎已有的iptables规定都有哪些。

敲黑板,留神啦

能够看到零碎创立了近千条iptables规定,大多数与K8s无关。咱们重点关注上图中的nat类型规定,发现了有如下几条引起了咱们的留神:

首先看红框局部规定

-A KUBE-SERVICES -m comment --comment "Kubernetes service cluster ip + port for masquerade purpose" -m set --match-set KUBE-CLUSTER-IP src,dst -j KUBE-MARK-MASQ

该规定示意如果拜访的源地址或者目标地址是cluster ip +端口,出于masquerade目标,将跳转至KUBE-MARK-MASQ链,masquerade也就是地址假装的意思!在NAT转换中会用到地址假装。

接下来看蓝框局部规定

-A KUBE-MARK-MASQ -j MARK --set-xmark 0x4000/0x4000

该规定示意对于数据包打上须要做地址假装的标记0x4000/0x4000。

最初看黄框局部规定

-A KUBE-POSTROUTING -m comment --comment "kubernetes service traffic requiring SNAT" -m mark --mark 0x4000/0x4000 -j MASQUERADE

该规定示意对于标记为0x4000/0x4000须要做SNAT的数据包,将跳转至MASQUERADE链进行地址假装。

这三条规定所做的操作貌似正是咱们须要iptables帮咱们实现的,然而从之前的测试来看显然这三条规定并没有失效。这是为什么呢?是否是K8s的网络组件里有某个参数管制着是否会对拜访clusterIP时的数据包进行SNAT?

这就要从负责service与pod之间网络代理转发的组件——kube-proxy的工作模式和参数进行钻研了。咱们曾经晓得service会对后端pod进行负载平衡和代理转发,要想实现该性能,依赖的是kube-proxy组件,从名称上能够看出这是一个代理性质的网络组件。它以pod模式运行在每个K8s节点上,当以service的clusterIP+端口形式拜访时,通过iptables规定将申请转发至节点上对应的随机端口,之后申请由kube-proxy组件接手解决,通过kube-proxy外部的路由和调度算法,转发至相应的后端Pod。最后,kube-proxy的工作模式是userspace(用户空间代理)模式,kube-proxy过程在这一时期是一个实在的TCP/UDP代理,相似HA Proxy。因为该模式在1.2版本K8s开始已被iptables模式取代,在此不做赘述,有趣味的童鞋能够自行钻研下。

1.2版本引入的iptables模式作为kube-proxy的默认模式,kube-proxy自身不再起到代理的作用,而是通过创立和保护对应的iptables规定实现service到pod的流量转发。然而依赖iptables规定实现代理存在无奈防止的缺点,在集群中的service和pod大量减少后,iptables规定的数量也会急剧减少,会导致转发性能显著降落,极其状况下甚至会呈现规定失落的状况。

为了解决iptables模式的弊病,K8s在1.8版本开始引入IPVS(IP Virtual Server)模式。IPVS模式专门用于高性能负载平衡,应用更高效的hash表数据结构,为大型集群提供了更好的扩展性和性能。比iptables模式反对更简单的负载平衡调度算法等。托管集群的kube-proxy正是应用了IPVS模式。

然而IPVS模式无奈提供包过滤,地址假装和SNAT等性能,所以在须要应用这些性能的场景下,IPVS还是要搭配iptables规定应用。等等,地址假装和SNAT,这不正是咱们之前在iptables规定中看到过的?这也就是说,iptables在不进行地址假装和SNAT时,不会遵循相应的iptables规定,而一旦设置了某个参数开启地址假装和SNAT,之前看到的iptables规定就会失效!于是咱们到kubernetes官网查找kube-proxy的工作参数,有了令人激动的发现:

好一个蓦然回首!攻城狮的第六感通知咱们,--masquerade-all参数就是解决咱们问题的要害!

第六局部:真·办法比艰难多

咱们决定测试开启下--masquerade-all这个参数。kube-proxy在集群中的每个节点上以pod模式运行,而kube-proxy的参数配置都以configmap模式挂载到pod上。咱们执行kubectl get cm -n kube-system查看kube-proxy的configmap,如图所示:

红框里的就是kube-proxy的配置configmap,执行kubectl edit cm kube-proxy-config-khc289cbhd -n kube-system编辑这个configmap,如图所示

找到了masqueradeALL参数,默认是false,咱们批改为true,而后保留批改。

要想使配置失效,须要逐个删除以后的kube-proxy pod,daemonset会主动重建pod,重建的pod会挂载批改过的configmap,masqueradeALL性能也就开启了。如图所示:

期待地搓手手

接下来激动人心的时刻到来了,咱们将拜访service的路由指向node B,而后在上海客户端上执行paping 10.0.58.158 -p 80察看测试后果(期待地搓手手):

此情此景,不禁让攻城狮流下了欣慰的泪水……

再测试下curl http://10.0.58.158 同样能够胜利!奥力给~

再测试下间接拜访后端Pod,以及申请转发至pod所在节点,都没有问题。至此客户的需要终于卍解,长舒一口气!

大结局:知其所以然

尽管问题曾经解决,然而咱们的探索还没有完结。开启masqueradeALL参数后,service是如何对数据包做SNAT,防止了之前的丢包问题呢?还是通过抓包进行剖析。

首先剖析转发至pod不在的节点时的场景,客户端申请服务时,在pod所在节点对客户端IP进行抓包,没有抓到任何包。

阐明开启参数后,到后端pod的申请不再是以客户端IP发动的。

在转发节点对pod IP进行抓包能够抓到转发节点的service端口与pod之间的交互包。

阐明pod没有间接回包给客户端172.16.0.50。这样看来,相当于客户端和pod相互不晓得彼此的存在,所有交互都通过service来转发。

再在转发节点对客户端进行抓包,包内容如下:

同时在pod所在节点对pod进行抓包,包内容如下:

能够看到转发节点收到序号708的curl申请包后,在pod所在节点收到了序号雷同的申请包,只不过源目IP从172.16.0.50/10.0.58.158转换为了10.0.32.23/10.0.0.13。这里10.0.32.23是转发节点的内网IP,实际上就是节点上service对应的随机端口,所以能够了解为源目IP转换为了10.0.58.158/10.0.0.13。而回包时的流程雷同,pod收回序号17178的包,转发节点将雷同序号的包发给客户端,源目IP从10.0.0.13/10.0.58.158转换为了10.0.58.158/172.16.0.50

依据以上景象能够得悉,service对客户端和后端都做了SNAT,能够了解为敞开了透传客户端源IP的负载平衡,即客户端和后端都不晓得彼此的存在,只晓得service的地址。该场景下的数据通路如下图:

对Pod的申请不波及SNAT转换,与masqueradeALL参数不开启时是一样的,因而咱们不再做剖析。

当客户端申请转发至pod所在节点时,service仍然会进行SNAT转换,只不过这一过程均在节点外部实现。通过之前的剖析咱们也曾经理解,客户端申请转发至pod所在节点时,是否进行SNAT对拜访后果没有影响。

总结

至此对于客户的需要,咱们能够给呈现阶段最优的计划。当然在生产环境,为了业务平安和稳固,还是不倡议用户将clusterIP类型服务和pod间接裸露在集群之外。同时masqueradeALL参数开启后,对集群网络性能和其余性能是否有影响也没有通过测试验证,在生产环境开启的危险是未知的,还须要审慎看待。通过解决客户需要的过程,咱们对K8s集群的service和pod网络机制有了肯定水平的理解,并理解了kube-proxy的masqueradeALL参数,对今后的学习和运维工作还是受益匪浅的。

在此感激各位童鞋浏览,如果可能对大家有所帮忙,欢送点赞转发,并关注咱们的公众号,更多精彩内容会继续放送!

举荐浏览

  • DevOps专题|玩转Kubernetes网络
  • 在线公开课 | 读完这篇,轻松开启Kubernetes之旅
  • 干货 | 京东云Kubernetes集群+Traefik实战

欢送点击【京东科技】,理解开发者社区

更多精彩技术实际与独家干货解析

欢送关注【京东科技开发者】公众号