云原生之网络篇

辰星2年前技术文章1394

前言:在云原生如火如荼的今天,作为云原生的基石:kubernetes(简称k8s)是不得不掌握的技术。而k8s的网络插件是大家绕不开的技术,但是由于k8s的开源包容性,以及网络的复杂性,导致网络插件出现百花齐放百家争鸣的情况,这种情况导致我们的学习成本大大增加。在这种情况下,我建议大家学习最为主流的网络插件,今天我们讲解的主角就是最为主流的网络插件之一:calico、cilium。希望大家通过今天的分享,能够熟悉k8s网络相关技术知识。为以后处理相关问题打下坚实基础。


1、calico ipip模式


k8s网络插件主要分为:underlay和overlay,calico 主要分为3种模式:BGP属于underlay、IPIP和VXLAN属于overlay,由于环境受限,我们只能使用ipip或者VXLAN,因为ipip模式比VXLAN更高效,所以我们主要分析ipip模式,ipip模式主要原理就是在pod ip的基础上再封装一层node ip,这样在通过对应的路由规则,就可以转发到对应的目的地。


环境:


  • node:20u5, ip:10.0.0.125,pod ip:100.118.198.197

  • node:20u8, ip:10.0.0.128,pod ip:100.93.79.4


我们在20u5节点上面pod ip:100.118.198.197,然后去ping 20u8节点pod ip:100.93.79.4


$ ping -c 1 100.93.79.4


a、首先我们在100.118.198.197 pod 上面进行网络抓包,可以看到图1以下包:


image.png图1


通过第一个包的ttl:64,我们可以看出来这个就是在 100.118.198.197 pod内发出的网络包,通过第二个返回的网络包的ttl:62,我们可以判断此网络包经过了2跳路由,哪到底是哪两跳呢?


b、通过图2路由我们知道,数据包是通过tunl0 发送给了 10.0.0.128


image.png

图2

c、我们在20u5 节点上面 tunl0网卡进行网络抓包,可以看到以下包:


image.png图3


通过此包的第一个包和第二个包的ttl都是63,我们可以看出来:tunl0网卡正是容器 100.118.198.197的下一跳路由。


d、通过在20u8节点上面 tunl0网卡进行网络抓包,可以看到以下包:


image.png图4


这里我们可以看到ttl并没有改变,网络包是怎么从20u5 节点tunl0网卡到达20u8节点tunl0网卡的?


e、最终我们在 100.93.79.4 pod 上面进行网络抓包,可以看到以下包:


image.png图5


可以看到第一个包的ttl为62,说明网络包经过2跳最终到达的此pod,而第二个包ttl为64,也说明此pod确实是为 100.93.79.4。


f、我们通过抓包看到了calico IPIP模式的大致流量走向,但是我们在d步骤发现数据包从20u5节点发送到20u8节点,网络包是怎么从20u5节点到达20u8节点的?


image.png图6


$ ip a | grep -C 1 '52:54:00:e9:91:b5'
2: enp1s0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
    link/ether 52:54:00:e9:91:b5 brd ff:ff:ff:ff:ff:ff
    inet 10.0.0.125/24 brd 10.0.0.255 scope global enp1s0


我们通过在20u5 节点上面执行 tcpdump -i any -w 20u5-host-all.pcap 命令抓取所有网卡的网络包,得到图6的数据包,我们发现在节点上面竟然有3次ICMP协议的ping请求,实际上我们只发送了一次ping请求。而我们分析数据包:通过TTL可以看出第一个包是从容器内发出,第二个包就是我们在tunl0网卡抓取到的,第三个包我们查看mac地址发现竟然是20u5节点的enp1s0网卡。从上面发现网络包竟然存在2个IP包头,一个是pod之间的,另一个是宿主机节点之间的。此网络包的大小也比之前的包大20个字节。由此可见中间转发的过程中,calico最终会在转发出node之前打上node节点的ip以及对应node节点的ip,整条链路为:pod网卡 ==> tunl0网卡 ==> 20u5 enp1s0宿主机网卡 ==> 20u8 enp1s0宿主机网卡 ==> tunl0网卡 ==> pod网卡,流转图见图7


image.png图7


2、优化ipip模式


我们可以看到calico ipip模式默认的配置需要在每个node节点内转发2次才能达到最终pod内,这样的效率有点太低了。那么我们可以怎么优化一下呢?ipip模式下可以选择配置在跨子网的时候才需要走tunl0网卡。


