程序锅

  • 首页
  • 分类
  • 标签
  • 归档
  • 关于

  • 搜索
基础知识 Etcd LeetCode 计算机体系结构 Kubernetes Containerd Docker 容器 云原生 Serverless 项目开发维护 ELF 深入理解程序 Tmux Vim Linux Kernel Linux numpy matplotlib 机器学习 MQTT 网络基础 Thrift RPC OS 操作系统 Clang 研途 数据结构和算法 Java 编程语言 Golang Python 个人网站搭建 Nginx 计算机通用技术 Git

Kubernetes-KubeProxy 原理

发表于 2023-08-02 | 分类于 Kubernetes | 0 | 阅读次数 3091

ClusterIP 是 kubernetes 的概念,是虚拟 IP,不是真实的 IP 地址。但是,我们通过 ClusterIP 和端口访问的时候,相应的访问是可以到达真正的后端 Pod。这一切的主要实现依赖于 Kube-Proxy。Kube-Proxy 是运行在 Kubernetes 每个节点上的一个网络代理组件。它会监听 service/endpoint/endpointslices/node 等资源,根据这些资源的情况维护相应节点上的网络规则,最终让发往 Service 的流量,可以负载均衡到正确的后端 Pod。

Kube-Proxy 支持多种模式实现上述的能力,相当于可以使用不同的底层能力来实现,

  • iptables
  • ipvs
  • kernelspace

官方文档见:https://kubernetes.io/zh-cn/docs/reference/networking/virtual-ips/。

简单来说,如果使用 iptables 模式的话,kube-proxy 其实是在宿主机上设置一系列 iptables 规则。同时,kube-proxy 还需要在控制循环里不断地刷新这些规则来确保它们始终是正确的。当集群中有大量 Pod 的时候,成百上千条 iptables 规则不断地被刷新,会大量占用该宿主机的 CPU 资源,甚至会让宿主机“卡”在这个过程中。因此,对于大规模集群来说,iptables 模式会存在瓶颈。此时更加推荐 kube-proxy 使用 ipvs 模式。下面对 iptables 和 ipvs 两种模式的原理进行介绍。

iptables 实现

如果使用的是 iptables 的话,kube-proxy 则是通过配置一系列的 iptables 规则来实现负载均衡,主要是 filter 和 nat 两个表。

集群节点访问 ClusterIP

以集群主机节点访问 DNS service 为例进行讲解,

$ kubectl -n kube-system get svc
NAME       TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)                  AGE
kube-dns   ClusterIP   10.32.0.10   <none>        53/UDP,53/TCP,9153/TCP   98d

