网站首页 > 技术文章 正文
一个请求如何从 Service 到达 Pod
当我们从一个K8s cluster的Pod里面向位于同cluster的另一个service发起请求这样的场景
1. 基础知识
它其实是一个包含多个基础知识的综合题。想要找到答案,得需要理解几个与之相关的重要基础知识:iptables、package flow和路由。我们先来依次过一下这几个基础概念
1.1 Service和Pod关系
首先我们先来复习一下Service和Pod之间的关系。Service存在的意义是将背后的Pod聚合在一起,以单一入口的方式对外提供服务。这里的外部访问者既可能是K8s cluster内部的Pod,也可以是K8s外部的进程
我们都知道service有一个可以在K8s内部访问到的虚拟IP地址(Cluster IP),这个地址你可以在kubedns里面找到,所以请求端通常通过类似下面这个FQDN prometheus-service.LanceAndCloudnative.svc.cluster.local来访问一个服务。但如果你K8s Node上无论是执行 ip a 还是 netstat -an 都无法找到这个虚拟地址。
另外我们还知道一个service背后会站着若干个Pod,每个Pod有自己的IP地址。如图1所示
图 1:Service和Pod之间的关系
1.2 netfilter package flow
图2所示的PREROUTING、INPUT、OUTPUT、FORWARD、POSTROUTING即为这里提到的5个钩子。每个钩子都像珍珠项链一样串联着若干规则,从而形成一个链。这些规则散落在五个表中,它们分布是NAT、Mangle、RAW、Security和Filter。其中Security表不常用,除去它,我们把其它部分合起来简称五链四表。
下面这张图能比较好地阐述链和表的关系。图片来自公众号:开发内功修炼。
图 2:五链四表对照表
了解了 netfilter之后,我们再来看看图3。这张图估计很多同学都不陌生。它非常清晰地展示了内核收到网络包后,netfilter和路由对这个包在数据内容修改和传输路径方面的影响。
为了突出本文的重点,我把流量从service转到Pod过程中涉及到的钩子和路由画出来了。你也看到了,图3里,我还在PREROUTING和OUTPUT这两个钩子处画出了KUBE-SERVICE和NAT。这里的KUBE-SERVICE是由kube-proxy创建的一个自定义链,kube-proxy还在NAT表中定义了分别在这两个钩子处生效的规则。既然规则存放在NAT表里,那肯定表示这些规则与地址转换有关。
图3中有两处出现了“路由选择”的标记,毫无疑问,这里涉及到路由。我将与本文有关的路由表信息也画在了图上。
图 3:netfilter package flow
回到本文讨论的场景。当我们从一个K8s Cluster的Pod向位于同集群的另一个service发起的请求时,请求从图3左下角的红框内(圈1处)进入。请求可能最终流入到作为本地进程的K8s Pod(圈2处,准确地说应该是Pod里面的container),也可能会被转发到位于其它Node上的K8s Pod(圈3处)。
下面我们来结合测试环境、iptables和路由表来详细看看
2. 测试环境
service名字为nginx-web-service,它背后运行有3个名为nginx-web的Pod。简单起见,本文仅讨论类型为CluseterIP的service。
Service Cluster-IP为:172.16.255.220, Service服务分配的CLUSTER-IP以及监听的端口均是虚拟的。Pod的IP分别为:10.204.0.13,10.204.1.3和10.204.1.8
如图1所示,IP为10.204.0.13的Pod运行在Node 1上,它所在的Node IP地址为130.211.97.55,相应地,IP为10.204.1.3和10.204.1.8的Pod运行在IP地址为130.211.99.206的Node 2上
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-web
namespace: LanceAndCloudnative
labels:
app: nginx-web
spec:
replicas: 3
selector:
matchLabels:
app: nginx-web
template:
metadata:
labels:
app: nginx-web
spec:
containers:
- name: nginx-web
image: nginx
ports:
- containerPort: 80
......
---
apiVersion: v1
kind: Service
metadata:
name: nginx-web-service
namespace: LanceAndCloudnative
spec:
selector:
app: nginx-web
type: ClusterIP
ports:
- port: 80
targetPort: 80
3. 细节详解
铺垫了这么多,终于到了详述细节的环节了。下面是用命令
# iptables-save | grep LanceAndCloudnative在Node 1上dump出来的iptables
## 通过NAT重新分发到具体的Pod
-A KUBE-SEP-OALS23FQATZ4JKLQ -s 10.204.0.13/32 -m comment --comment "LanceAndCloudnative/nginx-web-service:" \
-j KUBE-MARK-MASQ
-A KUBE-SEP-OALS23FQATZ4JKLQ -p tcp -m comment --comment "LanceAndCloudnative/nginx-web-service:" \
-m tcp -j DNAT --to-destination 10.204.0.13:80
-A KUBE-SEP-U34NONI5MGHBFYFA -s 10.204.1.3/32 -m comment --comment "LanceAndCloudnative/nginx-web-service:" \
-j KUBE-MARK-MASQ
-A KUBE-SEP-U34NONI5MGHBFYFA -p tcp -m comment --comment "LanceAndCloudnative/nginx-web-service:" \
-m tcp -j DNAT --to-destination 10.204.1.3:80
-A KUBE-SEP-IYP2JLAPHWQ5VKF7 -s 10.204.1.8/32 -m comment --comment "LanceAndCloudnative/nginx-web-service:" \
-j KUBE-MARK-MASQ
-A KUBE-SEP-IYP2JLAPHWQ5VKF7 -p tcp -m comment --comment "LanceAndCloudnative/nginx-web-service:" \
-m tcp -j DNAT --to-destination 10.204.1.8:80
## 负载均衡
-A KUBE-SVC-4LFXAAP7ALRXDL3I -m comment --comment "LanceAndCloudnative/nginx-web-service:" \
-m statistic --mode random --probability 0.33333333349 -j KUBE-SEP-OALS23FQATZ4JKLQ
-A KUBE-SVC-4LFXAAP7ALRXDL3I -m comment --comment "LanceAndCloudnative/nginx-web-service:" \
-m statistic --mode random --probability 0.50000000000 -j KUBE-SEP-U34NONI5MGHBFYFA
-A KUBE-SVC-4LFXAAP7ALRXDL3I -m comment --comment "LanceAndCloudnative/nginx-web-service:" \
-j KUBE-SEP-IYP2JLAPHWQ5VKF7
## 入口
-A KUBE-SERVICES -d 172.16.255.220/32 -p tcp -m comment \
--comment "LanceAndCloudnative/nginx-web-service: cluster IP" -m tcp --dport 80 -j KUBE-SVC-4LFXAAP7ALRXDL3I
-A PREROUTING -m comment --comment "kubernetes service portals" -j KUBE-SERVICES
-A OUTPUT -m comment --comment "kubernetes service portals" -j KUBE-SERVICES
这份iptables配置分为三大部分:入口、负载均衡、通过NAT修改目的地址。我们分别来瞧瞧。
3.2 负载均衡
同名子链 KUBE-SVC-4LFXAAP7ALRXDL3I 有三个。为啥是3个?聪明的你一定能猜到,因为Pod replica是3。也就是说因为这个service背后有三个Pod提供支撑服务,所以这里有三条子链。那如果replica是100呢?呵呵,你猜。
不但如此,每个Node上都有着这样类似的三条子链。为啥每个Node都有?好问题,留着以后再聊吧。
岔个话题:我只能说K8s默认使用iptables来实现Service到Pod的转换欠下了大量的技术债。K8s的问题列表里面曾经记录了一个问题#44613:在100个Node的K8s集群里,kube-proxy有时会消耗70%的CPU。还有一个更恐怖的对比数据:当K8s里有5k个services(每个service平均需要插入8条rule,一共40k iptables rules)的时候,插入一条新的rule需要11分钟;而当services数量scale out 4倍到20k(160k rules)时,需要花费5个小时,而非44分钟,才能成功加入一条新的rule。可以看到时间消耗呈指数增加,而非线性。
那均衡的策略是什么呢?你也看到了,这里按30%-50%-20%的比例分配流量。
3.3 通过NAT修改目的地址
我们现在假设子链 KUBE-SEP-OALS23FQATZ4JKLQ 被负载均衡策略选中。在它的规则里我们很容易地读懂它通过DNAT,把dest IP替换成了10.204.0.13。还记得吗?10.204.0.13 是为这个service提供支撑的其中一个Pod 的IP地址。干得漂亮,通过这种方式,完成了从service IP地址到Pod IP地址的转换
3.4 路由
可单单转换地址还不行,还得把流量导到那个Pod手上才算完成任务。
看到图2的左边的“路由选择”标记了吗?在它旁边的路由表里面写着:如果去子网10.204.0.13/24的话,从cni离开,且下一跳为0.0.0.0。
cni0是什么?哦,它是一个bridge,Pod都插在它的端口上。0.0.0.0表示目标和本机同属一个局域网,不需要经过任何gateway去路由,可以直接通过二层设备发送,比如switch,hub或者bridge。这也暗示了一点:在这种情况下,发起请求的Pod和处理请求的Pod位于同一个Node上
那如果上一步中负载均衡策略选中的子链是 KUBE-SEP-U34NONI5MGHBFYFA 的话,很显然应该轮到Pod 10.204.1.3来提供服务了。按照路由表的设置:如果去子网10.204.1.0/24的话,这次得从flannel.1离开,且下一跳IP为10.204.1.1。这种场景就涉及到另外一个话题了:跨Node间Pod通信
图 4是K8s Overlay网络模型下,跨Node间Pod通信时的细节放大图。这节所说的两种Pod间通信时的路由情况都浓缩在这张图里了
图 4:VXLAN容器网络方案全景图
- 上一篇: grep命令使用速览(grep命令详解)
- 下一篇: Shell脚本--AWK、SED、GREP三剑客
猜你喜欢
- 2024-09-10 oracle实用sql分享:杀进程、长时间操作等等
- 2024-09-10 走在前沿的弄潮儿,怎能不会Git的那些奇技淫巧
- 2024-09-10 TCP“三次握手,四次挥手”你真的懂吗?
- 2024-09-10 制作 deb 软件包(如何制作deb包)
- 2024-09-10 详解虚拟化之KVM概念、架构、功能、常用工具及部署
- 2024-09-10 Linux find命令一定要知道这些(linux find命令的使用)
- 2024-09-10 inux 文本处理三剑客--grep/sed/awk
- 2024-09-10 db2入门必看命令清单--日常运维必需
- 2024-09-10 浅谈Linux中的&&和ll(linux中atime)
- 2024-09-10 浅谈Linux中的&&和ll,补充&和l
- 1508℃桌面软件开发新体验!用 Blazor Hybrid 打造简洁高效的视频处理工具
- 520℃Dify工具使用全场景:dify-sandbox沙盒的原理(源码篇·第2期)
- 490℃MySQL service启动脚本浅析(r12笔记第59天)
- 469℃服务器异常重启,导致mysql启动失败,问题解决过程记录
- 467℃启用MySQL查询缓存(mysql8.0查询缓存)
- 447℃「赵强老师」MySQL的闪回(赵强iso是哪个大学毕业的)
- 427℃mysql服务怎么启动和关闭?(mysql服务怎么启动和关闭)
- 424℃MySQL server PID file could not be found!失败
- 最近发表
- 标签列表
-
- c++中::是什么意思 (83)
- 标签用于 (65)
- 主键只能有一个吗 (66)
- c#console.writeline不显示 (75)
- pythoncase语句 (81)
- es6includes (73)
- windowsscripthost (67)
- apt-getinstall-y (86)
- node_modules怎么生成 (76)
- chromepost (65)
- c++int转char (75)
- static函数和普通函数 (76)
- el-date-picker开始日期早于结束日期 (70)
- js判断是否是json字符串 (67)
- checkout-b (67)
- localstorage.removeitem (74)
- vector线程安全吗 (70)
- & (66)
- java (73)
- js数组插入 (83)
- linux删除一个文件夹 (65)
- mac安装java (72)
- eacces (67)
- 查看mysql是否启动 (70)
- 无效的列索引 (74)