a、我们把ipipMode配置修改为CrossSubnet


$ kubectl get ippool -n kube-system -o yaml 
apiVersion: v1
items:
- apiVersion: crd.projectcalico.org/v1
  kind: IPPool
  metadata:
    name: default-ipv4-ippool
  spec:
    blockSize: 26
    cidr: 100.64.0.0/10
    ipipMode: CrossSubnet
    natOutgoing: true
    nodeSelector: all()
    vxlanMode: Never


b、我们在20u5 node节点上面抓取网络包,可以看到如图8:


image.png图8


首先我们看到只转发了1次,比之前的网络包少转发一次,其次我们在图6已经看到过一次这个mac地址了,这个是20u5节点的enp1s0网卡的mac地址,这就说明在同子网的情况下没有经过tunl0网卡。那这个是怎么做到的呢?


c、我们通过图9可以看到:之前的路由配置已经改变了,直接路由转发到对应的node节点去了


image.png图9


由于是处于同一子网下面的,所以通过node节点的mac地址就可以通信了,calico只需要配置对应的路由规则就可以了。如图10所示:


image.png图10


3、Cilium eBPF


Cilium是一个开源软件,用于透明地提供和保护使用Kubernetes,Docker和Mesos等Linux容器管理平台部署的应用程序服务之间的网络和API连接。Cilium基于一种名为BPF的新Linux内核技术,它可以在Linux内部动态插入强大的安全性,可见性和网络控制逻辑。 除了提供传统的网络级安全性之外,BPF的灵活性还可以在API和进程级别上实现安全性,以保护容器或容器内的通信。由于BPF在Linux内核中运行,因此可以应用和更新Cilium安全策略,而无需对应用程序代码或容器配置进行任何更改。


image.png 图11


a、cilium的流程


如上图11所示,几个步骤:


  1. Cilium agent 生成 eBPF 程序

  1. 用 LLVM 编译 eBPF 程序,生成 eBPF 对象文件(object file,*.o)。

  1. 用 eBPF loader 将对象文件加载到 Linux 内核

  1. 校验器(verifier)对 eBPF 指令会进行合法性验证,以确保程序是安全的,例如 ,无非法内存访问、不会 crash 内核、不会有无限循环等。

  1. 对象文件被即时编译(JIT)为能直接在底层平台(例如 x86)运行的 native code。

  1. 如果要在内核和用户态之间共享状态,BPF 程序可以使用 BPF map,这种一种共享存储 ,BPF 侧和用户侧都可以访问。

  1. BPF 程序就绪,等待事件触发其执行。对于这个例子,就是有数据包到达网络设备时,触发 BPF 程序的执行。

  1. BPF 程序对收到的包进行处理,例如 mangle。最后返回一个裁决(verdict)结果。

  1. 根据裁决结果,如果是 DROP,这个包将被丢弃;如果是 PASS,包会被送到更网络栈的 更上层继续处理;如果是重定向,就发送给其他设备。


b、eBPF的特点


  1. 最重要的一点:不能 crash 内核。

  1. 执行起来,与内核模块(kernel module)一样快。

  2. 提供稳定的 API。

    image.png


这意味着什么?简单来说,如果一段 BPF 程序能在老内核上执行,那它一定也能继续在新 内核上执行,而无需做任何修改。


这就像是内核空间与用户空间的契约,内核保证对用户空间应用的兼容性,类似地,内核也 会保证 eBPF 程序的兼容性。


4、kube-proxy 包转发路径


从网络角度看,使用传统的 kube-proxy 处理 Kubernetes Service 时,包在内核中的 转发路径是怎样的?如下图12所示:


image.png图12


