一个Linxu容器汇总能够看见的网络栈,实际上就是被隔离在自己的Network Namepsace当中的
所谓的网络栈,就包含了网卡 Network Interface 回环设备 Loopback Device 路由表 Routing Table 和iptables的规则,对一个进程来说,这些是网络交互必不可少的东西
而一个容器,可以直接使用宿主机的网络栈(-net=host),不开启Network Namespace,
docker run -d -net=host -name nginx nginx
这种情况下,这个容器启动了,直接监听的就是宿主机的80端口
直接使用宿主机网络栈的方式,虽然可以为容器使用较好的性能,但是不可避免因的引入网络资源问题,端口冲突,所以,我们一般希望容器能够使用自己Network Namespace的网络栈,用于自己的IP地址和端口
这个被隔离的容器进程,如何和其他的Network Namepsace交互呢?
我们可以将每一个容器都看作一个主机,都有一个独立的网络栈
如果想要实现两台主机之间的通信,最直接的方式,是利用交换机
而在Linux,虚拟化交换机的设备,叫网桥,这是一个工作在数据链路层DataLink的设备,根据Mac地址学习将数据包转发到网桥的不同端口上
为了实现上面的目的,Docker项目会默认在宿主机上创建一个docker0的网桥,凡是连接在docker0网桥上的容器,通过这个来进行通信
如何将这个容器连接到docker0网桥上呢?就需要Veth Pair的虚拟设备了,Veth Pair设备的特点,就是被创建后,总是以两张虚拟网卡Veth Peer的形式存在
其中一个网卡发出的数据包,直接可以出现在对应的另一张网卡上,哪怕是在不同的Network Namespace中
这样,我们如果启动了一个叫做nginx-1的容器
$ docker run –d –name nginx-1 nginx
然后进入这个容器看一下对应的网络设备
# 在宿主机上
$ docker exec -it nginx-1 /bin/bash # 在容器里 root@2b3c181aecf1:/# ifconfig eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500 inet 172.17.0.2 netmask 255.255.0.0 broadcast 0.0.0.0 inet6 fe80::42:acff:fe11:2 prefixlen 64 scopeid 0x20<link> ether 02:42:ac:11:00:02 txqueuelen 0 (Ethernet) RX packets 364 bytes 8137175 (7.7 MiB) RX errors 0 dropped 0 overruns 0 frame 0 TX packets 281 bytes 21161 (20.6 KiB) TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0 lo: flags=73<UP,LOOPBACK,RUNNING> mtu 65536 inet 127.0.0.1 netmask 255.0.0.0 inet6 ::1 prefixlen 128 scopeid 0x10<host> loop txqueuelen 1000 (Local Loopback) RX packets 0 bytes 0 (0.0 B) RX errors 0 dropped 0 overruns 0 frame 0 TX packets 0 bytes 0 (0.0 B) TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0 $ route Kernel IP routing table Destination Gateway Genmask Flags Metric Ref Use Iface default 172.17.0.1 0.0.0.0 UG 0 0 0 eth0 172.17.0.0 0.0.0.0 255.255.0.0 U 0 0 0 eth0 |
eth0是一个Veth Pair设备,在这个容器的这一端
通过Route命令查看nginx-1容器的路由表,可以看到,这个eth0网卡是容器中默认的路由设备,同时所有对172.17.0.0/16网段的请求,会被交给eth0来处理
在宿主机上,可以看到这些对应的网卡
# 在宿主机上
$ ifconfig … docker0 Link encap:Ethernet HWaddr 02:42:d8:e4:df:c1 inet addr:172.17.0.1 Bcast:0.0.0.0 Mask:255.255.0.0 inet6 addr: fe80::42:d8ff:fee4:dfc1/64 Scope:Link UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1 RX packets:309 errors:0 dropped:0 overruns:0 frame:0 TX packets:372 errors:0 dropped:0 overruns:0 carrier:0 collisions:0 txqueuelen:0 RX bytes:18944 (18.9 KB) TX bytes:8137789 (8.1 MB) veth9c02e56 Link encap:Ethernet HWaddr 52:81:0b:24:3d:da inet6 addr: fe80::5081:bff:fe24:3dda/64 Scope:Link UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1 RX packets:288 errors:0 dropped:0 overruns:0 frame:0 TX packets:371 errors:0 dropped:0 overruns:0 carrier:0 collisions:0 txqueuelen:0 RX bytes:21608 (21.6 KB) TX bytes:8137719 (8.1 MB) $ brctl show bridge name bridge id STP enabled interfaces docker0 8000.0242d8e4dfc1 no veth9c02e56 |
对应的在宿主机上,nginx-1容器对应的Veth Pair设备,在设备上是一个虚拟网卡,而且会绑定上了docker容器上
而且一个宿主机上不同的docker中,可以彼此进行访问
这是因为,从1容器访问2容器这个IP会走如下的流程,从nginx1发出,访问nginx2时候,会先匹配到nginx1的路由,发往网桥,然后经由网桥发往2容器
而且一旦一张网卡被插在了网桥上,就会变成网桥的从设备,从设备会剥夺调用网络协议栈处理数据包的资格,变成一个网络上的端口,只负责将数据包给网桥推拉流
所以中间的网桥,就是二层交换机的角色,这个docekr0网桥根据包的目的MAC地址,在对应的CAM表通过MAC地址学习到维护的端口和MAC地址对应的表,查到对应的port,并将包发过去
所以2容器会通过网桥,收到了流入的数据包,然后将响应返回给nginx-1
这就是一个宿主机上不同容器通过docker0网桥进行通信的流程了
实际传输中,还是需要Linxu 内核 Netfilter参与其中,感兴趣的话,可以打开iptables的TRACE功能查看到数据包的传输过程
# 在宿主机上执行
$ iptables -t raw -A OUTPUT -p icmp -j TRACE
$ iptables -t raw -A PREROUTING -p icmp -j TRACE
这样docker0网桥的工作方式,在默认情况下,就被限制在Network Namesapce的容器进程,实际上是通过Veth Pair设备 和 宿主机网桥的方式,完成相同宿主机内的数据交换
外界访问宿主机的时候,也是先根据路由信息到达docker0,然后转发到对应的Veth Pair设备,最终出现在容器中,过程如下
一个容器内访问别的宿主机的话,发出的请求数据包,会先经过docker0网桥出现在宿主机上,然后是宿主机根据自己的路由表,进行相关的请求转发
那么,不同宿主机之间的Docker容器如何互通呢?
那么早期的解决方案,就是创建一个集群公用的网桥,然后将集群的所有容器都连接到这个网桥上,就可以互相通信了
这样我们整体网络就如下所示
这种构建网络的核心在于,需要在已有的宿主机网络上,通过软件构建一个覆盖在已有宿主机网络上,可以将所有容器联通在一起的虚拟网络,这就是Overlay Network
这个Overlay Network本身,就是由每个宿主机上每一个特殊网桥组成的,Node上的Container1访问Node2上的Container3时候,需要将数据包发送到正确的宿主机上,比如Node2,而Node2上的特殊网桥,也需要知道正确的转发路径
我们说了单机容器网络实现原理和docker0网桥的作用
容器想和外解决进行通信,发出的IP包就需要从Network Namespace里出来,到宿主机上
所以我们使用了Veth Pair和网桥进行配合使用
Host Network模式有些缺陷,如果在生产环境中使用容器的HOST模式,需要做什么额外的工作
对于使用host网络连接,需要考虑的第一件事就是端口的冲突可能性
如果在一个宿主机上运行多个相同进程,必然会出现端口冲突的问题