Docker Swarm 架构、特性与基本实践

Docker 集群管理和编排的特性是通过 SwarmKit 进行构建的, 其中 Swarm mode 是 Docker Engine 内置支持的一种默认实现。Docker 1.12 以及更新的版本,都支持 Swarm mode,我们可以基于 Docker Engine 来构建 Swarm 集群,然后就可以将我们的应用服务(Application Service)部署到 Swarm 集群中。创建 Swarm 集群的方式很简单,先初始化一个 Swarm 集群,然后将其他的 Node 加入到该集群即可。本文主要基于 Docker Swarm 官网文档,学习总结。

基本特性

Docker Swarm 具有如下基本特性:

  • 集群管理集成进 Docker Engine

使用内置的集群管理功能,我们可以直接通过 Docker CLI 命令来创建 Swarm 集群,然后去部署应用服务,而不再需要其它外部的软件来创建和管理一个 Swarm 集群。

  • 去中心化设计

Swarm 集群中包含 Manager 和 Worker 两类 Node,我们可以直接基于 Docker Engine 来部署任何类型的 Node。而且,在 Swarm 集群运行期间,我们既可以对其作出任何改变,实现对集群的扩容和缩容等,如添加 Manager Node,如删除 Worker Node,而做这些操作不需要暂停或重启当前的 Swarm 集群服务。

  • 声明式服务模型(Declarative Service Model)

在我们实现的应用栈中,Docker Engine 使用了一种声明的方式,让我们可以定义我们所期望的各种服务的状态,例如,我们创建了一个应用服务栈:一个 Web 前端服务、一个后端数据库服务、Web 前端服务又依赖于一个消息队列服务。

  • 服务扩容缩容

对于我们部署的每一个应用服务,我们可以通过命令行的方式,设置启动多少个 Docker 容器去运行它。已经部署完成的应用,如果有扩容或缩容的需求,只需要通过命令行指定需要几个 Docker 容器即可,Swarm 集群运行时便能自动地、灵活地进行调整。

  • 协调预期状态与实际状态的一致性

Swarm 集群 Manager Node 会不断地监控集群的状态,协调集群状态使得我们预期状态和实际状态保持一致。例如我们启动了一个应用服务,指定服务副本为 10,则会启动 10 个 Docker 容器去运行,如果某个 Worker Node 上面运行的 2 个 Docker 容器挂掉了,则 Swarm Manager 会选择集群中其它可用的 Worker Node,并创建 2 个服务副本,使实际运行的 Docker 容器数仍然保持与预期的 10 个一致。

  • 多主机网络

我们可以为待部署应用服务指定一个 Overlay 网络,当应用服务初始化或者进行更新时,Swarm Manager 在给定的 Overlay 网络中为 Docker 容器自动地分配 IP 地址,实际是一个虚拟 IP 地址(VIP)。

  • 服务发现

Swarm Manager 会给集群中每一个服务分配一个唯一的 DNS 名称,对运行中的 Docker 容器进行负载均衡。我们可以通过 Swarm 内置的 DNS Server,查询 Swarm 集群中运行的 Docker 容器状态。

  • 负载均衡

在 Swarm 内部,可以指定如何在各个 Node 之间分发服务容器(Service Container),实现负载均衡。如果想要使用 Swarm 集群外部的负载均衡器,可以将服务容器的端口暴露到外部。

  • 安全策略

在 Swarm 集群内部的 Node,强制使用基于 TLS 的双向认证,并且在单个 Node 上以及在集群中的 Node 之间,都进行安全的加密通信。我们可以选择使用自签名的根证书,或者使用自定义的根 CA(Root CA)证书。

  • 滚动更新(Rolling Update)

对于服务需要更新的场景,我们可以在多个 Node 上进行增量部署更新,Swarm Manager 支持通过使用 Docker CLI 设置一个 delay 时间间隔,实现多个服务在多个 Node 上依次进行部署。这样可以非常灵活地控制,如果有一个服务更新失败,则暂停后面的更新操作,重新回滚到更新之前的版本。

基本架构

Docker Swarm 提供了基本的集群能力,能够使多个 Docker Engine 组合成一个 group,提供多容器服务。Swarm 使用标准的 Docker API,启动容器可以直接使用 docker run 命令。Swarm 更核心的则是关注如何选择一个主机并在其上启动容器,最终运行服务。
Docker Swarm 基本架构,如下图所示(来自网络,详见后面参考链接):
docker-swarm-architecture
如上图所示,Swarm Node 表示加入 Swarm 集群中的一个 Docker Engine 实例,基于该 Docker Engine 可以创建并管理多个 Docker 容器。其中,最开始创建 Swarm 集群的时候,Swarm Manager 便是集群中的第一个 Swarm Node。在所有的 Node 中,又根据其职能划分为 Manager Node 和 Worker Node,具体分别如下所示:

  • Manager Node

Manager Node 负责调度 Task,一个 Task 表示要在 Swarm 集群中的某个 Node 上启动 Docker 容器,一个或多个 Docker 容器运行在 Swarm 集群中的某个 Worker Node 上。同时,Manager Node 还负责编排容器和集群管理功能(或者更准确地说,是具有 Manager 管理职能的 Node),维护集群的状态。需要注意的是,默认情况下,Manager Node 也作为一个 Worker Node 来执行 Task。Swarm 支持配置 Manager 只作为一个专用的管理 Node,后面我们会详细说明。

  • Worker Node

Worker Node 接收由 Manager Node 调度并指派的 Task,启动一个 Docker 容器来运行指定的服务,并且 Worker Node 需要向 Manager Node 汇报被指派的 Task 的执行状态。

构建 Swarm 集群

我们实践 Swarm 集群,包括三个 Node,对应的主机名和 IP 地址分别如下所示:

manager  192.168.1.107
worker1  192.168.1.108
worker2  192.168.1.109

首先,需要保证各个 Node 上,docker daemon 进程已经正常启动,如果没有则执行如下命令启动:

systemctl start docker

接下来就可以创建 Swarm 集群,创建 Swarm 的命令,格式如下所示:

docker swarm init --advertise-addr <MANAGER-IP>

我们在准备好的 Manager Node 上,登录到该 Node,创建一个 Swarm,执行如下命令:

docker swarm init --advertise-addr 192.168.1.107

上面--advertise-addr选项指定 Manager Node 会 publish 它的地址为 192.168.1.107,后续 Worker Node 加入到该 Swarm 集群,必须要能够访问到 Manager 的该 IP 地址。可以看到,上述命令执行结果,如下所示:

Swarm initialized: current node (5pe2p4dlxku6z2a6jnvxc4ve6) is now a manager.

To add a worker to this swarm, run the following command:

    docker swarm join \
    --token SWMTKN-1-4dm09nzp3xic15uebqja69o2552b75pcg7or0g9t2eld9ehqt3-1kb79trnv6fbydvl9vif3fsch \
    192.168.1.107:2377

To add a manager to this swarm, run 'docker swarm join-token manager' and follow the instructions.

该结果中给出了后续操作引导信息,告诉我们如何将一个 Worker Node 加入到 Swarm 集群中。也可以通过如下命令,来获取该提示信息:

docker swarm join-token worker

在任何时候,如果我们需要向已经创建的 Swarm 集群中增加 Worker Node,只要新增一个主机(物理机、云主机等都可以),并在其上安装好 Docker Engine 并启动,然后执行上述 docker swarm join 命令,就可以加入到 Swarm 集群中。
这时,我们也可以查看当前 Manager Node 的基本信息,执行 docker info 命令,输出信息中可以看到,包含如下关于 Swarm 的状态信息:

Swarm: active
 NodeID: qc42f6myqfpoevfkrzmx08n0r
 Is Manager: true
 ClusterID: qi4i0vh7lgb60qxy3mdygb27f
 Managers: 1
 Nodes: 1

可以看出,目前 Swarm 集群只有 Manager 一个 Node,而且状态是 active。也可以在 Manager Node 上执行 docker node ls 命令查看 Node 状态,如下所示:

ID                           HOSTNAME  STATUS  AVAILABILITY  MANAGER STATUS
qc42f6myqfpoevfkrzmx08n0r *  manager   Ready   Active        Leader

接下来,我们可以根据上面提示信息,我们分别在 worker1、worker2 两个 Worker Node 上,执行命令将 Worker Node 加入到 Swarm 集群中,命令如下所示:

docker swarm join \
    --token SWMTKN-1-4dm09nzp3xic15uebqja69o2552b75pcg7or0g9t2eld9ehqt3-1kb79trnv6fbydvl9vif3fsch \
    192.168.1.107:2377

如果成功,可以看到成功加入 Swarm 集群的信息。这时,也可以在 Manager Node 上,查看 Swarm 集群的信息,示例如下所示:

