关于kubernetes:一次客户需求引发的K8S网络探究

35次阅读

共计 13545 个字符,预计需要花费 34 分钟才能阅读完成。

前言

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

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

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

clusterIP 类型,创立 service 时如果不指定类型的话的默认会创立该类型 service:

service 类型简介

  • clusterIP 类型

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

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

小结

通过对 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 参数,对今后的学习和运维工作还是受益匪浅的。

正文完
 0