聊聊 Docker 1.9 的新网络特性

2016-01-18 15:19:17 +08:00
 AlaudaCloud

Docker 在 1.9 版本中引入了一整套的自定义网络命令和跨主机网络支持。这是 libnetwork 项目从 Docker 的主仓库抽离之后的一次重大变化。不论你是否已经注意到了, Docker 的网络新特性即将对用户的习惯产生十分明显的改变。
‌‌

libnetwork 和 Docker 网络

libnetwork 项目从 lincontainer 和 Docker 代码的分离早在 Docker 1.7 版本就已经完成了(从 Docker 1.6 版本的网络代码中抽离)。在此之后,容器的网络接口就成为了一个个可替换的插件模块。由于这次变化进行的十分平顺,作为 Docker 的使用者几乎不会感觉到其中的差异,然而这个改变为接下来的一系列扩展埋下了很好的伏笔。

概括来说, libnetwork 所做的最核心事情是定义了一组标准的容器网络模型( Container Network Model ,简称 CNM ),只要符合这个模型的网络接口就能被用于容器之间通信,而通信的过程和细节可以完全由网络接口来实现。

Docker 的容器网络模型最初是由思科公司员工 Erik 提出的设想,比较有趣的是 Erik 本人并不是 Docker 和 libnetwork 代码的直接贡献者。最初 Erik 只是为了扩展 Docker 网络方面的能力,设计了一个 Docker 网桥的扩展原型,并将这个思路反馈给了 Docker 社区。然而他的大胆设想得到了 Docker 团队的认同,并在与 Docker 的其他合作伙伴广泛讨论之后,逐渐形成了 libnetwork 的雏形。

在这个网络模型中定义了三个的术语: Sandbox 、 Endpoint 和 Network 。

如上图所示,它们分别是容器通信中『容器网络环境』、『容器虚拟网卡』和『主机虚拟网卡 /网桥』的抽象。

  1. Sandbox :对应一个容器中的网络环境,包括相应的网卡配置、路由表、 DNS 配置等。 CNM 很形象的将它表示为网络的『沙盒』,因为这样的网络环境是随着容器的创建而创建,又随着容器销毁而不复存在的;
  2. Endpoint :实际上就是一个容器中的虚拟网卡,在容器中会显示为 eth0 、 eth1 依次类推;
  3. Network :指的是一个能够相互通信的容器网络,加入了同一个网络的容器直接可以直接通过对方的名字相互连接。它的实体本质上是主机上的虚拟网卡或网桥。

这种抽象为 Docker 的 1.7 版本带来了十分平滑的过渡,除了文档中的三种经典『网络模式』被换成了『网络插件』,用户几乎感觉不到使用起来的差异。

直到 1.9 版本的到来, Docker 终于将网络的控制能力完全开放给了终端用户,并因此改变了连接两个容器通信的操作方式(当然, Docker 为兼容性做足了功夫,所以即便你不知道下面所有的这些差异点,仍然丝毫不会影响继续用过去的方式使用 Docker )。

Docker 1.9 中网络相关的变化集中体现在新的『 docker network 』命令上。

$ docker network --help

Usage:  docker network [OPTIONS] COMMAND [OPTIONS]

Commands:
  ls                       List all networks
  rm                       Remove a network
  create                   Create a network
  connect                  Connect container to a network
  disconnect               Disconnect container from a network
  inspect                  Display detailed network information

简单介绍一下这些命令的作用。

1 、 docker network ls

这个命令用于列出所有当前主机上或 Swarm 集群上的网络:

$ docker network ls
NETWORK ID          NAME                DRIVER
6e6edc3eee42        bridge              bridge
1caa9a605df7        none                null
d34a6dec29d3        host                host

在默认情况下会看到三个网络,它们是 Docker Deamon 进程创建的。它们实际上分别对应了 Docker 过去的三种『网络模式』:

  1. bridge :容器使用独立网络 Namespace ,并连接到 docker0 虚拟网卡(默认模式)
  2. none :容器没有任何网卡,适合不需要与外部通过网络通信的容器
  3. host :容器与主机共享网络 Namespace ,拥有与主机相同的网络设备

在引入 libnetwork 后,它们不再是固定的『网络模式』了,而只是三种不同『网络插件』的实体。说它们是实体,是因为现在用户可以利用 Docker 的网络命令创建更多与默认网络相似的网络,每一个都是特定类型网络插件的实体。

