【编者按】本文作者为 Mesosphere 开发大使 Michael Hausenblas,主要介绍配置 Docker 单主机网络的基本知识,由 OneAPM工程师编译。想要了解有关 Docker 网络的更多信息,包括多主机网络介绍,请查看本文作者 Michael Hausenblas 撰写的报告——《Docker 网络与服务探索》,点此下载该报告。
当你开始扩大 Docker 的应用范围时,忽然发现,你需要了解很多关于网络的知识。作为 Docker 网络的入门介绍,本文将从小处着手,首先你要考虑如何管理容器之间的连接。Docker 容器需要有个主机才能运行,该主机既可以是实体机器(例如:企业数据中心的裸机服务器),也可以是 on-prem 或云中的虚拟机。如图一所示,主机上会运行 Docker 后台程序与 Docker 客户端。一方面,你可以与 Docker 注册表交互(pull 或 push Docker 图片);另一方面,也可以启动、停止或监视容器。
主机与容器之间的关系为 1:N。也就是说,一个主机上通常会运行着多个容器。例如,Facebook 报告称,取决于机器的规模,每个主机上平均运行着10到40个容器。此外,Mesosphere 对裸机进行的多项负载测试结果显示,在每个主机上运行250以内的容器是可能的。
不过,无论是单主机部署模式,还是采用了机器集群,你都不可避免地需要面对网络:
对大多数单主机部署而言,问题可以归结为通过共享卷组(shared volume)进行数据交换还是通过网络(基于 HTTP 或其他协议)进行数据交换。尽管 Docker 数据卷组简单易用,但也会导致紧密耦合,这意味着更难将单主机部署转变为多主机部署。通常,共享卷组的优势在于数据处理速度。
在多主机部署中,你需要考虑两个方面:单个主机内的容器如何相互交流?不同主机之间的容器又如何交流?性能考虑与安全要素都将影响你的设计决定。当单主机的容量无法满足需求(请参考前文关于单主机承载的平均容器数及最大容器数的讨论),或打算使用诸如 Apache Spark, HDFS 或 Cassandra 之类的分布式系统时,多主机部署就变得必不可少了。
分布式系统与数据局部性
使用分布式系统(进行计算或存储)的主要好处通常来自并发处理,后者常常伴随数据局部性。此处,数据局部性是指将代码发布至数据所处位置的原则,而不是传统的那样倒过来。试思考以下情况:如果你的数据集大小以 TB 计,而代码量以 MB 计,在集群中移动代码其实比将多个 TB 的数据移至中央处理区更加高效。除了获得并发处理的优势,分布式系统还具备更好的容错性,因为系统的一些部分仍或多或少保留了独立运转的能力。
简而言之,Docker 网络是 Docker 提供的原生容器 SDN 解决方法。概括来说,Docker 网络提供了四种可选模式:桥接模式、主机模式、容器模式以及无网络模式。本文将逐一讨论这四种模式与单主机配置相关的内容,并在文末总结时提及诸如安全之类的一般话题。
在此模式下(参考图2),Docker 后台程序会创建 docker0,一个虚拟的以太网桥,用于自动转发与之连接的任意网络接口间的数据包。默认情况下,该后台进程会创建一对对等接口(peer interface),分派其一为容器的 eth0 接口,另一个则分派在主机的命名空间下,同时从私有 IP 地址范围中为该桥接分配一个 IP 地址或子网络,由此,同一主机上的所有容器都连接到此内网中。
实例1. 运行中的 Docker 桥接模式网络
$ docker run -d -P --net=bridge nginx:1.9.1$ docker ps
CONTAINER ID IMAGE COMMAND CREATED
STATUS PORTS NAMES17d447b7425d nginx:1.9.1 nginx -g 19 seconds ago
Up 18 seconds 0.0.0.0:49153->443/tcp, 0.0.0.0:49154->80/tcp trusting_feynman
注意
由于桥接模式是 Docker 默认的连接模式,在示例1中,你也可以使用docker run -d -P nginx:1.9.1。如果你没有使用 -P 参数(用于发布容器的所有公开端口),也没有使用 -p host_port:container_port(用于发布特定的端口),IP 数据包在主机之外就无法路由至容器。
该模式能有效禁止 Docker 容器的网络隔离。因为容器共享了主机的网络命名空间,它会直接暴露于公网。因此,你需要通过端口映射(port mapping)开展协调工作。
实例2. 运行中的 Docker 主机模式网络
$ docker run -d --net=host ubuntu:14.04 tail -f /dev/null$ ip addr | grep -A 2 eth0:2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 9001 qdisc mq state UP group default qlen 1000 link/ether 06:58:2b:07:d5:f3 brd ff:ff:ff:ff:ff:ff
inet **10.0.7.197**/22 brd 10.0.7.255 scope global dynamic eth0
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED
STATUS PORTS NAMES
b44d7d5d3903 ubuntu:14.04 tail -f 2 seconds ago
Up 2 seconds jovial_blackwell
$ docker exec -it b44d7d5d3903 ip addr2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 9001 qdisc mq state UP group default qlen 1000 link/ether 06:58:2b:07:d5:f3 brd ff:ff:ff:ff:ff:ff
inet **10.0.7.197**/22 brd 10.0.7.255 scope global dynamic eth0
正如实例2所示,容器与主机的 IP 地址是一致的,同为 10.0.7.197。
在图3中,在使用主机模式时,容器直接继承了其所在主机的 IP 地址。该模式相比桥接模式运转更迅速(因为不存在路由的开销),但是它直接将容器暴露于公网中,而后者包含了诸多安全隐患。
在此模式下,Docker 会重用其他容器的网络命名空间。通常,如果你想提供客制化的网络堆栈,该模式最为有用。该模式也为Kubernetes网络所采用。
实例3. 运行中的 Docker 容器模式网络
$ docker run -d -P --net=bridge nginx:1.9.1$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS
PORTS NAMES
eb19088be8a0 nginx:1.9.1 nginx -g 3 minutes ago Up 3 minutes0.0.0.0:32769->80/tcp,0.0.0.0:32768->443/tcp admiring_engelbart
$ docker exec -it admiring_engelbart ip addr8: eth0@if9: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 9001 qdisc noqueue state UP group default link/ether 02:42:ac:11:00:03 brd ff:ff:ff:ff:ff:ff
inet **172.17.0.3**/16 scope global eth0
$ docker run -it --net=container:admiring_engelbart ubuntu:14.04 ip addr...8: eth0@if9: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 9001 qdisc noqueue state UP group default link/ether 02:42:ac:11:00:03 brd ff:ff:ff:ff:ff:ff
inet **172.17.0.3**/16 scope global eth0
结果(如实例3所示)正如我们所想:通过 –net=container 启动的第二个容器与第一个容器拥有相同的 IP 地址,即自动分配的admiring_engelbart,也即 172.17.0.3。
该模式将容器置于其网络堆栈之内,但并不进行配置。由此,断绝了网络连接,主要适用于以下两种情况:不需要网络的容器(例如:向磁盘卷组进行写入的批量作业),以及当你想配置自定义网络时。
实例4. 运行中的 Docker 无网模式
$ docker run -d -P --net=none nginx:1.9.1$ docker ps
CONTAINER ID IMAGE COMMAND CREATED
STATUS PORTS NAMES
d8c26d68037c nginx:1.9.1 nginx -g 2 minutes ago
Up 2 minutes grave_perlman
$ docker inspect d8c26d68037c | grep IPAddress "IPAddress": "", "SecondaryIPAddresses": null,
正如实例4所见,并未配置网络连接——这正是我们所期望的。
你可以在此Docker 文档中了解有关网络以及配置选项的更多知识。
注意
本文提及的所有 Docker 指令都在 CoreOS 环境中实践过,Docker 客户度与主机端的版本号均为 1.7.1。
除了上文提及的四种 Docker 单主机网络模式,你还应了解以下几个方面的内容(这些内容也都与多主机部署密切相关)。
分配 IP 地址
当容器数量变化频繁且涉及的数量较多时,手动分配 IP 地址是不可行的。桥接模式在一定程度上能解决该问题。为了避免本地网络中的 ARP (Address Resolution Protocol,地址解析协议)冲突,Docker 后台进程会根据分配的 IP 地址随机生成 MAC 地址。
分配端口
你会发现自己不是处于固定端口分配阵营,就是动态端口分配阵营。这可以是随服务或应用而变的策略,也可以是全局策略,但是,你必须下定决心。记住,在桥接模式下,Docker 能自动分配可路由的(UDP 或 TCP)端口。
网络安全
默认情况下,Docker 启用了容器间通讯(这意味着默认设置为 –icc=true),也就是说,同一主机上的容器可以毫无限制地相互交流,这有可能导致拒绝服务(DOS)攻击。此外,Docker 通过 –ip_forward 与 –iptables 标记控制容器与外部世界的交流。你应该学习这些标记的默认值,并让公司安全团队也有所了解,同时在 Docker 后台进程的设置中体现你的认知。对了,由 StackEngine 的 Boyd Hemphill 完成的 Docker 安全分析也是很好的学习材料。
另一个需要考虑的网络安全问题是在线加密。然而,在撰写本文时,针对这一问题的解决方案非常有限。只有两个系统提供了开箱即用的解决方案:使用 NaCl 的 Weave 以及包含基于 TLS 设置的 OpenVPN。据笔者从 Docker 安全主管 Diogo Mónica 处得到的消息,在线加密可能从 1.9版本后开始提供。
最后,建议阅读 Adrian Mouat 撰写的《Docker 使用入门》,该文档详细介绍了有关网络安全的方方面面。
小贴士
自动 Docker 安全检查
若要在生产环境中自动检查部署 Docker 容器的常见安全问题,笔者强烈推荐使用 The Docker Bench for Security。