我一直使用 docker 管理公司的开发和测试环境。为了提高团队的工作效率。至少人手一整套环境 (除了产品各模块外还包含了数据库,etcd 等持久化组件) 以减少环境冲突。 所以随着业务发展,团队人数增加, 越来越多的环境将服务器资源耗尽。到了一定程度后就会分部门使用不同的服务器以缓解资源的消耗。但到了这时候就有主要了以下两个痛点。
所以最近工作有空闲的时候开始调研 swarm mode,mesos 和 kubernetes 这种分布式的资源调度框架。今天主要说明一下 swarm mode 的机制。
docker machine 是一个非常有用的学习工具,它能够帮我们迅速搭建起多套装有 docker 的宿主机。用以我们试验管理集群。建议大家学会它。下面简单介绍下使用方式。
curl -L https://github.com/docker/machine/releases/download/v0.8.2/docker-machine-`uname -s`-`uname -m` >/usr/local/bin/docker-machine && \
chmod +x /usr/local/bin/docker-machine
如果是在 mac 上,需要使用一个虚拟机来帮助创建 docker host。下载链接:https://www.virtualbox.org/wiki/Downloads
docker-machine create --driver virtualbox default (manager node 我暂时就命名为default了)
docker-machine create --driver virtualbox slave1
docker-machine create --driver virtualbox slave2
上面我们就很简单的创建了 3 个带有 docker 最新版本的宿主机供我们这次 demo 使用。下面介绍一下简单的命令使用。
例如我运行 docker-machine ssh default 这条命令。我们就会以 ssh 的方式进入到 manager 这台主机中。
swarm mode 是即 swarm 和 swarmkit 之后,内置在 docker1.12 极其以上版本的集群管理框架。特点是简单,易用,易理解。相较于 mesos 和 kubernetes,它的简单易用很适合在公司内搭建非生产环境集群,尤其是给运维经验不足的 QA 使用。普通的 docker 使用者可以很快的过渡到 swarm mode 的使用方式上。当然业界现在基本没人使用它搭建生产环境,尤其是其 overlay 的网络性能尤其让人诟病。
用上面 docker-machine 准备的三台机器我们来搭建一个集群
docker swarm init --listen-addr 192.168.99.100:2377 --advertise-addr 192.168.99.100
如果机器有多个 ip 地址要指定--adverties-addr 选择一个 ip,否则会报下面的错:
Error response from daemon: could not choose an IP address to advertise since this system has multiple addresses on different interfaces (10.0.2.15 on eth0 and 192.168.99.100 on eth1) - specify one with --advertise-addr
命令成功后会返回一个全球唯一的 token。这是初始化命令到 docker hub 上申请的 token。用来唯一标识 swarm 的 manager。 之后 node 加入到集群的时候会用到。这样我们就将
docker swarm join \
--token SWMTKN-1-47e8gcgrv91taf01zdr252wp7no10m3nb9tgjcgyjgy9r1fszp-7y7h5c06f6qul2t6hwml0d1c7 \
192.168.99.100:2377
在这里使用之前初始化 manager 的时候生成的全球唯一的 token。
在这里需要注意的是在 swarm mode 中对外暴露的是服务(service)的概念,而不是容器。在 swarm mode 的设计中,为了保持高可用架构,它准许同时启动多个容器共同支撑一个服务,如果一个容器挂了,它会自动使用另一个容器。所以这里单机的 docker engine 不同。大家要适应这个概念。当然我们在测试和开发环境中基本上不用着什么高可用,我们知道有这么个东西就行了。
docker service create --replicas 3 --mount "type=bind,source=$PWD,target=/var/lib/registry" --publish 8080:80 --name helloworld alpine ping docker.com
在 manager 节点上运行上面这条命令就使用 alpine 这个镜像创建了名为 helloworld 的服务。--replicas 3 这个参数代表给这个服务启动 3 个容器 (刚才说的高可用) ,--mount 这个参数其实就是 docker engine 的-v,将数据卷挂载到宿主机。 --publish 就是 docker engine 的 port,将容器的 80 端口 map 到集群的 8080 端口中。(注意:不论你访问集群中哪一个 node 的 ip 地址。只要访问这个端口号。 swarm node 都会路由到正确的容器中)
下面我们运行 docker service ls 这条命令来查看一下我们所有的服务
可以看到我们刚才创建的 helloworld 上的 3 个实例都已经启动了。我们再运行一个 docker service ps helloworld 来查看一下部署的详细情况
可以看到 swarm 根据内置的资源分配策略,选择了在 slave1 上部署了两个容器,在 manager node 上部署了一个容器。
swarm mode 还可以让我们控制更多东西,例如跟 docker engine 的-e 一样可以控制环境变量的-env。可以限制容器资源使用的--reserve 系列参数等等。大家可以使用--help 或者去官方文档中查看。一切都跟 docker engine 很像,我就不多说了。
我们都知道在宿主机中,docker 会创建一个默认的 docker0 网桥用以桥接宿主机和容器。 这个 docker0 网桥对于宿主机来说是一个虚拟的网络设备。但是对于 docker 容器来说就是一个三层交换机。所以我们的容器可以通过 docker0 访问外网 (三层交换机有路由功能)。但是外部无法访问容器,因为每台宿主机上的 docker 容器都分别在自己的一个私有的局域网络中,也就是说外部路由器没有这些容器的路由。所以我们在没有集群的时候一般选择 briage 方案将 docker 容器与宿主机部署再同一个网络下,或者直接以 map 容器端口到宿主机端口上,或者直接以 host 网络模式启动容器来达到外部访问容器服务的目的。 这几种方案我不详细讲了,之后转们开一个帖子说 docker 的网络方案。今天我们讲一下 swarm node 内置的 overlay 网络。
简单介绍下 overlay,我们学过计算机网络基础。都知道有一句常说的话是 2 层 (数据链路层) 转发,3 层 (网络层) 路由。 2 层设备 (网桥,2 层交换机) 并不记录逻辑地址 (ip 地址),而是记录物理地址 (mac) 通过广播的形式寻址。 而三层设备 (三层交换机,路由器) 通过路由规则解析逻辑地址并一跳跟着一跳的通往目的主机。 他们都属于底层的网络通信协议。同一台宿主机的同一个网桥下的所有容器可以互相访问,因为他们处于同一个物理网络,走 2 层协议用 mac 地址进行通信。 容器可以访问外网,因为默认的 docker0 相当于一个三层交换机,具有三层网络的路由功能。上面说的使用 briage 模式达到外部通过 ip 地址访问容器的方式利用的就是三层网络的通信协议。而 overlay 处于应用层,是上层的网络通信协议。通常需要借助一些 K,V 存储设备来保存信息,例如 etcd,ZK 等等,它的性能比较慢,在网上看的资料说 docker 的 overlay 网络方案有 30% 的性能折损。这也是 swarm mode 不适合生产环境的原因之一。具体的原理我也没深研究过,就不卖弄了,大家有兴趣的自己去网上查一下资料吧。
swarm mode 内部已经支持了 overlay 的网络,所以不需要我们增加额外的 K,V 机制去配置 overlay 的网络。 你需要确保以下的端口已经被打开
运行命令:
docker network create \
--driver overlay \--subnet 10.0.9.0/24 \--opt encrypted \
my-network
这时候我们就创建了一个 overlay 的网络。然后我们在创建服务的时候使用--network 参数指定使用这个 overlay 网络。那么不论容器处于集群中的哪个节点。他们都可以正常的互相通信。
好了现在我们的容器可以互相访问了,但是我们怎么知道目的主机的 ip 地址呢? 答案是不知道,因为每次启动容器都是又 overlay 动态的分配一个 VIP(虚拟 IP 地址)。只有在容器创建之后你才知道 IP 地址是什么。那么在制作镜像的时候你怎么知道使用哪个 ip 地址才能访问到我们真正想要去的容器呢? 熟悉 docker engine 的人一定知道 link 这个参数。 swarm mode 的 service discovery 同样实现了类似的功能。swarm 会将容器的 VIP map 到 swarm 的内置的 dns 中,使用 service name 作为域名。所以集群的容器可以使用服务名称当做 IP 地址来互相访问。
接下来我们讲讲 swarm mode 的一致性策略,也是 swarm mode 的高可用机制,作为 QA 的话这个其实不用了解,因为测试环境一般没有那么大的规模需要做高可用,对此有兴趣的同学可以看一看。先讲讲高可用的概念,我们的例子里只有一个 manager node 控制所有的 worker node,这个唯一的节点维护着所有的服务,健康检查,service api。一旦 manager 节点挂掉我们的服务就会瘫痪。所以需要运行多个 manager 节点来保持我们的高可用。但是多个 manager 节点会碰到另一个问题就是信息的一致性问题,如果当前的 leader 的 manager 节点挂了,那么上面的信息怎么办?我们知道早期的 swarm 以及其他的一些分布式框架都是用 etcd 或者 ZK 这种 K,V 存储辅以一致性算法来保证 master 节点 (在 swarm mode 中叫 manager 节点) 的高可用的。swarm mode 虽然不使用 etcd 或者 ZK 这些东西 (可能是已内置了),但是它依然是使用 Raft 一致性算法的。这里简单介绍一下 Raft 一致性算法的概念:
在一个由 Raft 协议组织的集群中有三类角色:
就像一个民主社会,领袖由民众投票选出。刚开始没有领袖,所有集群中的参与者都是群众,那么首先开启一轮大选,在大选期间所有群众都能参与竞选,这时所有群众的角色就变成了候选人,民主投票选出领袖后就开始了这届领袖的任期,然后选举结束,所有除领袖的候选人又变回群众角色服从领袖领导。这里提到一个概念「任期」,用术语 Term 表达。关于 Raft 协议的核心概念和术语就这么多而且和现实民主制度非常匹配,所以很容易理解。三类角色的变迁图如下,结合后面的选举过程来看很容易理解。
Leader 选举过程
在极简的思维下,一个最小的 Raft 民主集群需要三个参与者(如下图:A、B、C),这样才可能投出多数票。初始状态 ABC 都是 Follower,然后发起选举这时有三种可能情形发生。下图中前二种都能选出 Leader,第三种则表明本轮投票无效(Split Votes),每方都投给了自己,结果没有任何一方获得多数票。之后每个参与方随机休息一阵(Election Timeout)重新发起投票直到一方获得多数票。这里的关键就是随机 timeout,最先从 timeout 中恢复发起投票的一方向还在 timeout 中的另外两方请求投票,这时它们就只能投给对方了,很快达成一致。
选出 Leader 后,Leader 通过定期向所有 Follower 发送心跳信息维持其统治。若 Follower 一段时间未收到 Leader 的心跳则认为 Leader 可能已经挂了再次发起选主过程。
Leader 节点对一致性的影响
Raft 协议强依赖 Leader 节点的可用性来确保集群数据的一致性。数据的流向只能从 Leader 节点向 Follower 节点转移。当 Client 向集群 Leader 节点提交数据后,Leader 节点接收到的数据处于未提交状态(Uncommitted),接着 Leader 节点会并发向所有 Follower 节点复制数据并等待接收响应,确保至少集群中超过半数节点已接收到数据后再向 Client 确认数据已接收。一旦向 Client 发出数据接收 Ack 响应后,表明此时数据状态进入已提交(Committed),Leader 节点再向 Follower 节点发通知告知该数据状态已提交。
在这个过程中,主节点可能在任意阶段挂掉。我们通过在这些节点中复制信息的方式防止数据信息丢失,其中的细节在这不描述了。有兴趣的在家网上查一下资料吧。我的图也是网上剽窃下来的哈哈。
下面是 swarm node 的架构图:
在 swarm mode 中,所有节点分为 manager node 和 worker node。 manager node 作为整个集群的调度者,负责分配资源,分发 task,管理各种 service。 同时 manager node 使用一种 Raft 一致性算法来保持集群的高可用状态。 这种算法是在所有的 manager node 中采取选举方式,决定哪一个 ndoe 是 leader。 所以为了保持高可用和容错率,需要保持多个 manager node。通过上面的 Raft 一致性算法的描述我们知道,这种算法在决定 leader 的时候是通过选举的方式,所以为了保持这种机制的有效性,必须维持至少 3 个以上的容器才能保持有效的容错率和高可用。 下面是官网的信息:
Docker recommends a maximum of seven manager nodes for a swarm.(推荐在一个集群中保持 7 个 manager node)
同时一个 manager ndoe 也是一个 worker node,也就是说 manager node 也可以执行 task,启动容器。 但是我们可以配置 manager 节点为 manager only 模式。上面说推荐在一个集群中保持 7 个 manager node。既然 manager node 也可以同时是 worker node,那为什么不让所有的节点都是 manager 呢? 因为 manager node 为了保持一致性会有很多额外的资源开销。
worker node:
worker node 就是实际执行任务的节点。 它不负责维护集群的整体信息,也不负责维护集群的 API。这个就没啥好说的了。
加入 manager node 的方式也很简单,我们上面 init 一个 swarm 集群的时候会返回一个 token,那个 token 是 worker 节点加入集群的 token,现在我们在 manager 节点上运行 docker swarm join-token manager 这个命令就会得到一个 manager node 的 token。当我们用这个 token 加入一个集群的时候,就是添加了一个 manager node。如下:
接下来会去调研一下 mesos 和 kubernetes,然后继续发帖子比较一下他们的不同和优劣。