暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

K8s系列(一):Kube-proxy代理方案

johopig 2021-09-14
2805
  • 背景知识
    • 跨主机通信解决方案
    • 虚拟化网络
    • Netfilter
  • Kube-proxy代理方案
    • 包转发路径
    • userspace模式
    • iptables模式
    • ipvs模式
    • BPF模式

背景知识

跨主机通信解决方案

主机 A 上的实例(容器、VM 等)如何与主机 B 上的另一个实例通信?有很多解决方案:

  • 直接路由:BGP 等
  • 隧道:VxLAN, IPIP, GRE 等
  • NAT:例如 docker 的桥接网络模式
  • 其它方式

虚拟化网络

LAN

缺点: 广播风暴

  • 假设图中的计算机A需要与B通信,必须在数据帧中指定B的MAC地址,因此A必须先广播ARP
    请求来获取B的MAC地址。
  • 交换机1收到广播帧(ARP)请求后,会将它转发到自己的除接收端口之外的所有端口,交换机2、3亦是如此,那这样就会导致广播风暴的发生,这样消耗了整个网络的整体带宽和CPU运算资源。

VLAN

特点:快啊,传输效率高

原理:将原先的网络架构改造为互通的大二层网络并划分出一个个广播域,即通过普通的交换机或者三层交换机直接路由

解决的问题: 跨主机/容器通信

方式:

  • 静态VLAN------基于端口
  • 动态VLAN
    1. 基于MAC地址
    2. 基于IP地址(基于子网的VLAN,是通过所连计算机的IP地址,来决定端口所属VLAN的)
    3. 基于用户

缺点:

  1. 需要二层网络设备的支持,通用性和灵活性差;

  2. 通常交换机可用的VLAN
    数量在4000个左右,这对容器集群规模造成了限制,不能满足大型的公/私有云场景;

    VLAN tag 总共有 4 个字节,其中有 12 bit
    用来标识不同的二层网络(即 LAN ID
    或叫TCI
    ),故而最多只能支持 []{.math .math-inline .is-loaded},即 4096
    个子网的划分

VXLAN

原理: 始报文经过 VTEP
,被 Linux 内核添加上 VXLAN
头部以及外层的UDP
头部,再发送出去,对端 VTEP
接收到 VXLAN 报文后拆除外层 UDP
头部,并根据 VXLAN 头部的 VNI
把原始报文发送到目的服务器。但这里有一个问题,第一次通信前双方如何知道所有的通信信息(类ARP)?这些信息包括:

  • 哪些 VTEP
    需要加到一个相同的 VNI 组?
    答: 管理员一开始进行分组
  • 发送方如何知道对方的 MAC
    地址?
  • 如何知道目的服务器在哪个节点上(即目的 VTEP 的地址)?答: 实际上,VTEP
    也会有自己的转发表,转发表通过泛洪和学习机制来维护,对于目标 MAC地址在转发表中不存在的未知单播,广播流量,都会被泛洪给除源 VTEP外所有的 VTEP,目标 VTEP 响应数据包后,源 VTEP 会从数据包中学习到MAC
    VNI
    VTEP
    的映射关系,并添加到转发表中,后续当再有数据包转发到这个 MAC地址时,VTEP 会从转发表中直接获取到目标 VTEP地址,从而发送单播数据到目标 VTEP。

数据包传输过程

  1. 绿色的 10.0.0.1 会将 IP 数据包发送给 VTEP;
  2. 服务器 1 的 VTEP 收到 10.0.0.1 发送的数据包后;
    1. 从收到的 IP 数据包中获取目的虚拟机的 MAC 地址;
    2. 在本地的转发表中查找该 MAC 地址所在服务器的 IP 地址,即204.79.197.200;
    3. 将服务器1所在的虚拟网络标识符(VxLAN NetworkIdentifier、VNI)以及原始的 IP 数据包作为负载,构建新的 UDP数据包;
    4. 将新的 UDP 数据包发送到网络中;
  3. 服务器 2 的 VTEP 收到 UDP 数据包后;
    1. 去掉 UDP 数据包中的协议头;
    2. 查看数据包中VNI(根据VNI转发到指定的子网中,这里也就是10.0.0.2);
    3. 将 IP 数据包转发给目标的绿色服务器 10.0.0.2;
  4. 绿色的 10.0.0.2 会收到绿色服务器 10.0.0.1 发送的数据包;

