Docker 的网络实现其实就是利用了 Linux 上的网络命名空间和虚拟网络设备(特别是 veth pair)。建议先熟悉了解这两部分的基本概念再阅读本文。
首先,要实现网络通信,机器需要至少一个网络接口(物理接口或虚拟接口)来收发数据包;此外,如果不同子网之间要进行通信,需要路由机制。
虚拟接口与 veth pair
Docker 中的网络接口默认都是虚拟的接口。虚拟接口的优势之一是转发效率较高。Linux 通过在内核中进行数据复制来实现虚拟接口之间的数据转发,发送接口的发送缓存中的数据包被直接复制到接收接口的接收缓存中。对于本地系统和容器内系统看来就像是一个正常的以太网卡,只是它不需要真正同外部网络设备通信,速度要快很多。
Docker 容器网络就利用了这项技术。它在本地主机和容器内分别创建一个虚拟接口,并让它们彼此连通(这样的一对接口叫做 veth pair)。
创建容器时的网络操作
Docker 创建一个容器的时候,会执行如下操作:
- 创建一对虚拟接口,分别放到本地主机和新容器中;
- 本地主机一端的虚拟接口连接到默认的
docker0网桥或指定的网桥上,并具有一个以veth开头的独特名字; - 容器一端的虚拟接口被移动到新容器中,并重命名成
eth0,这个接口只在容器的命名空间可见; - 从网桥的可用地址段中获取一个空闲地址分配给容器的
eth0,并配置默认路由到网桥网卡的 IP。
完成这些之后,容器就可以使用 eth0 虚拟网卡来连接其他容器和其他网络。
容器的网络模式
可以在 docker run 的时候通过 --net 参数来指定容器的网络配置,有 4 个可选值:
bridge:默认选项,用 veth pair 来连接容器和docker0网桥;none:容器有独立的网络命名空间,但没有进行任何网络设置,如分配 veth pair 和网桥连接、IP 等(--net=none);container:NAME_or_ID:让这个容器与另外一个已存在的容器共享一个网络命名空间,容器间共享网络,但进程等其他资源依旧独立;host:告诉 Docker 不要将容器网络放到隔离的命名空间中,即不要容器化容器网络,容器内看到的网络配置跟宿主机保持一致。
用户使用 --net=none 后,可以自行配置网络,让容器达到跟平常一样具有访问网络的权限。通过这个过程,可以了解 Docker 配置网络的细节。
手动配置容器网络
首先,启动一个 /bin/bash 容器,指定 --net=none 参数:
$ docker run -i -t --rm --net=none base /bin/bash
root@63f36fc01b5f:/#
在本地主机查找容器的进程 id,并为它创建网络命名空间:
$ docker inspect -f '{{.State.Pid}}' 63f36fc01b5f
2778
$ pid=2778
$ sudo mkdir -p /var/run/netns
$ sudo ln -s /proc/$pid/ns/net /var/run/netns/$pid
检查桥接网卡的 IP 和子网掩码信息:
$ ip addr show docker0
21: docker0: ...
inet 172.17.42.1/16 scope global docker0
...
创建一对 veth pair 接口 A 和 B,绑定 A 到网桥 docker0,并启用它:
$ sudo ip link add A type veth peer name B
$ sudo brctl addif docker0 A
$ sudo ip link set A up
将 B 放到容器的网络命名空间,命名为 eth0,启动它并配置一个可用 IP(桥接网段)和默认网关:
$ sudo ip link set B netns $pid
$ sudo ip netns exec $pid ip link set dev B name eth0
$ sudo ip netns exec $pid ip link set eth0 up
$ sudo ip netns exec $pid ip addr add 172.17.42.99/16 dev eth0
$ sudo ip netns exec $pid ip route add default via 172.17.42.1
以上,就是 Docker 配置网络的具体过程。
当容器结束后,Docker 会清空容器,容器内的 eth0 会随网络命名空间一起被清除,A 接口也被自动从 docker0 卸载。
此外,用户可以使用 ip netns exec 命令来在指定网络命名空间中进行配置,从而配置容器内的网络。