一个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网络连接,需要考虑的第一件事就是端口的冲突可能性

如果在一个宿主机上运行多个相同进程,必然会出现端口冲突的问题

发表评论

邮箱地址不会被公开。 必填项已用*标注