$ kubectl -n kube-system get pod  -o wide | grep coredns
coredns-66f779496c-bjn9z                          1/1     Running   0              98d   10.222.69.194    izbp1bndrgrf54r0gjzf1bz   <none>           <none>
coredns-66f779496c-tc5vb                          1/1     Running   0              98d   10.222.69.193    izbp1bndrgrf54r0gjzf1bz   <none>           <none>
  • 由于是在主机节点上访问 ClusterIP,因此访问的数据包会先经过 OUTPUT 链。OUTPUT 链上的规则,会将所有流量都转发到 KUBE-SERVICES 自定义链进行处理。

    Chain OUTPUT (policy ACCEPT 288 packets, 17404 bytes)
     pkts bytes target     prot opt in     out     source               destination
      21M 1291M KUBE-SERVICES  all  --  *      *       0.0.0.0/0            0.0.0.0/0
    
  • KUBE-SERVICES 自定义链上设置了 3 条规则,这些规则都是关于 DNS service。

    • 如果目的地址是 10.32.0.10,使用的是 tcp 协议,端口号是 53 的话,则转发给 KUBE-SVC-ERIFXISQEP7F7OF4 自定义链。

    • 如果目的地址是 10.32.0.10,使用的是 tcp 协议,端口号是 9153 的话,则转发给 KUBE-SVC-JD5MR3NA4I4DYORP 自定义链。

    • 如果目的地址是 10.32.0.10,使用的是 udp 协议,端口号是 53 的话,则转发给 KUBE-SVC-TCOU7JCQXEZGVUNU 自定义链。

    Chain KUBE-SERVICES (2 references)
     pkts bytes target     prot opt in     out     source               destination
        0     0 KUBE-SVC-ERIFXISQEP7F7OF4  tcp  --  *      *       0.0.0.0/0            10.32.0.10 tcp dpt:53
        0     0 KUBE-SVC-JD5MR3NA4I4DYORP  tcp  --  *      *       0.0.0.0/0            10.32.0.10 tcp dpt:9153
        0     0 KUBE-SVC-TCOU7JCQXEZGVUNU  udp  --  *      *       0.0.0.0/0            10.32.0.10 udp dpt:53
      187 11160 KUBE-NODEPORTS  all  --  *      *       0.0.0.0/0            0.0.0.0/0 ADDRTYPE match dst-type LOCAL
    
  • 以 KUBE-SVC-ERIFXISQEP7F7OF4 自定义链为例继续阐述,其他两条 KUBE-SVC 链也都是类似的。

    • 如果源地址不是集群内 Pod IP 的地址范围的话,则执行 KUBE-MARK-MASQ 自定义链中的规则。KUBE-MARK-MASQ 链的规则就是给数据包打上 0x4000 标签。
    • 之后选择 KUBE-SEP-XALEUKC7I6NMGUZX 或 KUBE-SEP-I2G536MQTYUSDQGT 中的一条链进行执行。其中选中比率更是 0.5,这个是这么理解的,iptables 规则的匹配是从上往下逐条进行的,为了保证这 2 条规则被选中的概率都相同,如果第 1 条没有选中,那么就只剩下第 2 条了,所以第 2 条的 probability 设置 1 表示在第 1 条没中的时候,第 2 条必中。
    Chain KUBE-SVC-ERIFXISQEP7F7OF4 (1 references)
     pkts bytes target     prot opt in     out     source               destination
        2   120 KUBE-MARK-MASQ  tcp  --  *      *      !10.244.0.0/16        10.32.0.10    tcp dpt:53
        0     0 KUBE-SEP-XALEUKC7I6NMGUZX  all  --  *      *       0.0.0.0/0            0.0.0.0/0 statistic mode random probability 0.50000000000
        2   120 KUBE-SEP-I2G536MQTYUSDQGT  all  --  *      *       0.0.0.0/0            0.0.0.0/0
    
    Chain KUBE-MARK-MASQ (11 references)
     pkts bytes target     prot opt in     out     source               destination
        0     0 MARK       all  --  *      *       0.0.0.0/0            0.0.0.0/0            MARK or 0x4000
    
  • 这两条 KUBE-SEP 链中的规则是类似的,都是进行 DNAT。其中一条是将目的地址和端口改为 10.222.69.193:53,另一条则是改为 10.222.69.194:53。这两个地址则是 DNS Pod 实际的 IP 地址。

    Chain KUBE-SEP-XALEUKC7I6NMGUZX (1 references)
     pkts bytes target     prot opt in     out     source               destination
        0     0 KUBE-MARK-MASQ  all  --  *      *       10.222.69.193        0.0.0.0/0
        0     0 DNAT       tcp  --  *      *       0.0.0.0/0            0.0.0.0/0 tcp to:10.222.69.193:53
    
    Chain KUBE-SEP-I2G536MQTYUSDQGT (1 references)
     pkts bytes target     prot opt in     out     source               destination
        0     0 KUBE-MARK-MASQ  all  --  *      *       10.222.69.194        0.0.0.0/0
        2   120 DNAT       tcp  --  *      *       0.0.0.0/0            0.0.0.0/0 tcp to:10.222.69.194:53
    
  • OUTPUT 链上的规则处理完成之后,数据包还会经过 POSTROUTING 链的处理。POSTROUTING 链最终会使用 KUBE-POSTROUTING 自定义链上的规则进行处理。

    Chain POSTROUTING (policy ACCEPT 2256 packets, 136K bytes)
     pkts bytes target     prot opt in     out     source               destination         
      21M 1291M KUBE-POSTROUTING  all  --  *      *       0.0.0.0/0            0.0.0.0/0           
    
  • KUBE-POSTROUTING 自定义链会先检查是否打上了 0x4000 的标签。如果打上了的话,则去掉 0x4000 的标签,然后进行源地址替换,也就是将该数据包的源地址替换为宿主机的 IP 地址。

    Chain KUBE-POSTROUTING (1 references)
     pkts bytes target     prot opt in     out     source               destination
     2256  136K RETURN     all  --  *      *       0.0.0.0/0            0.0.0.0/0            mark match ! 0x4000/0x4000
        0     0 MARK       all  --  *      *       0.0.0.0/0            0.0.0.0/0            MARK xor 0x4000
        0     0 MASQUERADE  all  --  *      *       0.0.0.0/0            0.0.0.0/0            /* kubernetes service traffic requiring SNAT */
    
  • 之后宿主机将这个数据包发送出去,此时数据包的源地址是宿主机 IP 地址,目的地址是 DNS Pod 地址,经过一系列的路由最终到达 Pod。