02 、 docker network create / docker network rm

这两个命令用于新建或删除一个容器网络,创建时可以用『– driver 』参数使用的网络插件,例如:

$ docker network create – driver=bridge br0
b6942f95d04ac2f0ba7c80016eabdbce3739e4dc4abd6d3824a47348c4ef9e54

现在这个主机上有了一个新的 bridge 类型的 Docker 网络:

$ docker network ls
NETWORK ID NAME DRIVER
b6942f95d04a br0 bridge

Docker 容器可以在创建时通过『– net 』参数指定所使用的网络,连接到同一个网络的容器可以直接相互通信。
当一个容器网络不再需要时,可以将它删除:

$ docker network rm br0

03 、 docker network connect / docker network disconnect

这两个命令用于动态的将容器添加进一个已有网络,或将容器从网络中移除。为了比较清楚的说明这一点,我们来看一个例子。

参照前面的 libnetwork 容器网络模型示意图中的情形创建两个网络:

$ docker network create – driver=bridge frontend
$ docker network create – driver=bridge backend

然后运行三个容器,让第一个容器接入 frontend 网络,第二个容器同时接入两个网络,三个容器只接入 backend 网络。首先用『– net 』参数可以很容易创建出第一和第三个容器:

$ docker run -td – name ins01 – net frontendindex.alauda.cn/library/busybox
$ docker run -td – name ins03 – net backendindex.alauda.cn/library/busybox

如何创建一个同时加入两个网络的容器呢?由于创建容器时的『– net 』参数只能指定一个网络名称,因此需要在创建过后再用 docker network connect 命令添加另一个网络:

$ docker run -td – name ins02 – net frontendindex.alauda.cn/library/busybox
$ docker network connect backend ins02

现在通过 ping 命令测试一下这几个容器之间的连通性:

$ docker exec -it ins01 ping ins02
可以连通
$ docker exec -it ins01 ping ins03
找不到名称为 ins03 的容器
$ docker exec -it ins02 ping ins01
可以连通
$ docker exec -it ins02 ping ins03
可以连通
$ docker exec -it ins03 ping ins01
找不到名称为 ins01 的容器
$ docker exec -it ins03 ping ins02
可以连通

这个结果也证实了在相同网络中的两个容器可以直接使用名称相互找到对方,而在不同网络中的容器直接是不能够直接通信的。此时还可以通过 docker networkdisconnect 动态的将指定容器从指定的网络中移除:

$ docker network disconnect backend ins02
$ docker exec -it ins02 ping ins03
找不到名称为 ins03 的容器

可见,将 ins02 容器实例从 backend 网络中移除后,它就不能直接连通 ins03 容器实例了。

04 、 docker network inspect

最后这个命令可以用来显示指定容器网络的信息,以及所有连接到这个网络中的容器列表:

$ docker network inspect bridge
[{
“ Name ”:” bridge ”,
“ Id ”: “ 6e6edc3eee42722df8f1811cfd76d7521141915b34303aa735a66a6dc2c853a3 ”,
“ Scope ”: “ local ”,
“ Driver ”:” bridge ”,
“ IPAM ”: {
“ Driver ”:” default ”,
“ Config ”: [{“ Subnet ”: “ 172.17.0.0/16 ”}]
},
“ Containers ”: {
“ 3d77201aa050af6ec8c138d31af6fc6ed05964c71950f274515ceca633a80773 ”:{
“ EndpointID ”:” 0751ceac4cce72cc11edfc1ed411b9e910a8b52fd2764d60678c05eb534184a4 ″,
“ MacAddress ”:” 02:42:ac:11:00:02 ″,
“ IPv4Address ”: “ 172.17.0.2/16 ”,
“ IPv6Address ”:””
}
},
…

值得指出的是,同一主机上的每个不同网络分别拥有不同的网络地址段,因此同时属于多个网络的容器会有多个虚拟网卡和多个 IP 地址。

由此可以看出, libnetwork 带来的最直观变化实际上是: docker0 不再是唯一的容器网络了,用户可以创建任意多个与 docker0 相似的网络来隔离容器之间的通信。然而,要仔细来说,用户自定义的网络和默认网络还是有不一样的地方。

默认的三个网络是不能被删除的,而用户自定义的网络可以用『 docker networkrm 』命令删掉;

连接到默认的 bridge 网络连接的容器需要明确的在启动时使用『– link 』参数相互指定,才能在容器里使用容器名称连接到对方。而连接到自定义网络的容器,不需要任何配置就可以直接使用容器名连接到任何一个属于同一网络中的容器。这样的设计即方便了容器之间进行通信,又能够有效限制通信范围,增加网络安全性;

在 Docker 1.9 文档中已经明确指出,不再推荐容器使用默认的 bridge 网卡,它的存在仅仅是为了兼容早期设计。而容器间的『– link 』通信方式也已经被标记为『过时的』功能,并可能会在将来的某个版本中被彻底移除。

Docker 的内置 Overlay 网络

内置跨主机的网络通信一直是 Docker 备受期待的功能,在 1.9 版本之前,社区中就已经有许多第三方的工具或方法尝试解决这个问题,例如 Macvlan 、 Pipework 、 Flannel 、 Weave 等。虽然这些方案在实现细节上存在很多差异,但其思路无非分为两种:二层 VLAN 网络和 Overlay 网络。

简单来说,二层 VLAN 网络的解决跨主机通信的思路是把原先的网络架构改造为互通的大二层网络,通过特定网络设备直接路由,实现容器点到点的之间通信。这种方案在传输效率上比 Overlay 网络占优,然而它也存在一些固有的问题。

相比之下, Overlay 网络是指在不改变现有网络基础设施的前提下,通过某种约定通信协议,把二层报文封装在 IP 报文之上的新的数据格式。这样不但能够充分利用成熟的 IP 路由协议进程数据分发,而且在 Overlay 技术中采用扩展的隔离标识位数,能够突破 VLAN 的 4000 数量限制,支持高达 16M 的用户,并在必要时可将广播流量转化为组播流量,避免广播数据泛滥。因此, Overlay 网络实际上是目前最主流的容器跨节点数据传输和路由方案。

在 Docker 的 1.9 中版本中正式加入了官方支持的跨节点通信解决方案,而这种内置的跨节点通信技术正是使用了 Overlay 网络的方法。

说到 Overlay 网络,许多人的第一反应便是:低效,这种认识其实是带有偏见的。 Overlay 网络的实现方式可以有许多种,其中 IETF (国际互联网工程任务组)制定了三种 Overlay 的实现标准,分别是:虚拟可扩展 LAN ( VXLAN )、采用通用路由封装的网络虚拟化( NVGRE )和无状态传输协议( SST ),其中以 VXLAN 的支持厂商最为雄厚,可以说是 Overlay 网络的事实标准。

而在这三种标准以外还有许多不成标准的 Overlay 通信协议,例如 Weave 、 Flannel 、 Calico 等工具都包含了一套自定义的 Overlay 网络协议( Flannel 也支持 VXLAN 模式),这些自定义的网络协议的通信效率远远低于 IETF 的标准协议[5],但由于他们使用起来十分方便,一直被广泛的采用而造成了大家普遍认为 Overlay 网络效率低下的印象。然而,根据网上的一些测试数据来看,采用 VXLAN 的网络的传输速率与二层 VLAN 网络是基本相当的。
解除了这些顾虑后,一个好消息是, Docker 内置的 Overlay 网络是采用 IETF 标准的 VXLAN 方式,并且是 VXLAN 中普遍认为最适合大规模的云计算虚拟化环境的 SDN Controller 模式。

到目前为止一切都是那么美好,大家是不是想动手尝试一下了呢?

且慢,待我先稍泼些冷水。在许多的报道中只是简单的提到,这一特性的关键就是 Docker 新增的『 overlay 』类型网卡,只需要用户用『 docker networkcreate 』命令创建网卡时指定『– driver=overlay 』参数就可以。看起来就像这样:

$ docker network create – driver=overlay ovr0

但现实的情况是,直到目前为止, Docker 的 Overlay 网络功能与其 Swarm 集群是紧密整合的,因此为了使用 Docker 的内置跨节点通信功能,最简单的方式就是采纳 Swarm 作为集群的解决方案。这也是为什么 Docker 1.9 会与 Swarm1.0 同时发布,并标志着 Swarm 已经 Product-Ready 。此外,还有一些附加的条件:

我们先不解释为什么必须使用 Swarm ,稍后大家很快就会发现原因。假设上述条件的 1 和 3 都是满足的,接下来就需要建立一个外部配置存储服务,为了简便起见暂不考虑高可用性,可以采用单点的服务。

以 Consul 为例,用 Docker 来启动它,考虑到国内访问 Docker Hub 比较慢,建议采用『灵雀云』的 Docker 镜像仓库:

$ docker run -d \
– restart=” always ” \
– publish=” 8500:8500 ″ \
– hostname=” consul ” \
– name=” consul ” \
index.alauda.cn/sequenceiq/consul:v0.5.0-v6 -server -bootstrap

如果使用 Etcd ,可以用下面的命令启动容器,同样可以用『灵雀云』的 Docker 镜像仓库:

$ docker run -d \
– restart=” always ” \
– publish=” 2379:2379 ″ \
– name=” etcd ” \
index.alauda.cn/googlecontainer/etcd:2.2.1 etcd \
-name etcd0-advertise-client-urls http://<Etcd 所在主机 IP>:2379 \
-listen-client-urlshttp://0.0.0.0:2379 -initial-cluster-state new

然后修改每个主机 Docker 后台进程启动脚本里的『 DOCKER_OPTS 』变量内容,如果是 Consul 加上下面这两项:

– cluster-store=consul://<Consul 所在主机 IP>:8500 -– cluster-advertise=eth1:2376

如果是 Etcd 则加上:

– cluster-store=etcd://<Etcd 所在主机 IP>:2379/store-– cluster-advertise=eth1:2376

然后重启每个主机的 Docker 后台进程,一切准备就绪。当然,由于修改和重启 Docker 后台进程本身是比较麻烦的事情,如果用户业务可能会使用到跨节点网络通信,建议在架设 Docker 集群的时候就事先准备配置存储服务,然后直接在添加主机节点时就可以将相应参数加入到 Docker 的启动配置中了。

至于配置存储服务的运行位置,通常建议是与运行业务容器的节点分开,使用独立的服务节点,这样才能确保所有运行业务容器的节点是无状态的,可以被平等的调度和分配运算任务。

接下来到了创建 Overlay 网络的时候,问题来了,我们要建的这个网络是横跨所有节点的,也就是说在每个节点都应该有一个名称、 ID 和属性完全一致的网络,它们之间还要相互认可对方为自己在不同节点的副本。如何实现这种效果呢?目前的 Docker network 命令还无法做到,因此只能借助于 Swarm 。

构建 Swarm 集群的方法在这里不打算展开细说,只演示一下操作命令。为了简便起见,我们使用 Swarm 官方的公有 token 服务作为节点组网信息的存储位置,首先在任意节点上通过以下命令获取一个 token :

$ docker run – rm swarm create 6856663cdefdec325839a4b7e1de38e8

任意选择其中一个节点作为集群的 Master 节点,并在主机上运行 Swarm Master 服务:

$ docker run -d -p 3375:2375 swarm manage token://<前面获得的 token 字符串>

在其他作为 Docker 业务容器运行的节点上运行 Swarm Agent 服务:

$ docker run -d swarm join – addr=<当前主机 IP>:2375token://<前面获得的 token 字符串>

这样便获得了一个 Swarm 的集群。当然,我们也可以利用前面已经建立的 Consul 或 Etcd 服务替代官方的 token 服务,只需稍微修改启动参数即可,具体细节可以参考 Swarm 的文档。

Swarm 提供与 Docker 服务完全兼容的 API ,因此可以直接使用 docker 命令进行操作。注意上面命令中创建 Master 服务时指定的外部端口号 3375 ,它就是用来连接 Swarm 服务的地址。现在我们就可以创建一个 Overlay 类型的网络了:

$ docker -H tcp://<Master 节点地址>:3375network create – driver=overlay ovr0

这个命令被发送给了 Swarm 服务, Swarm 会在所有 Agent 节点上添加一个属性完全相同的 Overlay 类型网络。也就是说,现在任意一个 Agent 节点上执行『 docker networkls 』命令都能够看到它,并且使用『 docker network inspect 』命令查看它的信息时,将在每个节点上获得完全相同的内容。通过 Docker 连接到 Swarm 集群执行 network ls 命令就可以看到整个集群网络的全貌:

$ docker -H tcp://<Master 节点地址>:3375network ls
$ docker network ls
NETWORK ID NAME DRIVER
445ede8764da swarm-agent-1/bridge bridge
2b9c1c73cc5f swarm-agent-2/bridge bridge
…
90f6666a9c5f ovr0 overlay

在 Swarm 的网络里面,每个网络的名字都会加上节点名称作为前缀,但 Overlay 类型的网络是没有这个前缀的,这也说明了这类网络是被所有节点共有的。
下面我们在 Swarm 中创建两个连接到 Overlay 网络的容器,并用 Swarm 的过滤器限制这两个容器分别运行在不同的节点上。

$ docker -H tcp://<Master 节点地址>:3375 run-td – name ins01 – net ovr0 – env=” constraint:node==swarm-agent-1 ″ index.alauda.cn/library/busybox
$ docker -H tcp://<Master 节点地址>:3375 run-td – name ins02 – net ovr0 – env=” constraint:node==swarm-agent-2 ″ index.alauda.cn/library/busybox

然后从 ins01 容器尝试连接 ins02 容器:

$ docker -H tcp://<Master 节点地址>:3375 exec-it ins01 ping ins02
可以连通

至此,我们就已经在 Docker 的 Overlay 网络上成功的进行了跨节点的数据通信。

想简单点?用灵雀云吧

不知大家发现了么有,不论是过去的 Pipework 、 Flannel 、 Weave 方式,还是 Docker 1.9 内置的 Overlay 网络,尽管所有的这些方案都宣传自己足够的简单易用,构建跨节点通信的容器依然是一件不得已而为之的事情。

之所以这样说,是因为在现实应用场景中往往由于服务直接错综复杂的连接,需要相互通信的容器数量远远的超过的单个主机节点所能够承受的容量,这才使得我们不得不在现有的基础实施上自行维护一套服务机制,将容器间的通信扩展到多个节点上。但维护这些跨节点通信基础设施本身是不为企业带来实质的业务价值的!

在当下,云基础设施迅速发展的大环境已经为许多企业创造了弯道超车的机遇:通过采用云平台,企业省去了自己购置和组建大规模网络和计算资源以及管理庞大运维团队的投入,只需专注于业务本身的创意和设计就能收获巨大的利润。与此同时,容器作为新一代的服务部署和调度工具被越来越广泛的采用,而解决容器通信问题本不应该成为企业需要关注的要点,现在却成为扩大服务规模时横在眼前无法忽视的阻碍。

灵雀云作为容器云服务平台中的佼佼者,为容器的使用者屏蔽了容器运行节点的细节,使得用户完全感觉不到跨节点通信所带来的差异。那么如何在灵雀云中运行两个容器,并使之相互通信呢?

首先安装灵雀的 alauda 命令行工具,使用『 alauda login 』命令登陆。

$ alauda login
Username: fanlin
Password: *灵雀密码*
[alauda] Successfully logged in as fanlin.
[alauda] OK

然后运行两个容器,前者运行 Nginx 服务,后者用于测试与前者的连接。

$ alauda service run – publish=80/http web index.alauda.cn/library/nginx:1.9.9
[alauda] Creating and starting service “ web ”
[alauda] OK
$ alauda service run client index.alauda.cn/library/busybox:latest
[alauda] Creating and starting service “ client ”
[alauda] OK

尝试从 client 容器实例访问 web 容器实例提供的 HTTP 服务,注意灵雀云自动生成的地址格式。

$ alauda service exec client wget -O- http://web-fanlin.myalauda.cn
Password for fanlin@exec.alauda.cn:
…
<html>
<head>
<title>Welcome to nginx!</title>
<style>
…

可以看到返回了 Nginx 默认首页的内容,证明这两个容器之间可以连通。除了建立单个的任务,还可以使用『 alauda compose 』命令快速建立多个容器组成的服务集合。

那么着两个容器分别是运行在哪个主机上的?管他呢,灵雀云已经将这些底层细节统统藏起来了,所以只管往云上继续创建更多的服务吧,再也不用担心主机资源耗尽,面对跨节点怎么通信的问题啦。

作者简介:林帆, ThoughtWorks 公司软件工程师及 DevOps 咨询师,具有丰富的持续交付和服务器运维自动化实践经验,专注于 DevOps 和容器技术领域。在 InfoQ 、 CSDN 网站和《程序员》杂志上发表有多篇相关领域文章,著有《 CoreOS 实践之路》一书。

3204 次点击
所在节点    Docker
0 条回复

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/251600

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX