编配(orchestration)是一个没有严格定义的概念。这个概念大概描述了自动配置,协作和管理服务的功能。在Docker的世界里,编配用来描述一组实践过程,这个过程会管理运行在多个Docker容器里的应用,而这些Docker容器有可能运行在多个宿主机上。Docker对编配的原生支持非常弱,不过整个社区围绕编配开发和集成了很多很棒的工具。

在现在的生态环境里,已经围绕Docker构建和集成了很多的工具。一些工具只是简单地将多个容器快捷地"连"在一起,使用简单的组合来构建应用程序栈。另外一些工具提供了在更大规模多个Docker宿主机上进行协作的能力,以及复杂的调度和执行能力。

我们将关注一下3个领域:

** 简单的容器编配。这部分会介绍Docker Compose。Docker Compose(之前的Fig)是由Orchard团队开发的开
源Docker编配工具,后来2014年被Docker公司收购。整个工具用Python编写,遵守Apache 2.0许可。

** 分布式服务发现。这部分会介绍Consul。Consul使用Go语言开发,以MPL 2.0许可授权开源。这个工具提供了
分布式且高可用的服务发现功能。

** Docker的编配和集群。这部分将介绍Swarm。Swarm是一个开源的,基于Apache 2.0许可证发布的软件。它用
Go语言编写,由Docker公司团队开发。

1. Docker Compose

现在先来熟悉一下Docker Compose。使用Docker Compose,可以用一个YAML文件定义一组要启动的容器,以及容器运行时的属性。Docker Compose称这些容器为"服务",像这样定义:

容器通过某些方法并指定一些运行时的属性来和其它容器产生交互。
 

下面会介绍如何安装Docker Compose,以及如何使用Docker Compose构建一个简单的多容器应用程序栈。

1.1 安装Docker Compose

现在开始安装Docker Compose。Docker Compose目前可以在Linux,Window和OS X 上使用。可以通过直接安装可执行包来安装,或者通过Docker Toolbox安装,也可以通过Python Pip包来安装。

为了在Linux上安装Docker Compose,可以从GitHub下载Docker Compose的可执行包,并让其执行。和Docker一样,Docker Compose目前只能安装在64位Linux上。可以使用curl命令来完成安装,代码清单:

** 在Linux上安装Docker Compose
$ sudo bash -c "curl -L https://github.com/docker/compose/releases/download/1.5.0/docker-compose-`uname -s`-`uname -m` > 
	/usr/local/bin/docker-compose"
$ sudo chmod +x /usr/local/bin/docker-compose

这个命令会从GitHub下载docker-compose可执行程序并安装到/usr/local/bin目录中。之后使用chmod命令确保可执行程序docker-compose可执行。

如果是在OS X上,Docker Toolbox已经包含了Docker Compose,或者可以像如下代码清单,进行安装:

** 在OS X上安装Docker Compose
$ sudo bash -c "curl -L https://github.com/docker/compose/releases/download/1.5.0/docker-compose-Darwin-x86_64 > /usr/local/bin/docker-compose"
$ sudo chmod +x /usr/local/bin/docker-compose

如果是在Windows平台上,也可以用Docker Toolbox,里面包含了Docker Compose。

如果是在其它平台上或者偏好使用包来安装,Compose也可以作为Python包来安装。这需要预先安装Python-Pip工具,保证存在pip命令。这个命令在大部分Red Hat,Debian或者Ubuntu发行版里,都可以通过python-pip包安装,代码清单如下:

** 通过pip安装Docker Compose
$ sudo pip install -U docker-compose

安装好 docker-compose可执行程序后,就可以通过使用--version标志调用docker-compose命令来测试其可以正常工作,代码清单:

** 测试Docker Compose是否工作
$ docker-compose --version

注意:如果在1.3.0之前的版本升级而来,那么需要将容器格式也升级到1.3.0版本,这可以通过docker-compose migrate-to-labels命令来实现。

1.2 获取示例应用

为了演示Compose是如何工作的,这里使用一个Python Flask应用作为例子,这个例子使用了以下两个容器。

** 应用容器,运行Python示例程序。
** Redis容器,运行Redis数据库。

现在开始构建示例应用。首先创建一个目录并创建Dockerfile,代码清单:

** 创建composeapp目录
$ mkdir composeapp
$ cd composeapp
$ touch Dockerfile

这里创建了一个叫作composeapp的目录来保存示例应用。之后进入这个目录,创建了一个空的Dockerfile,用于保存构建Docker镜像的指令。

之后,需要添加应用程序源码。创建一个名叫app.py的文件,并写入代码清单所示的Python代码。

** app.py文件
from flask imposrt Flask
from redis import Redis
import os

app = Flask(__name__)
redis = Redis(host="redis_1",port=6379)

@app.route('/')
def hello():
	redis.incr('hits')
	return 'Hello Docker Book Reader! I have been seen {0} times'.format(redis.get('hits'))

if __name__ == '__main__'
	app.run(host="0.0.0.0",debug=True)

这个简单的Flask应用程序追踪保存在Redis里的计数器。每次访问根路径/时,计数器会自增。

现在还需要创建requirements.txt文件来保存应用程序的依赖关系。创建这个文件,并加入代码清单列出的依赖。

** requirements.txt文件
flask
redis

现在来看看Dockerfile,代码清单如下:

** composeapp的Dockerfile
# Compose示例应用的镜像
FROM python:2.7
MAINTAINER James Turnbull <james@example.com>

ADD . /composeapp

WORKDIR /composeapp

RUN pip install -r requirements.txt

这个Dockerfile很简单。它基于python:2.7镜像构建。首先添加文件app.py和requirements.txt到镜像中的/composeapp目录。之后Dockerfile将工作目录设置为/composeapp,并执行pip命令来安装应用的依赖:flask和redis。

使用docker build来构建镜像,代码清单如下:

**构建composeapp应用
$ sudo docker build -t jamtur01/composeapp .
....

这样就创建了一个名叫jamtur01/composeapp的容器。这个容器包含了示例应用和应用需要的依赖。现在可以使用Docker Compose来部署应用了。

注意:之后会从Docker Hub上的默认Redis镜像直接创建Redis容器,这样就不需要重新构建或者定制Redis容器了。

现在应用镜像已经构建好了,可以配置Compose来创建需要的服务了,在Compose中,我们定义了一组要启动的服务(以Docker容器的形式表现),我们还定义了我们希望这些服务要启动的运行时属性,这些属性和docker run命令需要的参数类似。将所有与服务有关的属性都定义在一个YAML文件里。之后执行docker-compose up命令,Compose会启动这些容器,使用指定的参数来执行,并将所有日志输出合并到一起。

