加快软件部署 - Docker Swarm 教程
已发表: 2022-03-11除非您一直住在集装箱内,否则您可能听说过集装箱。 该行业一直在从持久性基础设施转向临时基础设施,而容器在这一转变过程中是方正的。 原因很简单:虽然容器确实可以帮助开发团队快速启动和运行,但它们更有潜力彻底改变运营的面貌。
但这究竟是什么样子的呢? 当您准备好在本地运行容器或在几台服务器上手动运行容器时会发生什么? 在一个理想的世界里,你只想把你的应用程序扔到一个服务器集群上,然后说“运行它!”
谢天谢地,这就是我们今天所处的位置。
在本文中,我们将探讨 Docker Swarm 是什么,以及它必须提供的一些出色功能。 然后我们将看看实际使用 Swarm 模式并部署到一个 swarm 是什么样子的,我们将用一些示例来说明使用已部署的 swarm 的日常操作。 绝对建议您了解 Docker 和容器的基本知识,但如果您不熟悉容器,可以先查看这篇出色的博客文章。
什么是 Docker Swarm?
在我们深入创建和部署到我们的第一个 swarm 之前,了解 Docker Swarm 是什么会很有帮助。 Docker 本身已经存在多年,今天大多数人认为它是一个容器运行时。 但实际上,Docker 由许多不同的部分组成,它们一起工作。 例如,该容器运行时部分由两个较小的组件处理,称为 runC 和 containerd。 随着 Docker 的发展和回馈社区,他们发现创建这些更小的组件是增长和快速添加功能的最佳方式。 因此,我们现在有 SwarmKit 和 Swarm 模式,它直接内置在 Docker 中。
Docker Swarm 是一个容器编排引擎。 在高层次上,它需要在不同主机上运行多个 Docker 引擎,并允许您一起使用它们。 用法很简单:将您的应用程序声明为服务堆栈,并让 Docker 处理其余部分。 服务可以是从应用程序实例到数据库的任何东西,也可以是 Redis 或 RabbitMQ 等实用程序。 如果您曾经在开发中使用过 docker-compose,这听起来应该很熟悉,因为它是完全相同的概念。 事实上,堆栈声明实际上只是一个docker-compose.yml
文件,具有 3.1 版本的语法。 这意味着您可以使用类似的(并且在许多情况下相同的)组合配置进行开发和集群部署,但是我在这里有点超前了。 当您在 Swarm 模式下拥有 Docker 实例时会发生什么?
不要从木筏上掉下来
在 Swarm 世界中,我们有两种类型的节点(服务器):管理器和工作器。 重要的是要记住,经理也是工人,他们只是有额外的责任让事情继续运转。 每个 swarm 都从一个指定为领导者的管理节点开始。 从那里,只需运行一个命令即可将节点安全地添加到集群中。
由于实现了 Raft 算法,Swarm 具有很高的可用性。 我不会详细介绍 Raft,因为已经有一个关于它如何工作的很棒的教程,但这里有一个大体思路:领导节点不断地与其他管理节点检查并同步它们的状态。 为了“接受”状态更改,管理节点要达成共识,这发生在大多数节点确认状态更改时。
这样做的美妙之处在于,管理节点可以在不影响群体共识的情况下偶尔下降。 如果状态更改达成共识,我们知道它保证存在于大多数管理器节点上,并且即使当前领导者失败也会持续存在。
假设我们有三个管理节点,分别名为 A、B 和 C。当然,A 是我们无畏的领导者。 好吧,有一天,一个短暂的网络错误使 A 离线,只剩下 B 和 C。 在很长一段时间(几百毫秒)没有收到 A 的消息后,B 和 C 等待一个随机生成的时间段,然后将自己进行选举并通知对方。 当然,在这种情况下,第一个要选举选举的选举将被选为。 在此示例中,B 成为新的领导者,并且恢复了法定人数。 但是,情节转折:当 A 重新上线时会发生什么? 它会认为它仍然是领导者,对吧? 每次选举都有一个与之相关的术语,所以A实际上是在第1期的一期间选出的。一旦返回在线并开始订购B和C,他们会让它知道B是第2期的领导者,而且A将下台。
当然,同样的过程在更大的范围内起作用。 您可以拥有三个以上的管理器节点。 不过,我会添加另一个快速说明。 每个 Swarm 只能承受特定数量的经理损失。 一群 n 个管理器节点可以失去(n-1)/2
管理器而不会失去法定人数。 这意味着对于三个管理器群,您可能会失去一个,对于五个管理器,您可能会失去两个,等等。其根本原因回到了多数共识的想法,并且在您进行生产时绝对要牢记这一点。
任务调度和协调
到目前为止,我们已经确定我们的经理非常善于保持同步。 伟大的! 但他们实际上在做什么? 还记得我说过你将一堆服务部署到 Swarm 吗? 当你声明你的服务时,你向 Swarm 提供了关于你希望你的服务如何运行的重要信息。 这包括参数,例如您想要每个服务的副本数量、副本应该如何分布、它们是否应该只在某些节点上运行等等。
部署服务后,管理人员的工作就是确保继续满足您设置的任何部署要求。 假设您部署了一个 Nginx 服务并指定应该有三个副本。 管理器将看到没有容器正在运行,并将三个容器均匀地分布在可用节点上。
不过,更酷的是,如果一个容器发生故障(或整个节点下线),Swarm 将自动在其余节点上创建容器以弥补差异。 如果您说要运行三个容器,那么您将运行三个容器,而 Swarm 处理所有细节。 另外——这是一个很大的优势——扩大或缩小规模就像给 Swarm 一个新的复制设置一样容易。
服务发现和负载平衡
我想从最后一个例子中指出一个重要但微妙的细节:如果 Swarm 在它选择的节点上智能地启动容器,我们不一定知道这些容器将在哪里运行。 起初这听起来可能很吓人,但它实际上是 Swarm 最强大的功能之一。
继续同样的 Nginx 示例,假设我们告诉 Docker 这些容器应该公开端口 80。如果您将浏览器指向在端口 80 上运行该容器的节点,您将看到该容器的内容。 那里没有惊喜。 但可能令人惊讶的是,如果您将请求发送到未运行该容器的节点,您仍然会看到相同的内容! 这里发生了什么事?
Swarm 实际上是使用入口网络将您的请求发送到运行该容器的可用节点,并且同时对其进行负载平衡。 因此,如果您向同一个节点发出三个请求,您很可能会访问三个不同的容器。 只要您知道 swarm 中单个节点的 IP,就可以访问其中运行的任何内容。 相反,这使您可以将负载均衡器(例如 ELB)指向 swarm 中的所有节点,而无需担心在哪里运行什么。
它不会停留在外部连接上。 在同一堆栈上运行的服务有一个覆盖网络,可以让它们相互通信。 无需在代码中硬编码 IP 地址,您只需使用服务名称作为要连接的主机名即可。 例如,如果您的应用程序需要与名为“redis”的 Redis 服务通信,它可以简单地使用“redis”作为主机名,Swarm 会将其请求路由到适当的容器。 而且因为这在使用 docker-compose 的开发和使用 Docker Swarm 的生产中无缝工作,所以在部署应用程序时无需担心一件事。
滚动更新
如果您在操作中,当产品更新出现严重错误时,您可能会经历恐慌发作。 这可能是错误的代码更新,甚至只是配置错误,但突然生产停止了! 很可能老板不会在意任何一种方式。 他们只会知道这是你的错。 好吧,别担心,Swarm 也支持这个。
更新服务时,您可以定义一次应该更新多少个容器,以及如果新容器开始失败应该发生什么。 在某个阈值之后,Swarm 可以停止更新或(从 Docker 17.04 开始)将容器回滚到之前的镜像和设置。 不用担心明天早上必须给你的老板带杯咖啡。
安全
最后但并非最不重要的一点是,Docker Swarm 具有开箱即用的强大安全功能。 当一个节点加入 swarm 时,它会使用一个令牌,该令牌不仅可以验证自己,还可以验证它是否正在加入您认为的 swarm。 从那一刻起,节点之间的所有通信都使用相互 TLS 加密进行。 这种加密全部由 Swarm 自动配置和管理,因此您无需担心更新证书和其他典型的安全问题。 当然,如果您想强制密钥轮换,则有一个命令。
最新版本的 Docker Swarm 还带有内置的秘密管理。 这使您可以将密钥和密码等秘密安全地部署到需要它们的服务,并且只部署到需要它们的服务。 当您为服务提供密钥时,该服务的容器将在其文件系统中安装一个特殊文件,其中包含密钥的值。 不言而喻,但这比使用环境变量安全得多,后者是传统方法。
潜入虫群
如果你和我一样,你会渴望加入并体验所有这些功能! 所以事不宜迟,让我们开始吧!
Docker Swarm 示例应用程序
我创建了一个非常基本的 Flask 应用程序来展示使用 Docker Swarm 的强大功能和易用性。 Web 应用程序只显示一个页面,告诉您哪个容器为您的请求提供了服务,总共提供了多少个请求,以及“秘密”数据库密码是什么。
它分为三个服务:实际的 Flask 应用程序、一个 Nginx 反向代理和一个 Redis 密钥库。 在每个请求上,应用程序都会增加 Redis 中的num_requests
键,因此无论您点击哪个 Flask 实例,您都会看到反映的请求的正确数量。
如果您想“查看”正在发生的事情,所有源代码都可以在 GitHub 上找到。
与 Docker 一起玩!
学习本教程时,您可以随意使用自己的服务器,但如果您想直接加入,我强烈建议您使用 play-with-docker.com。这是一个由一些 Docker 开发人员运行的站点,可让您启动多个网络预先安装了 Docker 的节点。 它们将在四个小时后关闭,但这对于这个示例来说已经足够了!
创建一个 Swarm
好的,我们开始吧! 继续在 PWD(play-with-docker)中创建三个实例,或者在您最喜欢的 VPS(虚拟专用服务器)服务中启动三个服务器,并在所有这些服务器上安装 Docker 引擎。 请记住,您始终可以创建一个图像并在将来添加节点时重新使用它。 管理节点和工作节点之间在软件方面没有区别,因此您不需要维护两个不同的图像。
还在转? 别担心,我等着。 好的,现在我们将创建我们的第一个管理器和领导节点。 在您的第一个实例中,初始化一个 swarm:
docker swarm init --advertise-addr <node ip here>
将<node_ip_here>
替换为您节点的 IP 地址。 在 PWD 上,IP 地址显示在顶部,如果您使用自己的 VPS,只要可以从网络中的其他节点访问,请随意使用服务器的私有 IP 地址。
你现在有一群! 不过,这是一个非常无聊的集群,因为它只有一个节点。 让我们继续添加其他节点。 您会注意到,当您运行init
时,它会显示一条长消息,说明如何使用加入令牌。 我们不会使用那个,因为它会使其他节点成为工作节点,我们希望他们成为管理者。 让我们通过在第一个节点上运行它来获取管理器的加入令牌:
docker swarm join-token manager
复制生成的命令并在第二个和第三个节点上运行它。 看哪,一个三节点集群! 让我们验证我们所有的节点是否真的存在。 docker node ls
命令将列出我们 swarm 中的所有节点。 您应该看到如下内容:
$ docker node ls ID HOSTNAME STATUS AVAILABILITY MANAGER STATUS su1bgh1uxqsikf1129tjhg5r8 * node1 Ready Active Leader t1tgnq38wb0cehsry2pdku10h node3 Ready Active Reachable wxie5wf65akdug7sfr9uuleui node2 Ready Active Reachable
请注意我们的第一个节点如何在 ID 旁边有一个星号。 这只是告诉我们这是我们当前连接的节点。 我们还可以看到这个节点当前是Leader,如果有什么事情发生,其他节点都是Reachable。

