关于后端:追踪-Kubernetes-中的数据包

38次阅读

共计 3295 个字符,预计需要花费 9 分钟才能阅读完成。

网络和操作系统内核,对我来说是既生疏又满是吸引,心愿可能拨开层层迷雾找到背地的假相。

在 上一篇文章 中我深入探讨了 Kubernetes 网络模型,这次我想更深刻一点:理解数据包在 Kubernetes 中的传输,为学习 Kubernetes 的 eBPF 网络减速做筹备,加深对网络和操作系统内核的了解。文中可能有疏漏之处,还望大家赐教。

在开始之前,我能够用一句话来总结我的学习成绩:数据包的流转其实就是一个网络套接字描述符(Socket File Descriptor,中文有点简短,以下简称 socket fd)的寻址过程。它不是简略的指 socket fd 的内存地址,还包含它的网络地址。

在 Unix 和类 Unix 零碎中,所有皆文件,也能够通过文件描述符来操作 socket。

基础知识

数据包

既然要探讨数据包的流转,先看看什么是数据包。

网络数据包(network packet),也称为网络数据报(network datagram)或网络帧(Network frame),是通过计算机网络传输的数据单位。拿最常见的 TCP 数据包来看蕴含如下几个局部:

  • Ethernet header:链路层信息,次要包含目标 MAC 地址和源 MAC 地址,以及报文的格局,这里是 IP 包。
  • IP header:网络层信息,次要包含长度、源 IP 地址和目标 IP 地址以及报文的格局,当然这里必须是 TCP 包。
  • TCP header:传输层信息,包含源端口和目标端口。
  • 数据:个别是第 7 层的数据,比方 HTTP 等。

这里没有介绍的 checksum 和 FCS 通常是用来查看数据包在传输过程中是否被篡改或者产生了谬误。

应用程序应用 socket 向网络发送数据的过程能够简略了解为应用头信息封装数据的过程:TCP 数据包、IP 数据包、Ethernet 数据包;反过来,从网络接管以太网数据包到应用程序能够解决的数据,就是解包的过程。封包和解包的过程是由内核网络协议栈来实现的。

上面别离说一下 socket 和内核网络协议栈的解决。

socket 套接字

Socket 是一种在计算机网络中应用的编程接口,位于用户空间(用户利用程序运行的空间)和内核网络协议栈(内核中对数据进行封包和解包的组件)之间。

作为编程接口,socket 提供了如下操作(只列出局部):

  • socket
  • connect
  • bind
  • listen
  • accept
  • 数据传输

    • send
    • sendto
    • sendmsg
    • recv
    • recvfrom
    • recvmsg
  • getsockname
  • getpeername
  • getsockoptsetsockopt 获取或设置 socket 层或协定层选项
  • close

通过上面的图,能够直观感触各个操作的作用:

开始解说内核网络协议栈之前,先说下数据包在内存中的数据结构:sk_buff。

sk_buff

sk_buff 是 Linux 内核中用于管理网络数据包的数据结构,它蕴含了接管和发送的网络数据包的各种信息和属性,如数据包的协定、数据长度、源和指标地址等。sk_buff 是一种能够在网络层和数据链路层之间传递的数据结构,能够被用于所有类型的网络协议栈,例如 TCP/IP、UDP、ICMP 等。

sk_buff 在 Linux 内核中广泛应用于网络协议栈的各个层级,如数据链路层、网络层、传输层等。sk_buff 数据结构的字段很多,有 4 个重要的字段且都是指针类型。sk_buff 在不同层的应用,就是通过批改这些指针来实现的:加 header(封包)和移除 header(解包)。

这个过程操作做的是指针,数据是零拷贝的,能够极大地晋升效率。

内核网络协议栈

封包

