Docker之十七: 高级网络功能
Docker 网络基本原理
Docker 的本地网络实现其实就是利用了 Linux 上的网络命名空间和虚拟网络设备(特别是 veth pair)。
要实现网络通信,需要至少一个物理或虚拟网络接口与外界相通,收发数据,如需在不同子网之间进行通信,还需要路由机制。
Docker 中的网络接口默认都是虚拟接口。其基本原理是:Linux 通过在内核中进行数据复制来实现虚拟接口之间的数据转发,即发送接口的发送缓存中的数据包将直接复制到接收接口的接受缓存中,而无需通过外部网络设备进行交换。所以虚拟接口的转发效率极高。
Docker 容器网络利用了 Linux 虚拟网络技术,它在本地主机和容器内分别建立一个虚拟接口 veth,并连通(这样的一对虚拟接口叫做 veth pair)。
Docker 创建容器的时候,按照如下步骤操作:
- 创建一对虚拟接口,分别放到本地主机和新容器的命名空间中;
- 本地主机一端的虚拟接口连接到默认的 docker0 网桥或指定网桥上,并且有一个以 veth 开头的唯一名字,如 veth1234;
- 容器一端的虚拟接口将放到新创建的容器中,并修改名字作为 eth0U。这个接口只在容器命名空间可见;
- 从网桥可用地址段中获取一个空闲的地址分配给容器的 eth0(例如 172.17.0.2/16),并配置默认路由网关为 docker0 网卡的内部接口 docker0 的 IP 地址(例如 172.17.42.1/16)。
这样,容器就可以使用它所能看到的 eth0 虚拟网卡来连接其他容器和访问外部网络。
Docker 的网络模式
Docker 支持的网络模式
docker容器的网络有五种模式:
bridge 模式,–net=bridge (默认)
这是 Dokcer 网络的默认设置,为容器创建独立的网络命名空间,容器具有独立的网卡等所有单独的网络栈,是最常用的使用方式。
在 docker run 启动容器的时候,如果不加 –net 参数,就默认采用这种网络模式。安装完 Docker,系统会自动添加一个供 Docker 使用的网桥 docker0,我们创建一个新的容器时,容器通过 DHCP 获取一个与 docker0 同网段的 IP 地址,并默认连接到 docker0 网桥,以此实现容器与宿主机的网络互通。host 模式,–net=host
这个模式下创建出来的容器,直接使用容器宿主机的网络命名空间。
容器将不拥有自己独立的 Network Namespace,即没有独立的网络环境。它使用宿主机的 IP 和端口。none 模式,–net=none
为容器创建独立网络命名空间,但不为它做任何网络配置,容器中只有 lo,用户可以在此基础上,对容器网络做任意定制。
这个模式下,Dokcer 不为容器进行任何网络配置。需要我们自己为容器添加网卡,配置 IP。
因此,若想使用 pipework 配置 Docker 容器的 IP 地址,必须要在 none 模式下才可以。其他容器模式(即 container 模式),–net=container:NAME_or_ID
与 host 模式类似,只是容器将与指定的容器共享网络命名空间。
这个模式就是指定一个已有的容器,共享该容器的 IP 和端口。除了网络方面两个容器共享,其他的如文件系统,进程等还是隔离开的。用户自定义:Docker 1.9 版本以后新增的特性,允许容器使用第三方的网络实现或者创建单独的 bridge 网络,提供网络隔离能力。
Docker 默认的网络模式
bridge 模式是 Docker 默认的网络模式,也是开发者最常使用的网络模式。在这种模式下,Docker 为容器创建独立的网络栈,保证容器内的进程使用独立的网络环境,实现容器之间、容器与宿主机之间的网络栈隔离。同时,通过宿主机上的 docker0 网桥,容器可以与宿主机乃至外界进行网络通信。
其网络模型如下图所示:
从上面的网络模型可以看出,容器从原理上是可以与宿主机乃至外界的其他机器通信的。
同一宿主机上,容器之间都是连接到 docker0 这个网桥上的,它可以作为虚拟交换机使容器可以相互通信。然而,由于宿主机的 IP 地址与容器 veth pair 的 IP 地址均不在同一个网段,故仅仅依靠 veth pair 和 namespace 的技术,还不足以使宿主机以外的网络主动发现容器的存在。为了使外界可以访问容器中的进程,Docker 采用了端口绑定的方式,也就是通过 iptables 的 NAT,将宿主机上的端口端口流量转发到容器内的端口上。
# 使用 run 命令创建容器,并将宿主机的 3306 端口绑定到容器的 3306 端口
$ docker run -itd --name db -p 3306:3306 MySQL
# 宿主机上,通过下面命令可以查到一条 DNAT 规则
$ iptables -t nat -L -n
DNAT tcp -- 0.0.0.0/0 0.0.0.0/0 tcp dpt:3306 to:172.17.0.5:3306
上面的 172.17.0.5 即为 bridge 模式下,创建的容器 IP。
很明显,bridge 模式的容器与外界通信时,必定会占用宿主机上的端口,从而与宿主机竞争端口资源,对宿主机端口的管理会是一个比较大的问题。同时,由于容器与外界通信是基于三层上 iptables NAT,性能和效率上的损耗是可以预见的。
# 列出当前主机网桥
$ brctl show
bridge name bridge id STP enabled interfaces
docker0 8000.0242369ab552 no vethef56aac
# 查看当前 docker0 ip
$ ifconfig
...
docker0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
inet 172.17.0.1 netmask 255.255.0.0 broadcast 172.17.255.255
ether 02:42:36:9a:b5:52 txqueuelen 0 (Ethernet)
RX packets 36626 bytes 1544566 (1.5 MB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 41832 bytes 142181730 (142.1 MB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
...
# 在容器运行时,每个容器都会分配一个特定的虚拟机口并桥接到docker0。
# 每个容器都会配置同 docker0 ip 相同网段的专用ip 地址
# docker0的IP地址被用于所有容器的默认网关。
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
b5f03e866505 mysql:latest "docker-entrypoint.s…" 3 hours ago Up 3 hours 0.0.0.0:3306->3306/tcp, 33060/tcp mysql-test
$ docker inspect b5f03e866505 | grep IPAddress
"SecondaryIPAddresses": null,
"IPAddress": "172.17.0.2",
"IPAddress": "172.17.0.2",
# 宿主机的ip路由转发功能一定要打开,否则所创建的容器无法联网
# 查看宿主机网络转发是否开启 1,开启 0,关闭
$ cat /proc/sys/net/ipv4/ip_forward
1
# 或
$ sysctl net.ipv4.ip_forward
net.ipv4.ip_forward = 1
HOST 模式
容器和宿主机共享同一个网络命名空间,换言之,容器的 IP 地址即为宿主机的 IP 地址。所以容器可以和宿主机一样,使用宿主机的任意网卡,实现和外界的通信。其网络模型可以参照下图:
采用 host 模式的容器,可以直接使用宿主机的 IP 地址与外界进行通信,若宿主机具有公有 IP,那么容器也拥有这个公有 IP。同时容器内服务的端口也可以使用宿主机的端口,无需额外进行 NAT