Skip to content

Files

Latest commit

3ac63cb · Dec 27, 2021

History

History
1011 lines (586 loc) · 46.1 KB

File metadata and controls

1011 lines (586 loc) · 46.1 KB

十、DockerSwarm

现在我们知道了如何安装 Docker、拉取映像和使用容器,接下来我们需要的是一种大规模处理事物的方法。这就是 Docker Swarm 发挥作用的地方。

像往常一样,我们将把这一章分为三个部分:

  • TLDR
  • 深潜
  • 命令

对接者 swarm-tldr

Docker Swarm 主要是两件事:

  1. Docker 主机的企业级安全集群
  2. 一个编排微服务应用的引擎

在集群前端,Swarm 将一个或多个 Docker 节点分组,并允许您将它们作为集群进行管理。开箱即用,您可以获得加密的分布式集群存储、加密的网络、相互 TLS、安全的集群加入令牌,以及使管理和轮换证书变得轻而易举的 PKI。您甚至可以无中断地添加和删除节点。这是一件美好的事情。

虽然我们在本章中讨论了 Swarm 安全性的一些方面,但在第 15 章中我们将深入讨论。

在流程编排方面,Swarm 公开了一个丰富的应用编程接口,允许您轻松部署和管理复杂的微服务应用。您可以在声明性清单文件中定义您的应用,并使用本机 Docker 命令将它们部署到 Swarm。您甚至可以执行滚动更新、回滚和缩放操作。同样,都是用简单的命令。

Docker Swarm 直接与 Kubernetes 竞争——它们都编排了容器化的应用。虽然 Kubernetes 确实有更大的发展势头和更活跃的社区和生态系统,但 Docker Swarm 是一项出色的技术,并且更容易配置和部署。对于中小型企业和应用部署来说,这是一项出色的技术。

DockerSwarm-深潜

我们将把这一章的深潜部分分成如下几部分:

  • Swarm 第一个
  • 构建安全的 Swarm 集群
  • 部署一些集群服务
  • 解决纷争

Swarm 第一个

在集群前端,一个由一个或多个 Docker 节点组成。这些可以是物理服务器、虚拟机、树莓 Pi 或云实例。唯一的要求是所有节点都安装了 Docker,并且可以通过可靠的网络进行通信。

节点配置为经理工人管理人员负责管理集群的控制平面,这意味着类似集群状态和将任务分派给工作人员这样的事情。工人接受经理的任务并执行。

的配置和状态保存在位于所有管理器上的分布式 etcd 数据库中。它被保存在内存中,并且是最新的。但是它最好的一点是它不需要配置——它是作为群体的一部分安装的,只需要照顾好自己。

在集群领域改变游戏规则的是安全方法。TLS 集成如此紧密,没有它是不可能建立一个群体的。在今天的安全意识世界里,像这样的事情应该得到所有的喝彩。 Swarm 使用 TLS 来加密通信、认证节点和授权角色。自动旋转按键也是锦上添花。最棒的是……这一切发生得如此顺利,以至于你甚至不知道它就在那里。

在应用编排方面,群调度的原子单元是服务。这是 API 中的一个新对象,与 swarm 一起引入,是一个更高级的构造,它将一些高级特性包装在容器周围。其中包括扩展、滚动更新和简单回滚。将服务视为增强的容器是有用的。

群的高级视图如图 10.1 所示。

Figure 10.1 High-level swarm

Figure 10.1 High-level swarm

这就足够了。我们举几个例子来说明一下。

构建安全的群集

在本节中,我们将构建一个安全的 Swarm 集群,其中有三个管理节点和三个工作节点。您可以使用不同数量的经理工人以及不同名称和 IPs 的不同实验室,但是下面的示例将使用图 10.2 中的值。

Figure 10.2

Figure 10.2

节点可以是虚拟机、物理服务器、云实例或树莓皮系统。唯一的要求是他们安装了 Docker,并且可以通过可靠的网络进行通信。如果配置了名称解析,这也是有益的—它可以更容易地识别命令输出中的节点,并在故障排除时提供帮助。

在网络方面,您需要在节点之间的路由器和防火墙上打开以下端口:

  • 2377/tcp:用于安全的客户端到群组通信
  • 7946/tcp and udp:为控制平面八卦
  • 4789/udp:用于基于 VXLAN 的覆盖网络

Mac 和 Windows 的 Docker 桌面仅支持单个 Docker 节点。您可以初始化一个单节点集群,并遵循大多数示例。或者,你可以试试在 https://labs.play-with-docker.com 玩 Docker。