集群 Pod 访问 ClusterIP

集群中 Pod 访问 DNS Service 也是类似的。但是,由于 Pod 有自己的命名空间,访问的数据会先通过路由进入宿主机的命名空间。

  • 在 PREROUTING 阶段就会进行上述的处理。
  • 由于进行了 DNAT,目的地址变成了 DNS Pod 的 IP 地址,相应的数据包不会进入 INPUT 阶段,而是直接进入 FORWARD 阶段,接着再进入 POSTROUTING 阶段。
Chain PREROUTING (policy ACCEPT 25 packets, 1332 bytes)
 pkts bytes target     prot opt in     out     source               destination
 588K   29M KUBE-SERVICES  all  --  *      *       0.0.0.0/0            0.0.0.0/0

IPVS 实现

IPVS 原理概述

IPVS 通过在 Netfilter 框架中的不同位置注册自己的处理函数来捕获数据包,并根据与 IPVS相关的信息表 对数据包进行处理,按照 IPVS 规则中定义的不同的包转发模式,对数据包进行不同的转发处理。

  • Netfilter 为包过滤提供了 5 个 Hook 点。而 IPVS 将其相关处理函数挂载到 Netfilter 框架提供的 LOCAL_IN、FORWARD 和 LOCAL_OUT 这三个 Hook 上(内核 6.x,不同版本的内核,ipvs 的函数名称和注册 hook 的位置可能会不一样,比如有 hook 到 POST_ROUTING 的)。其中 LOCAL_IN、LOCAL_OUT 上注册的 ip_vs_in 函数是负责 DNAT 的,也就是做 VIP 到 RIP 的转换。

  • IPVS 的规则由用户通过用户空间的管理工具 ipvsadm 写入。

  • IPVS 包含以下模式:NAT、IP tunneling 和 Direct Routing。由于只有 NAT 模式可以进行端口映射,因此 kube-proxy 中 ipvs 的实现使用了 NAT 模式,用来将 service IP 和 service port 映射到后端的 container ip 和container port。

IPVS 的 NAT 模式对数据包的流程如图所示。假设一个由外部网络发出的,访问局域网内服务的数据包会经历下面的流程(这个例子来自于:https://zhuanlan.zhihu.com/p/627514565):

  1. 外部网络 CIP 10.0.0.1:8888 发出访问 DS VIP 10.0.0.2:80 的数据包。
  2. 请求到达 Director Server,此时报文的源 IP 为 CIP,目标 IP 为 VIP。由于 VIP 是本机上的 IP 地址,因此会进入 INPUT 链。
  3. 到了 INPUT 链之后就会进入 IPVS 注册的 hook 函数中进行处理。此时,会通过 IPVS 规则,找到该数据包实际要转发的 RS(Real Server) 192.168.1.1:8080。找到 RS 信息后,DS 将数据包的目的地址和端口换成 IPVS 规则中配置的 RS 记录,并将这条连接添加到记录 ESTABLISHED 类型连接的哈希表中,如果后续有相同源地址端口的数据包到来,负载均衡将根据哈希表记录统一转发给同一个 RS。之后再将数据包发至 POSTROUTING 链。 此时报文的源 IP 为 CIP,目标 IP 为 RIP。
  4. 然后 POSTROUTING 链通过选路,将数据包发送给 RS。
  5. 数据包到达 RS 192.168.1.1,RS 处理完成后开始构建响应报文回给 Director Server。此时报文的源 IP 为 RIP,目标IP 为 CIP。之后 RS 通过默认网关 192.168.1.254 将数据包发回 DS 192.168.1.254;
  6. DS 收到 RS 返回的数据包后,对该数据包的源地址和端口做转换,更换成对应 VIP 和端口;
  7. DS 通过其与外部网络相连的网卡 10.0.0.2 将数据包发出去;
  8. 外部网络 CIP 10.0.0.1 收到数据包;
  9. 连接结束,DS 删除位于 ESTABLISHED 哈希表中的相关数据。

在使用 NAT 模式时可以看到会有以下这些隐式要求:

  1. DS 提供至少两个 IP,一个连接外部网络(VIP),一个连接局域网(DIP)。VIP 需要是主机上的 IP,这样才能进入 INPUT 链,从而进行地址替换。

  2. 处于局域网内的 RS 必须以 DS 为默认网关。

kubernetes 模拟

对于 Kubernetes 来说,由于上述的隐性要求。如果想要使用 IPVS 实现地址映射,需要额外的操作:

  • 需要创建一个 dummy 网卡,将 ClusterIP 注册到这个网卡上。这样收到的数据包才能进入 INPUT 链,从而继续接下去的操作。
  • 在 POSTROUTING 阶段,需要有 SNAT 的能力。这样就可以解除 RS 必须以 DS 为默认网关的限制。

因此,kube-proxy 在使用 IPVS 实现的时候,还会使用 iptables 等能力,同时为了控制 iptables 规则的数量,还使用了 ipset。下面模仿 kube-proxy 获取到 Service 和 Endpoint 的信息后,使用 IPVS 模式在每个节点上创建负载均衡的例子。

# 创建 server
$ ipvsadm -A -t 10.32.0.10:53 -s rr

# 管理 Real Service
$ ipvsadm -a -t 10.32.0.10:53 -r 10.222.69.193:53 -m
$ ipvsadm -a -t 10.32.0.10:53 -r 10.222.69.194:53 -m

# 添加 SNAT
$ iptables -t nat -A POSTROUTING -m ipvs --vaddr 10.32.0.10 --vport 53 -j MASQUERADE

# 绑定VIP到一张 dummy 网卡
$ ip link add kube-ipvs0 type dummy
$ ip addr add 10.32.0.10/32 dev kube-ipvs0

# 开启 contrack
$ echo 1 >  /proc/sys/net/ipv4/vs/conntrack

集群节点访问 ClusterIP

接下去我们以 kube-proxy 开启了 IPVS 模式为前提,梳理集群主机节点访问 DNS service 的过程。

$ kubectl -n kube-system get svc
NAME       TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)                  AGE
kube-dns   ClusterIP   10.32.0.10   <none>        53/UDP,53/TCP,9153/TCP   98d

$ kubectl -n kube-system get pod  -o wide | grep coredns
coredns-66f779496c-bjn9z                          1/1     Running   0              98d   10.222.69.194    izbp1bndrgrf54r0gjzf1bz   <none>           <none>
coredns-66f779496c-tc5vb                          1/1     Running   0              98d   10.222.69.193    izbp1bndrgrf54r0gjzf1bz   <none>           <none>

# 查看 IPVS 的配置
$ ipvsadm -Ln
IP Virtual Server version 1.2.1 (size=4096)
Prot LocalAddress:Port Scheduler Flags
  -> RemoteAddress:Port           Forward Weight ActiveConn InActConn
TCP  10.32.0.10:53 rr
  -> 10.222.69.193:53             Masq    1      0          0
  -> 10.222.69.194:53             Masq    1      0          0