解决的问题:

  1. 相比于VLAN最多支持的4096个子网来说,VXLAN 的报文 Header 预留了24 bit
    来标识不同的二层网络(即 VNI
    ,VXLAN Network Identifier),即 3 个字节,可以支持 []{.math .math-inline .is-loaded} 个子网。

Overlay Network

特点:不改变网络基础设施的前提下将二层报文封装在IP报文上的新的数据格式原理:使用虚拟局域网拓展技术(Virtual Extensible LAN,VxLAN)组建Overlay
网络。在下图可以看到两台物理机能够通过三层的IP网络互相访问。

VxLAN使用虚拟隧道端点(Virtual Tunnel EndPoint、VTEP)设备对服务器发出和收到的数据包进行二次封装和解析。

在数据包传输的过程中,通信双方都不知道底层网络做的转换,它们认为两者可以通过二层的网络相互访问,但其实经过了三层的IP网络中转,通过VTEP
之间建立的隧道实现了联通。除了VxLAN之外,Overlay还有很多的实现方案,大同小异。

解决的问题:

  1. 方便云计算集群内、跨集群、虚拟机和实例的迁移
  2. 前面VLAN提到的缺点

缺点: 封包和拆包带来额外的开销

Netfilter

Netfilter 是 Linux内核内部的包过滤和处理框架,我们平常使用的iptables就是依赖它而做的客户端工具。

一些要点:

  • 主机上的所有数据包都将通过 netfilter 框架
  • 在 netfilter 框架中有 5 个钩子点:PRE_ROUTING
    , INPUT
    ,FORWARD
    , OUTPUT
    , POST_ROUTING
    此外,这 5个钩子还可以与内核的其他网络设施,如内核路由子系统进行协同工作。
  • 命令行工具 iptables
    可用于动态地将规则插入到钩子点中
  • 可以通过组合各种 iptables
    规则来操作数据包(接受/重定向/删除/修改,等等)

默认有四表五链
,在下面的文章中无论是docker还是kube-proxy都会在filter、nat
表中添加新链

Kube-proxy代理方案

K8s 里实现 Service 的组件是 kube-proxy,实现的主要功能就是将访问 VIP的请求转发(及负载均衡)到相应的后端pods。下文提到的iptables规则都由它创建以及管理。kube-proxy 是 K8s 的可选组件,如果不需要 Service功能,可以不启用它

包转发路径

步骤:

  1. 网卡收到一个包(通过 DMA 放到 ring-buffer)。
  2. 包经过 XDP hook 点。
  3. 内核给包分配内存,此时才有了大家熟悉的skb
    (包的内核结构体表示),然后 送到内核协议栈。
  4. 包经过 GRO 处理,对分片包进行重组。
  5. 包进入 tc(traffic control)的 ingresshook。接下来,所有橙色的框都是 Netfilter 处理点
  6. Netfilter:在 PREROUTING
    hook 点处理 raw
    table 里的 iptables规则。
  7. 包经过内核的连接跟踪(conntrack)模块。
  8. Netfilter:在 PREROUTING
    hook 点处理 mangle
    table 的 iptables规则。
  9. Netfilter:在 PREROUTING
    hook 点处理 nat
    table 的 iptables规则。
  10. 进行路由判断(FIB:Forwarding InformationBase,路由条目的内核表示,译者注) 。接下来又是四个 Netfilter处理点。
  11. Netfilter:在 FORWARD
    hook 点处理 mangle
    table 里的 iptables规则。
  12. Netfilter:在 FORWARD
    hook 点处理 filter
    table 里的 iptables规则。
  13. Netfilter:在 POSTROUTING
    hook 点处理 mangle
    table 里的 iptables规则。
  14. Netfilter:在 POSTROUTING
    hook 点处理 nat
    table 里的 iptables规则。
  15. 包到达 TC egress hook点,会进行出方向(egress)的判断,例如判断这个包是到本地设备,还是到主机外。
  16. 对大包进行分片。根据 step 15 判断的结果,这个包接下来可能会:
  17. 发送到一个本机 veth 设备,或者一个本机 service endpoint,
  18. 或者,如果目的 IP 是主机外,就通过网卡发出去。

XDP
是位于网卡驱动程序里的一个快速处理包的HOOK点,位于linux网络栈最底层(在网卡的驱动程序处),无需将包送到复杂的协议栈进行处理(通过将eBFP
字节码规则灌进XDP
中作为过滤规则),所以能够用来处理高速数据流量,相比于使用原始的iptables防火墙来丢包亦或者使用带有连接跟踪模块的iptables防火墙来说,性能提高了数倍。