一旦你满足了先决条件,你就可以继续建立一个群体。

建立群的过程叫做初始化群,高级过程是这样的:初始化第一个管理节点>加入额外的管理节点>加入工作节点>完成。

初始化新的群

不属于集群的 Docker 节点被称为处于单引擎模式。一旦它们被加入蜂群,就会自动切换到蜂群模式

在 Docker 主机上以单引擎模式运行docker swarm init会将该节点切换到群模式,创建一个新的,并使该节点成为群的第一个管理器

然后,其他节点可以作为工作人员和管理人员加入群。作为操作的一部分,将 Docker 主机加入现有集群会将它们切换到集群模式

以下步骤将使 mgr1 进入蜂群模式并初始化一个新的蜂群。然后,它将加入 wrk1wrk2wrk3 作为工作节点,作为流程的一部分,自动将它们置于集群模式。最后会增加 mgr2mgr3 作为额外的管理者,切换到集群模式。在程序结束时,所有 6 个节点将处于群模式,并作为同一群的一部分运行。

这个例子将使用如图 10.2 所示的节点的 IP 地址和域名。你的可能不一样。

  1. Log on to mgr1 and initialize a new swarm (don’t forget to use backticks instead of backslashes if you’re following along with Windows in a PowerShell terminal).

    $ 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. 
    

    `该命令可以分解如下:

    • docker swarm init:这告诉 Docker 初始化一个新的群,并使这个节点成为第一个管理器。它还在节点上启用集群模式。
    • --advertise-addr:顾名思义,这是群 API 端点,会向群内其他节点进行通告。它通常是节点的 IP 地址之一,但也可以是外部负载平衡器地址。这是一个可选标志,除非您想在具有多个接口的节点上指定负载平衡器或特定的 IP 地址。
    • --listen-addr:这是节点将接受群集流量的 IP 地址。如果未明确设置,则默认为与--advertise-addr相同的值。如果--advertise-addr是负载均衡器,您必须使用--listen-addr为集群流量指定本地 IP 或接口。

    我建议你具体一点,始终使用两个标志。

    群组模式运行的默认端口是 2377 。这是可定制的,但是习惯上使用2377/tcp进行安全的(HTTPS)客户端到群的连接。 * List the nodes in the swarm.

    $ docker node ls
    ID            HOSTNAME   STATUS  AVAILABILITY  MANAGER STATUS
    d21...qzkx *  mgr1       Ready   Active        Leader 
    

    注意 **mgr1** 是当前群中唯一的节点,被列为*领袖*。我们一会儿再谈这个。 * From **mgr1** run the docker swarm join-token` command to extract the commands and tokens required to add new workers and managers to the 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...)之外,加入工作者和管理器的命令是相同的。这意味着节点是作为工作者还是作为管理者加入完全取决于您在加入它时使用的令牌。**您应该确保您的加入令牌保持安全,因为它们是将节点加入群所需的唯一东西!** * Log on to **wrk1** and join it to the swarm using the docker swarm join` command with the worker join token.

    $ 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`标志可选。我已经添加了它们,因为我认为在网络配置方面最好尽可能具体。` `* 在 wrk2wrk3 上重复上一步,让他们作为工人加入群体。如果您指定了`--advertise-addr`和`--listen-addr`标志,请确保您使用了 wrk2wrk3 的各自的 IP 地址。* 登录到 mgr2 并使用带有管理器加入令牌的`docker swarm join`命令将其作为管理器加入群。

    $ 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. 
    

    * 在 **mgr3** 上重复上一步,记住将 **mgr3 的** IP 地址用于advertise-addr--listen-addr标志。* 通过从群中的任何管理器节点运行docker node ls`,列出群中的节点。

    $ 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 
    
    
    

如果您查看`MANAGER STATUS`列,您将看到三个管理器节点显示为“可达”或“领导者”。我们将很快了解更多关于领导者的信息。`MANAGER STATUS`列中没有任何内容的节点是*工作人员*。还要注意显示 **mgr2** 的行上的标识后面的星号(`*`)。这将告诉您登录到哪个节点并从哪个节点执行命令。在这种情况下,命令是从 **mgr2** 发出的。

> **注意:**每次加入群节点时指定`--advertise-addr`和`--listen-addr`标志是一件痛苦的事情。然而,如果你把你的群的网络配置弄错了,这可能是一个更大的痛苦。此外,手动向群中添加节点不太可能是一项日常任务,因此使用标志是值得的。尽管这是你的选择。在实验室环境或只有一个 IP 的节点中,您可能不需要使用它们。

现在您已经有一群*启动并运行,让我们来看看管理器高可用性(HA)。*

*#### 群集管理器高可用性

到目前为止,我们已经为一个集群添加了三个管理器节点。为什么是三个?他们是如何合作的?

Swarm *管理器*具有对高可用性(HA)的本地支持。这意味着一个或多个可以失败,幸存者将保持群体运行。

从技术上讲,swarm 实现了一种主动-被动多管理器 HA 的形式。这意味着,尽管您有多个*经理*,但在任何给定时刻,只有其中一个*处于活动状态*。这位活跃的经理被称为“T4”领袖,这位领袖是唯一一个会对*虫群*发出实时命令的人。所以,只有领导者才会改变配置,或者给员工分配任务。如果一个跟随者管理器(被动)接收到群体的命令,它会将其代理给领导者。

这个过程如图 10.3 所示。步骤`1`是来自远程 Docker 客户端的*管理器*的命令。第二步是非领导者经理接收命令并将其代理给领导者。第三步是领导者在群体上执行命令。

![Figure 10.3](img/figure10-3.png)

Figure 10.3



如果你仔细看图 10.3,你会注意到经理要么是*领导者*要么是*追随者*。这是 Raft 术语,因为 swarm 使用 [Raft 共识算法](https://raft.github.io/)的实现来跨多个高可用性管理器保持一致的集群状态。

关于高可用性主题,以下两种最佳实践适用:

1.  部署奇数个经理。
2.  不要部署太多经理(建议部署 3 或 5 名)

拥有奇数个*经理*会降低大脑分裂的几率。例如,如果您有 4 个管理器,并且网络被分区,那么您可能会在分区的每一侧剩下两个管理器。这就是所谓的大脑分裂——每一方都知道过去有 4 个,但现在只能看到 2 个。但至关重要的是,双方都无法知道其他两人是否还活着,以及是否拥有多数(法定人数)。群集群集在裂脑条件下继续运行,但您不再能够更改配置或添加和管理应用工作负载。

但是,如果您有 3 个或 5 个管理器,并且出现相同的网络分区,则不可能在分区的两侧有相同数量的管理器。这意味着一侧达到法定人数,完整的群集管理服务仍然可用。图 10.4 右侧的示例显示了一个分区集群,其中拆分的左侧知道它拥有大多数管理器。

![Figure 10.4](img/figure10-4.png)

Figure 10.4



与所有共识算法一样,更多的参与者意味着达成共识需要更多的时间。这就像决定去哪里吃饭——3 个人做出快速决定总是比 33 个人更快更容易!考虑到这一点,最佳做法是为高可用性配备 3 或 5 名经理。7 可能有用,但普遍认为 3 或 5 是最佳选择。你肯定不希望超过 7 个,因为达成共识所需的时间会更长。

关于医院管理局的最后一点警告。虽然将您的管理人员分散到网络内的可用性区域显然是一种好的做法,但是您需要确保连接他们的网络是可靠的,因为网络分区可能是一个很难排除和解决的问题。这意味着,在撰写本文时,跨多个云提供商(如 AWS 和 Azure)托管您的活动生产应用和基础架构的涅槃有点白日梦。花时间和精力确保您的经理和员工通过可靠的高速网络连接在一起。

##### 内置群组安全

Swarm 集群具有大量内置的安全性,这些安全性是用合理的默认值现成配置的——CA 设置、加入令牌、相互 TLS、加密集群存储、加密网络、加密节点 ID 等等。详见**第 15 章:Docker** 中的安全性。

##### 锁定一群

尽管有所有这些内置的本机安全性,重新启动旧的管理器或恢复旧的备份仍有可能危及群集。重新加入群的老经理会自动解密并获得对 Raft 日志时间序列数据库的访问权限,这可能会带来安全问题。恢复旧备份也可以擦除当前的群集配置。

为了防止这种情况,Docker 允许您使用自动锁定功能锁定群。这将强制重新启动的管理器在允许重新进入群集之前出示群集解锁密钥。

通过将`--autolock`标志传递给`docker swarm init`命令,可以将锁直接应用于新的群体。然而,我们已经建立了一个蜂群,所以我们将使用`docker swarm update`命令锁定我们现有的蜂群。

从集群管理器运行以下命令。



$ 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-5+ICW2kRxPxZrVyBDWzBkzZdSd0Yc7Cl2o4Uuf9NPU4

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`命令检查您当前的群解锁钥匙。

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