Swarm: active
 NodeID: qc42f6myqfpoevfkrzmx08n0r
 Is Manager: true
 ClusterID: qi4i0vh7lgb60qxy3mdygb27f
 Managers: 1
 Nodes: 3

想要查看 Swarm 集群中全部 Node 的详细状态信息,可以执行如下所示命令:

docker node ls

Swarm 集群 Node 的状态信息,如下所示:

ID                           HOSTNAME  STATUS  AVAILABILITY  MANAGER STATUS
oibbiiwrgwjkw0ni38ydrfsre    worker1   Ready   Active
oocli2uzdt2hy6o50g5z6j7dq    worker2   Ready   Active
qc42f6myqfpoevfkrzmx08n0r *  manager   Ready   Active        Leader

上面信息中,AVAILABILITY 表示 Swarm Scheduler 是否可以向集群中的某个 Node 指派 Task,对应有如下三种状态:

  • Active:集群中该 Node 可以被指派 Task
  • Pause:集群中该 Node 不可以被指派新的 Task,但是其他已经存在的 Task 保持运行
  • Drain:集群中该 Node 不可以被指派新的 Task,Swarm Scheduler 停掉已经存在的 Task,并将它们调度到可用的 Node 上

查看某一个 Node 的状态信息,可以在该 Node 上执行如下命令:

docker node inspect self

我们在 Manager Node 上执行上述命令,查看的状态信息如下所示:

[
    {
        "ID": "qc42f6myqfpoevfkrzmx08n0r",
        "Version": {
            "Index": 9
        },
        "CreatedAt": "2017-03-12T15:25:51.725341879Z",
        "UpdatedAt": "2017-03-12T15:25:51.84308356Z",
        "Spec": {
            "Role": "manager",
            "Availability": "active"
        },
        "Description": {
            "Hostname": "manager",
            "Platform": {
                "Architecture": "x86_64",
                "OS": "linux"
            },
            "Resources": {
                "NanoCPUs": 1000000000,
                "MemoryBytes": 1912082432
            },
            "Engine": {
                "EngineVersion": "17.03.0-ce",
                "Plugins": [
                    {
                        "Type": "Network",
                        "Name": "bridge"
                    },
                    {
                        "Type": "Network",
                        "Name": "host"
                    },
                    {
                        "Type": "Network",
                        "Name": "macvlan"
                    },
                    {
                        "Type": "Network",
                        "Name": "null"
                    },
                    {
                        "Type": "Network",
                        "Name": "overlay"
                    },
                    {
                        "Type": "Volume",
                        "Name": "local"
                    }
                ]
            }
        },
        "Status": {
            "State": "ready",
            "Addr": "127.0.0.1"
        },
        "ManagerStatus": {
            "Leader": true,
            "Reachability": "reachable",
            "Addr": "192.168.1.107:2377"
        }
    }
]

管理 Swarm Node

Swarm 支持设置一组 Manager Node,通过支持多 Manager Node 实现 HA。那么这些 Manager Node 之间的状态的一致性就非常重要了,多 Manager Node 的 Warm 集群架构,如下图所示(出自 Docker 官网):
swarm-multiple-manager-architecture
通过上图可以看到,Swarm 使用了 Raft 协议来保证多个 Manager 之间状态的一致性。基于 Raft 协议,Manager Node 具有一定的容错功能,假设 Swarm 集群中有 N 个 Manager Node,那么整个集群可以容忍最多有 (N-1)/2 个节点失效。如果是一个三 Manager Node 的 Swarm 集群,则最多只能容忍一个 Manager Node 挂掉。
下面,我们按照对 Node 的不同操作,通过命令的方式来详细说明:

(1)Node 状态变更管理

前面我们已经提到过,Node 的 AVAILABILITY 有三种状态:Active、Pause、Drain,对某个 Node 进行变更,可以将其 AVAILABILITY 值通过 Docker CLI 修改为对应的状态即可,下面是常见的变更操作:

  • 设置 Manager Node 只具有管理功能
  • 对服务进行停机维护,可以修改 AVAILABILITY 为 Drain 状态
  • 暂停一个 Node,然后该 Node 就不再接收新的 Task
  • 恢复一个不可用或者暂停的 Node

例如,将 Manager Node 的 AVAILABILITY 值修改为 Drain 状态,使其只具备管理功能,执行如下命令:

docker node update --availability drain manager

