张磊:浅谈容器网络( 二 )


可以看到 , 这个容器里有一张叫作eth0的网卡 , 它正是一个VethPair设备在容器里的这一端 。
通过route命令查看nginx-1容器的路由表 , 我们可以看到 , 这个eth0网卡是这个容器里的默认路由设备;所有对172.17.0.0/16网段的请求 , 也会被交给eth0来处理(第二条172.17.0.0路由规则) 。
而这个VethPair设备的另一端 , 则在宿主机上 。 你可以通过查看宿主机的网络设备看到它 , 如下所示:
#在宿主机上$ifconfig...docker0Linkencap:EthernetHWaddr02:42:d8:e4:df:c1inetaddr:172.17.0.1Bcast:0.0.0.0Mask:255.255.0.0inet6addr:fe80::42:d8ff:fee4:dfc1/64Scope:LinkUPBROADCASTRUNNINGMULTICASTMTU:1500Metric:1RXpackets:309errors:0dropped:0overruns:0frame:0TXpackets:372errors:0dropped:0overruns:0carrier:0collisions:0txqueuelen:0RXbytes:18944(18.9KB)TXbytes:8137789(8.1MB)veth9c02e56Linkencap:EthernetHWaddr52:81:0b:24:3d:dainet6addr:fe80::5081:bff:fe24:3dda/64Scope:LinkUPBROADCASTRUNNINGMULTICASTMTU:1500Metric:1RXpackets:288errors:0dropped:0overruns:0frame:0TXpackets:371errors:0dropped:0overruns:0carrier:0collisions:0txqueuelen:0RXbytes:21608(21.6KB)TXbytes:8137719(8.1MB)$brctlshowbridgenamebridgeidSTPenabledinterfacesdocker08000.0242d8e4dfc1noveth9c02e56
通过ifconfig命令的输出 , 你可以看到 , nginx-1容器对应的VethPair设备 , 在宿主机上是一张虚拟网卡 。 它的名字叫作veth9c02e56 。 并且 , 通过brctlshow的输出 , 你可以看到这张网卡被“插”在了docker0上 。
这时候 , 如果我们再在这台宿主机上启动另一个Docker容器 , 比如nginx-2:
$dockerrun–d--namenginx-2nginx$brctlshowbridgenamebridgeidSTPenabledinterfacesdocker08000.0242d8e4dfc1noveth9c02e56vethb4963f3
你就会发现一个新的、名叫vethb4963f3的虚拟网卡 , 也被“插”在了docker0网桥上 。
这时候 , 如果你在nginx-1容器里ping一下nginx-2容器的IP地址(172.17.0.3) , 就会发现同一宿主机上的两个容器默认就是相互连通的 。
这其中的原理其实非常简单 , 我来解释一下 。
当你在nginx-1容器里访问nginx-2容器的IP地址(比如ping172.17.0.3)的时候 , 这个目的IP地址会匹配到nginx-1容器里的第二条路由规则 。 可以看到 , 这条路由规则的网关(Gateway)是0.0.0.0 , 这就意味着这是一条直连规则 , 即:凡是匹配到这条规则的IP包 , 应该经过本机的eth0网卡 , 通过二层网络直接发往目的主机 。
而要通过二层网络到达nginx-2容器 , 就需要有172.17.0.3这个IP地址对应的MAC地址 。 所以nginx-1容器的网络协议栈 , 就需要通过eth0网卡发送一个ARP广播 , 来通过IP地址查找对应的MAC地址 。 备注:ARP(AddressResolutionProtocol) , 是通过三层的IP地址找到对应的二层MAC地址的协议 。 我们前面提到过 , 这个eth0网卡 , 是一个VethPair , 它的一端在这个nginx-1容器的NetworkNamespace里 , 而另一端则位于宿主机上(HostNamespace) , 并且被“插”在了宿主机的docker0网桥上 。 一旦一张虚拟网卡被“插”在网桥上 , 它就会变成该网桥的“从设备” 。 从设备会被“剥夺”调用网络协议栈处理数据包的资格 , 从而“降级”成为网桥上的一个端口 。 而这个端口唯一的作用 , 就是接收流入的数据包 , 然后把这些数据包的“生杀大权”(比如转发或者丢弃) , 全部交给对应的网桥 。 所以 , 在收到这些ARP请求之后 , docker0网桥就会扮演二层交换机的角色 , 把ARP广播转发到其他被“插”在docker0上的虚拟网卡上 。 这样 , 同样连接在docker0上的nginx-2容器的网络协议栈就会收到这个ARP请求 , 从而将172.17.0.3所对应的MAC地址回复给nginx-1容器 。 有了这个目的MAC地址 , nginx-1容器的eth0网卡就可以将数据包发出去 。 而根据VethPair设备的原理 , 这个数据包会立刻出现在宿主机上的veth9c02e56虚拟网卡上 。 不过 , 此时这个veth9c02e56网卡的网络协议栈的资格已经被“剥夺” , 所以这个数据包就直接流入到了docker0网桥里 。 docker0处理转发的过程 , 则继续扮演二层交换机的角色 。 此时 , docker0网桥根据数据包的目的MAC地址(也就是nginx-2容器的MAC地址) , 在它的CAM表(即交换机通过MAC地址学习维护的端口和MAC地址的对应表)里查到对应的端口(Port)为:vethb4963f3 , 然后把数据包发往这个端口 。 而这个端口 , 正是nginx-2容器“插”在docker0网桥上的另一块虚拟网卡 , 当然 , 它也是一个VethPair设备 。 这样 , 数据包就进入到了nginx-2容器的NetworkNamespace里 。 所以 , nginx-2容器看到的情况是 , 它自己的eth0网卡上出现了流入的数据包 。 这样 , nginx-2的网络协议栈就会对请求进行处理 , 最后将响应(Pong)返回到nginx-1 。 以上 , 就是同一个宿主机上的不同容器通过docker0网桥进行通信的流程了 。 我把这个流程总结成了一幅示意图 , 如下所示: