17|容器网络配置(2):容器网络延时要比宿主机上的高吗?

本文仅作为学习记录,非商业用途,侵删,如需转载需作者同意。

上一节说了网络接口配置中的,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的方式。

3.1、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的处理带来的开销要更明显一些。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值