顺便推荐了解一下eBPF
,eBPF技术和XDP
TC
(traffic control)组合使用可以实现功能更强大的网络功能

userspace模式

在内存中维护一份VIP:Port到Real IP:Port的映射关系,通过反向代理转发请求并返回客户端。该模式下kube-proxy会为每一个Service创建一个socket套接字用于接收和转发Client的请求。

缺点

需要来回在用户空间和内核空间交互通信,因此效率很差。

iptables模式

iptables的基础知识不是本文重点,下面的内容需要老哥提前了解相关基础知识

NodePort Service

首先这是Service的yaml,并且Service的类型为nodePort

Service的类型还有ClusterIP
LoadBalancer
ExternalName
这几种,但在流量拦截和转发方面底层实现非常相似

apiVersion: v1
kind: Service
meadata:
labels:
name: gym
name: gym
space:
ports:
- port: 6080
targetPort: 6080
nodePort: 30005
type: NodePort
selector:
app: gym

复制

client通过节点的端口访问nodePort
时,会进入kube-proxy针对nodePort
流量入口创建的KUBE-NODEPORTS链,但是这次只匹配协议类型和目的端口号,匹配成功后才转到对应的KUBE-SVC链

-A PREROUTING -m -comment --comment "kubernetes service portals" -j KUBE-SERVICES
-A OUTPUT -m -comment --comment "kubernetes service portals" -j KUBE-SERVICES
# 下面两条规则做的事情的实际含义是k8s会让所有k8s集群内部产生的数据包流经nat表的
# 自定义链“KUBE-MARK-MASQ”,然后在这里k8s会对这些数据包打一个标记(0x4000/0x4000),
# 在nat的自定义链“KUBE-POSTROUTING”中根据上述标记匹配所有的k8s集群内部的数据包,
# 匹配的目的是k8s会对这些包做SNAT操作
-A POSTROUTING -m -comment --comment "kubernetes postrouting rules " -j KUBE-POSTROUTING
-A KUBE-MARK-MASQ -j MARK --set-xmark 0x4000/0x4000

# 10.254.0.40/32代表的是K8s分配到ClusterIP所在的网段,如果有该网段内访问的流量
# 说明是来自Cluster内的访问,则直接跳转到SVC链
-A KUBE-SERVICES -d 10.254.0.40/32 -p tcp -m -comment --comment \
"default/tomcat: cluster ip" -m tcp --dport 6080 -j KUBE-SVC-67RLXXX
# 如果上1条没匹配到(说明不是通过ClusterIP访问流量),并且 dst 是
# LOCAL,跳转到 KUBE-NODEPORTS
# 另外,可以看到iptables在处理报文时会优先处理目的IP为cluster IP的报文,匹配失败之后再去使用NodePort方式
-A KUBE-SERVICES -m comment --comment "kubernetes service nodeport; NOTE: this must be the \
last rule in this chain " -m addrtype --dst-type LOCAL -j KUBE-NODEPORTS

-A KUBE-NODEPORTS -p tcp -m -comment --comment "default/gym:"\
-m tcp --dport 30005 -j KUBE-MARK-MASQ
-A KUBE-NODEPORTS -p tcp -m -comment --comment "default/gym:"\
-m -tpc --dport 30005 -j KUBE-SVC-67RLXXX

复制

跳转到KUBE-SVC链后,采用了iptables的random模块,使连接有50%的概率进入KUBE-SEP-ID6YXXX链,50%的概率进入KUBE-SEP-IN2YXXX链。因此,kube-proxy的iptables模式采用随机数实现了服务的负载均衡

-A KUBE-SVC-67RLXXX  -m comment --comment "default/gym:" -m \
statistic --mode random --probability 0.500000000 -j KUBE-SEP-ID6YXXX
-A KUBE-SVC-67RLXXX -m comment --comment "default/gym:" -j \
KUBE-SEP-IN2YXXX

复制

KUBE-SEP-ID6YXXX链的具体作用就是将请求通过DNAT发送到192.168.20.1的6080端口

# 如果 src_ip=192.168.20.1/32,说明是 Service->client 流量,则
# 需要做 SNAT(MASQ 是动态版的 SNAT,能够自动的寻找外网地址并改为当前正确的外网IP地址),
# 替换 src_ip -> svc_ip,这样客户端收到回程包时,
# 看到就是从 svc_ip 回的包,跟它期望的是一致的。
-A KUBE-SEP-ID6YXXX -s 192.168.20.1/32 -m -comment --comment "default/gym:"\
-j KUBE-MARK-MASQ
# 如果没有命中上面一条,说明 src_ip != 192.168.20.1/32,则说明是 client-> Service 流量,
# 需要做 DNAT,将 svc_ip -> pod1_ip
-A KUBE-SEP-ID6YXXX -p tcp -m -comment --comment "default/gym:"\
-m tcp -j DANT --to-destination 192.168.20.1:6080