$ service docker restart




 `尝试列出群中的节点。



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




 `虽然 Docker 服务已经在管理器上重新启动,但它不被允许重新加入群。您可以通过在另一个管理器节点上运行`docker node ls`命令来进一步证明这一点。重启后的管理器将显示为`down`和`unreachable`。

使用`docker swarm unlock`命令为重启的管理器解锁群。您需要在重新启动的管理器上运行此命令,并且需要提供解锁密钥。



$ docker swarm unlock Please enter unlock key:




 `该节点将被允许重新加入群,如果你运行另一个`docker node ls`,它将显示为`ready`和`reachable`。

建议在生产环境中锁定集群并保护解锁密钥。

现在,您已经构建了我们的*群*并了解了*领导者*和*经理 HA* 的基础架构概念,让我们继续讨论*服务*的应用方面。

#### 群集服务

我们在本章这一节所做的一切都在第 14 章的 Docker Stacks 中得到改进。然而,重要的是你要学会这里的概念,以便为第 14 章做好准备。

就像我们在群体初级读本中所说的……*服务*是 Docker 1.12 引入的新构造,它们只适用于*群体模式*。

服务让我们可以指定大多数熟悉的容器选项,例如*名称、端口映射、连接到网络、*和*映像*。但是它们增加了重要的云原生特性,包括*期望状态*和自动协调。例如,swarm 服务允许我们声明性地定义一个我们可以应用到 swarm 的应用的期望状态,并让 swarm 负责部署和管理它。

让我们看一个简单的例子。假设你有一个带有网络前端的应用。您有一个 web 服务器的映像,测试表明您需要 5 个实例来处理正常的日常流量。您将这个需求转换成一个单一的*服务*,声明要使用的映像,并且该服务应该总是有 5 个运行的副本。您将它作为您想要的状态发布给群,群负责确保始终有 5 个 web 服务器实例在运行。

我们将在一分钟内看到其他一些可以声明为服务一部分的东西,但是在此之前,让我们看看创建我们刚刚描述的内容的一种方法。

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

1.  必须在命令行上使用`docker service create`
2.  用栈文件声明

我们将在后面的章节中研究栈文件。现在我们将集中讨论命令式方法。

> **注意:**创建新服务的命令在 Windows 上是一样的。但是,此示例中使用的映像是 Linux 映像,在 Windows 上无法工作。您可以用该映像替换一个 Windows web 服务器映像,该命令将会起作用。请记住,如果您从 PowerShell 终端键入窗口命令,您需要使用 backtick(`)来指示下一行的继续。