步骤:


  1. 网卡收到一个包(通过 DMA 放到 ring-buffer)。

  1. 包经过 XDP hook 点。

  1. 内核给包分配内存,此时才有了大家熟悉的 skb(包的内核结构体表示),然后 送到内核协议栈。

  1. 包经过 GRO 处理,对分片包进行重组。

  1. 包进入 tc(traffic control)的 ingress hook。接下来,所有橙色的框都是 Netfilter 处理点

  1. Netfilter:在 PREROUTING hook 点处理 raw table 里的 iptables 规则。

  1. 包经过内核的连接跟踪(conntrack)模块。

  1. Netfilter:在 PREROUTING hook 点处理 mangle table 的 iptables 规则。

  1. Netfilter:在 PREROUTING hook 点处理 nat table 的 iptables 规则。

  1. 进行路由判断(FIB:Forwarding Information Base,路由条目的内核表示,译者注) 。接下来又是四个 Netfilter 处理点。

  1. Netfilter:在 FORWARD hook 点处理 mangle table 里的 iptables 规则。

  1. Netfilter:在 FORWARD hook 点处理 filter table 里的 iptables 规则。

  1. Netfilter:在 POSTROUTING hook 点处理 mangle table 里的 iptables 规则。

  1. Netfilter:在 POSTROUTING hook 点处理 nat table 里的 iptables 规则。

  1. 包到达 TC egress hook 点,会进行出方向(egress)的判断,例如判断这个包是到本 地设备,还是到主机外。

  1. 对大包进行分片。根据 step 15 判断的结果,这个包接下来可能会:

  1. 发送到一个本机 veth 设备,或者一个本机 service endpoint,

  1. 或者,如果目的 IP 是主机外,就通过网卡发出去。


5、Cilium eBPF 包转发路径


作为对比,再来看下( 图13) Cilium eBPF 中的包转发路径:


image.png图13


对比可以看出,Cilium eBPF datapath 做了短路处理:从 tc ingress 直接 shortcut 到 tc egress,节省了 9 个中间步骤(总共 17 个)。更重要的是:这个 datapath 绕过了 整个 Netfilter 框架(橘黄色的框们),Netfilter 在大流量情况下性能是很差的。


去掉那些不用的框之后,Cilium eBPF datapath (图14)长这样:


image.png图14


Cilium/eBPF 还能走的更远。例如,如果包的目的端是另一台主机上的 service endpoint,那你可以直接在 XDP 框中完成包的重定向(收包 1->2,在步骤 2 中对包 进行修改,再通过 2->1 发送出去),将其发送出去,如下图15所示:


image.png图15


可以看到,这种情况下包都没有进入内核协议栈(准确地说,都没有创建 skb)就被转 发出去了,性能可想而知。


XDP 是 eXpress DataPath 的缩写,支持在网卡驱动中运行 eBPF 代码,而无需将包送 到复杂的协议栈进行处理,因此处理代价很小,速度极快。


6、Cilium 的 Service load balancing 设计


image.png图16


如上图16所示,主要涉及两部分:


  1. 在 socket 层运行的 BPF 程序

  1. 在 XDP 和 tc 层运行的 BPF 程序


a、东西向流量


我们先来看 socker 层。


image.png图17


如上图17所示,


Socket 层的 BPF 程序主要处理 Cilium 节点的东西向流量(E-W)。


  • 将 Service 的 IP:Port 映射到具体的 backend pods,并做负载均衡。

  • 当应用发起 connect、sendmsg、recvmsg 等请求(系统调用)时,拦截这些请求, 并根据请求的 IP:Port 映射到后端 pod,直接发送过去。反向进行相反的变换。


这里实现的好处:性能更高。


  • 不需要包级别(packet leve)的地址转换(NAT)。在系统调用时,还没有创建包,因此性能更高。

  • 省去了 kube-proxy 路径中的很多中间节点(intermediate node hops)


可以看出,应用对这种拦截和重定向是无感知的(符合 k8s Service 的设计)。


b、南北向流量


再来看从 k8s 集群外进入节点,或者从节点出 k8s 集群的流量(external traffic), 即南北向流量(N-S)


区分集群外流量的一个原因是:Pod IP 很多情况下都是不可路由的(与跨主机选用的网 络方案有关),只在集群内有效,即,集群外访问 Pod IP 是不通的。

因此,如果 Pod 流量直接从 node 出宿主机,必须确保它能正常回来。而 node IP 一般都是全局可达的,集群外也可以访问,所以常见的解决方案就是:在 Pod 通过 node 出集群时,对其进行 SNAT,将源 IP 地址换成 node IP 地址;应答包回来时,再进行相 反的 DNAT,这样包就能回到 Pod 了。


image.png


如上图所示,集群外来的流量到达 node 时,由 XDP 和 tc 层的 BPF 程序进行处理, 它们做的事情与 socket 层的差不多,将 Service 的 IP:Port 映射到后端的 PodIP:Port,如果 backend pod 不在本 node,就通过网络再发出去。发出去的流程我们 在前面 Cilium eBPF 包转发路径 讲过了。


