赞
踩
本文仅作为学习记录,非商业用途,侵删,如需转载需作者同意。
上一节说了网络接口配置中的,veth 的接口配置方式,是大部分容器缺省用的网络配置方式。
veth 的方式:
一个数据包要从容器里发送到宿主机外,需要先从容器里的eth0(veth_container)把包发送到宿主机上 veth_host ,然后再在宿主机上通过nat或者路由的方式,经由宿主机上的eth0 向外发送。
上面这种方法,相比较宿主机上直接向外发送数据包的路径,多了一次接口层的发送和接收。尽管veth 是虚拟网络接口,在软件上还是会增加一些开销。
如果是从物理机上迁移到容器中,就会出现网络延迟增加的情况。
Netperf 是一个衡量网络性能的工具,它可以提供单向吞吐量和端到端延迟的测试。
可以使用这个工具来模式veth 接口导致的网络延迟情况。
我们需要两台虚拟机或者物理机,同处于一个二层网络中。
在第一台机器上启动一个veth 接口的容器,容器的启动和宿主机上的配置可以参考这个脚本脚本
在第二台机器上,只要启动一个netserver就可以了。
然后分别在容器里和宿主机上运行与 netserver 交互的netperf,再比较下它们延迟的差异。
netperf 里的 TCP_RR 测试用例,是专门用来测试网络延迟的,缺省每次运行10秒。
运行以后还要计算平均每秒钟TCP Request、Response 的次数,次数越高延时越小。
接下来,我们先在第一台机器的宿主机上直接运行 netperf 的 TCP_RR 测试用例 3 轮,得到的值分别是 2504.92,2410.14 和 2422.81,计算一下可以得到三轮 Transactions 平均值是 2446/s。
# ./netperf -H 192.168.0.194 -t TCP_RR MIGRATED TCP REQUEST/RESPONSE TEST from 0.0.0.0 (0.0.0.0) port 0 AF_INET to 192.168.0.194 () port 0 AF_INET : first burst 0 Local /Remote Socket Size Request Resp. Elapsed Trans. Send Recv Size Size Time Rate bytes Bytes bytes bytes secs. per sec 16384 131072 1 1 10.00 2504.92 16384 131072 # ./netperf -H 192.168.0.194 -t TCP_RR MIGRATED TCP REQUEST/RESPONSE TEST from 0.0.0.0 (0.0.0.0) port 0 AF_INET to 192.168.0.194 () port 0 AF_INET : first burst 0 Local /Remote Socket Size Request Resp. Elapsed Trans. Send Recv Size Size Time Rate bytes Bytes bytes bytes secs. per sec 16384 131072 1 1 10.00 2410.14 16384 131072 # ./netperf -H 192.168.0.194 -t TCP_RR MIGRATED TCP REQUEST/RESPONSE TEST from 0.0.0.0 (0.0.0.0) port 0 AF_INET to 192.168.0.194 () port 0 AF_INET : first burst 0 Local /Remote Socket Size Request Resp. Elapsed Trans. Send Recv Size Size Time Rate bytes Bytes bytes bytes secs. per sec 16384 131072 1 1 10.00 2422.81 16384 131072
同样,我们再在容器中运行一下 netperf 的 TCP_RR,也一样运行三轮,计算一下这三次的平均值,得到的值是 2141。
[root@4150e2a842b5 /]# ./netperf -H 192.168.0.194 -t TCP_RR MIGRATED TCP REQUEST/RESPONSE TEST from 0.0.0.0 (0.0.0.0) port 0 AF_INET to 192.168.0.194 () port 0 AF_INET : first burst 0 Local /Remote Socket Size Request Resp. Elapsed Trans. Send Recv Size Size Time Rate bytes Bytes bytes bytes secs. per sec 16384 131072 1 1 10.00 2104.68 16384 131072 [root@4150e2a842b5 /]# ./netperf -H 192.168.0.194 -t TCP_RR MIGRATED TCP REQUEST/RESPONSE TEST from 0.0.0.0 (0.0.0.0) port 0 AF_INET to 192.168.0.194 () port 0 AF_INET : first burst 0 Local /Remote Socket Size Request Resp. Elapsed Trans. Send Recv Size Size Time Rate bytes Bytes bytes bytes secs. per sec 16384 131072 1 1 10.00 2146.34 16384 131072 [root@4150e2a842b5 /]# ./netperf -H 192.168.0.194 -t TCP_RR MIGRATED TCP REQUEST/RESPONSE TEST from 0.0.0.0 (0.0.0.0) port 0 AF_INET to 192.168.0.194 () port 0 AF_INET : first burst 0 Local /Remote Socket Size Request Resp. Elapsed Trans. Send Recv Size Size Time Rate bytes Bytes bytes bytes secs. per sec 16384 131072 1 1 10.00 2173.79 16384 131072
那么我们拿这次容器环境中的平均值和宿主机上得到的值 2446 做比较,会发现 Transactions 下降了大概 12.5%,也就是网络的延时超过了 10%。
那为什么 veth 方式带来了这么高的网络延时呢?
veth 的虚拟网络接口一般都是成对出现,就像上图中的 veth_container 和 veth_host。
在每次网络传输的过程中,数据包都需要通过 veth_container 这个接口向外发送,而且必须保证 veth_host 先接收到这个数据包。
veth 这个虚拟网络接口,在接收数据包的操作上,和真实的网络接口没有太大的区别。
没有硬件中断的处理,软中断(softirq)的处理部分和真实的网络接口是一样的。
Linux 内核里的veth 驱动代码drivers/net/veth.c
veth 发送数据的函数是 veth_xmit() :找到veth peer 设备,然后触发peer 设备去接收数据包。
比如 veth_container 这个接口调用了 veth_xmit() 来发送数据包,最后触发了peer 设备 veth_host 去调用 netif_rx() 来接收数据包,主要代码如下:
static netdev_tx_t veth_xmit(struct sk_buff *skb, struct net_device *dev) { … /* 拿到veth peer设备的net_device */ rcv = rcu_dereference(priv->peer); … /* 将数据送到veth peer设备 */ if (likely(veth_forward_skb(rcv, skb, rq, rcv_xdp) == NET_RX_SUCCESS)) { … } static int veth_forward_skb(struct net_device *dev, struct sk_buff *skb, struct veth_rq *rq, bool xdp) { /* 这里最后调用了 netif_rx() */ return __dev_forward_skb(dev, skb) ?: xdp ? veth_xdp_rx(rq, skb) : netif_rx(skb); }
而 netif_rx() 是一个网络设备驱动里标准的接收数据包的函数,netif_rx() 里面为什么会有这个数据包 raise 一个 softirq (软中断)。
__raise_softirq_irqoff(NET_RX_SOFTIRQ);
在处理网络数据的时候,一些运行时间较长,而且不能在硬中断中处理的工作,就会通过 softirq 处理。
一般在硬件中断处理结束后,网络softirq 的函数才会再去执行没有完成的包的处理工作。
即使这里softirq 的执行速度很快,还是会带来额外的开销。
根据veth 这个虚拟网络设备的实现方式,必然会带来额外的开销,就会增加数据包的网络延时
除了veth,还有 macvlan、ipvlan的方式。
相同之处:
在一个物理的网络接口上再配置几个虚拟的网络接口,虚拟的网络接口上,都可以配置独立的IP,这些IP 可以属于不同的Namespace。
不同点:
对于macvlan 每个虚拟网络接口都有自己独立的mac地址;ipvlan的虚拟网络接口和物理网络接口共享同一个mac地址。
都有自己的L2/L3的配置方式。
运行如下命令,为容器手动配置上ipvlan的网络接口:
docker run --init --name lat-test-1 --network none -d registry/latency-test:v1 sleep 36000
pid1=$(docker inspect lat-test-1 | grep -i Pid | head -n 1 | awk '{print $2}' | awk -F "," '{print $1}')
echo $pid1
ln -s /proc/$pid1/ns/net /var/run/netns/$pid1
ip link add link eth0 ipvt1 type ipvlan mode l2
ip link set dev ipvt1 netns $pid1
ip netns exec $pid1 ip link set ipvt1 name eth0
ip netns exec $pid1 ip addr add 172.17.3.2/16 dev eth0
ip netns exec $pid1 ip link set eth0 up
命令解释:
1、启动一个容器,用"–network none" 方式启动,容器中没有配置任何的网络接口。
2、然后在宿主机eth0 的接口上增加了一个ipvlan 虚拟网络接口ipvt1
3、再把它加入到容器的Network Namespace里,重命名为容器内的eth0,并且配置上IP。
这样我们就配置好了第一个用ipvlan 网络接口的容器。
然后用相同的方式配置第二个容器,两个容器相互ping 一个ip,看看网络是否配置成功。
如图,使用macvlan,ipvlan的方式,容器的虚拟接口,直接连接到了宿主机的物理网络接口上,形成了一个网络的二层连接。
从容器中发送数据出去,看起来就是比veth的方式,经过链路要少了。
从下面的 ipvlan 接口的发送代码中可以看到:
往宿主机外发送数据,发送函数会直接找到 ipvlan 虚拟接口对应的物理接口。
比如在我们的例子中,这个物理接口就是宿主机上的 eth0,然后直接调用 dev_queue_xmit() ,通过物理接口把数据直接发送出去。
static int ipvlan_xmit_mode_l2(struct sk_buff *skb, struct net_device *dev) { … if (!ipvlan_is_vepa(ipvlan->port) && ether_addr_equal(eth->h_dest, eth->h_source)) { … } else if (is_multicast_ether_addr(eth->h_dest)) { … } /* * 对于普通的对外发送数据,上面的if 和 else if中的条件都不成立, * 所以会执行到这一步,拿到ipvlan对应的物理网路接口设备, * 然后直接从这个设备发送数据。 */ skb->dev = ipvlan->phy_dev; return dev_queue_xmit(skb); }
和veth 接口相比,我们用ipvlan 发送对外数据就要简单得多,因为这种方式没有内部额外的 softirq 处理开销。
下面这张图是网络延时的监控图,图里蓝色的线表示程序运行在 veth 容器中,黄色线表示程序运行在 ipvlan 的容器里,绿色的线代表程序直接运行在物理机上。
如上图,延时(Latency)图中。ipvlan比veth 要耗时明显低一些。
对于网络延时敏感的应用程序,我们可以考虑使用 ipvlan、macvlan的容器网络配置方式来替换缺省的veth网络配置。
容器缺省使用 veth 虚拟网络接口,不过 veth 接口会有比较大的网络延时。
使用netperf 这个工具来比较网络延时,veth 接口容器的网络延时会增加超过10%。
veth接口是成对工作的,在对外发送数据的时候,peer veth接口都会 raise softirq 来完成一次收包操作,这样就会带来数据包处理的额外开销。
可以使用 macvlan、ipvlan的网络接口来替代 veth 网络接口,降低容器网络延时。
ipvlan/macvlan 直接在物理网络接口上虚拟出接口,在发送数据包的时候直接通过物理接口完成,没有veth 的那种 softirq 的开销。容器使用 ipvlan/macvlan 的网络接口,网络延时非常接近物理网络接口的延时。
由于ipvlan/macvlan 网络接口直接挂载在物理接口上,对于需要使用 iptables 规则的容器,比如Kubernetes里使用service 的容器,就不能工作了!
1、
问题:
netif_rx 通常在硬件中断处理逻辑中调用。loopback、veth 等设备驱动是特例,通过调用 netif_rx 模拟数据包的接收。
除了 softirq 的性能损坏,应该还包括 docker0 网桥的自身处理逻辑(作为 veth_container 的主设备接管其数据包的处理权)以及 docker0 -> eth0 的转发逻辑。
docker inspect 输出为 json 格式,推荐使用 jq 命令查看 Pid ,命令比较简洁,缺点是默认末安装:
docker inspect lat-test-1 | jq .[0].State.Pid
回答:
没错,Linux bridge以及docker0 -> eth0的L3层的操作也会带来额外的开销,不过相比较而言,veth pair softirq的处理带来的开销要更明显一些。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。