$ docker service create --name web-fe
-p 8080:8080
--replicas 5
nigelpoulton/pluralsight-docker-ci

z7ovearqmruwk0u2vc5o7ql0p




 `请注意,许多熟悉的`docker container run`参数是相同的。在这个例子中,我们指定了`--name`和`-p`,它们对于独立容器和服务都是一样的。

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

我们用`docker service create`告诉 Docker 我们正在声明一个新的服务,并且我们用`--name`标志将其命名为 **web-fe** 。我们告诉 Docker 将群中每个节点上的端口 8080 映射到每个服务副本内部的 8080。接下来,我们使用`--replicas`标志来告诉 Docker 这个服务应该总是有 5 个副本。最后,我们告诉 Docker 哪个映像用于副本—理解所有服务副本使用相同的映像和配置非常重要!

我们点击`Return`后,命令被发送到一个管理器节点,作为领导者的管理器在*群*上实例化了 5 个副本——记住群管理器也作为工人。每个接收到工作任务的工作人员或管理人员都会提取该映像,并启动一个容器来监听端口 8080。群领导还确保将服务的*期望状态*的副本存储在集群中,并复制给每个管理人员。

但这还不是结束。所有*服务*都由群持续监控——群运行后台*协调循环*,不断将服务的*观察状态*与*期望状态*进行比较。如果这两个州匹配,世界就是一个幸福的地方,不需要采取进一步的行动。如果不匹配,swarm 会采取措施使*观察状态*与*期望状态*一致。

例如,如果托管 5 个 **web-fe** 副本之一的*工作者*失败,则 **web-fe** 服务的*观察状态*将从 5 个副本下降到 4 个。这将不再与 5 的*期望状态*匹配,因此群体将开始新的 **web-fe** 副本,以使*观察状态*回到与*期望状态*一致。这种行为是云原生应用的一个关键原则,允许服务在节点故障等情况下自我修复。

#### 查看和检查服务

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



$ docker service ls ID NAME MODE REPLICAS IMAGE PORTS z7o...uw web-fe replicated 5/5 nigel...ci:latest *: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/... mgr2 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/... mgr3 Running Running 2 mins dyl...p3e web-fe.5 nigelpoulton/... mgr1 Running Running 2 mins




 `命令的格式为`docker service ps <service-name or service-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/pluralsight-docker-ci:latest@sha256:7a6b01...d8d3d init: false Resources: Endpoint Mode: vip Ports: PublishedPort = 8080 Protocol = tcp TargetPort = 8080 PublishMode = ingress




 `上面的示例使用`--pretty`标志将输出限制为以易于阅读的格式打印的最有趣的项目。取消`--pretty`标志会给出更详细的输出。我强烈建议你通读`docker inspect`命令的输出,因为它们是一个很好的信息来源,也是了解幕后发生的事情的好方法。

稍后我们将回到这些输出中的一些。

#### 复制服务与全球服务

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

另一种模式是`global`,它在群中的每个节点上运行一个副本。

要部署*全球服务*,您需要将`--mode global`标志传递给`docker service create`命令。

#### 扩展服务

*服务*的另一个强大功能是能够轻松地上下扩展它们。

让我们假设业务正在蓬勃发展,我们看到网络前端的流量翻了一番。幸运的是,扩展 **web-fe** 服务就像运行`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
verify: Service converged




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



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




 `运行`docker service ps`命令将显示服务副本在群中的所有节点之间均匀平衡。



$ docker service ps web-fe ID NAME IMAGE NODE DESIRED CURRENT nwf...tpn web-fe.1 nigelpoulton/... mgr1 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/... mgr3 Running Running 7 mins 64y...m49 web-fe.6 igelpoulton/... wrk3 Running Running about a min ild...51s web-fe.7 nigelpoulton/... mgr1 Running Running about a min vah...rjf web-fe.8 nigelpoulton/... wrk2 Running Running about a mins xe7...fvu web-fe.9 nigelpoulton/... mgr2 Running Running 45 seconds ago l7k...jkv web-fe.10 nigelpoulton/... mgr2 Running Running 46 seconds ago




 `在幕后,swarm 运行一种称为“扩散”的调度算法,试图在群中的节点之间尽可能均匀地平衡副本。在编写本文时,这相当于在每个节点上运行相同数量的副本,而没有考虑诸如 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




 `现在您已经知道如何扩展服务,让我们看看如何删除一个。

#### 删除服务

移除服务很简单—可能太简单了。

以下`docker service rm`命令将删除之前部署的服务。



$ docker service rm web-fe web-fe




 `用`docker service ls`命令确认它消失了。



$ docker service ls ID NAME MODE REPLICAS IMAGE PORTS




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

现在服务已经从系统中删除,让我们看看如何将滚动更新推送到一个系统中。

#### 滚动更新

将更新推送到已部署的应用是一个现实。在很长一段时间里,这真的很痛苦。由于重大的应用更新,我已经失去了足够多的周末,我也不打算再做一次。

嗯……多亏了 Docker *服务*,将更新推送到设计良好的微服务应用变得很容易。

为了看到这一点,我们将部署一项新服务。但在此之前,我们将为该服务创建一个新的覆盖网络。这不是必需的,但是我想让您看看它是如何完成的,以及如何将服务附加到它上面。



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




 `这创建了一个名为“uber-net”的新覆盖网络,我们将在即将创建的服务中使用它。覆盖网络创建了一个新的第 2 层网络,我们可以在上面放置容器,并且上面的所有容器都可以通信。即使所有的群节点都在不同的底层网络上,这也是可行的。基本上,覆盖网络在潜在的多个不同底层网络之上创建一个新的第 2 层容器网络。

图 10.5 显示了由第 3 层路由器连接的两个底层网络上的四个群节点。覆盖网络跨越所有 4 个群节点,创建一个单一的平面第 2 层网络供容器使用。

![Figure 10.5](img/figure10-5.png)

Figure 10.5



运行`docker network ls`以验证网络创建正确,并且在 Docker 主机上可见。



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




 `在`swarm`范围内成功创建了`uber-net`网络,目前*仅在群中的管理节点上可见。当工作节点运行网络上配置的工作负载时,它将动态扩展到工作节点。*

让我们创建一个新服务并将其连接到网络。



$ docker service create --name uber-svc
--network uber-net
-p 80:80 --replicas 12
nigelpoulton/tu-demo:v1

dhbtgvqrg2q4sg07ttfuhg8nz overall progress: 12 out of 12 tasks 1/12: running
2/12: running
12/12: running
verify: Service converged




 `让我们看看我们刚刚用那个`docker service create`命令声明了什么。

