前言
在介绍容器网络知识前, 可以先看一下 man
关于 veth
ip-link
ip-netns
三个命令的解释
- https://man7.org/linux/man-pages/man4/veth.4.html
- https://man7.org/linux/man-pages/man8/ip-link.8.html
- https://man7.org/linux/man-pages/man8/ip-netns.8.html
这几天看到飞哥的文章受益匪浅 ,从源码层级讲明白了虚拟化网络的相关知识 ,几篇前置文章需要了解 :
先来复习一下 linux网络发送过程
,大致的过程如下 :
发送完后释放内存
上面这三张图就概括了数据发送的主要流程 ,其中有一个 skb
的东西, 这个东西的解释是
网络子系统中用来存储数据的缓冲区叫做套接字缓存,简称SKB,可处理变长数据,尽量避免数据的复制。
具体的信息看这篇文章
可以概括地讲数据发送的流程是 : 用户态 --> 内核态 --> 驱动 --> 网卡发送
然后我们再来看一下数据接收的过程
其实接收的过程
和发送的过程
很像, 其中设计的数据拷贝呀 ,服务端接收请求都是网络性能优化的点 ,例如zero copy , epoll 等
下面这样就是总体的流程(重要)
然后我们在看一下第三篇文章 , lo(回环网络)发送和接收的过程
, 我之前写过用 wiresharp 转包, 抓localhost(就是回环地址 127.0.0.1)是抓不到的 ,因为 wiresharp 只抓网卡上的数据包 ,而 localhost网络并没有走网卡导致抓包并没成功 . lo(回环网络)发送和接收的过程
主要就是从源码分析了lo网络发送和接收数据的流程 ,
这个就是lo网络的流量对比 ,我们需要认真对比这张图和上面那种图, 可以看到lo网络并没有经过网卡硬件这一步(废话!本地网络经过个屁啊), 并且软中断唤醒进程进行出来, 少了硬中断 ,但是即使是这样, 性能也不容小觑 ,因为最终还是调用了内核函数, 调用了底层驱动 .
记住这张图, 这会和下面讲的 veth
联系在一起.
最后飞哥的文章这里引出了一个优化 :
总的来说,本机网络 IO 和跨机 IO 比较起来,确实是节约了一些开销。发送数据不需要进 RingBuffer 的驱动队列,直接把 skb 传给接收协议栈(经过软中断)。但是在内核其它组件上,可是一点都没少,系统调用、协议栈(传输层、网络层等)、网络设备子系统、邻居子系统整个走了一个遍。连“驱动”程序都走了(虽然对于回环设备来说只是一个纯软件的虚拟出来的东东)。所以即使是本机网络 IO,也别误以为没啥开销。
最后再提一下,业界有公司基于 ebpf 来加速 istio 架构中 sidecar 代理和本地进程之间的通信。通过引入 BPF,才算是绕开了内核协议栈的开销,原理如下。
(鸡贼 , 不让它走协议栈而是通过队列直接给对方 ,绕开内核协议栈)
Networking Namespace
我们知道容器镜像底层原理是由 cgroup 和 Namespace 两种技术支持的, 其中 Networking Namespace (网络命名空间) 也是 Namespace 的范畴 , Namespace 的知识可以看这一篇.
可以看到进程对应的 Namespace 表有一个 Network_Namespace , Network_Namespace 底下挂着属于自己的设备 , 也就是进程私有的 .
Linux 的网络协议校是十分复杂的,为了支持独立的协议枝,相关的这些全局变量都必须修改为协议枝私有。最好的办法就是让这些全局变量成为一个Net Namespace 变量的成员,然后为协议枝的函数调用加入一个Namespace 参数。这就是Linux 实现网络命名空间的核心。
同时,为了保证对己经开发的应用程序及内核代码的兼容性,内核代码隐式地使用了命名空间内的变量。我们的程序如果没有对命名空间的特殊需求,那么不需要写额外的代码,网络命名空间对应用程序而言是透明的。
所有的网络设备(物理的或虚拟接口、桥等在内核里都叫作Net Device) 都只能属于一个命名空间。当然,通常物理的设备(连接实际硬件的设备)只能关联到`root`这个命名空间中。
虚拟的网络设备(虚拟的以太网接口或者虚拟网口对)则可以被创建并关联到一个给定的命名空间中,而且可以在这些命名空间之间移动。
飞哥这三篇文章非常不错 ,建议大家把源码部分也看了 ,确实有用!!
Veth (Virtual Ethernet Device) 和 Veth设备对
Veth 含义及解释
下面看一下 man
里的解释 (挺不错的认真看)
The veth devices are virtual Ethernet devices.(veth 是虚拟设备!!) They can act as
tunnels between network namespaces to create a bridge to a
physical network device in another namespace, but can also be
used as standalone network devices.
veth devices are always created in interconnected pairs. A pair
can be created using the command:(命令行)
# ip link add <p1-name> type veth peer name <p2-name>
In the above, p1-name and p2-name are the names assigned to the
two connected end points.
Packets transmitted on one device in the pair are immediately
received on the other device. When either devices is down the
link state of the pair is down.
veth device pairs are useful for combining the network facilities
of the kernel together in interesting ways. A particularly
interesting use case is to place one end of a veth pair in one
network namespace and the other end in another network namespace,
thus allowing communication between network namespaces. To do
this, one can provide the netns parameter when creating the
interfaces:(命令行)
# ip link add <p1-name> netns <p1-ns> type veth peer <p2-name> netns <p2-ns>
or, for an existing veth pair, move one side to the other
namespace:
# ip link set <p2-name> netns <p2-ns>
说到网络设备
, 那网络设备
还有哪一些呢 ,
Veth设备对
前面我们提到,由于网络命名空间代表的是一个独立的协议枝,所以它们之间是相互隔离的,彼此无法通信,在协议枝内部都看不到对方。那么有没有办法打破这种限制,让处于不同命名空间的网络相互通信,甚至和外部的网络进行通信呢?答案就是Veth 设备对。
"Veth 设备对 (veth pair)"的一个重要作用就是打通互相看不到的协议技之间的壁垒,它就像一个管子,一端连着这个网络命名空间的协议杖
,一端连着``另一个网络命名空间的协议栈`。所以如果想在两个命名空间之间进行通信,就必须有一个Veth 设备对。
Network_Namespace_1 <======= Veth =======> Network_Namespace_2
veth 数据流转
整理链路的源码分析可以看飞哥的[文章](https://mp.weixin.qq.com/s/sSQFINJ8RO8Nc4XtcyQIjQ)
整体的链路就是上图 ,和 lo网络
的链路可以说很像了, 我们知道 lo网络
的数据链路是没有经过网卡的,只是经过了驱动 ,然后软中断唤醒进程. veth 是为了虚拟化技术而生的,所以它多了个结对的概念 , 相当于两个 lo网络
小实验
创建Veth 设备对:
ip link add vethO type veth peer name veth1
创建后,可以查看veth 设备对的信息。使用ip link show 命令查看所有网络接口:
看到了吧,有两个设备生成了,一个是vethO ,它的peer 是veth1
现在这两个设备都在自己的命名空间内,那怎么能行呢?好了,如果将Veth 看作有两个头的网线,那么我们将另一个头甩给另一个命名空间吧:
ip 1ink set veth1 netns netns1
这时可在外面这个命名空间内看两个设备的情况:
只剩一个vethO 设备了,己经看不到另一个设备了,另一个设备己经转移到另→个网络命名空间了。
在netns1 网络命名空间中可以看到vethl 设备了,符合预期。
现在看到的结果是,两个不同的命名空间各自有二个Veth 的"网线头",各显示为一个Device
(在Docker 的实现里面,它除了将Veth 放入容器内,还将它的名字改成了ethO ,简直以假乱真,你以为它是一个本地网卡吗, 太6了这一波操作 , docker!!)。
现在可以通信了吗?不行,因为它们还没有任何地址,现在我们来给它们分配IP 地址吧:
ip netns exec netns1 ip addr add 10.1.1.1/24 dev veth1
ip addr add 10.1.1.2/24 dev veth0
再启动它们:
ip netns exec netns1 ip 1ink set dev veth1 up
ip 1ink set dev veth0 up
现在两个网络命名空间可以互相通信了:
总结
这篇文章很多内容来自飞哥和书上的, 是个人的学习总结 ,主要是对虚拟化 veth
这个东西有个认识.
veth
可以粗略地认为是我们的网卡设备 ,只是这是个虚拟的设备.
参考资料
- https://man7.org/linux/man-pages/man4/veth.4.html
- https://man7.org/linux/man-pages/man8/ip-link.8.html
- https://man7.org/linux/man-pages/man8/ip-netns.8.html
- <<Kubernetes权威指南>>
- 前言中飞哥的文章