作为一个容器,它可以声明直接使用宿主机的网络栈(–net=host),即:不开启 Network Namespace,比如:
1 | docker run –d –net=host --name nginx-host nginx |
直接使用主机网络模式虽然可以为容器提供良好的网络性能,但会引入共享网络资源的问题,比如端口冲突。所以,在大多数情况下,我们都希望容器进程能使用自己 Network Namespace 里的网络栈,即:拥有属于自己的 IP 地址和端口。
如何让隔离的容器进程跟其他 Network Namespace 里的容器进程进行交互呢?Linux 中可以通过虚拟网桥实现,docker中也是通过“网桥”实现的。
Docker Bridge
当 Docker 启动时,会自动在主机上创建一个 docker0 虚拟网桥,实际上是 Linux 的一个 bridge,可以理解为一个软件交换机。它会在挂载到它的网口之间进行转发。
创建一个 Docker 容器的时候,同时会创建了一对 veth pair 接口(当数据包发送到一个接口时,另外一个接口也可以收到相同的数据包)。这对接口一端在容器内,即 eth0 ;另一端在本地并被挂载到docker0 网桥,名称以 veth 开头(例如vethAQI)。通过这种方式,主机可以跟容器通信,容器之间也可以相互通信。Docker 就创建了在主机和所有容器之间一个虚拟共享网络。
brctl show查看当前的网桥:
上面显示中的接口是由对应的桥提供网络流转的,在宿主机上也可以看出只有bridge拥有ip地址,而挂在bridge上的vethxxx则没有ip。
网络流转拓扑
容器与宿主机通信
在桥接模式下,Docker Daemon 将 veth0 附加到 docker0 网桥上,保证宿主机的报文有能力发往 veth0。再将 veth1 添加到 Docker 容器所属的网络命名空间,保证宿主机的网络报文若发往 veth0 可以立即被 veth1 收到。
容器与外界通信
容器如果需要联网,则需要采用 NAT 方式。准确的说,是 NATP (网络地址端口转换) 方式。NATP 包含两种转换方式:SNAT 和 DNAT 。
外部访问容器
目的 NAT (Destination NAT,DNAT): 修改数据包的目的地址。
当宿主机以外的世界需要访问容器时,数据包的流向如下图所示:
由于容器的 IP 与端口对外都是不可见的,所以数据包的目的地址为宿主机的 ip 和端口,为 192.168.1.10:24 。
数据包经过路由器发给宿主机 eth0,再经 eth0 转发给 docker0 网桥。由于存在 DNAT 规则,会将数据包的目的地址转换为容器的 ip 和端口,为 172.17.0.n:24 。
宿主机上的 docker0 网桥识别到容器 ip 和端口,于是将数据包发送附加到 docker0 网桥上的 veth0 接口,veth0 接口再将数据包发送给容器内部的 veth1 接口,容器接收数据包并作出响应。
容器访问外部
目的 NAT (Destination NAT,DNAT): 修改数据包的目的地址。
当容器需要访问宿主机以外的世界时,数据包的流向为下图所示:
此时数据包的源地址为容器的 ip 和端口,为 172.17.0.n:24,容器内部的 veth1 接口将数据包发往 veth0 接口,到达 docker0 网桥。
宿主机上的 docker0 网桥发现数据包的目的地址为外界的 IP 和端口,便会将数据包转发给 eth0 ,并从 eth0 发出去。由于存在 SNAT 规则,会将数据包的源地址转换为宿主机的 ip 和端口,为 192.168.1.10:24 。
由于路由器可以识别到宿主机的 ip 地址,所以再将数据包转发给外界,外界接受数据包并作出响应。这时候,在外界看来,这个数据包就是从 192.168.1.10:24 上发出来的,Docker 容器对外是不可见的。
docker0 之所以能将对外报文转发到eth0,是因为在一个主机内,通过路由表规则进行转发,在主机内docker0可以reach到eth0所对应的IP。
当进行端口映射的时候我们可以查看到宿主机中多了一个进程,这就是前面所说的docker-proxy,每增加一个端口映射,宿主机就会多出一个docker-proxy进程,当其他的人访问这个主机的时候,docker-proxy就会自动找到对应容器的端口。