我们做的第一件事是命名服务,然后使用`--network`标志告诉它将所有副本放在新的`uber-net`网络上。然后,我们在整个集群中暴露端口 80,并将其映射到我们要求它运行的 12 个副本中的每个副本中的端口 80。最后,我们告诉它所有的复制品都基于 nigelpoulton/tu-demo:v1 映像。

运行`docker service ls`和`docker service ps`命令来验证新服务的状态。



$ docker service ls ID NAME REPLICAS IMAGE dhbtgvqrg2q4 uber-svc 12/12 nigelpoulton/tu-demo:v1

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




 `将`-p 80:80`标志传递给服务将确保创建**群范围的**映射,该映射将所有流量从端口 80 上的群中的任何节点映射到任何服务副本内的端口 80。

这种在集群中的每个节点上发布端口的模式,甚至是没有运行服务副本的节点,被称为*入口模式*,并且是默认模式。另一种模式是*主机模式*,它只在运行副本的群节点上发布服务。在*主机模式*下发布服务需要长格式语法,如下所示:



$ docker service create --name uber-svc
--network uber-net
--publish published=80,target=80,mode=host
--replicas 12
nigelpoulton/tu-demo:v1




 `打开一个网络浏览器,将它指向端口 80 上集群中任何节点的 IP 地址,以查看服务的运行情况。

![Figure 10.6](img/figure10-6.png)

Figure 10.6



如您所见,这是一个简单的投票应用,将为“足球”或“英式足球”注册投票。请随意将您的网络浏览器指向群中的其他节点。您将能够从任何节点访问 web 服务,因为`-p 80:80`标志在每个群节点上创建了*入口模式*映射。即使在没有运行服务副本的节点上也是如此— **每个节点都获得一个映射,因此可以将您的请求重定向到运行服务的节点**。

现在让我们假设这个特定的投票已经结束,并且您的公司想要运行一个新的投票。已经为新的轮询创建了一个新的容器映像,并将其添加到同一个 Docker Hub 存储库中,但是这个被标记为`v2`而不是`v1`。

让我们也假设您的任务是以分阶段的方式将更新后的映像推送到群集—一次 2 个副本,每个副本之间有 20 秒的延迟。您可以使用以下`docker service update`命令来完成此操作。



$ docker service update
--image nigelpoulton/tu-demo:v2
--update-parallelism 2
--update-delay 20s uber-svc

overall progress: 4 out of 12 tasks 1/12: running
2/12: running
3/12: running
4/12: running
5/12: starting
6/12: ready
12/12:




 `让我们回顾一下命令。`docker service update`让我们通过更新服务的期望状态来更新正在运行的服务。本示例指定了映像的新版本,标记为`v2`而不是`v1`。它还指定了`--update-parallelism`和`--update-delay`标志,以确保每次将新映像推送到 2 个副本,每组两个副本之间有 20 秒的冷却时间。最后,它指示群对`uber-svc`服务进行更改。

如果你在更新过程中运行一个`docker service ps uber-svc`,一些副本将在`v2`而一些仍在`v1`。如果您给操作足够的时间来完成(4 分钟),所有副本将最终达到使用`v2`映像的新的期望状态。



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




 `您可以通过打开群中任何节点的网络浏览器并点击几次刷新来实时见证更新的发生。一些请求将由运行旧版本的副本提供服务,一些将由运行新版本的副本提供服务。足够长的时间后,所有请求都将由运行该服务的更新版本的副本提供服务。

恭喜你。您刚刚将滚动更新推送到一个实时容器化应用。记住,在第 14 章中,Docker Stacks 将所有这些提升到下一个层次。

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



$ docker service inspect --pretty uber-svc ID: mub0dgtc8szm80ez5bs8wlt19 Name: uber-svc Service Mode: Replicated Replicas: 12 UpdateStatus: State: updating Started: About a minute Message: update in progress Placement: UpdateConfig: Parallelism: 2 Delay: 20s 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/tu-demo:v2@sha256:d3c0d8c9...cf0ef2ba5eb74c init: false Resources: Networks: uber-net Endpoint Mode: vip Ports: PublishedPort = 80 Protocol = tcp TargetPort = 80 PublishMode = ingress




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