花点时间体会一下这有多简单,让我们部署我们的第一个应用程序!
装运它!
就在此时,业务开发团队向客户承诺,他们的新应用程序将在一小时内部署并准备就绪! 典型的,我知道。 但不要担心,我们几乎不需要那么多时间,因为它是使用 Docker 构建的! 开发人员很友好地将他们的docker-compose
文件借给我们:
version: '3.1' services: web: image: lsapan/docker-swarm-demo-web command: gunicorn --bind 0.0.0.0:5000 wsgi:app deploy: replicas: 2 secrets: - db_password nginx: image: lsapan/docker-swarm-demo-nginx ports: - 8000:80 deploy: mode: global redis: image: redis deploy: replicas: 1 secrets: db_password: external: true
我们稍后会分解它,但现在还没有时间。 让我们部署它! 继续在您的第一个节点上创建一个名为docker-compose.yml
的文件,并使用上面的配置填充它。您可以使用echo "<pasted contents here>" > docker-compose.yml
。
通常,我们可以直接部署它,但我们的配置提到我们使用一个名为db_password
的秘密,所以让我们快速创建该秘密:
echo "supersecretpassword" | docker secret create db_password -
伟大的! 现在我们需要做的就是告诉 Docker 使用我们的配置:
docker stack deploy -c docker-compose.yml demo
当你运行这个命令时,你会看到 Docker 创建了我们定义的三个服务: web
、 nginx
和redis
。 但是,因为我们将堆栈命名为 demo,所以我们的服务实际上命名为demo_web
、 demo_nginx
和demo_redis
。 我们可以通过运行docker service ls
命令来查看我们正在运行的服务,它应该显示如下内容:
$ docker service ls ID NAME MODE REPLICAS IMAGE PORTS cih6u1t88vx7 demo_web replicated 2/2 lsapan/docker-swarm-demo-web:latest u0p1gd6tykvu demo_nginx global 3/3 lsapan/docker-swarm-demo-nginx:latest *:8000->80/ tcp wa1gz80ker2g demo_redis replicated 1/1 redis:latest
瞧! Docker 已将我们的图像下载到适当的节点并为我们的服务创建容器。 如果您的副本尚未满负荷,请稍等片刻,然后再次检查。 Docker 可能仍在下载图像。
眼见为实
不过,不要相信我的话(或 Docker 的话)。 让我们尝试连接到我们的应用程序。 我们的服务配置告诉 Docker 在端口 8000 上公开 NGINX。如果您使用的是 PWD,页面顶部应该有一个蓝色链接,上面写着“8000”。 PWD 实际上已经自动检测到我们在该端口上运行了一项服务! 单击它,它会将您路由到端口 8000 上的选定节点。如果您滚动自己的服务器,只需导航到端口 8000 上的服务器 IP 之一。
您将看到一个风格精美的屏幕,为您提供一些基本信息:
记下哪个容器为您的请求提供服务,然后刷新页面。 有可能变了。 但为什么? 好吧,我们告诉 Docker 创建我们的 Flask 应用程序的两个副本,并将请求分发到这两个实例。 你第二次碰巧撞到了另一个容器。 您还会注意到请求数量增加了,因为两个 Flask 容器都在与我们指定的单个 Redis 实例进行通信。
随意尝试从任何节点访问端口 8000。 您仍会被正确路由到该应用程序。
揭开魔法的神秘面纱
在这一点上,一切正常,希望你发现这个过程是无痛的! 让我们仔细看看那个docker-compose.yml
文件,看看我们实际上告诉了 Docker 什么。 在高层次上,我们看到我们已经定义了三个服务: web
、 nginx
和redis
。 就像普通的 compose 文件一样,我们为 Docker 提供了一个用于每个服务的映像,以及一个运行命令。 对于nginx
,我们还指定主机上的端口 8000 应该映射到容器中的端口 80。 到目前为止,所有这些都是标准的撰写语法。
这里的新功能是部署密钥和秘密密钥。 这些键会被docker-compose
忽略,因此它们不会影响您的开发环境,但会被docker stack
使用。 让我们看看网络服务。 很简单,我们告诉 Docker 我们想运行两个副本我们的 Flask 应用程序。 我们还让 Docker 知道 Web 服务需要db_password
密码。 这确保了容器将有一个名为/run/secrets/db_password
的文件,其中包含密钥的值。
向下移动到 Nginx,我们可以看到部署模式设置为global
。 默认值(在 web 中隐式使用)是replicated
,这意味着我们将指定我们想要多少副本。 当我们指定global
时,它告诉 Docker 集群中的每个节点都应该运行一个服务实例。 再次运行docker service ls
,你会注意到nginx
有三个副本,一个用于我们 swarm 中的每个节点。
最后,我们指示 Docker 在 swarm 的某处运行单个 Redis 实例。 在哪里都没有关系,因为我们的 Web 容器在请求 Redis 主机时会自动路由到它。
每天使用 Swarm
恭喜您将您的第一个应用程序部署到 Docker Swarm! 让我们花点时间回顾一下您将使用的一些常用命令。
检查你的 Swarm
需要检查您的服务吗? 尝试docker service ls
和docker service ps <service name>
。 前者向您显示每个服务的高级概述,后者为您提供有关为指定服务运行的每个容器的信息。 当您想查看哪些节点正在运行服务时,该功能特别有用。
滚动更新
当您准备好更新应用程序时怎么办? 好吧,关于docker stack deploy
很酷的一点是它实际上也会将更新应用到现有堆栈。 假设您已将一个新的 Docker 映像推送到您的存储库。 实际上,您可以只运行您第一次使用的相同部署命令,您的 swarm 将下载并部署新映像。
当然,您可能并不总是希望更新堆栈中的每个服务。 我们也可以在服务级别执行更新。 假设我最近更新了我的网络服务的图像。 我可以发出这个命令来更新我所有的 web 容器:
docker service update \ --image lsapan/docker-swarm-demo-web:latest \ demo_web
该命令的另一个好处是,如果您在原始配置中指定它应该应用滚动更新,它将应用滚动更新。 即使您没有这样做,您也可以将标志传递给更新,以指示它执行滚动更新,如下所示:
docker service update \ --image lsapan/docker-swarm-demo-web:latest \ --update-parallelism 1 --update-delay 30s \ demo_web
这将一次更新一个容器,在两次更新之间等待 30 秒。
向上或向下扩展服务
拥有两个 Web 容器很棒,但您知道哪个更好吗? 有十个! 在 swarm 中向上和向下扩展服务非常简单:
docker service scale demo_web=10
运行该命令并检查docker service ps demo_web
的输出。 您会看到我们现在有十个容器,其中八个是刚刚启动的。 如果你有兴趣,你也可以回到 web 应用程序,刷新几次页面,看看你现在得到的不仅仅是原来的两个容器 ID。
删除堆栈和服务
您的堆栈和服务已部署和扩展,太棒了! 但是现在您想让它们离线。 这可以通过相应的rm
命令来完成。 要删除我们的演示堆栈,请运行以下命令:
docker stack rm demo
或者,如果您只想删除一个服务,只需使用:
docker service rm demo_web
排水节点
还记得我们之前运行docker node ls
来检查 swarm 中的节点吗? 它提供了关于每个节点的大量信息,包括其可用性。 默认情况下,节点是Active ,这意味着它们是运行容器的公平游戏。 但是,有时您可能需要使节点暂时脱机以执行维护。 当然,您可以将其关闭,然后群会恢复,但是给 Moby(Docker 鲸鱼)一点通知是很好的。
这就是耗尽节点的用武之地。当您将节点标记为Drain时,Docker Swarm 会将其上运行的任何容器委托给其他节点,并且在您将其可用性更改回Active之前,它不会启动该节点上的任何容器。
假设我们要耗尽node1
。 我们可以运行:
docker node update --availability drain node1
简单! 当您准备好让它恢复工作时:
docker node update --availability active node1
包起来
正如我们所见,Docker 与 Swarm 模式相结合,使我们能够比以往更高效、更可靠地部署应用程序。 值得一提的是,Docker Swarm 绝不是唯一的容器编排引擎。 事实上,它是年轻人中的一员。 Kubernetes 存在的时间更长,而且肯定会在更多的生产应用程序中使用。 也就是说,Swarm 是 Docker 官方开发的,他们每天都在努力添加更多的功能。 无论您选择使用哪个,请保持容器化!