掘金 后端 ( ) • 2024-04-28 17:27

theme: v-green highlight: a11y-dark

一.Docker Swarm

Docker SwarmDocker Compose 样,都是 Docke官方容器编排项目,但不同的是,Docker Compose 是一个在单个服务器或主机上创建多个容器的工具,而 Docker swarm 则可以在多个服务器或主机上创建容器集群服务,对于微服务的部署,显然 Docker swarm 会更加适合。

Swarm的基本架构: 图片 这个图作为一个整体实际上都处于一个所谓的集群中,它可能对应了一到多台的实际服务器。每台服务器上都装有Docker并且开启了基于HTTP的DockerAPI。这个集群中有一个SwarmManager的管理者,用来管理集群中的容器资源。管理者的管理对象不是服务器层面而是集群层面的,也就是说通过Manager,我们只能笼统地向集群发出指令而不能具体到某台具体的服务器上要干什么(这也是Swarm的根本所在)。至于具体的管理实现方式,Manager向外暴露了一个HTTP接口,外部用户通过这个HTTP接口来实现对集群的管理。

二.相关概念

2.1 节点

运行 Docker 的主机可以主动初始化一个 Swarm 集群或者加入一个已存在的 Swarm 集群,这样这个运行 Docker 的主机就成为一个Swarm集群的节点(node) 。节点分为管理(manager) 节点工作(worker)节点

管理节点用于Swarm集群的管理,docker swarm命令基本只能在管理节点执行(节点退出集群命令 docker swarm leave 可以在工作节点执行)。一个Swarm集群可以有多个管理节点,但只有一个管理节点可以成为 leader,leader通过raft协议实现。

工作节点是任务执行节点,管理节点将服务(service)下发至工作节点执行。管理节点默认也作为工作节点。你也可以通过配置让服务只运行在管理节点。下图展示了集群中管理节点与工作节点的关系。 图片

2.2 服务和任务

任务(Task)是Swarm中的最小的调度单位,目前来说就是一个单一的容器。

服务(service)是指一组任务的集合,服务定义了任务的属性。服务有两种模式:

replicated services:按照一定规则在各个工作节点上运行指定个数的任务。
global services: 每个工作节点上运行一个任务。

两种模式通过 docker service create 的 --mode 参数指定。下图展示了容器、任务、服务的关系。 图片

三.Docker Swarm 命令

# 创建一个新的 Swarm。运行此命令的节点将成为第一个管理节点,并切换到 Swarm 模式。
docker swarm init

# 用于查看加入节点到 Swarm 所需的命令和令牌。使用 `docker swarm join-token manager` 命令查看加入新管理节点所需的命令,使用 `docker swarm join-token worker` 命令查看加入新工作节点所需的命令。务必保护好这些加入令牌的安全性。
docker swarm join-token

# 列出 Swarm 中的所有节点,包括哪些是管理节点以及哪个是 leader。
docker node ls

# 创建一个新的服务。
docker service create

# 列出正在运行的服务,并提供有关服务状态和运行的副本数量的基本信息。
docker service ls

# 提供有关单个服务副本的详细信息。
docker service ps <service>

# 提供有关服务的非常详细的信息。它接受 `--pretty` 标志,以返回最重要的信息。
docker service inspect

# 允许您增加或减少服务中副本的数量。
docker service scale

# 允许您更新正在运行的服务的许多属性。
docker service update

# 允许您查看服务的日志。
docker service logs

# 删除 Swarm 中的一个服务。请谨慎使用,因为它将删除所有服务副本,而不会要求确认。
docker service rm

四.案例

4.1 构建一个安全的Swarm集群

构建一个Swarm集群,包括三个管理节点和三个工作节点,如图所示: image.png

4.2 先决条件

如果您计划跟随操作,我建议使用Multipass在您的笔记本电脑或本地机器上创建多个Docker虚拟机。Multipass是免费且易于使用的,所有虚拟机都能够相互通信。只需安装Multipass,然后使用以下命令创建虚拟机并登录到它们:

  • 创建新的Docker虚拟机:multipass launch docker --name
  • 列出Multipass虚拟机及其IP地址:multipass ls
  • 登录到Multipass虚拟机:multipass shell
  • 从Multipass虚拟机注销:exit

我已经创建了6个虚拟机,并根据上图命名。但是,任何Docker环境都应该可以工作。唯一的要求是每个节点都已安装了Docker,并且可以在可靠的网络上进行通信。如果配置了名称解析,那将非常有益——它可以使在命令输出中更容易识别节点,并在故障排除时有所帮助。

如果遇到网络问题,请确保所有Swarm节点之间开放了以下端口:

  • 2377/tcp:用于安全的客户端到Swarm的通信
  • 7946/tcp和udp:用于控制平面消息传递
  • 4789/udp:用于基于VXLAN的覆盖网络

4.3 初始化一个新的Swarm

构建Swarm的过程称为初始化Swarm,其高级流程如下:初始化第一个管理节点 > 加入其他管理节点 > 加入工作节点 > 完成。不属于Swarm的Docker节点被称为处于单引擎模式。一旦它们被添加到Swarm中,它们会自动切换到Swarm模式。在单引擎模式下运行docker swarm init命令的Docker主机将会切换为Swarm模式,创建一个新的Swarm,并将该节点设为Swarm的第一个管理节点。然后可以将其他节点加入为工作节点或管理节点,加入过程会自动将它们切换为Swarm模式。

以下步骤将从mgr1初始化一个新的Swarm。然后,它将加入wrk1、wrk2和wrk3作为工作节点——在此过程中会自动将它们切换为Swarm模式。最后,它将添加mgr2和mgr3作为额外的管理节点,并切换它们到Swarm模式。在此过程结束后,所有6个节点都将处于Swarm模式,并作为同一Swarm的一部分运行。

此示例将使用上图中显示的节点的名称和IP地址。您的节点名称和IP地址可能会有所不同。

  1. 登录到mgr1,并初始化一个新的Swarm。此命令使用图10.2中的IP地址。您应该在您的Docker主机上使用适当的私有IP地址。如果您使用Multipass,通常会使用VM的192地址。
  $ docker swarm init \
  --advertise-addr 10.0.0.1:2377 \
  --listen-addr 10.0.0.1:2377

Swarm initialized: current node (d21lyz...c79qzkx) is now a manager.
<Snip>

该命令可以分解为以下部分:

  • docker swarm init:告诉Docker初始化一个新的Swarm,并将此节点设置为第一个管理节点。它还将节点切换到Swarm模式。
  • --advertise-addr:这是将被广告给其他管理节点和工作节点的Swarm API终点。通常,它将是节点的IP地址之一,但也可以是外部负载均衡器地址。这是一个可选标志,除非您需要在具有多个IP的节点上指定负载均衡器或特定IP。
  • --listen-addr:这是节点将接受Swarm流量的IP地址。如果不设置它,它将默认为与--advertise-addr相同的值。如果--advertise-addr是一个负载均衡器,您必须使用--listen-addr来指定Swarm流量的本地IP或接口。

在生产环境中,我建议您明确指定并始终使用这两个标志。在像我们的实验环境这样的实验室环境中,这并不那么重要。

Swarm模式的默认端口是2377。这是可以自定义的,但通常约定使用2377/tcp来进行安全(HTTPS)的客户端到Swarm的连接。

  1. 列出Swarm中的节点。
$ docker node ls
ID            HOSTNAME   STATUS  AVAILABILITY  MANAGER STATUS
d21...qzkx *  mgr1       Ready   Active        Leader

目前,mgr1是Swarm中唯一的节点,并被列为领导节点。我们稍后会再次回到这一点。

  1. 从mgr1运行docker swarm join-token命令,以提取添加新的工作节点和管理节点到Swarm所需的命令和令牌。
$ docker swarm join-token worker
To add a manager to this swarm, run the following command:
   docker swarm join \
   --token SWMTKN-1-0uahebax...c87tu8dx2c \
   10.0.0.1:2377

$ docker swarm join-token manager
To add a manager to this swarm, run the following command:
   docker swarm join \
   --token SWMTKN-1-0uahebax...ue4hv6ps3p \
   10.0.0.1:2377

请注意,加入工作节点和管理节点的命令除了加入令牌(SWMTKN...)之外完全相同。这意味着一个节点是加入为工作节点还是管理节点完全取决于您在加入时使用的令牌。您应该将加入令牌妥善保存,因为它们是加入节点到Swarm所需的唯一要素!

4.登录到wrk1,并使用带有工作节点加入令牌的docker swarm join命令将其加入Swarm。

$ docker swarm join \
    --token SWMTKN-1-0uahebax...c87tu8dx2c \
    10.0.0.1:2377 \
    --advertise-addr 10.0.0.4:2377 \
    --listen-addr 10.0.0.4:2377

This node joined a swarm as a worker.

--advertise-addr--listen-addr 标志是可选的。我已经添加它们,因为在生产环境中,对网络配置的具体性是最佳实践。在实验室环境中,您可能不需要它们。

  1. 重复前面的步骤,在wrk2和wrk3上执行相同的操作,以它们加入Swarm作为工作节点。如果您正在指定--advertise-addr--listen-addr 标志,请确保使用wrk2和wrk3的相应IP地址。
  2. 然后登录到mgr2,并使用管理节点加入令牌的docker swarm join命令将其加入Swarm作为管理节点。
$ docker swarm join \
    --token SWMTKN-1-0uahebax...ue4hv6ps3p \
    10.0.0.1:2377 \
    --advertise-addr 10.0.0.2:2377 \
    --listen-addr 10.0.0.2:2377

This node joined a swarm as a manager.
  1. 重复上一步骤在mgr3上执行,记得使用mgr3的IP地址作为advertise-addr--listen-addr标志的值。
  2. 然后从任何一个管理节点上运行docker node ls命令来列出Swarm中的节点。
$ docker node ls
ID               HOSTNAME     STATUS  AVAILABILITY  MANAGER STATUS
0g4rl...babl8 *  mgr2         Ready   Active        Reachable
2xlti...l0nyp    mgr3         Ready   Active        Reachable
8yv0b...wmr67    wrk1         Ready   Active
9mzwf...e4m4n    wrk3         Ready   Active
d21ly...9qzkx    mgr1         Ready   Active        Leader
e62gf...l5wt6    wrk2         Ready   Active

此时创建了一个包含3个管理节点和3个工作节点的6节点Swarm。在这个过程中,每个节点上的Docker引擎已经自动切换到Swarm模式,并使用TLS自动保护了Swarm。

如果您查看“MANAGER STATUS”列,您将看到三个管理节点显示为“Reachable”或“Leader”。我们很快将更多地了解关于领导节点的信息。在“MANAGER STATUS”列中没有任何内容的节点是工作节点。还请注意在显示mgr2的行的ID后面有一个星号(*)。这告诉您正在从哪个节点执行命令。前一个命令是从mgr2发出的。

注意:在每次将节点加入Swarm时都手动指定--advertise-addr--listen-addr 标志可能有些繁琐。然而,如果您配置Swarm的网络错误,可能会导致更大的麻烦。此外,手动将节点添加到Swarm不太可能是日常任务,因此值得在开始时额外付出努力来使用这些标志。当然,这是您的选择。在实验环境或仅具有单个IP的节点上,您可能不需要使用它们。

现在已经创建了一个正在运行的Swarm,让我们来看看管理节点的高可用性(HA)。

4.4 Swarm管理节点的高可用性(HA)

到目前为止,我们已经将三个管理节点添加到Swarm中。为什么是三个?它们是如何共同工作的?

Swarm管理节点具有本地支持高可用性(HA)的能力。这意味着一个或多个管理节点可以出现故障,而幸存的管理节点将继续运行Swarm。

从技术上讲,Swarm实现了主动/被动的多管理节点高可用性。这意味着在任何给定时刻只有一个管理节点是活动的。这个活动的管理节点被称为“领导者”,领导者是唯一一个会对Swarm发出更新的节点。因此,只有领导者会更改配置或向工作节点发出任务。如果一个跟随者管理节点(被动)接收到Swarm的命令,它会将这些命令代理到领导者。

这个过程如图10.3所示。第1步是命令从远程Docker客户端传入管理节点。第2步是非领导者管理节点接收命令并将其代理给领导者。第3步是领导者在Swarm上执行命令。

image.png

"Leaders" 和 "Followers" 是Raft算法的术语。这是因为Swarm使用Raft共识算法的一种实现来维护在多个高可用管理节点之间保持一致的集群状态。

在关于高可用性的主题上,以下两个最佳实践适用:

  1. 部署奇数个管理节点。
  2. 不要部署太多管理节点(建议使用3或5个)。
  3. 将管理节点分布在可用性区域之间。

部署奇数个管理节点减少了分裂脑(Split Brain)条件发生的机会。例如,如果我们有4个管理节点并且网络分区了,我们可能会在分区的两侧各留下两个管理节点。这被称为分裂脑 —— 每一侧都知道以前有4个管理节点,但现在只能看到2个。但至关重要的是,两侧都无法知道另外两个管理节点是否仍然存活,以及它们是否占据多数(法定多数)。Swarm集群上的应用程序在分裂脑条件下继续运行,但我们无法更改配置或添加和管理应用程序工作负载。

然而,如果我们有3或5个管理节点,并且发生了相同的网络分区,那么在分区的两侧不可能有相等数量的管理节点。这意味着一侧知道它拥有多数(法定多数),并且完整的集群管理服务仍然可用。图10.4右侧的示例显示了一个分区的集群,其中分裂的一侧知道它拥有大多数的管理节点。

image.png

与所有共识算法一样,参与者越多,达成共识所需的时间就越长。这就像决定在哪里吃饭 —— 对于3个人来说,总是比33个人更容易和更快地做出决定!因此,基于这一考虑,最佳做法是在高可用性方案中使用3个或5个管理节点。7个管理节点可能会起作用,但一般认为3个或5个是最佳选择。

关于管理节点的高可用性,最后需要警告的是,虽然将管理节点分布在可用性区域之间是一种良好的做法,但您需要确保连接它们的网络是可靠的,因为网络分区可能会很难排除故障和解决问题。这意味着,在编写时,将管理节点部署在不同的云平台上以实现多云高可用性可能不是一个好主意。

4.5 内建的Swarm安全性

Swarm集群具有大量内置的安全性,它们在开箱即用时配置了合理的默认值,包括CA设置、加入令牌、双向TLS、加密集群存储、加密网络、加密节点ID等。

4.6 锁定Swarm

尽管具有所有这些内置安全性,但重新启动旧的管理节点或恢复旧的备份可能会威胁到集群的安全性。重新加入的旧管理节点可能能够解密并访问Raft日志时间序列数据库。它们还可能会污染或抹消当前的Swarm配置。

为了防止出现这样的情况,Docker允许您使用Autolock功能来锁定Swarm。这将强制重新启动的管理节点在被允许重新加入集群之前提供一个密钥。

在初始化过程中,可以通过在docker swarm init命令中传递--autolock标志来锁定Swarm。但是,我们已经构建了一个Swarm,所以我们将使用docker swarm update命令来锁定Swarm。

从Swarm管理节点运行以下命令。

$ docker swarm update --autolock=true
Swarm updated.
To unlock a swarm manager after it restarts, run the `docker swarm unlock` command and 
provide the following key:

   SWMKEY-1-XDeU3XC75Ku7rvGXixJ0V7evhDJGvIAvq0D8VuEAEaw

Please remember to store this key in a password manager, since without it you will not be able
to restart the manager.

请确保将解锁密钥保存在安全的地方。您随时可以使用docker swarm unlock-key命令来检查当前的Swarm解锁密钥。

重新启动其中一个管理节点,看看它是否会自动重新加入集群。您可能需要在命令前加上sudo前缀。

$ service docker restart

尝试列出Swarm中的节点。

$ docker node ls
Error response from daemon: Swarm is encrypted and needs to be unlocked before it can be used.

尽管管理节点上的Docker服务已重新启动,但它还没有被允许重新加入Swarm。您可以通过在另一个管理节点上运行docker node ls命令来进一步证明这一点。重新启动的管理节点将显示为下线和无法访问。

运行docker swarm unlock命令以解锁重新启动的管理节点的Swarm。您需要在重新启动的管理节点上运行此命令,并提供解锁密钥。