您还应该注意`docker inspect`输出的`Networks`部分。这显示了`uber-net`网络以及群体范围的`80:80`端口映射。

#### 解决纷争

可以使用`docker service logs`命令查看群组服务日志。但是,并非所有日志记录驱动程序都支持该命令。

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

*   `journald`(仅适用于运行`systemd`的 Linux 主机)
*   `syslog`
*   `splunk`
*   `gelf`

`json-file`和`journald`最容易配置,两者都使用`docker service logs`命令。命令的格式为`docker service logs <service-name>`。

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

`daemon.json`配置文件的以下片段显示了配置为使用`syslog`的 Docker 主机。



{ "log-driver": "syslog" }




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

服务日志的工作前提是您的应用在其容器中作为 PID 1 运行,并将日志发送到`STDOUT`并将错误发送到`STDERR`。日志驱动程序将这些“日志”转发到通过日志驱动程序配置的位置。

以下`docker service logs`命令显示了`svc1`服务中所有副本的日志,这些副本在启动副本时经历了几次故障。



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




 `输出被修剪以适合页面,但是您可以看到显示了来自所有三个服务副本的日志(两个失败,一个正在运行)。每一行都以复制副本的名称开始,包括服务名称、复制副本编号、复制副本标识和计划运行的主机名称。下面是日志输出。

这很难说,因为它被修剪以适应书本,但看起来前两个副本失败了,因为它们试图连接到另一个仍在启动的服务(当相关服务启动时,这是一种竞争情况)。

你可以跟踪日志(`--follow`),跟踪日志(`--tail`),获取额外的细节(`--details`)。

#### 备份群

备份群将备份在发生灾难性损坏故障时恢复群所需的控制平面对象。从备份中恢复群集是极其罕见的情况。但是,业务关键型环境应该始终为最坏的情况做好准备。

您可能会问,如果控制平面已经复制并且高度可用(高可用性),为什么还需要备份。为了回答这个问题,考虑一个场景,一个恶意的参与者删除了一个群体的所有机密。在这种情况下,高可用性无能为力,因为机密将从自动复制到所有管理器节点的群集存储中删除。在这种情况下,高可用性的复制集群存储对您不利—快速传播删除操作。在这种情况下,您可以从源代码报告中保存的副本中重新创建已删除的对象,也可以尝试从最近的备份中恢复群。

以声明的方式管理集群和应用是防止需要从备份中恢复的一个好方法。例如,将群外的配置对象存储在源代码存储库中将使您能够重新部署网络、服务、机密和其他对象。然而,以声明的方式管理您的环境并严格使用源代码控制的回购需要纪律。

不管怎样,让我们看看如何**备份一群**。

群配置和状态存储在每个管理器节点上的`/var/lib/docker/swarm`中。配置包括;Raft 日志密钥、覆盖网络、机密、配置、服务等。群集备份是此目录中所有文件的副本。

由于此目录的内容被复制到所有管理器,您可以并且应该从多个管理器执行备份。但是,由于您必须停止正在备份的节点上的 Docker 守护程序,所以从非领导者管理器执行备份是一个好主意。这是因为停止对领导人的 Docker 将启动领导人选举。您还应该在业务不活跃的时候执行备份,因为如果另一个管理器在备份过程中出现故障,停止一个管理器会增加群丢失仲裁的风险。

我们将要遵循的过程是为演示目的而设计的,您需要根据您的生产环境进行调整。它还创建了几个群对象,以便后面的步骤可以证明恢复操作是有效的。

> **警告**:以下操作存在风险。您还应该确保定期执行测试备份和恢复操作,并测试结果。

以下命令将创建以下两个对象,以便您可以证明恢复操作:

*   名为“Unimatrix-01”的覆盖网络
*   一个名为“失踪的无人机”的机密,包含文本“九分之七”



$ docker network create -d overlay Unimatrix-01 w9l904ff73e7stly0gnztsud7

$ printf "Seven of Nine" | docker secret create missing_drones - i8oj3b2lid27t5202uycw37lg




 `让我们执行集群备份。

1.  Stop Docker on a non-leader swarm manager.

    如果有任何容器或服务任务在节点上运行,此操作可能会停止它们。

    

    ```
     $ service docker stop 
    ```

    

