前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >综合题:一个请求如何从service到达Pod ?

综合题:一个请求如何从service到达Pod ?

作者头像
LanceZhang
发布2022-03-10 10:54:05
1.9K2
发布2022-03-10 10:54:05
举报
文章被收录于专栏:二哥聊云原生二哥聊云原生

大家好,我是二哥。

今天我们来聊一个有意思的话题:当我们向一个K8s 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

Linux 在内核网络组件中很多关键位置处都布置了 netfilter 过滤器。Netfilter 框架是 Linux 防火墙和网络的主要维护者罗斯迪·鲁塞尔(Rusty Russell)提出并主导设计的,它围绕网络层(IP 协议)的周围,埋下了五个钩子(Hooks),每当有数据包流到网络层,经过这些钩子时,就会自动触发由内核模块注册在这里的回调函数,程序代码就能够通过回调来干预 Linux 的网络通信。

图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上。

代码语言:javascript
复制
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。

代码语言:javascript
复制
## 通过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.1 入口

在图2中,你一定看到了在PREROUTING和OUTPUT处的NAT table里有非常显眼的规则,这个规则很容易看懂:如果访问的IP是172.16.255.220,则跳转到子链 KUBE-SVC-4LFXAAP7ALRXDL3I 处。而172.16.255.220 即是我们这里谈及的service IP地址。

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容器网络方案全景图

3.5 Session Affinity(会话保持)

此处优(故)雅(意)地省略1000字。

4. 延展思考

写完这篇,二哥想到我坚持的一个观点:新技术真的是层出不穷,但更多的时候,它的本质是用旧积木搭出了新造型。比如这篇我们讨论到的iptables、package flow和路由等知识点都已经出现了一二十年了,其中iptables诞生于2001年。

本文没有讨论类型为LoadBalancer和NodePort的Service场景,但它们的实现都依赖于ClusterIP这种类型的Service。本文也没有讨论Session Affinity(会话保持)功能,但它实现的基础还是iptables。二哥觉得把本文的知识掌握了,再去看这些进阶的部分,应该会比较容易。

实际上将流量从service导到一个Pod还有其它实现方法,比如Cilium就基于eBPF来实现更快速和高效的流量处理。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2022-02-26,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 二哥聊云原生 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1. 基础知识
    • 1.1 Service和Pod关系
      • 1.2 netfilter package flow
      • 2. 测试环境
      • 3. 细节详解
        • 3.1 入口
          • 3.2 负载均衡
            • 3.3 通过NAT修改目的地址
              • 3.4 路由
                • 3.5 Session Affinity(会话保持)
                • 4. 延展思考
                相关产品与服务
                容器服务
                腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
                领券
                问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档