$ docker swarm unlock
Please enter unlock key: <enter your key>

重新启动的节点将被允许重新加入Swarm,如果您运行另一个docker node ls命令,它将显示为"ready"和"reachable"。

在生产环境中,建议锁定您的Swarm并保护解锁密钥。

4.7 专用管理节点

默认情况下,管理节点和工作节点都可以执行用户应用程序。在生产环境中,通常会配置Swarm,以便只有工作节点才能执行用户应用程序。这使得管理节点可以专注于控制平面的任务。

从任何一个管理节点运行以下三个命令,以防止所有三个管理节点运行应用程序容器。

$ docker node update --availability drain mgr1
$ docker node update --availability drain mgr2
$ docker node update --availability drain mgr3

当我们部署具有多个副本的服务时,您将在后续步骤中看到这一点。

现在我们已经构建了Swarm,并了解了领导者和管理节点HA的基础概念,让我们继续进行应用程序方面的工作。

4.8 部署Swarm服务

在本章的这一部分,我们所做的一切都将在后面的章节中由Docker Stacks进行改进。然而,重要的是您要在这里学习这些概念,以便为以后做好准备。

服务允许我们指定大多数熟悉的容器选项,如名称、端口映射、连接到网络和镜像。但它们还添加了重要的云原生特性,包括期望状态和协调。例如,Swarm服务允许我们定义应用程序的期望状态,让Swarm负责部署和管理它。

让我们看一个快速的示例。假设您有一个带有Web前端的应用程序。您有一个Web服务器的镜像,测试已经表明您需要5个实例来处理正常的日常流量。您可以将此要求转化为一个单一的服务,声明要使用的镜像,并且该服务应始终具有5个运行的副本。您将这个期望状态传递给Swarm,Swarm会负责确保始终有5个Web服务器实例在运行。

我们将在接下来的几分钟内看到可以在服务的一部分中声明的其他内容,但在我们这样做之前,让我们看一看创建我们刚刚描述的内容的一种方法。

您可以通过以下两种方式之一创建服务:

  1. 使用docker service create命令在命令行上以命令方式创建。
  2. 使用堆栈文件以声明方式创建。

我们将在后面的章节中介绍堆栈文件。现在,我们将专注于命令方式创建服务。

$ docker service create --name web-fe \
   -p 8080:8080 \
   --replicas 5 \
   nigelpoulton/ddd-book:web0.1

z7ovearqmruwk0u2vc5o7ql0p
overall progress: 5 out of 5 tasks
1/5: running   [==================================================>]
2/5: running   [==================================================>]
3/5: running   [==================================================>]
4/5: running   [==================================================>]
5/5: running   [==================================================>]
verify: Service converged

让我们回顾一下这个命令和输出。

docker service create命令告诉Docker部署一个新的服务。我们使用--name标志为其命名为web-fe。我们告诉Docker将每个Swarm节点的端口8080映射到每个服务副本(容器)内部的8080端口。接下来,我们使用--replicas标志告诉Docker,该服务应始终有5个副本。最后,我们告诉Docker应该基于哪个镜像创建这些副本 —— 需要理解的重要一点是,所有服务副本都使用相同的镜像和配置!

术语说明:服务部署容器,我们通常将这些容器称为副本。例如,部署三个副本的服务将部署三个相同的容器。

该命令被发送到一个管理节点,领导管理节点在整个Swarm中实例化了5个副本。此Swarm中的管理节点不允许运行应用程序容器,这意味着所有5个副本都部署到工作节点上。每个接收到工作任务的工作节点都会拉取镜像并启动一个监听8080端口的副本。Swarm领导者还确保了期望状态的副本存储在集群上,并被复制到每个管理节点。

但这并不是结束。所有服务都会被Swarm不断监视 —— Swarm会运行一个后台协调循环,不断比较服务的观察状态与期望状态。如果两个状态匹配,那么一切都正常,不需要进一步的操作。如果它们不匹配,Swarm将采取措施将观察状态与期望状态保持一致。

举个例子,如果托管5个副本之一的工作节点失败,那么服务的观察状态将从5个副本降到4个,不再与期望状态5个相匹配。因此,Swarm将启动一个新的副本,以使观察状态恢复与期望状态保持一致。我们称之为协调或自愈,这是云原生应用程序的关键原则之一。

4.9 查看和检查服务

您可以使用docker service ls命令来查看在Swarm上运行的所有服务的列表。

$ docker service ls
ID         NAME      MODE         REPLICAS   IMAGE                 PORTS
z7o...uw   web-fe    replicated   5/5        nigelpoulton/ddd...   *:8080->8080/tcp

输出显示了一个单一的服务以及一些基本的配置和状态信息。除其他事项外,我们可以看到服务的名称以及5个期望的副本中有5个正在运行。如果您在部署服务后不久运行此命令,它可能不会显示所有副本正在运行。这通常是因为工作节点正在拉取镜像。

您可以使用docker service ps命令来查看服务副本的列表以及每个副本的状态。

$ docker service ps web-fe
ID          NAME       IMAGE              NODE   DESIRED   CURRENT
817...f6z   web-fe.1   nigelpoulton/...   wrk1   Running   Running 2 mins
a1d...mzn   web-fe.2   nigelpoulton/...   wrk1   Running   Running 2 mins
cc0...ar0   web-fe.3   nigelpoulton/...   wrk2   Running   Running 2 mins
6f0...azu   web-fe.4   nigelpoulton/...   wrk3   Running   Running 2 mins
dyl...p3e   web-fe.5   nigelpoulton/...   wrk3   Running   Running 2 mins