`*   Backup the Swarm config.

    本示例使用 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> 
    ```

    

    `*   Verify the backup file exists.

    

    ```
     $ ls -l
     -rw-r--r-- 1 root   root   450727 May 4 14:06 swarm.bkp 
    ```

    

     `在现实世界中,您应该根据任何公司备份策略存储和轮换此备份。

    此时,群已备份,您可以在节点上重新启动 Docker。` `*   重启 Docker。

    

    ```
     $ service docker restart 
    ```

    ``` 

 ```现在您有了备份,让我们执行一次测试恢复。此过程中的步骤演示了操作。在现实世界中执行恢复可能略有不同,但总体过程是相似的。

> **注意**:如果您的群仍在运行,并且您只希望添加一个新的管理器节点,则不必执行恢复操作。在这种情况下,只需添加一个新经理。群集恢复仅适用于群集损坏或丢失,并且您无法从存储在源代码 repo 中的配置文件副本中恢复服务的情况。

我们将使用早期的`swarm.bkp`文件来恢复蜂群。**所有群节点必须停止其 Docker 守护程序,并删除其`/var/lib/docker/swarm`目录的内容。**

要使恢复操作起作用,以下条件也必须成立:

1.  您只能还原到运行与执行备份时相同版本的 Docker 的节点
2.  您只能恢复到与执行备份的节点具有相同 IP 地址的节点

从您希望恢复的群管理器节点执行以下任务。记住必须停止 Docker,必须删除`/var/lib/docker/swarm`的内容。

1.  Restore the Swarm configuration from backup.

    在这个例子中,我们将从一个名为`swarm.bkp`的压缩`tar`文件中恢复。此命令需要恢复到根目录,因为它将包含原始文件的完整路径,作为提取操作的一部分。这在您的环境中可能会有所不同。

    

    ```
     $ tar -zxvf swarm.bkp -C / 
    ```

    

`*   启动 Docker。启动 Docker 的方法因环境而异。

    

    ```
     $ service docker start 
    ```

    

    `*   Initialize a new Swarm cluster.

    请记住,您不是在恢复管理器并将其添加回工作集群。这个操作是为了恢复一个没有幸存管理器的失败群。`--force-new-cluster`标志告诉 Docker 使用您在步骤 1 中恢复的存储在`/var/lib/docker/swarm/`中的配置创建一个新集群。

    

    ```
     $ docker swarm init --force-new-cluster
     Swarm initialized: current node (jhsg...3l9h) is now a manager. 
    ```

    

    `*   Check that the network and service were recovered as part of the operation.

    

    ```
     $ docker network ls
     NETWORK ID          NAME                DRIVER              SCOPE
     z21s5v82by8q        Unimatrix-01        overlay             swarm

     $ docker secret ls
     ID                          NAME                DRIVER        
     i8oj3b2lid27t5202uycw37lg   missing_drones 
    ```

    

     `恭喜你。蜂群恢复了。` `*   添加新的管理节点和工作节点,并进行新的备份。````

 ```记住,定期彻底地测试这个程序。你不希望它在你最需要的时候失败!

### DockerSwarm-命令

*   `docker swarm init`是创建新蜂群的命令。运行该命令的节点将成为第一个管理器,并切换为在*集群模式*下运行。
*   `docker swarm join-token`揭示将工人和经理加入现有集群所需的命令和令牌。要公开加入新经理的命令,请使用`docker swarm join-token manager`命令。要获得加入工人的命令,请使用`docker swarm join-token worker`命令。
*   `docker node ls`列出群中的所有节点,包括哪些是管理者,哪些是领导者。
*   `docker service create`是创建新服务的命令。
*   `docker service ls`列出群中正在运行的服务,并给出服务及其正在运行的任何副本的状态的基本信息。
*   `docker service ps <service>`给出了关于单个服务副本的更详细的信息。
*   `docker service inspect`给出服务的非常详细的信息。它接受`--pretty`标志,将返回的信息限制为最重要的信息。
*   `docker service scale`允许您上下扩展服务中的副本数量。
*   `docker service update`允许您更新正在运行的服务的许多属性。
*   `docker service logs`允许您查看服务的日志。
*   `docker service rm`是从群中删除服务的命令。请谨慎使用,因为它会删除所有服务副本,而不要求确认。

### 章节总结

Docker Swarm 是 Docker 用于管理 Docker 节点集群以及部署和管理云原生应用的原生技术。它类似于 Kubernetes。

Swarm 的核心是一个安全的集群组件和一个编排组件。

安全集群组件是企业级的,提供了大量的安全和高可用性功能,这些功能可以自动配置,修改起来也非常简单。

编排组件允许您以简单的声明方式部署和管理云原生微服务应用。

我们将在第 14 章更深入地探讨以声明方式部署云原生微服务应用。```````````````````````````````*```