« 上一篇下一篇 »

Docker自定义网络的优点及配置方法详解代码

 

      Docker官方强烈推荐用户使用自己的自定义网络,所以在使用网络容器的时候最好使用自定义的网络,默认docker使用自带的bridge网络,bridge网络下的docker容器相互之间无法解析彼此的容器名称。如果您需要容器能够通过 bridge 网络进行通信,则官方允许使用旧版本的--link,这--link是建议使用的唯一用例。最后,官方强烈地考虑使用用户定义的网络。

 

一. 用户自定义 bridge 和默认 bridge 的差别

1.1 用户定义网桥提供更好的隔离和容器化应用之间的互操作性

连接到同一个用户自定义网桥的容器会自动互相暴露所有端口,并且不会暴露到外部。这会让容器化应用之间的通信更方便,而不会意外开放进入外部世界。

假设一个应用包含 web 前端和数据库后端。外部需要访问前端(可能是 80 端口),但是只有前端需要访问数据库后端。使用用户自定义网桥,只需要将前端的端口暴露到外部,数据库应用不需要开启任何端口,因为 web 前端可以通过用户自定义网桥直接访问到。

如果在默认网桥上运行同一个应用堆栈,需要同时打开 web 前端和数据库后端的端口,每次都需要使用 -p 或 --publish 标志。在意味着 Docker 主机需要通过其他方式来限制对数据库后端端口的访问。

1.2 用户自定义 bridge 提供容器间自动 DNS 解析(automatic DNS resolution)

默认网桥上的容器只能通过 IP 地址互相访问,除非你使用 --link 选项,这被认为是遗留的。在用户自定义网桥中,容器之间可以通过名字会别名互相访问。

这里还是用上面的例子分析,web 前端和数据库后端。如果容器称为 web 和 db,web 容器可以连接到 db 上的 db 容器(the web container can connect to the db container at db),不管这个应用堆栈运行在哪个 Docker 主机上。

如果在默认网桥上运行相同应用堆栈,需要人工创建容器之间的连接(使用遗留的 --link)标志。这些连接需要双向创建,所以当需要通信的容器个数大于 2 个时复杂度会呈指数增长。或者,你可以编辑容器内的 /etc/hosts 文件,但这会产生难以调试的问题。

1.3 容器可以在运行中与用户自定义网络连接和断开

在一个容器的生命周期中,可以在容器运行中将容器与用户自定义网络连接和断开。要从默认网桥中移除容器,需要停止容器并且通过不同的网络选项重新创建。

1.4 每个用户自定义网络创建一个可配置的桥

如果你的容器使用默认网桥,你可以配置它,但是所有容器都使用了相同设置,例如 MTU 和 iptables 规则。此外,对默认网桥的配置发生在 Docker 之外,需要重启 Docker。

用户自定义网桥通过 docker network create 来创建和配置。如果应用程序的不同分组有不同的网络需求,可以独立配置每个用户自定义网桥,就像独立创建一样。

1.5 默认网桥中连接的容器共享环境变量

最初,在两个容器之间共享环境变量的唯一方法是使用 --link 标志连接它们。用户自定义网络中无法使用这种类型的变量共享方式。然而,共享环境变量有更好的方式。一些想法:

多个容器可以使用 Docker volume 卷挂载用于共享信息的同一个文件或目录。

可以通过 docker-compose 同时启动多个容器,compose 文件可以定义共享变量。

可以使用 swarm 服务代替独立的容器,可以利用 swarm 的共享的 secrets 和 configs。

连接到同一个用户自定义网桥的容器可以有效地将所有端口暴露给对方。 要使不同网络上的容器或非 Docker 主机访问到容器的端口,该端口必须使用 -p 或 --publish 标志来发布。

 

  好了,开始讨论Docker的基本网络配置(没有使用overlay网络):

Docker 中的网络接口默认都是虚拟的接口。虚拟接口的优势之一是转发效率较高。 Linux 通过在内核中进行数据复制来实现虚拟接口之间的数据转发,发送接口的发送缓存中的数据包被直接复制到接收接口的接收缓存中。对于本地系统和容器内系统看来就像是一个正常的以太网卡,只是它不需要真正同外部网络设备通信,速度要快很多。

Docker 容器网络就利用了这项技术。它在本地主机和容器内分别创建一个虚拟接口,并让它们彼此连通(这样的一对接口叫做 veth pair)。

二、如何自定义Docker网络参数

Docker 创建一个容器的时候,会执行如下操作:

  • 创建一对虚拟接口,分别放到本地主机和新容器中;

  • 本地主机一端桥接到默认的 docker0 或指定网桥上,并具有一个唯一的名字,如 veth65f9;

  • 容器一端放到新容器中,并修改名字作为 eth0,这个接口只在容器的命名空间可见;

  • 从网桥可用地址段中获取一个空闲地址分配给容器的 eth0,并配置默认路由到桥接网卡 veth65f9。

完成这些之后,容器就可以使用 eth0 虚拟网卡来连接其他容器和其他网络。

可以在 docker run 的时候通过 --net 参数来指定容器的网络配置,有4个可选值:

  • --net=bridge 这个是默认值,连接到默认的网桥。

  • --net=host 告诉 Docker 不要将容器网络放到隔离的命名空间中,即不要容器化容器内的网络。此时容器使用本地主机的网络,它拥有完全的本地主机接口访问权限。容器进程可以跟主机其它 root 进程一样可以打开低范围的端口,可以访问本地网络服务比如 D-bus,还可以让容器做一些影响整个主机系统的事情,比如重启主机。因此使用这个选项的时候要非常小心。如果进一步的使用 --privileged=true,容器会被允许直接配置主机的网络堆栈。

  • --net=container:NAME_or_ID 让 Docker 将新建容器的进程放到一个已存在容器的网络栈中,新容器进程有自己的文件系统、进程列表和资源限制,但会和已存在的容器共享 IP 地址和端口等网络资源,两者进程可以直接通过 lo 环回接口通信。

  • --net=none 让 Docker 将新容器放到隔离的网络栈中,但是不进行网络配置。之后,用户可以自己进行配置。

三、网络配置细节

用户使用 --net=none 后,可以自行配置网络,让容器达到跟平常一样具有访问网络的权限。通过这个过程,可以了解 Docker 配置网络的细节。

首先,启动一个 /bin/bash 容器,指定 --net=none 参数。

$ sudo docker run -i -t --rm --net=none base /bin/bash
root@63f36fc01b5f:/#

在本地主机查找容器的进程 id,并为它创建网络命名空间。

$ sudo 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 和子网掩码信息。

...

创建一对 “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 命令来在指定网络命名空间中进行配置,从而配置容器内的网络。