该命令的格式是docker service ps <服务名称或服务ID>。输出将每个副本显示在自己的行上,显示其正在运行的节点,并显示期望状态和当前观察到的状态。

要获取有关服务的详细信息,请使用docker service inspect命令。

$ docker service inspect --pretty web-fe
ID:             z7ovearqmruwk0u2vc5o7ql0p
Name:		web-fe
Service Mode:	Replicated
 Replicas:	5
Placement:
UpdateConfig:
 Parallelism:	1
 On failure:	pause
 Monitoring Period: 5s
 Max failure ratio: 0
 Update order:      stop-first
RollbackConfig:
 Parallelism:	1
 On failure:	pause
 Monitoring Period: 5s
 Max failure ratio: 0
 Rollback order:    stop-first
ContainerSpec:
 Image:		nigelpoulton/ddd-book:web0.1@sha256:8d6280c0042...1b9e4336730e5
 Init:		false
Resources:
Endpoint Mode:	vip
Ports:
 PublishedPort = 8080
  Protocol = tcp
  TargetPort = 8080
  PublishMode = ingress

该示例使用--pretty标志来将输出限制为以易于阅读的格式打印的最有趣的项目。如果不使用--pretty标志,将获得更多信息。我强烈建议您阅读docker inspect命令的输出,因为它们是获取信息和了解底层情况的绝佳来源。

稍后我们会回到其中的一些输出。

4.10 复制服务与全局服务

服务的默认复制模式是replicated。这会部署所需数量的副本,并尽可能均匀地分布在整个集群中。

另一种模式是global。这在Swarm中的每个节点上运行一个副本。

要部署全局服务,您需要在docker service create命令中传递--mode global标志。全局服务不接受--replicas标志,因为它们总是在每个可用节点上运行一个副本。但是,它们确实尊重节点的可用性。例如,如果您已经使管理节点停止运行应用程序容器,全局服务将不会在它们上安排副本。

4.11 扩展服务

服务的另一个强大功能是轻松地将它们进行扩展和缩减。

假设业务蓬勃发展,我们看到前端Web收到的流量增加了一倍。幸运的是,我们可以使用docker service scale命令轻松地扩展该服务。

$ docker service scale web-fe=10
web-fe scaled to 10
overall progress: 10 out of 10 tasks
1/10: running   [==================================================>]
2/10: running   [==================================================>]
3/10: running   [==================================================>]
4/10: running   [==================================================>]
5/10: running   [==================================================>]
6/10: running   [==================================================>]
7/10: running   [==================================================>]
8/10: running   [==================================================>]
9/10: running   [==================================================>]
10/10: running  [==================================================>]

此命令将服务副本的数量从5个增加到10个。在后台,它将服务的期望状态从5个更新为10个。运行另一个docker service ls以验证操作是否成功。

$ docker service ls
ID         NAME     MODE         REPLICAS   IMAGE                 PORTS
z7o...uw   web-fe   replicated   10/10      nigelpoulton/ddd...   *:8080->8080/tcp

运行docker service ps命令将显示服务副本均匀分布在所有可用节点上。

$ docker service ps web-fe
ID         NAME      IMAGE             NODE  DESIRED  CURRENT
nwf...tpn  web-fe.1  nigelpoulton/...  wrk1  Running  Running 7 mins
yb0...e3e  web-fe.2  nigelpoulton/...  wrk3  Running  Running 7 mins
mos...gf6  web-fe.3  nigelpoulton/...  wrk2  Running  Running 7 mins
utn...6ak  web-fe.4  nigelpoulton/...  wrk3  Running  Running 7 mins
2ge...fyy  web-fe.5  nigelpoulton/...  wrk2  Running  Running 7 mins
64y...m49  web-fe.6  igelpoulton/...   wrk3  Running  Running about a min
ild...51s  web-fe.7  nigelpoulton/...  wrk1  Running  Running about a min
vah...rjf  web-fe.8  nigelpoulton/...  wrk1  Running  Running about a min
xe7...fvu  web-fe.9  nigelpoulton/...  wrk2  Running  Running 45 seconds ago
l7k...jkv  web-fe.10 nigelpoulton/...  wrk1  Running  Running 46 seconds ago

在幕后,Swarm运行一个称为spread的调度算法,试图尽可能均匀地分配副本到可用节点上。在撰写本文时,这意味着在每个节点上运行相同数量的副本,而不考虑CPU负载等因素。

运行另一个docker service scale命令,将副本数量从10减少到5。

$ docker service scale web-fe=5
web-fe scaled to 5
overall progress: 5 out of 5 tasks
1/5: running   [==================================================>]
2/5: running   [==================================================>]
3/5: running   [==================================================>]
4/5: running   [==================================================>]
5/5: running   [==================================================>]
verify: Service converged

现在您知道如何扩展服务了,让我们看看如何删除它们。

4.12 移除服务

删除服务很简单,运行以下docker service rm命令来删除web-fe服务。

$ docker service rm web-fe
web-fe

使用docker service ls命令确认它已经不存在了。

$ docker service ls
ID      NAME    MODE   REPLICAS    IMAGE      PORTS

小心使用这个命令,因为它会删除所有服务副本,而不会要求确认。

4.13 滚动更新

将更新推送到应用程序是现实生活中的一部分,很长一段时间以来,这是一个痛苦的过程。我个人已经花了足够多的周末来进行重大应用程序更新,我不打算再这样做了。

嗯...多亏了Docker服务,对于设计良好的微服务应用程序来说,推送更新变得很容易。