这里 BPF 做的事情:执行 DNAT。这个功能可以在 XDP 层做,也可以在 TC 层做,但 在 XDP 层代价更小,性能也更高。


总结起来,这里的核心理念就是:


  1. 东西向流量放在离 socket 层尽量近的地方做。

  1. 南北向流量放在离驱动(XDP 和 tc)层尽量近的地方做。


7、XDP/eBPF vs kube-proxy 性能对比


网络吞吐


测试环境:两台物理节点,一个发包,一个收包,收到的包做 Service loadbalancing 转发给后端 Pods。


image.png


可以看出:


  1. Cilium XDP eBPF 模式能处理接收到的全部 10Mpps(packets per second)。

  1. Cilium tc eBPF 模式能处理 3.5Mpps。

  1. kube-proxy iptables 只能处理 2.3Mpps,因为它的 hook 点在收发包路径上更后面的位置。

  2. kube-proxy ipvs 模式这里表现更差,它相比 iptables 的优势要在 backend 数量很多的时候才能体现出来
    我们生成了 1Mpps、2Mpps 和 4Mpps 流量,空闲 CPU 占比(可以被应用使用的 CPU)结果如下:

    image.png

CPU 利用率


结论与上面吞吐类似。


  • XDP 性能最好,是因为 XDP BPF 在驱动层执行,不需要将包 push 到内核协议栈

  • kube-proxy 不管是 iptables 还是 ipvs 模式,都在处理软中断(softirq)上消耗了大量 CPU


总结:根据以上内容,我们了解了calico ipip模式以及cilium eBPF模式,根据我们从calico官方文档得到的信息:calico也支持了eBPF,而cilium也在支持BGP。由此可见,云原生未来的方向是绕不开这些技术的。eBPF 可以称为最近这些年以来Linux内核最重要的模块之一,其所带来的深远影响,希望大家能尽快熟悉此模块。由于eBPF对Linux内核版本有着比较大的要求,在4.8以上版本才支持的比较好,大家都知道CentOS 7支持时间已经快要结束了。接下来不管是Redhat系还是Debian系的,内核版本都不会低于4.18。接下来就是eBPF大行其道的时间了。


引用1:https://docs.cilium.io/en/stable/


引用2:https://projectcalico.docs.tigera.io/about/about-calico


引用3:https://static.sched.com/hosted_files/kccnceu20/8f/Aug19_eBPF_and_Kubernetes_Little_Helper_Minions_for_Scaling_Microservices_Daniel_Borkmann.pdf


相关文章

Zabbix监控接入

Zabbix监控1、环境实验机器:118.31.158.83(zabbix server)172.17.6.11(zabbix proxy)172.17.6.11(zabbix agent)2、安装z...

Trino对接haproxy开启ssl

Trino对接haproxy开启ssl

1、背景客户在开启https后,在高并发的情况下,集群性能下降严重。初步估计是由于worker和Coordinator交互都需要进行加密和解密,会提高cpu使用率,导致服务变慢。现在采用haproxy...

触发kube-apiserver流控导致ACK集群中节点状态异常

触发kube-apiserver流控导致ACK集群中节点状态异常

问题现象ack集群中一个节点状态为notready,但是排查节点上的核心组件:kubelet、kube-proxy、flannel、etcd等状态均为正常。排查步骤1、查日志分别排查核心组件以及系统日...

大数据组件--Hive与Impala的异同

大数据组件--Hive与Impala的异同

一、同数据存储:使用相同的存储数据池都支持把数据存储于HDFS, HBase。元数据:两者使用相同的元数据。SQL语法:基本类似。二、异1)、底层运行使用的技术hive底层默认使用mapreduce引...

hadoop纠删码

hadoop纠删码

纠删码是CDH6/Hadop3引入的新功能,之前的HDFS都是副本方式容错,默认情况下,一个文件有三个副本,可以容忍任意2个副本(Datanode)不可用,是以牺牲空间的代价提供了数据的可用性,带来了...

Mac安装Hadoop文档-保姆级操作(一)

Mac安装Hadoop文档-保姆级操作(一)

首先配置ssh环境在Mac下如果想使用Hadoop,必须要配置ssh环境, 如果不执行这一步,后面启动hadoop时会出现Connection refused连接被拒绝的错误。首先终端命令框输入:ss...

发表评论    

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。