TCP  10.32.0.10:9153 rr
  -> 10.222.69.193:9153           Masq    1      0          0
  -> 10.222.69.194:9153           Masq    1      0          0
UDP  10.32.0.10:53 rr
  -> 10.222.69.193:53             Masq    1      0          0
  -> 10.222.69.194:53             Masq    1      0          0

# 查看是否创建了虚拟网卡,并配置了 IP 地址
$ ip addr
***
7: kube-ipvs0: <BROADCAST,NOARP> mtu 1500 qdisc noop state DOWN group default
    link/ether 86:44:bc:c7:71:d3 brd ff:ff:ff:ff:ff:ff
    inet 10.32.0.10/32 scope global kube-ipvs0
       valid_lft forever preferred_lft forever
  • 对于 IPVS 模式来说,kube-proxy 也会在 OUTPUT 链上注册若干规则。可以看到 OUTPUT 链上的规则,会将所有流量都转发到 KUBE-SERVICES 自定义链进行处理。

    Chain OUTPUT (policy ACCEPT 33 packets, 1980 bytes)
     pkts bytes target     prot opt in     out     source               destination
      21M 1292M KUBE-SERVICES  all  --  *      *       0.0.0.0/0            0.0.0.0/0            /* kubernetes service portals */
    
  • 查看 KUBE-SERVICES 自定义链上的情况。

    • 第 2 条的意思是,如果 source 不是 10.244.0.0/16 网段,且目的地址位于 KUBE-CLUSTER-IP 中的话,则执行 KUBE-MARK-MASQ 中的规则,也就是给数据包打上 0x4000 的标签。
    • 第 3 条的意思是 ADDRTYPE match dst-type LOCAL 的意思是,如果目的地址是本机地址(包括环回地址和在本机网络接口上配置的任何IP地址)的数据包,则执行 KUBE-NODE-PORT 自定义链中的情况。
    • 第 4 条的意思是,如果目的地址位于 KUBE-CLUSTER-IP 中的话则接受该数据包。
    Chain KUBE-SERVICES (2 references)
     pkts bytes target     prot opt in     out     source               destination
        8   480 RETURN     all  --  *      *       127.0.0.0/8          0.0.0.0/0
        0     0 KUBE-MARK-MASQ  all  --  *      *      !10.244.0.0/16        0.0.0.0/0 match-set KUBE-CLUSTER-IP dst,dst
       14   840 KUBE-NODE-PORT  all  --  *      *       0.0.0.0/0            0.0.0.0/0            ADDRTYPE match dst-type LOCAL
        0     0 ACCEPT     all  --  *      *       0.0.0.0/0            0.0.0.0/0            match-set KUBE-CLUSTER-IP dst,dst
    
    Chain KUBE-MARK-MASQ (13 references)
     pkts bytes target     prot opt in     out     source               destination
        0     0 MARK       all  --  *      *       0.0.0.0/0            0.0.0.0/0            MARK or 0x4000
    

    KUBE-CLUSTER-IP 中的地址如图所示,

    $ ipset list KUBE-CLUSTER-IP
    Name: KUBE-CLUSTER-IP
    Type: hash:ip,port
    Revision: 5
    Header: family inet hashsize 1024 maxelem 65536
    Size in memory: 376
    References: 3
    Number of entries: 4
    Members:
    10.32.0.10,tcp:9153
    10.32.0.1,tcp:443
    10.32.0.10,udp:53
    10.32.0.10,tcp:53
    
  • 之后数据包进入 ipvs 进行 DNAT。

  • 接着数据包来到 POSTROUTING 链,继续处理。可以看到 OUTPUT 链上的规则,会将所有流量都转发到 KUBE-POSTROUTING 自定义链进行处理。

    Chain POSTROUTING (policy ACCEPT 2 packets, 120 bytes)
     pkts bytes target     prot opt in     out     source               destination
      21M 1293M KUBE-POSTROUTING  all  --  *      *       0.0.0.0/0            0.0.0.0/0            /* kubernetes postrouting rules */
    
  • KUBE-POSTROUTING 链中的规则如下,最终的效果是相当于对数据包进行源地址替换。

    • 第 1 条表示如果目的地址和源地址均在 KUBE-LOOP-BACK 中的话,则进行源地址替换。
    • 第 2 条表示如果没有打上标签的话,则返回。
    • 第 3 条表示在打上标签的前提下,去掉这个标签。
    • 第 4 条表示对数据包进行源地址替换。
    Chain KUBE-POSTROUTING (1 references)
     pkts bytes target     prot opt in     out     source               destination
        0     0 MASQUERADE  all  --  *      *       0.0.0.0/0            0.0.0.0/0 match-set KUBE-LOOP-BACK dst,dst,src
        2   120 RETURN     all  --  *      *       0.0.0.0/0            0.0.0.0/0            mark match ! 0x4000/0x4000
        0     0 MARK       all  --  *      *       0.0.0.0/0            0.0.0.0/0            MARK xor 0x4000
        0     0 MASQUERADE  all  --  *      *       0.0.0.0/0            0.0.0.0/0            /* kubernetes service traffic requiring SNAT */
    

    KUBE-LOOP-BACK 中的信息如下所示,

    $ ipset list KUBE-LOOP-BACK
    Name: KUBE-LOOP-BACK
    Type: hash:ip,port,ip
    Revision: 5
    Header: family inet hashsize 1024 maxelem 65536
    Size in memory: 608
    References: 1
    Number of entries: 6
    Members:
    10.222.69.194,tcp:53,10.222.69.194
    10.222.69.194,tcp:9153,10.222.69.194
    10.222.69.193,tcp:53,10.222.69.193
    10.222.69.194,udp:53,10.222.69.194
    10.222.69.193,tcp:9153,10.222.69.193
    10.222.69.193,udp:53,10.222.69.193
    