应用程序应用 socket 的 sendmsg 操作发送数据(这里不深刻解说 netfilter、traffic control、queue discipline):

  1. 先调配 sk_buff
  2. 接下来开始网络协议栈的解决
  3. 设置传输层信息(这里是 TCP 头中的源和目标端口)
  4. 依据指标 IP 查找路由
  5. 设置网络层信息(源和目标 IP 地址等)
  6. 调用 netfilter(LOCAL_OUT
  7. 设置接口(interface)和协定(protocol)
  8. 调用 netfilter(POST_ROUTING
  9. 如果包过长,分段传输
  10. L2 寻址,即查找能够领有指标 IP 地址的设施的 MAC 地址
  11. 设置链路层信息,
  12. 至此内核网络协议栈的操作实现
  13. 调用 tc(traffic control)egress(能够对包进行重定向)
  14. 进入队列 queue discipline(qdisc)
  15. 写入 NIC(network interface controler)
  16. 发送到网络

解包

NIC 收到网络发来的数据包(这里不深刻解说 direct memory access、netfilter、traffic control):

  1. 将数据包写如 DMA 中(Direct Memory Access 间接内存拜访,不须要依赖 CPU,由 NIC 间接写入到内存中)
  2. 调配 sk_buff,并填充元数据,比方 protocol 为 Ethernet 类型,接管数据包的网络接口等
  3. 将链路层信息保留在 sk_buff 的 mac_header 字段中,并“移除”数据包中的链路层信息(挪动指针)
  4. 接下来开始网络协议栈的解决
  5. 将网络层信息保留在 network_header 字段中
  6. 调用 tc ingress
  7. “移除”网络层信息
  8. 将传输层信息保留在 transport_header 字段中
  9. 调用 netfilter(PRE_ROUTING
  10. 查找路由
  11. 合并多个分包
  12. 调用 netfilter(LOCAL_IN
  13. “移除”传输层信息
  14. 查找监听指标端口的 socket,或者发送 reset
  15. 将数据写入 socket 的接管队列中
  16. 发信号告诉有数据写入队列
  17. 至此内核网络协议栈的操作实现
  18. sk_buff 从 socket 接管队列中出队
  19. 将数据写入应用程序的缓冲区
  20. 开释 sk_buff

Kubernetes 的网络模型

另一部分的基础知识就是 Kubernetes 的网络模型了,能够参考之前的那篇 深刻摸索 Kubernetes 网络模型和网络通信。

Kubernetes 中的数据包流转

这里持续探讨之前文章中的三种通信场景,pod 间的通信应用 pod IP 地址。如果要探讨通过 Service 来拜访,则要退出 netfilter 的探讨篇幅会减少不少。

同 pod 内的容器间通信

pod 内两个容器间的形式通常应用回环地址 127.0.0.1,在封包的 #4 路由过程中确定了应用回环网卡 lo 进行传输。

同节点上的 pod 间通信

curl 收回的申请在封包 #4 过程中确定应用 eth0 接口。而后通过与 eth0 相连的隧道 veth1 达到节点的根网络空间。

veth1 通过网桥 cni0 与其余 pod 相连虚构以太接口 vethX 相连。在封包 #10 L2 寻址中,ARP 申请通过网桥发送给所有相连的接口是否领有原始申请中的目标 IP 地址(这里是 10.42.1.9

拿到了 veth0 的 MAC 地址后,在封包 #11 中设置数据包的链路层信息。数据包收回后,通过 veth0 隧道进入 pod httpbineth0 接口中,而后开始解包的过程。

解包的过程没啥特地,确定了 httpbin 应用的 socket。

不同节点的 pod 间通信

这里略微不同,就是在通过 cni0 发送 ARP 申请没有收到应答,应用根命名空间也就是主机的路由表,确定了指标主机 IP 地址后,而后通过主机的 eth0 放 ARP 申请并收到指标主机的响应。将其 MAC 地址在封包 #11 中写入。

数据包发送到指标主机后,开始解包的过程,最终进入指标 pod。

在集群层面有一张路由表,外面存储着每个节点的 Pod IP 网段(节点退出到集群时会调配一个 Pod 网段(Pod CIDR),比方在 k3s 中默认的 Pod CIDR 是 10.42.0.0/16,节点获取到的网段是 10.42.0.0/2410.42.1.0/2410.42.2.0/24,顺次类推)。通过节点的 Pod IP 网段能够判断出申请 IP 的节点,而后申请被发送到该节点。

总结

统计一下在三个场景中,通过内核网络协议栈的解决次数都是两次(包含 netfilter 的解决。),即便是同 pod 或者同节点内。而这两种状况理论都产生在同一个内核空间中。

如果同一个内核空间中的两个 socket 能够间接传输数据,是不是就能够省掉内核网络协议栈解决带来的提早?

下篇持续。

正文完
 0