术语说明:我们使用诸如rollouts、updates和rolling updates等术语来表示相同的事情 - 更新正在运行的应用程序。

为了查看一个rollout,我们将部署一个新的服务。但在此之前,我们将为该服务创建一个新的overlay网络。这并不是必需的,但我希望您看到它是如何完成的以及如何将服务连接到它。

$ docker network create -d overlay uber-net
43wfp6pzea470et4d57udn9ws

运行docker network ls来验证网络是否正确创建并在Docker主机上可见。

$ docker network ls
NETWORK ID          NAME                DRIVER      SCOPE
43wfp6pzea47        uber-net            overlay     swarm
<Snip>

uber-net网络已成功创建,具有swarm范围,目前只在swarm的管理节点上可见。当工作负载使用它运行时,它将动态扩展到工作节点。

Overlay网络是一个可以跨越所有swarm节点的第二层网络。在相同overlay网络上的所有容器都可以通信,即使它们部署在不同的节点上也是如此。即使所有swarm节点位于不同的底层网络上,也可以正常工作。

显示了连接到两个不同的底层网络的四个swarm节点,这两个底层网络由一个三层路由器连接。Overlay网络跨越所有4个节点,并创建一个单一的扁平第二层网络,抽象了所有底层网络。

image.png

让我们创建一个新的服务,并将其连接到uber-net网络。

$ docker service create --name uber-svc \
   --network uber-net \
   -p 8080:8080 --replicas 12 \
   nigelpoulton/ddd-book:web0.1

dhbtgvqrg2q4sg07ttfuhg8nz
overall progress: 12 out of 12 tasks
1/12: running   [==================================================>]
2/12: running   [==================================================>]
3/12: running   [==================================================>]
<Snip>
12/12: running  [==================================================>]
verify: Service converged

首先,我们命名了这个服务为uber-svc。然后,我们使用--network标志告诉它将所有副本连接到uber-net网络。然后,我们将端口8080暴露到整个swarm并将其映射到我们要求运行的每个12个副本的内部端口8080。最后,我们告诉它将所有副本基于nigelpoulton/ddd-book:web0.1镜像。

在swarm的每个节点上发布端口的这种模式,甚至不运行服务副本的节点,称为入口模式,是默认模式。另一种模式是主机模式,它仅将服务发布到运行副本的swarm节点。

运行docker service lsdocker service ps来验证新服务的状态。

$ docker service ls
ID            NAME      REPLICAS  IMAGE
dhbtgvqrg2q4  uber-svc  12/12     nigelpoulton/ddd-book:web0.1

$ docker service ps uber-svc
ID        NAME          IMAGE                NODE  DESIRED   CURRENT STATE
0v...7e5  uber-svc.1    nigelpoulton/ddd...  wrk3  Running   Running 1 min
bh...wa0  uber-svc.2    nigelpoulton/ddd...  wrk2  Running   Running 1 min
23...u97  uber-svc.3    nigelpoulton/ddd...  wrk2  Running   Running 1 min
82...5y1  uber-svc.4    nigelpoulton/ddd...  wrk2  Running   Running 1 min
c3...gny  uber-svc.5    nigelpoulton/ddd...  wrk3  Running   Running 1 min
e6...3u0  uber-svc.6    nigelpoulton/ddd...  wrk1  Running   Running 1 min
78...r7z  uber-svc.7    nigelpoulton/ddd...  wrk1  Running   Running 1 min
2m...kdz  uber-svc.8    nigelpoulton/ddd...  wrk3  Running   Running 1 min
b9...k7w  uber-svc.9    nigelpoulton/ddd...  wrk3  Running   Running 1 min
ag...v16  uber-svc.10   nigelpoulton/ddd...  wrk2  Running   Running 1 min
e6...dfk  uber-svc.11   nigelpoulton/ddd...  wrk1  Running   Running 1 min
e2...k1j  uber-svc.12   nigelpoulton/ddd...  wrk1  Running   Running 1 min

打开一个Web浏览器,将其指向任何swarm节点的IP地址,端口设置为8080,以查看运行中的服务。

image.png

随时可以将您的Web浏览器指向swarm中的其他节点。您可以从任何节点访问Web服务,因为该服务已在整个swarm中发布。

现在让我们假设有另一本书需要添加到网站上。还假设已经为其创建了一个新的镜像,并将其添加到相同的Docker Hub存储库中,但此镜像标记为web0.2而不是web0.1。

还假设您的任务是以分阶段的方式将更新后的镜像推送到swarm中,每次2个副本,之间延迟20秒。您可以使用以下docker service update命令来完成此操作。


$ docker service update \
   --image nigelpoulton/ddd-book:web0.2 \
   --update-parallelism 2 \
   --update-delay 20s \
   uber-svc

uber-svc
overall progress: 2 out of 12 tasks
1/12: running   [==================================================>]
2/12: running   [==================================================>]
3/12: ready     [======================================>            ]
4/12: ready     [======================================>            ]
5/12:
6/12:
<Snip>
11/12:
12/12:

docker service update 允许我们通过更新服务的期望状态来对运行中的服务进行更新。此示例指定了镜像的新版本(web0.2而不是web0.1)。它还指定了 --update-parallelism--update-delay 标志,以确保每次推送2个副本,之间有20秒的冷却时间。最后,它指示swarm对 uber-svc 服务进行更改。

如果在更新正在进行时运行 docker service ps uber-svc,一些副本将使用新版本,而另一些副本将使用旧版本。如果给予操作足够的时间来完成,所有副本最终将达到使用 web0.2 镜像的新期望状态。