复制

iptables模式最主要的链是KUBE-SERVICES、KUBE-SVC-*和KUBE-SEP-*

  • KUBE-SERVICES链是访问集群内服务的数据包入口点,它会根据匹配到的目标IP:port将数据包分发到相应的KUBE-SVC-*链或KUBE-NODEPORTS链
  • KUBE-SVC-*链相对于一个负载均衡器,它会将数据平均分发到KUBE-SEP-*链,每个KUBE-SVC-*链后面的KUBE-SEP-*都和Service的后端Pod数量一样。
  • KUBE-SEP-*链通过DNAT将连接目的地址和端口从Service的IP:port替换为后端Pod的IP:port,从而将流量转发到相应的Pod。

缺点

  • iptables模式与userspace模式相比,虽然在稳定性和性能上均有不小的提升,但可扩展性差。随着service数据达到数千个,其控制面和数据面的性能都会急剧下降。原因在于iptables控制面的接口设计中,每添加一条规则,需要遍历和修改所有的规则,其控制面性能是O(n²)。在数据面,规则是用链表组织的,其性能是O(n)。后果就是:
    • 例如5k个service时,iptables规则将达到25k条,后果就是packet latency
      ,每个包都要捋一遍这些规则直到匹配到某条规则
    • iptables是不支持单独更新某条规则的,只能全部读出来,更新集合,再将集合下发到宿主机,规则一多慢的要死
    • iptables 依赖 Netfilter和连接跟踪模块(conntrack),在大流量场景下会出现一些竞争问题(raceconditions);UDP场景尤其明显,会导致丢包、应用的负载升高等问题。常见的就conntrack表被挤爆了。
    • LB调度算法仅支持随机转发

ipvs模式

当客户端访问Service(L4LB节点)时,Service将做双向 NAT,数据流如下图所示:

其实和普通的iptables
模式没有什么区别,主要是底层规则的数据结构变了,这样就提升了很多效率

Full NAT,依赖connTrack
connTrack
工作在三层),

不管是 iptables 还是 ipvs 转发模式,Kubernetes 中访问 Service 都会进行 DNAT,将原本访问 ClusterIP:Port
的数据包 DNAT 成 Service 的某个Endpoint (PodIP:Port)
,然后内核将连接信息插入 conntrack
表以记录连接,目的端回包的时候内核从 conntrack
表匹配连接并反向 NAT即SNAT,这样原路返回形成一个完整的连接链路

IPVS 是专门为LB设计的。它用hash table管理service,对service的增删查找都是O(1)的时间复杂度。不过IPVS内核模块没有SNAT功能,因此借用了iptables的SNAT功能。IPVS针对报文做DNAT后,将连接信息保存在nf_conntrack中,iptables据此接力做SNAT。该模式是目前Kubernetes网络性能最好的选择。但是由于nf_conntrack的复杂性,带来了很大的性能损耗。

BPF模式

例如k8s的CNI插件Cilium
就使用到了eBPF
。通过将eBPF
字节码规则注入到XDP
中,直接就在最底层对数据包进行处理。对比上面Netfilter
的包转发路径,可以看到省去了很多环节。后续会写其他的文章来介绍这些CNI插件

通过XDP
的redirect,如果包的目的端是另一台主机上的 service endpoint,那你可以直接在 XDP 框中完成包的重定向(包转发路径收包1->2
,在步骤 2
中对包 进行修改,再通过 2->1
发送出去),将其发送出去,如下图所示:

Reference

  • https://cloud.tencent.com/developer/article/1644114?from=article.detail.1470033
  • https://www.cnblogs.com/jojoword/p/11214256.html#autoid-2-1-0
  • http://arthurchiao.art/blog/conntrack-design-and-implementation-zh/#15-应用
  • https://zhuanlan.zhihu.com/p/35616289
  • https://cloudnative.to/blog/k8s-node-proxy/
  • https://zhuanlan.zhihu.com/p/130277008
  • https://draveness.me/whys-the-design-overlay-network/
  • http://arthurchiao.art/blog/ebpf-and-k8s-zh/


文章转载自johopig,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论