一,来龙去脉

最近遇到了一个很weird的问题。我们的生产环境中的mysql使用了docker容器来提供服务,docker容器的网卡通过veth口挂到宿主机的br0(linux bridge),而宿主机的聚合口team0也挂到br0上,具体如下:

eno0 -|
      |-- team0 -- br0 -- vethxxx -- eth0(docker)
eno1 -|

但前几天BJ机房掉电,重启后发现宿主机无法登陆,网络不通。ipmi登陆上去,检查team0状态、br0状态都正常,tcpdump抓包发现,报文能够到达team0的子接口(eno0),但无法送到br0,因此ping宿主机不通。

偶然发现,从外面ping宿主机网络,如果在team0口、eno0口都执行tcpdump,宿主机、docker容器,网络均可达。

我们做了几个实验: 1、将docker服务停掉systemctl stop docker后,宿主机网络可通。 2、将team0从br0中移除,然后重启服务器,重启后宿主机网络可通;之后将team0加入br0,宿主机、docker容器,网络均可通

我的同事八神告诉我,这里的问题在于br0的mac地址不固定,因为br0的mac地址是由挂在br0上的各子接口的最小mac决定的,而docker容器的mac地址在每次启动的时候是变化的(所以这个问题的出现有一定几率)。如果br0的mac地址跟team0的mac地址一致,那么报文可以上送到br0,物理网络可通;如果跟veth的mac地址一致,则报文不能到达br0。他尝试让br0的地址跟team0地址一致,但发现docker容器的网卡仍然不可达。

他的判断是应该在物理网卡上开启混杂模式,让物理网卡允许非本地mac的报文上送本机到br0,从而做到宿主机网络、docker网络可达。

其实一开始我是拒绝的,因为我们还有一套集群,同样的配置,ifconfig物理网卡并没有看到PROMISC标记。

但是从前面“偶然发现”的ping+tcpdump的现象来看,的确可能是混杂模式的原因,因为tcpdump的一个副作用就是可以让网卡进入混杂模式。如果细心的话,可以发现每次tcpdump,都会在/var/log/messages里看到这样两行:

kernel: device enp7s0f0 entered promiscuous mode
kernel: device enp7s0f0 left promiscuous mode

所以才会出现这个“偶然发现”的现象。

…可是为什么另外一套集群的网卡上没有PROMISC标记呢?

实际上,网卡是否处于PROMISC模式,ifconfig并不是最直接的判断依据,换句话说就是ifconfig能看到PROMISC标记表示一定处于混杂模式,但处于混杂模式并不一定能看到PROMISC标记。内核判断网卡是否处于混杂模式是看/sys/class/net/ifname/flags的值,如果置位了0x100,则处于混杂模式。来看if.h:

#define	IFF_PROMISC	0x100		/* receive all packets		*/

正常集群的物理网卡的确是置位了的,问题原因很清楚了。

二,回头望月

上面啰嗦了这么多,那么什么是混杂模式呢?

混杂模式(英语:promiscuous mode)是计算机网络中的术语。是指一台机器的网卡能够接收所有经过它的数据流,而不论其目的地址是否是它。
一般计算机网卡都工作在非混杂模式下,此时网卡只接受来自网络端口的目的地址指向自己的数据。当网卡工作在混杂模式下时,网卡将来自接口的所有数据都捕获并交给相应的驱动程序。

维基百科

三,亢龙有悔

其实还有一个问题。为什么在重启之前一切正常呢?查看内核的日志,从team0开始挨个都进入了promiscuous mode,特别好。

Nov  9 20:29:30 localhost kernel: device team0 entered promiscuous mode
Nov  9 20:29:30 localhost kernel: device enp132s0f1 entered promiscuous mode
Nov  9 20:29:30 localhost kernel: device enp132s0f0 entered promiscuous mode
Nov  9 20:29:30 localhost kernel: br0: port 1(team0) entered forwarding state
Nov  9 20:29:30 localhost kernel: br0: port 1(team0) entered forwarding state

但是在问题机器上,就只有team0进入了混杂模式,2个子接口并没有进入,所以导致问题发生。那么解决这个问题有2个办法,一个是在系统启动后做patch补救,即再执行ifconfig ifname promisc;另一个是找到team0没有带着子接口进入混杂模式的原因,自然的解决。但是恕我愚钝,没找到原因,两个服务器的配置基本一致,唯一不同的是问题机器上会有system-teamd服务。

CGroup: /system.slice/system-teamd.slice └─teamd@team0.service └─1467 /usr/bin/teamd -U -D -o -t team0 -f /run/teamd/team0.conf

而正常环境并没有启动这个服务,但是有teamd进程:

/usr/bin/teamd -o -n -U -D -N -t team0 -c {"runner":{"name":"lacp"}}

team driver相对bond来说一个特点是其工作很多是发生在用户态的,配置方法除了可以netlink下内核配置外,也可以靠teamd这个用户态的工具去配置。猜测可能是因为teamd与networkmanager的时序上可能出了问题,导致子接口没能跟随team0进入混杂模式。

另外在解决这个问题的时候发现了一个centos7上的team driver的bug,他的问题是team0根本就加不到br0上,跟我的还不太一样,从7.2的发布时间来看,应该也是解决了的,但是这引起了我对team driver稳定性的担忧。

四,潜龙勿用

这个问题的解决办法也比较不要脸,后面我们的服务器都会弃用team driver,而是改回到更稳定的bond driver。配置方法也很简单。

  nmcli connection add type bond con-name bond0 ifname bond0 mode 4
  nmcli connection add type bond-slave ifname $ETH1 master bond0
  nmcli connection add type bond-slave ifname $ETH2 master bond0

  nmcli connection modify bond0 ipv4.addresses $IP/24 ipv4.gateway $GW
  nmcli connection modify bond0 ipv4.method manual
  nmcli connection up bond0

改用bond0以后,子接口都可以自动进入混杂模式,不需要人工干预,宿主机和docker容器的网络均正常。