先来为这个应用创建docker-compose.yml文件,代码清单如下:

** 创建docker-cmopose.yml文件
$ touch docker-compose.yml

现在来看看docker-compose.yml文件的内容。docker-compose.yml是YAML格式的文件,包括了一个或者多个运行Docker容器的指令。现在来看看示例应用使用的指令,代码清单:

** docker-compose.yml文件
web:
image: jamtur01/composeapp
command: python app.py
ports:
	- "5000:5000"
volumes:
	- .:/composeapp
links:
	- redis
redis:
image: redis

每个要启动的服务都使用一个YAML的散列键定义:web和redis。

对于web服务,指定了一些运行时参数。首先,使用image指定了要使用的镜像:jamtur01/composeap镜像。Compose也可以构建Docker镜像。可以使用build指令,并提供一个到Dockerfile的路径,让Compose构建一个镜像,并使用这个镜像创建服务,代码清单如下:

** build指令的示例
web:
build: /home/james/composeapp
...

这个build指令会使用/home/james/composeapp目录下的Dockerfile来构建Docker镜像。

我们还使用command指定服务启动时要执行的命令。接下来使用ports和volumes指定了我们的服务要映射到的端口和卷,我们让服务里的5000端口映射到主机的5000端口,并创建了卷/composeapp。最后使用了links指定了要连接到服务的其它服务:将Redis服务链接到Web服务。

如果想用同样的配置,在代码中使用docker run执行服务,需要像代码清单:

** 同样效果的docker run命令
$ sudo docker run -d -p 5000:5000 -v .:/composeapp -link redis:redis \
--name jamtur01/composeapp python app.py

之后指定了另一个名叫redis的服务。这个服务没有指定任何运行时的参数,一切使用默认的配置。之前也用过这个redis镜像,这个镜像默认会在标准端口上启动一个Redis数据库。这里没必要修改这个默认配置。

提示:可以在Docker Compose官网( https://docs.docker.com/compose/yml/ )查看docker-compose.yml所有可用的指令列表。

1.4 运行Compose

一旦在docker-compose.yml中指定了需要的服务,就可以使用docker-compose up 命令来执行这些服务,代码清单

** 使用docker-compose up 启动实例应用服务
$ cd composeapp
$ sudo docker-compose up
....

提示:必须在docker-compose.yml文件所在的目录执行大多数Compose命令。

可以看到Compose创建了composeapp_redis_1和composeapp_web_1这两个新的服务。那么,这两个名字是从哪儿来的呢?为了保证服务是唯一的,Compose将docker-compose.yml文件中指定的服务名字加上了目录名作为前缀,并分别使用数字作为后缀。

Compose之后接管了每个服务输出的日志,输出的日志每一行都使用缩短的服务名字作为前缀,并交替输出在一起,代码清单:

** Compose服务输出的日志
redis_1 | [1] 13 Aug 01:48:32.218 # server started, Redis version 2.8.13

服务(和Compose)交替运行。这意味着,如果使用Ctrl+C来停止Compose运行,也会停止运行的服务。也可以在运行Compose时指定-d标志,以守护进程的模式来运行服务(类似于docker run -d 标志),代码清单: 

** 以守护进程的方式运行Compose
$ sudo docker-compose up -d

来看看现在宿主机上运行的示例应用。这个应用绑定在宿主机所有网络接口的5000端口上,所以可以使用宿主机的IP或者通过localhost来浏览该网站。

可以看到这个页面上显示了当前计数器的值。刷新网站,会看到这个数值在增加。每次刷新都会增加保存在Redis里的值。Redis更新是通过Compose控制的Docker容器之间的链接实现的。

提示:在默认情况下,Compose会试图链接到本地的Docker守护进程,不过也会收到DOCKER_HOST环境变量的影响,去连接到一个远程的Docker宿主机。

1.5 使用Compose

现在来看看Compose的其它选项。首先,使用Ctrl+C关闭正在运行的服务,然后守护进程的方式启动这些服务。

在composeapp目录下按Ctrl+C,之后使用-d标志重新运行docker-cmopose up命令,代码清单如下:

** 使用守护进程模式重启Docker Compose
$ sudo docker-compose up -d
$ ...

可以看到Docker Compose重新创建了这些服务,启动它们,最后返回到命令行。

现在,在宿主机上以守护进程的方式运行了受Docker Compose管理的服务。使用docker-compose ps命令(docker ps命令的近亲)可以查看这些服务的运行状态。

提示:执行docker-compose help加上想要了解的命令,可以看到相关的Compose帮助,比如docker-compose help ps命令可以看到与ps有关的帮助。

docker-compose ps命令列出了本地docker-compose.yml文件里定义的正在运行的所有服务,代码清单如下:

** 运行docker-compose ps命令
$ cd composeapp
$ sudo docker-compose ps
...

这个命令展示了正在运行的Compose服务的一些基本信息:每个服务的名字,启动服务的命令以及每个服务映射到的端口。

还可以使用docker-compose logs命令来进一步查看服务的日志事件,代码清单如下:

**显示Docker Compose服务的日志
$ sudo docker-compose logs
...

这个命令会追踪服务的日志文件,很类似tail -f命令。与tail -f命令一样,想要退出可以使用Ctrl+C。

使用docker-compose stop命令可以停止正在运行的服务,代码清单如下:

** 停止正在运行的服务
$ sudo docker-compose stop
...

这个命令会同时停止两个服务。如果该服务没有停止,可以使用docker-compose kill命令强制杀死该服务。

现在可以用docker-compose ps命令来验证服务确实停止了,代码清单如下:

** 验证Compose服务已经停止了
$ sudo docker-compose ps
...

如果使用docker-compose stop或者docker-compose kill命令停止服务,还可以使用docker-compose start命令重新启动这些服务。这与使用docker start命令重启服务很类似。

最后,可以使用docker-compose rm命令来删除这些服务,代码清单:

**删除Docker Compose服务
$ sudo docker-compose rm
....

首先会提示你确认需要删除服务,确认之后两个服务都会被删除。docker-compose ps命令现在会显示没有运行中或者已经停止的服务,代码清单:

** 显示没有Compose服务
$ sudo docker-compose ps
...

1.6 Compose小结

现在,使用一个文件就可以构建好一个简单的Python-Redis栈!可以看出使用这种方法能够非常简单地构建一个需要多个Docker容器的应用程序。而这个例子,只展现了Compose最表层的能力。在Compose官网上有很多例子,比如使用Rails,Django和Wordpress来展现更高级的概念。还可以将Compose与提供图形化用户界面的Shipyard一起使用。

2. Consul,服务发现和Docker

服务发现是分布式应用程序之间管理相互关系的一种机制。一个分布式程序一般由多个组件组成。这些组件可以都放在一台服务器上,也可以分布在多个数据中心,甚至分布在不同的地理位置。这些组件通常可以为其他组件提供服务,或者为其他组件消费服务。

服务发现允许某个组件在想要与其他组件交互时,自动找到对方。由于这些应用本身是分布式的,服务发现机制也需要是分布式的。而且,服务发现作为分布式应用不同组件之间的"胶水",其本身还需要足够动态,可靠,适应性强,而且可以快速且一致的共享关于这些服务的数据。

另外,Docker主要关注分布式应用以及面向服务架构与微服务架构。这些关注点很适合与某个服务发现工具集成。每个Docker容器可以将其中运行的服务注册到服务发现工具里。注册信息可以是IP地址或者端口,或者两者都有,以便服务之间进行交互。

Consul是一个使用一致性算法的特殊数据存储器。Consul(http://www.consul.io)使用Raft一致性算法来提供确定的写入机制。Consul暴露了键值存储系统和服务分类系统,并提供高可用性,高容错能力,并保证强一致性。服务可以将自己注册到Consul,并以高可用且分布式的方式共享这些信息。

Consul还提供了一些有趣的功能。
** 提供了根据API进行服务分类,代替了大部分传统服务发现工具的键值对存储。
** 提供两种接口来查询信息:基于内置的DNS服务的DNS查询接口和基于HTTP的REST API查询接口。选择合适的接口,尤其是基于DNS的接口,可以很方便地将Consul与现有环境集成。
** 提供了服务监控,也称作健康监控。Consul内置了强大的服务监控系统。

为了更好地理解Consul是如何工作的,先介绍如何在Docker容器里分布式运行Consul。之后会从Docker容器将服务注册到Consul,并从其他Docker容器访问注册的数据。为了更有挑战,会让这些容器运行在不同的Docker宿主机上。

为了做到这些,需要做到以下几点:
** 创建Consul服务的Docker镜像。
** 构建3台运行Docker的宿主机,并在每台上运行一个Consul。这3台宿主机会提供一个分布式环境,来展现Consul如何处理弹性和失效工作的。
** 构建服务,并将其注册到Consul,然后从其他服务查询该数据。

注意:可以在http://www.consul.io/intro/index.html找到对Consul更通用的介绍。

2.1 构建Consul镜像

首先创建一个Dockerfile来构建Consul镜像。先来创建用来保存Dockerfile的目录,代码清单:

** 创建目录来保存Consul的Dockerfile
$ mkdir consul
$ cd consul
$ touch Dockerfile

现在来看看用于Consul镜像的Dockerfile的内容,代码清单如下:

** Consul Dockerfile
FROM ubuntu:14.04
MAINTAINER James Turnbull <james@example.com>
ENV REFRESHED_AT 2014-08-01

RUN apt-get -qqy update
RUN apt-get -qqy install curl unzip

ADD https://dl.bintray.com/mitchellh/consul/0.3.1_linux_am64.zip /tmp/consul.zip
RUN cd /usr/sbin && unzip /tmp/consul.zip && chmod +x /usr/sbin/consul && rm /tmp/consul.zip

ADD https://dl.bintray.com/mitchellh/consul/0.3.1_web_ui.zip
RUN cd /tmp/ && unzip webui.zip && mv dist/ /webui/

ADD consul.json /config/
EXPOSE 53/udp 8300 8301 8301/udp 8302 8302/udp 8400 8500

VOLUME ["/data"]
ENTRYPOINT [ "/usr/sbin/consul", "agent", "-config-dir=/config" ]
CMD []

这个Dockerfile很简单。它是基于Ubuntu 14.04镜像,它安装了curl和unzip。然后我们下载了包含consul可执行程序的zip文件。将这个可执行文件移动到/usr/sbin并修改属性使其可以执行。我们还下载了Consul网页界面,将其放在名为/webui的目录里。一会儿就会看到这个界面。

之后将Consul配置文件consul.json添加到/config目录。现在来看看配置文件的内容,代码清单如下:

** consul.json配置文件
{
"data_dir":"/data",
"ui_dir":"/weui",
"client_addr":"0.0.0.0",
"ports":{
	"dns":53
},
"recursor":"8.8.8.8"
}

consul.json配置文件是做过JSON格式化后的配置,提供了Consul运行时需要的信息。我们首先指定了数据目录/data来保存Consul的数据,之后指定了网页界面文件的位置:/webui。设置client_addr变量,将Consul绑定到容器内的所有网页界面。

之后使用ports配置Consul服务运行时需要的端口。这里指定Consul的DNS服务运行在53端口。之后,使用recursor选项指定了DNS服务器,这个服务器会用来解析Consul无法解析的DNS请求。这里指定的8.8.8.8是Google的公共DNS服务(https://developers.google.com/speed/public-dns)的一个IP地址。

提示:可以在http://www.consul.io/docs/agent/opitons.html找到所有可用的Consul配置选项。

回到之前的Dockerfile,我们用EXPOSE指令打开了一系列端口,这些端口是Consul运行时需要操作的端口。下表列出了每个端口的用途:

Consul的默认端口

端 口				用 途
53/udp			    DNS服务器
8300				服务器RPC
8301+udp			Serf的LAN端口
8302+udp			Serf的WAN端口
8400				RPC接入点
8500				HTTP API

比较重要的是53/udp端口,Consul会使用这个端口运行DNS。之后会使用DNS来获取服务信息。另一个需要关注的是8500端口,它用于提供HTTP API和网页界面。其余的端口用于处理后端通信,将多个Consul节点组成集群。之后我们会使用这些端口配置Docker容器,但并不深究其用途。

注意:可以在http://www.consul.io/docs/agent/options.html找到每个端口更详细的信息。

之后,使用VOLUME指令将/data目录设置为卷。

最后,使用ENTRYPOINT指令指定从镜像启动容器时,启动Consul服务的consul可执行文件。

现在来看看使用的命令行选项。首先我们已经指定了consul执行文件所在的目录为/usr/sbin/。参数agent告诉Consul以代理节点的模式运行,-config-dir标签指定了配置文件consul.json所在的目录是/config。

现在来构建镜像,代码清单如下:

** 构建Consul镜像
$ sudo docker build -t="jamtur01/consul" .

注意:可以从官网(http://dockerbook.com/code/7/consul/)或者GitHub(https://github.com/jamtur01/dockerbook-code/tree/master/code/7/consul/)获得Consul的Dockerfile以及相关的配置文件。

2.2 在本地测试Consul容器

在多个宿主机上运行Consul之前,先来看看在本地单独运行一个Consul的情况。从jamtur01/consul镜像启动一个容器,代码清单如下:

** 执行一个本地Consul节点
$ sudo docker run -p 8500:8500 -p 83:53/udp -h node1 jamtur01/consul -server -bootstrap
....

使用docker run创建了一个新容器。这个容器映射到了两个端口,容器中的8500端口映射到了主机的8500端口,容器中的53端口映射到了主机的53端口。我们还使用-h标志指定了容器的主机名node。这个名字也是Consul节点的名字。之后我们指定了要启动的Consul镜像jamtur01/consul。

最后,给consul可执行文件传递了两个标志:-server和-bootstrap。标志-server告诉Consul代理以服务器的模式运行,标志-bootstrap告诉Consul本节点可以自选举为集群领导者。这个参数会让本节点以服务器模式运行,并可以执行Raft领导者选举。

警告:有一点很重要:每个数据中心最多只有一台Consul服务器可以自启动(bootstrap)模式运行。否则如果有多个可以进行自选举的节点,整个集群无法保证一致性。后面将其他节点加入集群时会介绍更多的相关信息。

可以看到,Consul启动了node节点,并在本地进行了领导者选举。因为没有别的Consul节点运行,刚启动的节点也没有其余的连接操作。

还可以通过Consul网页界面来查看节点情况,在浏览器里打开本地IP的8500端口。

2.3 使用Docker运行Consul集群

由于Consul是分布式的,通常可以简单地在不同的数据中心,云服务商或者不同地区创建3个(或者更多)服务器。甚至给每个服务器添加一个Consul代理,以保证分布服务具有足够的可用性。我们会在3个运行Docker守护进程的宿主机上运行Consul。每个主机上已经安装了Docker守护进程,之后拉取jamtur01/consul镜像,代码清单:

** 拉取Consul镜像
$ sudo docker pull jamtur01/consul

在每台宿主机上都使用jamtur01/consul镜像运行一个Docker容器。要做到这一点,首先需要选择运行Consul的网络。大部分情况下,这个网络应该是个私有的网络,不过既然是模拟Consul集群,这里使用每台宿主机上的公共接口,让Consul运行在一个公共网络上。这需要每台宿主机都有一个公共的IP地址。这个地址也是Consul代理要绑定到的地址。

首先来获取larry的公共IP地址,并将这个地址赋值给环境变量$PUBLIC_IP,代码清单如下:

** 给larry主机设置公共的IP地址
larry$ PUBLIC_IP="$(ifconfig eth0 | awk -F ' *|:' '/inet addr/{
print $4})"

larry$ echo $PUBLIC_IP
104.131.38.54

之后在curly和moe上创建同样的$PUBLIC_IP变量,代码清单如下:

** 在cruly和moe上设置公共IP地址
curly$ PUBLIC_IP="$(ifconfig eth0 | awk -F ' *|:' '/inet addr/{print $4}')"
curly$ echo $PUBLIC_IP
104.131.38.55

moe$ PUBLIC_IP="$(ifconfig eth0 | awk -F ' *|:' '/inet addr/{print $4}')"
moe$ echo $PUBLIC_IP
104.131.38.56

现在3台宿主机有3个IP地址,每个地址都赋值给了$PUBLIC_IP环境变量。

** Consul宿主机IP地址

宿主机				    IP地址
larry					104.131.38.54
curly					104.131.38.55
moe					    104.131.38.56

现在还需要指定一台宿主机为自启动的主机,来启动整个集群。这里指定larry主机。这意味着,需要将larry的IP地址告诉curly和moe,以便啊让后两个宿主机知道要链接到Consul节点的哪个集群。现在将larry的IP地址104.131.38.54添加到宿主机curly和moe的环境变量$JOIN_IP,代码清单如下:

** 添加集群IP地址
curly$ JOIN_IP=104.131.38.54
moe$ JOIN_IP=104.131.38.54

最后,修改每台宿主机上的Docker守护进程的网络配置,;以便更容易使用Consul。将Docker守护进程的DNS查找设置为:

** 本地Docker的IP地址,以便使用Consul来解析DNS。
** Google的DNS服务地址,来解析其他请求。
** 为Consul查询指定搜索域。

要做到这一点,首先需要知道Docker接口docker0的IP地址,代码清单如下:

** 获取docker0的IP地址
larry$ ip addr show docker0
...

可以看到这个接口的IP地址是172.17.42.1。

使用这个地址,将/etc/default/docker文件中的Docker启动选项从代码清单:

** Docker的默认值
# DOCKER_OPTS="--dns 8.8.8.8 --dns 8.8.4.4"

** larry上新的Docker默认值
DOCKER_OPTS='--dns 172.17.42.1 --dns 8.8.8.8 --dns-search service .consul'

之后在curly和moe上进行同样的配置:找到docker0的IP地址,并更新到/etc/default/docker文件中的DOCKER_OPTS标志里。

提示:其他的分布环境需要使用合适的机制更新Docker守护进程的默认值。

之后在每台宿主机上重启Docker守护进程,代码清单:

** 在larry上重启Docker守护进程
larry$ sudo service docker restart

2.4 启动具有自启动功能的Consul节点

现在在larry启动用来初始化整个集群的自启动节点。由于要映射很多端口,使用的docker run命令会有些复杂。而且,由于Consul既要运行在容器里,又要和其他宿主机上的容器进行通信,所以需要将每个端口都映射到本地宿主机对应的端口上。这样可以既在本内部又可以在外部访问Consul了。

来看看要用到的docker run命令,代码清单如下:

** 启动具有自启动功能的Consul节点
larry$ sudo docker run -d -h $HOSTNAME \
-p 8300:8300 -p 8301:8301 \
-p 8301:8301/udp -p 8302:8302 \
-p 8302:8302/udp -p 8400:8400 \
-p 8500:8500 -p 53:53/udp \
--name larry_agent jamtur01/consul \
--server -advertise $PUBLIC_IP -bootstrap-expect 3

这里以守护进程的方式从jamtur01/consul镜像启动了一个容器,用来运行Consul代理。命令使用-h标志将容器的主机名设置为$HOSTNAME环境变量。这会让Consul代理使用本地主机名larry。该命令还将8个端口映射到本地宿主机对应的端口。

该命令还指定了一些Consul代理的命令行参数,代码清单如下:

** Consul代理的命令行参数
-server -advertise $PUBLIC_IP -bootstrap-expect 3

-server标志告诉代理运行在服务器模式。-advertise标志告诉代理通过环境变量$PUBLIC_IP指定IP广播自己。最后,-bootstrap-expect标志告诉Consul集群中有多少代理。在这个例子里,指定了3个代理。这个标志还指定了本节点具有自启动的功能。

现在使用docker logs命令来看看初始Consul容器的日志,代码清单如下:

** 启动具有自启动功能的Consul节点
larry$ sudo docker logs larry_agent
...

可以看到larry上的代理已经启动了,但是因为现在还没有其他节点加入集群,所以没有触发选举操作。从仅有的一条错误信息可以看到这一点。

** 有关集群领导者的错误信息
[ERROR] agent:failed to sync remote state: No cluster leader

2.5 启动其余节点

现在集群已经启动好了,需要将剩下的curly和moe节点加入进来。先来启动curly。使用docker run命令来启动第二个代理,代码清单如下:

** moe上的Consul日志
moe$ sudo docker logs moe_agent
....

从这个日志中可以看出,moe已经链接了集群。这样Consul集群就达到了预设的节点数量,并且触发了领导者选举。这里larry被选举为集群领导者。

在larry上也可以看到最后一个代理节点加入Consul的日志。

通过浏览器Consul的网页界面,选择Consul服务也可以看到当前的状态。

最后,可以通过dig命令测试DNS服务正在工作,代码清单:

** 测试Consul的DNS服务
larry$ dig @172.17.42.1 consul.service.consul
...

这里查询了本地Docker接口的IP地址,并将其作为DNS服务器地址,请求它返回关于consul.service.consul的相关信息。这个域名的格式是使用Consul的DNS快速查询相关服务时的格式:consul是主机名,而service.consul是域名。这里consul.service.consul代表Consul服务的DNS入口。

例如,如下代码清单会返回所有关于webservice服务的DNS A记录。

** 通过DNS查询其他Consul服务
larry$ dig @172.17.42.1 webservice.service.consul

提示:可以在http://www.consul.io/docs/agent/dns.html找到更多关于Consul的DNS接口的信息。

现在,这3台不同的宿主机依靠其中运行的Docker容器组成了一个Consul集群。这看上去很酷,但还没有什么实际用处。下面来看看如何在Consul中注册一个服务,并获得相关数据。

2.6 配合Consul, 在Docker里运行一个分布式服务

为了演示如何注册服务,先基于uWSGI框架(http://uwsgi-docs.readthedocs.org/en/latest/)创建一个演示用的分布式应用程序。这个应用程序由以下两部分组成。

** 一个Web应用:distributed_app。它在启动时会启动相关的Web工作进程(worker),并将这些程序作为服务注册到Consul。
** 一个客户端:distributed_client。它从Consul读取与distributed_app相关的信息,并报告当前应用程序的状态和配置。

distributed_app会在两个Consul节点(即larry和curly)上运行,而distributed_client客户端会在moe节点上运行。

(1)构建分布式应用

现在来创建用于构建distributed_app的Dockerfile。先来创建用于保存镜像的目录,代码清单如下:

** 创建用于保存distributed_app的Dockerfile的目录
$ mkdir distributed_app
$ cd distributed_app
$ touch Dockerfile

现在来看看用于构建distributed_app的Dockerfile的内容,代码清单如下:

** distributed_app使用的Dockerfile
FROM ubuntu:14.04
MAINTAINER James Turnbull "james@example.com"
ENV REFRESHED_AT 2014-06-01

RUN apt-get -qqy update
RUN apt-get -qqy install ruby-dev git libcurl4-openssl-dev curl build-essential python
RUN gem install --no-ri --no-rdoc uwsgi sinatra
RUN uwsgi --build-plugin https://github.com/unbit/uwsgi-consul

RUN mkdir -p /opt/distributed_app
WORKDIR /opt/distributed_app

ADD uwsgi-consul.ini /opt/distributed_app/
ADD config.ru /opt/distributed_app/

ENTRYPOINT [ "uwsgi", "--ini", "uwsgi-consul.ini", "--ini", "uwsgi-consul.ini:server1", "--ini", "uwsgi-consul.ini:server2" ]
CMD []

这个Dockerfile安装了一些需要的包,包括uWSGI框架和Sinatra框架,以及一个可以让uWSGI写入Consul的插件(https://github.com/unbit/uwsgi-consul)。之后创建了目录/opt/distributed_app/,并将其作为工作目录。之后将两个文件uwsgi-consul.ini和config.ru加到这个目录中。

文件uwsgi-consul.ini用于配置uWSGI,来看看这个文件的内容,代码清单:

** uWSGI的配置
[uwsgi]
plugins = consul
socket = 127.0.0.1:9999
master = true
enable-threads = true

[server1]
consul-register = url=http://%h.node.consul:8500,name=distributed_app, id=server1,port=2001
mule = config.ru

[server2]
consul-register = url=http://%h.node.consul:8500,name=distributed_app, id=server2,port=2002
mule = config.ru

文件uwsgi-consul.ini使用uWSGI的Mule结构来运行两个不同的应用程序,这两个应用都是在Sinatra框架中写成的输出"Hello Wrold"的。现在来看看config.ru文件,代码清单如下:

** distributed_app使用的config.ru文件
require 'rubygems'
require 'sinatra'

get '/' do
"Hello World!"
end

run Sinatra::Application

文件uwsgi-consul.ini每个块里定义了一个应用程序,分别标记为server1和server2。每个块里还包含一个对uWSGI Consul插件的调用。这个调用连到Consul实例,并将服务以distributed_app的名字,与服务名server1或者server2,一同注册到Consul。每个服务使用不同的端口,分别是2001和2002。

当该框架开始运行时,会创建两个Web应用的工作进程,并将其分别注册到Consul。应用程序会使用本地的Consul节点来创建服务。参数%h是主机名的简写,执行时会使用正确的主机名替换,代码清单:

** Consul插件的URL
url=http://&h.node.consul:8500...

最后,Dockerfile会使用ENTRYPOINT指令来自动运行应用的工作进程。

现在来构建镜像,代码清单如下:

** 构建distributed_app镜像
$ sudo docker build -t="jamtur01/distributed_app" .

 (2)构建分布式客户端

现在来创建用于构建distributed_client镜像的Dockerfile文件。先来创建用来保存镜像的目录,代码清单:

** 创建保存distributed_client的Dockerfile的目录
$ mkdir distributed_client
$ cd distributed_client
$ touch Dockerfile

现在来看看distributed_client应用程序的Dockerfile的内容,代码清单如下:

** distributed_client使用的Dockerfile
FROM ubuntu:14.04
MAINTAINER James Turnbull "james@example.com"
ENV REFRESHED_AT 2014-06-01

RUN apt-get -qqy update
RUN apt-get -qqy install ruby ruby-dev build-essential
RUN gem install --no-ri --no-rdoc json

RUN mkdir -p /opt/distributed_client
ADD client.rb /opt/distributed_client/

WORKDIR /opt/distributed_client

ENTRYPOINT [ "ruby", "/opt/distributed_client/client_rb" ]
CMD []

这个Dockerfile先安装了Ruby以及一些需要的包和gem,然后创建了/opt/distributed_client目录,并将其作为工作目录。之后将包含了客户端应用程序代码的client.rb文件复制到镜像的/opt/distributed_client目录。

现在来看看这个应用程序的代码,代码清单:
require "rubygems"
require "json"
require "/net/http"
require "uri"
require "resolv"

uri = URI.parse("http://consul.service.consul:8500/v1/catalog/service/distributed_app")

http = Net::HTTP.new(uri.host, uri.port)
request = Net::HTTP::Get.new(uri.request_uri)
response = http.request(request)

while true
if response.body =="{}"
	puts "There are no distributed applications regtistered in Consul"
	sleep(1)
elsif
	result = JSON.parse(response.body)
...

这个客户端首先检查Consul HTTP API和Consul DNS,判断是否存在名叫distributed_app的服务。客户端查询宿主机consul.service.consul,返回的结果和之前看到的包含所有Consul集群节点的A记录的DNS CNAME记录类似。这可以让我们的查询变简单。

如果没有找到服务,客户端会在控制台(cnsloe)上显示一条信息。如果检查到distributed_app服务,就会:

** 解析从API返回的JSON输出,并将一些有用的信息输出到控制台;
** 对这个服务执行DNS查找,并将返回的所有A记录输出到控制台。

这样,就可以查看启动Consul集群中distributed_app容器的结果。

最后,Dockerfile用ENTRYPOINT命令指定了容器启动时,运行client.rb命令来启动应用。

现在来构建镜像,代码清单如下:

** 构建distributed_client镜像
$ sudo docker build -t="jamtur01/distributed_client" .

(3)启动分布式应用

现在已经构建好了所需的镜像,可以在larry和curly上启动distributed_app应用程序容器了。假设这两台机器已经按照之前的配置正常运行了Consul。先从在larry上运行一个应用程序例开始,代码清单:

** 在larry启动distributed_app
larry$ sudo docker run -h $HOSTNAME -d --name larry_distributed \
jamtur01/distributed_app

这里启动了jamtur01/distributed_app镜像,并且使用-h标志指定了主机名。主机名很重要,因为uWSGI使用的主机名来获知Consul服务注册到了哪个节点。之后将这个容器命名为larry_distributed,并以守护方式模式运行该容器。

如果检查容器的输出日志,可以看到uWSGI启动了Web应用工作进程,并将其作为服务注册到Consul,代码清单:

** distributed_app日志输出
larry$ sudo docker logs larry_distributed
....

这里展示了部分日志。从这个日志里可以看到uWSGI已经启动了。Consul插件为每个distributed_app工作进程构造了一个服务项(http://www.consul.io/docs/agent/services.html),并将服务注册到Consul里。如果现在检查Consul网页界面,应该可以看到新注册的服务。

现在在curly上再启动一个Web应用工作进程,代码清单:

** 在curly上启动distributed_app
curly$ sudo docker run -h $HOSTNAME -d --name curly_distributed \
	jamtur01/distributed_app

如果查看日志或者Consul网页界面,应该可以看到更多已经注册的服务。
 

(4)启动分布式应用客户端

现在已经在larry和curly启动了Web应用工作进程,继续在moe上启动应用客户端,看看能不能从Consul查询到数据,代码清单如下:

** 在moe上启动distributed_client
moe$ sudo docker run -h $HOSTNAME -d --name moe_distributed \
	jamtur01/distributed_client

其次,在moe上运行了jamtur01/distributed_client镜像,并将容器命名为moe_distributed。现在来看看容器输出的日志,看一下分布式客户端是不是找到了Web应用工作进程的相关信息,代码清单:

** moe上的distributed_client日志
moe$ sudo docker logs moe_distributed
....

在真实的分布式应用程序里,客户端和工作进程可以通过这些信息在分布式应用的节点间进行配置,链接,分派消息。这提供了一种简单,方便且有弹性的方法来构建分离的Docker容器和宿主机里运行的分布式应用程序。

3. Docker Swarm

Docker Swarm是一个原生的Docker集群管理工具。Swarm将一组Docker主机作为一个虚拟的Docker主机来管理。Swarm有一个非常简单的架构,它将多台Docker主机作为一个集群,并在集群级别上以标准Docker API的形式提供服务。这非常强大,它将多台Docker主机作为一个集群,并在集群级别上以标准Docker API的形式提供服务。这非常强大,它将Docker容器抽象到集群级别,而又不需要重新学习一套新的API。这也使得Swarm非常容易和那些已经集成了Docker的工具再次集成,包括标准的Docker客户端。对Docker客户端来说,Swarm集群不过是另一台普通的Docker主机而已。

Swarm也像其他Docker工具一样,遵循了类似笔记本电池一样的设计原则,虽然自带了电池,但是也可以选择不使用它。这意味着,Swarm提供了面向简单应用场景的工具以及后端集成,同时提供了API(目前还处于成长期)用于与更复杂的工具及应用场景进行集成。Swarm基于Apache 2.0许可证,可以在Github(https://github.com/docker/swarm)上找到它的源码。

注意:Swarm仍是一个新项目,它的基本雏形已现,但是亦可以期待随着项目的进化,它可以开发和演化更多的API。可以在GitHub上找到它的发展蓝图(https://github.com/docker/swarm/blob/master/ROADMAP.md)。

3.1 按照Swarm

按照Swarm最简单的方法就是使用Docker自己。我知道这听起来可能有一点超前,但是Docker公司为Swarm提供了一个实时更新的Docker镜像,可以轻易下载并运行这个镜像。我们这里也将采用这种安装方式。

要想支持Swarm,Docker有一个最低的版本。用户的所有Docker主机都必须在1.4.0或者更高的版本之上。此外,运行Swarm的所有Docker节点也都必须运行着同一个版本的Docker。不能混合搭配不同的版本,比如应该让Docker上的每个节点都运行在1.6.0之上,而不能混用1.5.0版本和1.6.0版本的节点。

我们将在两台主机上安装Swarm,这两台主机分别为smoker和joker。smoker的主机IP是10.0.0.125,joker的主机IP是10.0.0.135。两台主机都安装并运行着最新版本的Docker。

让我们先从smoker主机开始,在其上拉取swarm镜像,代码清单:

** 在smoker上拉取Docker Swarm镜像
smoker$ sudo docker pull swarm

之后再到joker上做同样的操作,代码清单:
** 在joker上拉取Docker Swarm镜像
joker$ sudo docker pull swarm

我们可以确认下Swarm镜像是否下载成功,代码清单:
** 查看Swarm镜像
$ docker images swarm
...

3.2 创建Swarm集群

我们已经在两台主机上下载了swarm镜像,之后就可以创建Swarm集群了。集群中的每台主机都运行着一个Swarm节点代理。每个代理都将该主机上的相关Docker守护进程注册到集群中。和节点代理相对的是Swarm管理者,用于对集群进行管理。

集群注册可以通过多种可能的集群发现后端(discover backend)来实现。默认的集群发现后端是基于Docker Hub。它允许用户在Docker Hub中注册一个集群,然后返回一个集群ID,我们之后可以使用这个集群ID向集群添加额外的节点。

提示:其他的集群发现后端包括etcd, Consul和Zookeeper,甚至是一个IP地址的静态列表。我们能使用之前创建的Consul集群为Docker Swarm集群提供发现方式。可以在https://docs.ocker.com/swarm/discovery/获得更多关于集群发现的说明。

这里我们使用默认的Docker Hub作为集群发现服务创建我们的第一个Swarm集群。我们还是在smoker主机上创建Swarm集群,代码清单如下:

** 创建Docker Swarm
smoker$ sudo docker run --rm swarm create
b811b0bc438cb9a06fb68a25f1c9d8ab

我们看到该命令返回了一个字符串"b811b0bc438cb9a06fb68a25f1c9d8ab",即集群的ID。这是一个唯一的ID,我们能利用这个ID向Swarm集群中添加节点。用户应该保管好这个ID,并且只有当用户想向集群中添加节点时才拿出来使用。

接着我们在每个节点上运行Swarm代理。让我们从smoker主机开始,代码清单如下:

** smoker上运行swarm代理
smoker$ sudo docker run -d swarm join --addr=10.0.0.125:2375 toker://b811b0bc438cb9a06fb68a25f1c9d8ab
...

接着在joker上运行Swarm代理,代码清单如下:

** 在joker上运行swarm代理
joker$ sudo docker run -d swarm join --addr=10.0.0.135:2375 toker://b811b0bc438cb9a06fb68a25f1c9d8ab

这将创建两个Swarm代理,这些代理运行在运行了swarm镜像的Docker化容器中。我们通过传递给容器的join标志,通过-addr选项传递的本机IP地址,以及代表集群ID的token,启动一个代理。每个代理都会绑定到它们所在主机的IP地址上。每个代理都会加入Swarm集群中去。

提示:像Docker一样,用户也可以让自己的Swarm通过TLS和Docker节点进行连接。

我们可以查看代理容器的日志来了解代理内部是如何工作的。

** 查看smoker代理的日志
smoker$ docker logs b5fb4ecab5cc

我们可以看到,代理每隔25秒就会向发现服务进行注册。这将告诉发现后端Docker Hub该代理可用,该Docker服务器也可以被使用。

下面我们就来看看集群是如何工作的。我们可以在任何运行着Docker的主机上执行这一操作,而不一定必须要在Swarm集群中的节点中。我们甚至可以在自己的笔记本电脑上安装好Docker并下载了swarm镜像后,本地运行Swarm集群,代码清单如下:

** 列出我们的Swarm节点
$ docker run --rm swarm list token://b811b0bc438cb9a06fb68a25f1c9d8ab
...

这里我们运行了swarm镜像,并指定了list标志以及集群的token。该命令返回了集群中所有节点的列表。下面让我们来启动Swarm集群管理者。我们可以通过Swarm集群管理者来对集群进行管理。同样,我们也可以在任何安装了Docker的主机上执行以下命令,代码清单如下:

** 启动Swarm集群管理者
$ docker run -d -p 2380:2375 swarm manage token://b811b0bc438cb9a06fb68a25f1c9d8ab

这将创建一个新容器来运行Swarm集群管理者。同时我们还将2380端口映射到了2375端口。我们都知道2375是Docker的标准端口。我们将使用这个端口来和标准Docker客户端或者API进行交互。我们运行了swarm镜像,并通过指定manager选项来启动管理者,还指定了集群ID。现在我们就可以通过这个管理者来向集群发送命令了。让我们从在Swarm集群中运行docker info开始。这里我们通过-H选项来指定Swarm集群管理节点的API端点,代码清单如下:

** 在Swarm集群中运行docker info命令
$ sudo docker -H tcp://localhost:2380 info
...

我们看到,处理标准的docker info输出外,Swarm还向我们输出了所有节点信息。我们可以看到每个节点,节点的IP地址,每台节点上有多少容器在运行,以及CPU和内存这样的容量。

3.3 创建容器

现在让我们通过一个小的shell循环操作来创建6个Nginx容器,代码清单如下:

** 通过循环创建6个Nginx容器
$ for i in `seq 1 6`;do sudo docker -H tcp://localhost:2380 run -d --name www-$i -p 80 nginx;done
....

这里我们运行了包装在一个shell循环里的docker run命令。通过-H选项为Docker客户端指定了tcp://localhost:2380地址,也就是Swarm管理者的地址。我们告诉Docker以守护方式启动容器,并将容器命名为www-加上一个循环变量$i。这些容器都是基于nginx镜像创建的,并都打开了80端口。

我们看到上面的命令返回了6个容器的ID,也就是Swarm在集群中启动的6个容器。

让我们来看看这些正在运行中的容器,代码清单如下:

** Swarm在集群中执行docker ps的输出
$ sudo docker -H top://localhost:2380 ps
...

注意:这里我们省略了输出中部分列的信息,以节省篇幅,包括容器启动时运行的命令,容器当前状态以及容器创建的时间。

我们可以看到我们已经运行了docker ps命令,但它不是在本地Docker守护进程中,而是跨Swarm集群运行的。我们看到结果中有6个容器在运行,平均分配在集群的两个节点上。那么,Swarm是如何决定容器应该在哪个节点上运行呢?

Swarm根据过滤器(filter)和策略(strategy)的结合来决定在哪个节点上运行容器。

3.4 过滤器

过滤器是告知Swarm该优先在哪个节点上运行容器的明确指令。

目前Swarm具有如下5种过滤器:
** 约束过滤器(constraint filter)
** 亲和过滤器(affinity filter)
** 依赖过滤器(dependency filter)
** 端口过滤器(port filter)
** 健康过滤器(health filter)

(1)约束过滤器

约束过滤器依赖于用户给各个节点赋予的标签。举例来说,用户想为使用特殊存储类型或者指定操作系统的节点来分组。约束过滤器需要在启动Docker守护进程时,设置键值对标签,通过--label标注来设置,代码清单:

** 运行Docker 守护进程时设置的约束标签
$ sudo docker daemon --label datacenter=us-east1

Docker还提供了一些Docker守护进程启动时标准的默认约束,包括内核版本,操作系统,执行驱动(execution driver)和存储驱动(storage driver)。如果我们将这个Docker实例加入Swarm集群,就可以通过代码在容器启动时选择这个Docker实例。

** 启动容器时指定约束过滤器
$ sudo docker -H tcp://localhost:2380 run -e constraint:datacenter==us-east1 -d --name www-user1 -p 80 nginx

这里我们启动了一个名为www-user1的容器,并通过-e选项指定约束条件,这里用来匹配datacenter==us-east1。这样将会在设置了这个标签的Docker守护进程中启动该容器。这个约束过滤器支持相等匹配==和不等匹配!=,也支持使用正则表达式,代码清单如下:

** 启动容器时在约束过滤器中使用正则表达式
$ sudo docker -H tcp://localhost:2380 run -e constraint:datacenter==us-east* -d --name www-use1 -p 80 nginx

这会在任何设置了datacenter标签并且标签值匹配us-east*的Swarm节点上启动容器。

(2)亲和过滤器

亲和过滤器让容器运行更互相接近,比如让容器web1挨着haproxy1容器或者挨着指定ID的容器运行,代码清单:

** 启动容器时指定亲和过滤器
$ sudo docker run -d --name www-use2 -e affinity:container==www-use1 nginx

这里我们通过亲和过滤器启动了一个容器,并告诉这个容器运行在www-use1容器所在的Swarm节点上。我们也可以使用不等于条件,代码如下:

** 启动容器时,在亲和过滤器中使用不等于条件

$ sudo docker run -d --name db1 -e affinity:container!=www-use1 mysql

这将告诉Docker在任何没有运行www-use1容器的Swarm节点上运行这个容器。

我们也能匹配已经拉取了指定镜像的节点,如affinity:image==nginx将会让容器在任何已经拉取了nginx镜像的节点上运行。或者,像约束过滤器一样,我们也可以通过按名字或者正则表达式来搜索容器来匹配特定的节点,代码清单:

** 启动容器时在亲和过滤器中使用正则表达式
$ sudo docker run -d --name db1 -e affinity:container!=www-use* mysql

(3)依赖过滤器

在具备指定卷或容器链接的节点上启动容器。

(4)端口过滤器

通过网络端口进行调度,在具有指定端口可用的节点上启动容器,代码清单:

** 使用端口过滤器
$ sudo docker -H tcp://localhost:2380 run -d --name haproxy -p 80:80 haproxy

(5)健康过滤器

利用健康过滤器,Swarm就不会让任何容器调度到被认为不健康的节点上。通常来说,不健康是指Swarm管理者或者发现服务报告某集群节点有问题。

可以在http://docs.docker.com/swarm/scheduler/filter/查看到Swarm过滤器的完整列表以及它们的具体位置。

提示:可以通过swarm manage 命令传递--filter标志来控制哪些过滤器能用。

3.5 策略

策略允许用户用集群节点更隐式的特性来对容器进行调度,比如该节点可用资源的数量等,只在拥有足够内存或者CPU的节点上启动容器。Docker Swarm现在有3中策略:平铺(Spread)策略,紧凑(BinPacking)策略和随机(Random)策略。但只有平铺策略和紧凑策略才真正称得上是策略。默认的策略是平铺策略。

 

(1)平铺策略

平铺策略是选择已运行容器数量最少的节点。使用平铺策略会让所有容器比较平均地分配到集群中的每个节点上。

(2)紧凑策略

紧凑策略会根据每个节点上可用的CPU和内存资源为节点打分,它会先返回使用最紧凑的节点。这将会保证节点最大程度地被使用,避免碎片化,并确保在需要启动更大地容器时有最大数量的空间可用。

(3)随机策略

随机策略会随机选择一个节点来运行容器。这主要用于调式中,生产环境下请不要使用这种策略。

3.6 小结

可能希望看到Swarm还是很有潜力的,也有了足够的基础知识来尝试一下Swarm。这里我再次提醒一下,Swarm还处于beta阶段,还不推荐在生产环境中使用。

4. 其他编配

正如前面提到的,Compose和Consul不是Docker编配工具这个家族里唯一的选择。编配工具是一个快速发展的生态环境,没有办法列出这个领域中的所有可用工具。这些工具的功能不尽相同,不过大部分都属于以下两种类型:

** 调度和集群管理
** 服务发现

4.1 Fleet和etcd

Fleet和etcd由CoreOS团队发布。Fleet是一个集群管理工具,而etcd是一个高可用性的键值数据库,用于共享配置和服务发现。Fleet与systemd和etcd一起,为容器提供了集群管理和调度能力。可以把Fleet看作是systemd的扩展,只是不是工作在主机层面上,而是工作在集群这个层面上。

4.2 Kubernetes

Kubernetes是由Google开源的容器集群管理工具。这个工具可以使用Docker在多个宿主机上发布并扩展应用程序。Kubernetes主要关注需要使用多个容器的应用程序,如弹性分布式微服务。

4.3 Apache Mesos

Apache Mesos项目是一个高可用的集群管理工具。Mesos从Mesos 0.20开始,已经内置了Docker集成,允许利用Mesos使用容器。Mesos在一些创业公司里很流行,如著名的Twitter和AirBnB。

4.4 Helios

Helios项目由Spotify的团队发布,是一个为了在全流程中发布和管理容器而设计的Docker编配平台。这个工具可以创建一个抽象的"作业"(job),之后可以将这个作业发布到一个或者多个运行Docker的Helios宿主机。

4.5 Centurion

Centurion是一个基于Docker的部署工具,由New Relic团队打造并开源。Centurion从Docker Registry里找到容器,并在一组宿主机上使用正确的环境变量,主机卷映射和端口映射来运行这个容器。这个工具的目的是帮助开发者利用Docker做持续部署。