$ docker service ps uber-svc
ID        NAME          IMAGE            NODE  DESIRED   CURRENT STATE
7z...nys  uber-svc.1    nigel...web0.2   mgr2  Running   Running 13 secs
0v...7e5  _uber-svc.1  nigel...web0.1   wrk3  Shutdown  Shutdown 13 secs
bh...wa0  uber-svc.2    nigel...web0.1   wrk2  Running   Running 1 min
e3...gr2  uber-svc.3    nigel...web0.2   wrk2  Running   Running 13 secs
23...u97  _uber-svc.3  nigel...web0.1   wrk2  Shutdown  Shutdown 13 secs
82...5y1  uber-svc.4    nigel...web0.1   wrk2  Running   Running 1 min
c3...gny  uber-svc.5    nigel...web0.1   wrk3  Running   Running 1 min
e6...3u0  uber-svc.6    nigel...web0.1   wrk1  Running   Running 1 min
78...r7z  uber-svc.7    nigel...web0.1   wrk1  Running   Running 1 min
2m...kdz  uber-svc.8    nigel...web0.1   wrk3  Running   Running 1 min
b9...k7w  uber-svc.9    nigel...web0.1   wrk3  Running   Running 1 min
ag...v16  uber-svc.10   nigel...web0.1   wrk2  Running   Running 1 min
e6...dfk  uber-svc.11   nigel...web0.1   wrk1  Running   Running 1 min
e2...k1j  uber-svc.12   nigel...web0.1   wrk1  Running   Running 1 min

您可以通过在任何Swarm节点上打开Web浏览器,然后多次刷新来实时观察更新。一些请求将由运行旧版本的副本处理,而一些请求将由运行新版本的副本处理。经过足够的时间后,所有请求将由运行更新版本的副本处理。

此时就完成了一个零停机时间的滚动更新到一个运行中的容器化应用程序。

如果对该服务运行 docker service inspect --pretty 命令,您将看到更新并行性和更新延迟设置已合并到服务的定义中。这意味着将来的更新将自动使用这些设置,除非您在 docker service update 命令中覆盖它们。


$ docker service inspect --pretty uber-svc
ID:             mub0dgtc8szm80ez5bs8wlt19
Name:           uber-svc
Service Mode:   Replicated
 Replicas:      12
<Snip>
UpdateConfig:
 Parallelism:   2        <<--------
 Delay:         20s      <<--------
 <Snip>
ContainerSpec:
 Image:    nigelpoulton/ddd-book:web0.2@sha256:8fc6161f981b...4c2d16062678d
Resources:
Networks: uber-net
Ports:
 PublishedPort = 8080
  Protocol = tcp
  TargetPort = 8080
  PublishMode = ingress

您还应该注意一些关于服务的网络配置的事项。在运行服务副本的Swarm中的所有节点都将具有我们之前创建的uber-net覆盖网络。我们可以通过在运行副本的任何节点上运行 docker network ls 来验证这一点。

您还应该注意 docker service inspect 输出的“Networks”部分。这显示了uber-net网络以及整个Swarm范围的(PublishMode: ingress)端口映射。

4.14 故障排除

Swarm服务日志可以使用docker service logs命令查看,但并非所有日志驱动都支持该命令。

默认情况下,Docker节点配置服务使用json-file日志驱动程序,但还存在其他驱动程序,包括:

  • awslogs
  • gelf
  • gcplogs
  • journald(仅适用于运行systemd的Linux主机)
  • splunk
  • syslog

json-filejournald是最容易配置的,两者都与docker service logs命令一起使用。命令的格式是docker service logs <service-name>

如果使用第三方日志驱动程序,应使用该日志平台的原生工具查看这些日志。

以下来自daemon.json配置文件的代码段显示了一个Docker主机配置为使用syslog。daemon.json的默认位置是/etc/docker/daemon.json,但该文件可能不存在,除非您手动创建它以配置自定义设置。

{
  "log-driver": "syslog"
}

您可以通过在docker service create命令中传递--log-driver--log-opts标志,强制单个服务使用不同的日志驱动程序。这些标志将覆盖daemon.json中设置的任何内容。

服务日志要求应用程序以PID 1运行,并将日志发送到STDOUT,将错误发送到STDERR。日志驱动程序将这些“日志”转发到通过日志驱动程序配置的位置。

以下docker service logs命令显示了名为svc1的服务中所有副本的日志,该服务经历了一些启动副本失败的情况。

$ docker service logs svc1
svc1.1.zhc3cjeti9d4@wrk2 | [emerg] 1#1: host not found...
svc1.1.zhc3cjeti9d4@wrk2 | nginx: [emerg] host not found..
svc1.1.6m1nmbzmwh2d@wrk2 | [emerg] 1#1: host not found...
svc1.1.6m1nmbzmwh2d@wrk2 | nginx: [emerg] host not found..
svc1.1.1tmya243m5um@mgr1 | 10.255.0.2 "GET / HTTP/1.1" 302

输出被裁剪以适应页面,但您可以看到显示了所有三个服务副本的日志(两个失败的副本和一个正在运行的副本)。每一行都以副本的名称开头,其中包括服务名称、副本编号、副本ID和其计划运行的主机的名称。之后是日志输出。

很难看出来,因为它被裁剪以适应书籍,但看起来前两个副本失败是因为它们尝试连接到另一个仍在启动的服务。

您可以跟踪日志(--follow)、尾随日志(--tail),以及获取额外的详细信息(--details)。

4.15 备份和恢复一个Swarm

备份一个Swarm是备份控制平面的过程,可以在发生灾难性故障或数据损坏时用于恢复Swarm。从备份中恢复Swarm的情况非常罕见。然而,对于业务关键环境,始终应准备好最坏的情况。

您可能会问,如果控制平面是复制和高可用(HA)的,为什么需要备份。要回答这个问题,考虑这样的情况:恶意行为者删除了Swarm上的所有Secrets。在这种情况下,HA无法帮助,因为删除操作会自动复制到所有管理节点。在这种情况下,高度可用的复制集群存储正在反对您 - 快速传播删除操作。您唯一的恢复选项是要么从存储库中的副本重新创建已删除的对象,例如vault或源代码存储库,要么尝试从最近的备份中进行恢复。

以声明方式管理Swarm和应用程序是防止需要从备份中恢复的需求的绝佳方法。例如,将配置对象存储在Swarm之外的版本控制存储库中将为您提供重新部署网络、服务、Secrets和其他对象等选项。

无论如何,让我们看看如何备份Swarm。

4.16 备份一个Swarm

Swarm的配置和状态存储在每个管理节点的/var/lib/docker/swarm目录中。这包括Raft日志键、覆盖网络、Secrets、Configs、Services等内容。Swarm备份是该目录中所有文件的副本。

由于该目录的内容被复制到所有管理节点,因此可以从多个管理节点执行备份。但是,由于在该过程中必须停止Docker守护程序,因此最好从非领导管理节点执行备份。这是因为在领导节点上停止Docker将启动领导者选举。您还应该在业务的安静时间执行备份,因为在备份过程中停止管理节点会增加Swarm在备份期间如果另一个管理节点失败,可能失去法定人数(quorum)的风险。

在开始备份之前,请创建以下网络。我们将在执行恢复后的步骤中检查这一点。

$ docker network create -d overlay unimatrix01

我们即将执行的程序是一个高风险的程序,仅用于演示目的。您需要根据生产环境进行调整。您可能还需要在命令前加上sudo。

  1. 在非领导管理节点上停止Docker。 如果在节点上运行任何容器或服务副本,则此操作可能会停止它们。但是,如果您一直在跟随操作,您的管理节点不会运行任何应用程序容器。如果锁定了Swarm,请确保有Swarm解锁密钥的副本。
$ service docker stop
  1. 备份Swarm配置。 此示例使用Linux的tar工具执行文件复制,将其作为备份。您可以随意使用其他工具。
$ tar -czvf swarm.bkp /var/lib/docker/swarm/
 tar: Removing leading `/' from member names
 /var/lib/docker/swarm/
 /var/lib/docker/swarm/docker-state.json
 /var/lib/docker/swarm/state.json
 <Snip>
  1. 验证备份文件是否存在
$ ls -l
 -rw-r--r-- 1 root   root   450727 May 22 12:34 swarm.bkp

应该根据公司的备份保留政策存储和轮换此备份。此时,集群已备份,您可以重新启动节点上的 Docker。

  1. 重启Docker
$ service docker restart
  1. 解锁 Swarm 以允许重新启动的管理节点加入。只有在您的 Swarm 被锁定时才需要执行此步骤。如果您忘记了 Swarm 解锁密钥,可以在不同的管理节点上运行 docker swarm unlock-key 命令。
$ docker swarm unlock
        
     Please enter unlock key:

4.17 恢复 Swarm

从备份还原 Swarm 仅适用于 Swarm 已损坏、丢失或无法从配置文件副本中恢复对象的情况。您将需要 swarm.bkp 文件以及您的 Swarm 解锁密钥的副本(如果您的 Swarm 已锁定)。

要使恢复操作成功,必须满足以下要求:

  • 只能将恢复操作执行到与执行备份的 Docker 版本相同的节点上。
  • 只能将恢复操作执行到与执行备份的节点具有相同 IP 地址的节点上。

在执行备份的管理节点上执行该操作。您可能需要在命令前加上 sudo。

1.在管理节点上停止 Docker。

$ service docker stop

2.删除 Swarm 配置。

$ rm -r /var/lib/docker/swarm

此时,管理节点已关闭,并准备好进行还原操作。

3.从备份文件中还原 Swarm 配置,并验证文件是否正常还原。在本示例中,我们将从名为 swarm.bkp 的压缩 tar 文件中还原。还原到根目录是必需的,因为备份包括了原始文件的完整路径。在您的环境中可能会有所不同。

$ tar -zxvf swarm.bkp -C /
$ ls /var/lib/docker/swarm
certificates  docker-state.json  raft  state.json  worker

4.启动 Docker。

$ service docker start

5.使用 Swarm 解锁密钥解锁您的 Swarm。

$ docker swarm unlock
Please enter unlock key: <your key>

6.使用备份的配置初始化一个新的 Swarm。确保在执行还原操作的节点上使用适当的 IP 地址。

$ docker swarm init --force-new-cluster \
  --advertise-addr 10.0.0.1:2377 \
  --listen-addr 10.0.0.1:2377

Swarm initialized: current node (jhsg...3l9h) is now a manager.

7.检查 unimatrix01 网络是否作为操作的一部分恢复。

$ docker network ls
NETWORK ID          NAME                DRIVER              SCOPE
z21s5v82by8q        unimatrix01         overlay             swarm

此时Swarm 已经恢复。

8.添加新的管理节点和工作节点,并进行新的备份。 记得定期和彻底测试这个过程。在需要的时候,您不希望它失败。