这样,Manager Node 不能被指派 Task,也就是不能部署实际的 Docker 容器来运行服务,而只是作为管理 Node 的角色。

(2)给Node添加标签元数据

每个 Node 的主机配置情况可能不同,比如有的适合运行 CPU 密集型应用,有的适合运行 IO 密集型应用,Swarm 支持给每个 Node 添加标签元数据,这样可以根据 Node 的标签,来选择性地调度某个服务部署到期望的一组 Node 上。
给 SWarm 集群中的某个 Worker Node 添加标签,执行如下命令格式如下:

docker node update --label-add 键名称=值

例如,worker1 主机在名称为 bjidc 这个数据中心,执行如下命令添加标签:

docker node update --label-add datacenter=bjidc

(3)Node 提权/降权

改变 Node 的角色,Worker Node 可以变为 Manager Node,这样实际 Worker Node 有工作 Node 变成了管理 Node,对应提权操作,例如将 worker1 和 worker2 都升级为 Manager Node,执行如下命令:

docker node promote worker1 worker2

对上面已提权的 worker1 和 worker2 执行降权,需要执行如下命令:

docker node demote worker1 worker2

(4)退出 Swarm集群

如果 Manager 想要退出 Swarm 集群, 在 Manager Node 上执行如下命令:

docker swarm node leave

就可以退出集群,如果集群中还存在其它的 Worker Node,还希望 Manager 退出集群,则加上一个强制选项,命令行如下所示:

docker swarm node leave --force

同样,如果 Worker 想要退出 Swarm 集群,在 Worker Node上,执行如下命令:

docker swarm node leave

即使 Manager 已经退出 SWarm 集群,执行上述命令也可以使得 Worker Node 退出集群,然后又可以加入到其它新建的 Swarm 集群中。

管理服务

在 Swarm 集群上部署服务,必须在 Manager Node 上进行操作。先说明一下 Service、Task、Container(容器)这个三个概念的关系,如下图(出自 Docker 官网)非常清晰地描述了这个三个概念的含义:
services-diagram
在 Swarm mode 下使用 Docker,可以实现部署运行服务、服务扩容缩容、删除服务、滚动更新等功能,下面我们详细说明。

(1)创建服务

创建 Docker 服务,可以使用 docker service create 命令实现,例如,我们要创建如下两个服务,执行如下命令:

docker service create --replicas 1 --name myapp alpine ping shiyanjun.cn
docker service create --replicas 2 --name myredis redis

第一个命令行,从 Docker 镜像 alpine 创建了一个名称为 myapp 的服务,其中指定服务副本数为 1,也就是启动一个 Docker 容器来运行该服务。第二个命令行, 创建一个 Redis 服务,服务副本数为 2,那么会启动两个 Docker 容器来运行 myredis 服务。查看当前,已经部署启动的全部应用服务,执行如下命令:

docker service ls

执行结果,如下所示:

ID            NAME     MODE        REPLICAS  IMAGE
kilpacb9uy4q  myapp    replicated  1/1       alpine:latest
vf1kcgtd5byc  myredis  replicated  2/2       redis

也可以查询指定服务的详细信息,执行如下命令:

docker service ps myredis

查看结果信息,如下所示:

ID            NAME       IMAGE  NODE     DESIRED STATE  CURRENT STATE           ERROR  PORTS
0p3r9zm2uxpl  myredis.1  redis  manager  Running        Running 48 seconds ago
ty3undmoielo  myredis.2  redis  worker1  Running        Running 44 seconds ago

上面信息中,在 manager 和 worker1 这两个 Node 上部署了 myredis 这个应用服务,也包含了它们对应的当前状态信息。此时,也可以通过执行 docker ps 命令,在 Manager Node 上查看当前启动的 Docker 容器:

CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS               NAMES
07f93f82a407        redis:latest        "docker-entrypoint..."   7 minutes ago       Up 7 minutes        6379/tcp            myredis.1.0p3r9zm2uxple5i1e2mqgnl3r

在 Worker1 上查看当前启动的 Docker 容器,也就是我们的另一个 myredis 实例在该 Node 上:

CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS               NAMES
41c31e96cccb        redis:latest        "docker-entrypoint..."   8 minutes ago       Up 8 minutes        6379/tcp            myredis.2.ty3undmoielo18g7pnvh0nutz

创建服务时,我们可以对运行时服务容器进行配置,例如如下命令:

docker service create --name helloworld \
  --env MYVAR=myvalue \
  --workdir /tmp \
  --user my_user \
  alpine ping docker.com

上面,通过--env选项来设置环境变量,通过--workdir选项来设置工作目录,通过--user选项来设置用户信息。

(2)扩容缩容服务

Docker Swarm 支持服务的扩容缩容,Swarm 通过--mode选项设置服务类型,提供了两种模式:一种是 replicated,我们可以指定服务 Task 的个数(也就是需要创建几个冗余副本),这也是 Swarm 默认使用的服务类型;另一种是 global,这样会在 Swarm 集群的每个 Node 上都创建一个服务。如下图所示(出自 Docker 官网),是一个包含 replicated 和 global 模式的 Swarm 集群:
docker-swarm-replicated-vs-global
上图中,黄色表示的 replicated 模式下的 Service Replicas,灰色表示 global 模式下 Service 的分布。
服务扩容缩容,在 Manager Node 上执行命令的格式,如下所示:

docker service scale 服务ID=服务Task总数

例如,将前面我们部署的 2 个副本的 myredis 服务,扩容到 3 个副本,执行如下命令:

docker service scale myredis=3

通过命令 docker service ls 查看,扩容操作结果如下所示:

ID            NAME     MODE        REPLICAS  IMAGE
kilpacb9uy4q  myapp    replicated  1/1       alpine:latest
vf1kcgtd5byc  myredis  replicated  3/3       redis

进一步通过 docker service ps myredis 查看一下 myredis 的各个副本的状态信息,如下所示:

ID            NAME       IMAGE  NODE     DESIRED STATE  CURRENT STATE                   ERROR  PORTS
0p3r9zm2uxpl  myredis.1  redis  manager  Running        Running 14 minutes ago
ty3undmoielo  myredis.2  redis  worker1  Running        Running 14 minutes ago
zxsvynsgqmpk  myredis.3  redis  worker2  Running        Running less than a second ago

可以看到,我们目前 3 个 Node 的 Swarm 集群,每个 Node 上都有一个 myredis 应用服务的副本,可见也实现了很好的负载均衡。
缩容服务,只需要将副本数小于当前应用服务拥有的副本数即可实现,大于指定缩容副本数的副本会被删除。

(3)删除服务

删除服务,只需要在 Manager Node 上执行如下命令即可:

docker service rm 服务ID

例如,删除 myredis 应用服务,执行 docker service rm myredis,则应用服务 myredis 的全部副本都会被删除。

(4)滚动更新

服务的滚动更新,这里我参考官网文档的例子说明。在 Manager Node 上执行如下命令:

docker service create \
  --replicas 3 \
  --name redis \
  --update-delay 10s \
  redis:3.0.6

上面通过指定--update-delay选项,表示需要进行更新的服务,每次成功部署一个,延迟 10 秒钟,然后再更新下一个服务。如果某个服务更新失败,则 Swarm 的调度器就会暂停本次服务的部署更新。
另外,也可以更新已经部署的服务所在容器中使用的 Image 的版本,例如执行如下命令:

docker service update --image redis:3.0.7 redis

将 Redis 服务对应的 Image 版本有 3.0.6 更新为 3.0.7,同样,如果更新失败,则暂停本次更新。

(5)添加Overlay网络

在 Swarm 集群中可以使用 Overlay 网络来连接到一个或多个服务。具体添加 Overlay 网络,首先,我们需要创建在 Manager Node 上创建一个 Overlay 网络,执行如下命令:

docker network create --driver overlay my-network

创建完 Overlay 网络 my-network 以后,Swarm 集群中所有的 Manager Node 都可以访问该网络。然后,我们在创建服务的时候,只需要指定使用的网络为已存在的 Overlay 网络即可,如下命令所示:

docker service create \
  --replicas 3 \
  --network my-network \
  --name myweb \
  nginx

这样,如果 Swarm 集群中其他 Node 上的 Docker 容器也使用 my-network 这个网络,那么处于该 Overlay 网络中的所有容器之间,通过网络可以连通。

参考链接

Creative Commons License

本文基于署名-非商业性使用-相同方式共享 4.0许可协议发布,欢迎转载、使用、重新发布,但务必保留文章署名时延军(包含链接:http://shiyanjun.cn),不得用于商业目的,基于本文修改后的作品务必以相同的许可发布。如有任何疑问,请与我联系

发表评论

电子邮件地址不会被公开。 必填项已用*标注

您可以使用这些HTML标签和属性: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>