可以发现,在 IPVS 模式下,kube-proxy 通过 ipset 控制了 iptables 的规则数量(Pod 数量的增加只会增加 ipset list 中的 IP 数量,对于规则数来说是没有增加的)。同时 ipset 支持 hash 查找的方式,可将时间复杂度控制在 O(1)。

集群 Pod 访问 ClusterIP

对于集群中 Pod 访问 DNS service 也是类似的。但是,由于 Pod 有自己的命名空间,访问的数据会先通过路由进入宿主机的命名空间。

  • 在 PREROUTING 阶段就会进行上述的处理。
  • 之后就进入 IPVS 进行 DNAT。
  • IPVS 的时候会直接调用到 POSTROUTING 阶段。
Chain PREROUTING (policy ACCEPT 0 packets, 0 bytes)
 pkts bytes target     prot opt in     out     source               destination
 589K   29M KUBE-SERVICES  all  --  *      *       0.0.0.0/0            0.0.0.0/0            /* kubernetes service portals */

相关链接

虚拟 IP 和服务代理:https://kubernetes.io/zh-cn/docs/reference/networking/virtual-ips/

kubernetes的Kube-proxy的iptables转发规则:https://blog.csdn.net/qq_36183935/article/details/90734847

《一篇搞懂》系列之二——IPVS:https://zhuanlan.zhihu.com/p/627514565

ipvs的hook点位置以及hook点钩子函数剖析:https://blog.csdn.net/qq_43684922/article/details/128692789

Kubernetes IPVS 工作模式原理剖析:https://cloud.tencent.com/developer/article/2369702

卷死我
dawnguo 微信支付

微信支付

dawnguo 支付宝

支付宝

  • 本文作者: dawnguo
  • 本文链接: /archives/241
  • 版权声明: 本博客所有文章除特别声明外,均采用CC BY-NC-SA 3.0 许可协议。转载请注明出处!
# Kubernetes
Kubernetes Calico 实现原理
Kubernetes 声明式 API
  • 文章目录
  • 站点概览
dawnguo

dawnguo

215 日志
24 分类
37 标签
RSS
Creative Commons
© 2018 — 2025 程序锅
0%