网络无处不在,每当基础设施出现问题,被抱怨的通常是网络。很大一部分原因是,网络负责连接一切——无网络,无APP。
11.1 Docker网络——简介
Docker在容器内部运行应用,这些应用之间的交互依赖于大量不同的网络,这意味着Docker 需要强大的网络功能。
幸运的是,Docker对于容器之间、容器与外部网络和VLAN之间的连接均有相应的解决方案。后者对于那些需要跟外部系统(如虚拟机和物理机)的服务打交道的容器化应用来说至关重要。
Docker网络架构源自一种叫做容器的网络模型(CNM)的方案,该方案是开源的并且支持插式连接。Libnetwork式Docker对CNM的一种实现,提供了Docker核心网络架构的全部功能。不同的驱动可以通过插拔的方式接入Libnerwork来提供定制化的网络拓扑。
为了实现开箱即用的效果,Docker封装了一系列本地驱动、覆盖了大部分常见的网络需求。其中包括单机桥接网络、多级覆盖网络,并且支持接入现有的VLAN。Docker生态系统中的合作伙伴通过提供驱动的方式,进一步拓展了Docker的网络功能。
最后要说的是,Libnetwork提供了本地服务发现和基础的容器负载均衡的解决方案。
11.2 Docker网络——详解
11.2.1 基础理论
在顶层设计中,Docker网络架构由3个主要部分构成:CNM、Libnetwork和驱动。
其中CNM是涉及标准,在CNM中,规定了Docker网络架构的基础组成要素;Libnetwork是CNM的具体实现,并且被Docker采用。Libnetwork通过Go语言编写,并实现了CNM中列举的核心组件。驱动通过实现特定网络拓扑的方式来拓展该模型的能力。
1.CNM
CNM定义了3个基本要素:沙盒(Sandbox)、终端(Endpoint)、和网络(Network)。
沙盒是一个独立的网络栈。其中包括以太网接口、端口、路由表以及DNS配置;终端就是虚拟网络接口。就像是普通的网络接口一样,终端主要职责是负责创建连接。在CNM中,终端负责将沙盒连接到网络;网络就是 802.1d网桥(交换机)的软件实现。因此,网络就是需要交互的终端的集合,并且终端之间相互独立。
Docker环境中最小的调度单位就是容器,而CNM也恰如其名,负责为容器提供网络功能。下图展示了CNM组件时如何与容器进行关联的——沙盒放置在容器内部,为容器提供给网络连接。
上图可以看到,容器A只有一个接口(终端)并连接到网络A。容器B有两个接口(终端)并且分别接入了网络A和B。容器A和B之间时可以相互通信的,因为都接入了网络A。但是,如果没有三层路由器的支持,容器B的两个终端之间时不能通信的。
这时候可以把容器类比为我们现实中的PC,一个PC可以有多个网卡接口(终端),所以可以接入到不同网络中。接入到同一网络中的两个容器可以相互通信,接入不同网络的容器必须依赖三层路由才能实现通信。
- Libnetwork
CNM是设计规范文档,Libnetwork是标准的实现。在早期,网络部分的代码都存在daemon中,daemon显得非常臃肿。后来Docker将该网络部分从daemon中拆分,并重构了一个叫做Libnetwork的外部类库。现在,Docker核心网络架构代码都在Libnetwork当中。
Libnetwork除了实现CNM中定义的三个组件外,还实现了本地服务发现、基于 Ingress的容器负载均衡以及网络控制层和管理层功能。
- 驱动
如果说Libnetwork实现了控制层和管理层功能,那么驱动就负责实现数据层。比如网络连通性和隔离性就是由驱动来处理的,驱动层实际创建网络对象也是如此。
Docker封装了若干内置驱动,通常被叫做原生驱动或者本地驱动。在Linux中包括Bridge、overlay以及Macvlan,在Windows上包括NAT、Overlay、Transport以及L2 Bridge。当然也有第三方的一些驱动。
每个驱动都负责其上所有网络资源的创建和管理。为了满足复杂且不固定的环境需求,Libnetwork支持同时激活多个网络驱动。这意味着Docker环境可以支持一个庞大的异构网络。
11.2.2 单机桥接网络
最简单的Docker网络就是单机桥接网络了。从名称中可以看到两点。
- 单机意味着该网络只能在单个Docker主机上运行,并且只能与所在Docker主机上的容器进行连接。
- 桥接意味着这是 802.1d桥接的一种实现(二层交换机)。
Linux Docker创建单机桥接网络采用内置的桥接驱动,而Windows Docker创建时使用内置的NAT驱动。实际上,着两种驱动工作起来毫无差异。
每个Docker主机都有一个默认的单机桥接网络。在Linux上网络名称为bridge,在Windows上叫做nat。除非读者通过命令创建容器的时候指定参数 --network ,否则默认情况下,新创建的容器都会连接到该网络上。
使用docker network ls 可以查看Docker主机的网络情况,一开始只有一个默认的网络。
[pangcm@docker01 ~]$ docker network ls
NETWORK ID NAME DRIVER SCOPE
7cb75625ceeb bridge bridge local
使用 docker network inspect 命令可以查看网络的更多内容。
在Linux主机中,Docker网络由Bridge驱动创建,而Bridge底层是基于Linux内核中久经考验达15年之久的Linux Bridge技术。这意味着Bridge是高性能并且非常稳定的。同时这还表示可以通过标准的Linux去查看这些网络,比如 ip link show 。
[pangcm@docker01 ~]$ ip link show docker0
3: docker0: mtu 1500 qdisc noqueue state DOWN mode DEFAULT group default
link/ether 02:42:01:12:07:d9 brd ff:ff:ff:ff:ff:ff
在Linux Docker主机之上,默认的 bridge 网络被映射到内核中为 docker0 的Linux网桥。所以,我们也可以通过 dokcer network inspect 命令观察到上面输出的内容。
我们除了可以使用默认的bridge网络之外,我们可以自己新建一个单机桥接的网络。使用 docker network create 命令创建。
docker network create -d bridge localnet
就这样,新网络创建成功,名为 localnet,我们可以使用docker network ls 去查看。和默认的bridge网络一样,新创建的网络一样在我们Docker 主机上新建一个Linux网桥。通过使用 brctl show 命令即可查看。
[pangcm@docker01 ~]$ brctl show
bridge name bridge id STP enabled interfaces
br-e6bca1080f16 8000.0242126f947e no
docker0 8000.0242011207d9 no
目前这两个网络都没有任何设备的接入,STP也没有开启。下面我们新建一个容器,并加入到新建的桥接网络中去。
docker container run -d --name test2 --network localnet alpine sleep 1d
就这样,新创建的容器加入了我们上面创建的网络中去了。可以通过 docker network inspect 命令去确认。
[pangcm@docker01 ~]$ docker network inspect localnet format
...
"Containers": {
"e1e87148a8a3b06cf3315991a0502b82314721877c425a52657e4b1ec72e74ed": {
"Name": "test2",
"EndpointID": "e8903620447d66e987ea36dc332c5f65ec7ea8bdb922dfd8e7d6565dde892dbf",
"MacAddress": "02:42:ac:13:00:02",
"IPv4Address": "172.19.0.2/16",
"IPv6Address": ""
}
},
...
可以看到容器 test2 已经加入到桥接网络 localnet上了,我们再次使用 brctl 查看下。
[pangcm@docker01 ~]$ brctl show
bridge name bridge id STP enabled interfaces
br-e6bca1080f16 8000.0242126f947e no vethbff0b42
docker0 8000.0242011207d9 no
前面我们有提到加入同一个桥接网络的不同容器是可以相互通信的(同一个局域网),事实上在我们自定义的网络中除了可以使用IP来进行通信之外,还可以使用容器名称去进行通信(默认的bridge不行)。原因是新容器都会注册到指定的Docker DNS 服务中,所以相同网络的容器可以解析其他容器的名称。下面我们来测试下吧。
##新建一个 test3 容器,加入localnet
docker container run -it --name test3 --network localnet alpine sh
##进入到容器里面,我们直接ping test2
ping test2
可以验证ping命令是生效的。
在上面,我们验证了同一个Docker主机同一个桥接网络之间的容器可以相互通信的。那么不同Docker主机上的桥接网络容器可以相互通信么?答案是可以的,怎么做呢。
这个我们前面已经验证过很多次了,就是用端口映射。把桥接网络中的容器端口映射到Docker主机上,然后访问Docker主机的端口流量就会转发到Docker的端口上了。例如我要把一个nginx 容器的80端口映射到Docker主机的8888端口上。
docker container run -d --name test4 --network localnet -p 8888:80 nginx
在其他网络上访问Docker主机的8888端口就能访问到Docker容器的80端口了。
11.2.3 多机覆盖网络
后面会有专门的介绍,有点复杂。
11.2.4 接入现有网络
能够将容器化应用连接到外部系统以及物理网络的能力是非常必要的。常见的例子是部分容器化的应用——应用中已容器化的部分需要与那些运行在物理网络和VLAN上未容器化部分进行通信。
Docker内置的Macvlan驱动(Windows上是Transparent)就是为此场景而生。通过为容器提供MAC和IP地址,让容器在物理网络上成为“一等公民”。(就是和物理网络同一个网段)
Macvlan的优点是性能优异,因为无须端口映射或者额外桥接,可以直接通过主机接口(或子接口)访问容器接口。但是,Macvlan的缺点是需要将主机网卡设置为混杂模式(Promiscuous Mode),这在大部分的公有云平台上是不允许的。所以Macvlan对于公司内部数据中心网络来说很好,但是在共有用上却不可行。
macvlan的实验这里不做介绍了,因为用得不多。
11.2.5 服务发现
作为核心网络架构,Libnetwork还提供了一些重要的网络服务。服务发现运行容器和Swarm服务通过名称相互定位。唯一的要求就是需要处于统一网络当中。
其底层实现是利用了Docker内置的DNS服务器,为每个容器提供DNS解析功能。这一点我们上面有提到过,DNS解析的过程和真实的DNS解析过程类似,本地解析器找不到域名的话会向Docker的DNS服务发起一个递归查询。
每个启动时使用了 --name 参数的Swarm服务或者独立容器,都会将自己的名称和IP地址注册到Docker DNS服务。这意味着容器和服务副本可以通过Docker DNS服务相互发现。但是,服务发现时受网络限制的。这意味着名称解析只对同一个网络中的容器和服务生效。如果两个容器在不同的网络,那么就不能相互解析了。
用户可以为Swarm服务和独立容器进行自定义的DNS配置。举个例子, --dns参数允许读者指定自定义的DNS服务列表,以防出现内置的Docker DNS服务器解析失败的情况。此外也可以使用 --dns-search 参数指定自定义查询时所使用的域名(例如当查询名称并非完整域名的时候)。
在Linux上,上述工作都是通过容器内部 /et/resolve.conf 文件内部增加条目来实现的。下面的例子会启动一个新的容器,并添加声名狼藉的 8.8.8.8 Google DNS服务器,同时指定 dockercerts.com 作为域名添加到非完整查询当中。
docker container run -it --name test5 --dns=8.8.8.8 \
--dns-search=dockercerts.com alpine sh
11.2.6 Ingress 网格
Swarm支持两种服务发布模式,两种模式均保证服务从集群外可访问。分别是:Ingress模式(默认)和Host模式。
通过Ingress模式发布的服务,可以保证从Swarm集群内任一节点(即使没有运行服务的副本)都能访问该服务;以Host模式发布的服务只能通过运行服务副本的节点来访问。
Ingress模式是默认方式,如果要使用Host模式发布服务,读者需要使用 --publish 参数的完整格式,并添加 mode=host。下面来看下怎么做。
docker service create -d --name svc1 \
--publish published=5000,target=80,mode=host nginx
上面就是完整的 --publish 参数示例。
在底层,Ingress模式采用名为 Service Mesh 或者 Swarm Mode Service Mesh 的四层路由网络来实现。书本在这里有个示例,这里不做演示,后面在第14章中有对应的实验。这里只给出书中的两个结论。
一是使用Ingress模式的服务,访问任意一个节点(即使没有运行对应的容器),Docker都能把流量转到实际运行容器的节点上,;二是并且如果存在多个运行中的副本,流量会均衡到每个副本上。
11.3 Docker网络——命令
- docker network ls 用于列出运行在本地Docker主机上的全部网络
- docker network create 创新的Docker网络。可以使用 -d 来指定网络的类型,如:
docker network create -d overlay overnet
- docker network inspect 提供 Docker网络的详细配置信息。
- docker network prune 删除Docker主机上全部未使用的网络
- docker network rm 删除Docker主机上指定的网络
11.4 本章小结
容器网络模型(CNM)是Docker网络架构的主要设计文档,它定义了Docker网络中用到的3个主要结构——沙盒、终端以及网络。
Libnetwork是开源库,采用Go编写,实现了CNM。Docker使用了该库,并且Docker网络架构的核心代码都在该库中。Libnetwork同时还提供了Docker网络控制层和管理层的功能。
驱动通过实现特定网络类型的方式拓展了Docker网络栈,例如桥接网络和覆盖网络。Docker内置了几种网络驱动,同时也支持第三方驱动。
单机桥接网络是基本的Docker网络类型,对于本地开发和小型应用来说也十分适用。单机桥接网络不可拓展,并且对外发布服务依赖于端口映射。Linux Docker使用内置的Bridge驱动实现了单机桥接网络。
覆盖网络是当下流行的方式,并且是一种出色的多机容器网络方案。下一章会介绍。
Macvlan启动允许容器接入现存物理网络以及VLAN。通过赋予容器MAC和IP地址的方式,让容器称为网络中的一等公民。不过,该驱动需要主机的NIC支持混杂模式,这意味着该驱动在公有云上没法使用。
Docker使用Libnetwork实现了基础服务发现功能,同时还实现了服务网格,支持对入站流量实现容器级别的负载均衡。