Docker-Swarm-原生集群(全)

Docker Swarm 原生集群(全)

原文:zh.annas-archive.org/md5/9B6C0DB62EFC5AC8A8FAA5F289DFA59D

译者:飞龙

协议:CC BY-NC-SA 4.0

前言

欢迎来到具有 Swarm 的本地 Docker 集群!这是一本关于容器和分布式系统的书。我们将展示如何使用本地 Docker 工具来建模微服务,生成任务,扩展应用程序的规模,并将容器推送到 Docker 集群的极限!一句话,我们将讨论 Docker 编排。

随着 Swarm Mode 的最近崛起以及在 Docker Engine 内部启用 Swarm,事实证明编排 Docker 的最佳方式是……Docker!

不错,但是“编排 Docker”是什么意思?什么是编排?更好的是,什么是管弦乐队?

前言

管弦乐队是由指挥家带领的音乐家组成的合奏团,指挥家指挥着节奏、节奏和声音的形状。弦乐、木管、打击乐、键盘和其他乐器都遵循指挥家的指导来演奏令人惊叹的交响乐,例如贝多芬的第九交响曲。

同样,在容器编排系统中,音乐家是任务,指挥是领导服务(Swarm 原语)。任务不仅仅演奏交响乐,或者至少不仅仅是:更抽象地说,它们执行一些计算工作,例如运行 Web 服务器。指挥家 Swarm 负责它们的配置、可用性、链接和扩展。这(以及更多)就是我们所说的“Docker 编排”。

本书展示了如何配置这样的 Docker“管弦乐队”,如何保证服务的可用性,如何连接任务以及如何扩展平台,以演奏您应用程序的交响乐。

本书涵盖内容

第一章,“欢迎来到 Docker Swarm”,介绍了 Swarm,并解释了为什么您需要一个容器的集群解决方案。它说明了 Swarm 的特性,并对其架构进行了高层次的描述。我们定义了一些用例,并描述了 Swarm 与 Fleet、Kubernetes 和 Mesos 的不同之处。本章介绍了 Docker 工具的安装,最后介绍了两个 Swarm 配置:本地 Swarm Standalone 和 DigitalOcean 上的远程 Swarm Mode 集群。

第二章,“发现发现服务”,是一个描述性和大部分抽象的章节。我们将学习发现机制和共识算法是什么,以及它们对分布式系统的重要性。我们将详细描述 Raft 及其实现 Etcd,这是 Swarm 模式中包含的共识机制。我们还将通过使用 Consul 扩展本地微小示例来展示第一章中使用的发现机制的局限性,并重新部署它。

第三章,“了解 Docker Swarm 模式”,讲述了允许创建任意大小任务集群的新 Docker 工具包。我们将介绍 Swarmit,Docker Swarm 模式的基础,展示它在 Docker 1.12+中的工作原理,讨论其架构、概念,以及它与“旧”Swarm 的不同之处,以及它如何通过抽象服务和任务来组织工作负载。

第四章,“创建生产级别的 Swarm”,展示并讨论了由社区驱动的项目 Swarm2k 和 Swarm3k,我们的 2300 和 4800 节点 Swarm 集群实验,其中运行了数十万个容器。我们演示了如何规划、配置这样庞大的集群,并总结了我们所学到的经验教训。

第五章,“管理 Swarm 集群”,是关于基础设施的一章。我们将展示如何增加或减少 Swarm 的大小,如何提升和降级节点,以及如何更新集群和节点属性。我们将介绍 Shipyard 和 Portainer.io 作为 Swarm 的图形用户界面。

第六章,“在 Swarm 上部署真实应用程序”,是我们将在 Swarm 上运行真实应用程序并在讨论中添加一些关于 Compose、Docker Stacks 和 Docker Application Bundles 的注释的地方。我们将展示典型的部署工作流程,如何在集群上过滤和调度容器,将它们作为服务启动,以任务的形式处理容器。我们将开始定义一个带有 Nginx 的 web 服务,然后部署一个必需的带有 MySQL 的 Wordpress 示例。最后,我们将继续使用一个更现实的应用程序:Apache Spark。

第七章,“扩展你的平台”,将从上一章开发新的主题。在这里,我们将介绍 Flocker,为 Spark on Swarm 增加存储容量,并展示如何在与 Swarm 结合的规模上自动安装和使用它。我们将通过运行一些真正的大数据作业和为这个基础设施设置一个基本的监控系统来完善我们的 Spark 示例。

第八章,“探索 Swarm 的其他功能”,讨论了一些对 Swarm 非常重要的高级主题,比如 Libnetwork 和 Libkv。

第九章,“保护 Swarm 集群和 Docker 软件供应链”,将重点讨论 Swarm 集群的安全考虑。其中包括证书、平台防火墙概念,以及对 Notary 的提及。

第十章,“Swarm 和云”,是一章介绍在云提供商上运行 Swarm 的最流行选项。我们将在 AWS 和 Azure 上安装 Swarm,然后介绍 Docker Datacenter,最后我们将转向 OpenStack,展示如何在 Magnum 的顶部安装和管理 Swarm,Magnum 是 OpenStack 的容器即服务解决方案。

第十一章,“接下来是什么?”,通过概述下一个 Docker 编排趋势,比如软件定义基础设施、Infrakit、unikernels、容器即服务,结束了讨论。冒险还在继续!

你需要为这本书做些什么

我们假设读者有一些使用 Docker 命令行的经验:在整本书中,我们将不断地拉取镜像、运行容器、定义服务、暴露端口和创建网络。

另外,理想的读者具有对网络协议的基本理解,并熟悉公共和私有云概念,比如虚拟机和租户网络。

为了跟随文本中的示例,你需要 Docker 及其工具。第一章,“欢迎来到 Docker Swarm”,涵盖了它们的安装。

另外,为了从示例中获得最大的收益,你需要访问一个公共(例如 AWS、Azure 或 DigitalOcean)或私有(例如 OpenStack)云来实例化虚拟机。

这本书适合谁

这本书是为 Docker 用户 - 开发人员和系统管理员 - 而写的,他们想要利用当前的 Swarm 和 Swarmkit 功能来扩展容器的大规模应用。

约定

在这本书中,您会发现许多文本样式,用于区分不同类型的信息。以下是一些样式的例子及其含义的解释。

文本中的代码词、数据库表名、文件夹名、文件名、文件扩展名、路径名、虚拟 URL、用户输入和 Twitter 句柄显示如下:"当执行 docker swarm init 时,只需复制粘贴输出的行"

一块代码设置如下:

digitalocean:
      image: “docker-1.12-rc4”
      region: nyc3
      ssh_key_fingerprint: “your SSH ID”
      ssh_user: root

任何命令行输入或输出都是这样写的:

      **# Set $GOPATH here
      go get https://github.com/chanwit/belt

新术语重要单词以粗体显示。您在屏幕上看到的单词,例如菜单或对话框中的单词,会在文本中出现,如:"UI 具有预期的选项,包括启动容器的模板列表,例如MySQL私有注册表,但在撰写本文时尚不支持 Swarm 服务"

注意

警告或重要说明会出现在这样的框中。

提示

技巧和窍门是这样出现的。

第一章:欢迎来到 Docker Swarm

毫无疑问,Docker 是当今最受欢迎的开源技术之一。原因很容易理解,Docker 使容器技术对所有人都可用,并且附带一个可移除的包含电池,并得到了一个充满活力的社区的祝福。

在早期,用户们开始使用 Docker,因为他们被这个易于使用的工具所迷住,这个工具让他们能够解决许多挑战:拉取、打包、隔离和使应用程序在系统之间可移植几乎没有负担。

欢迎来到 Docker Swarm

简化的 Docker 生态系统

您可能会注意到这里有一群鲸鱼与其他人相处融洽。然而,自容器问世以来,人们一直在寻找有效地编排大量容器的工具。Docker 团队在 2015 年发布了 Docker Swarm,简称 Swarm,作为 Docker 生态系统的一部分,与 Docker Machine 和 Docker Compose 一起发布。前面的图片显示了简化的 Docker 生态系统,其中 Docker Machine 提供了一个新的 Docker-ready 机器,然后一组机器将形成一个 Docker Swarm 集群。稍后,我们将能够使用 Docker Compose 将容器部署到集群中,就像它是一个普通的 Docker Engine 一样。

在 2014 年初,为 Docker 原生地创建一个集群管理系统的计划开始,作为一个名为Beam的通信协议项目。后来,它被实现为一个守护进程,用于控制具有 Docker API 的异构分布式系统。该项目已更名为libswarm,其守护进程为Swarmd。保持允许任何 Docker 客户端连接到一组 Docker 引擎的相同概念,该项目的第三代已经重新设计为使用相同的一组 Docker 远程 API,并于 2014 年 11 月更名为"Swarm"。基本上,Swarm 最重要的部分是其远程 API;维护者们努力保持它们与每个 Docker Engine 版本 100%兼容。我们将第一代 Swarm 称为"Swarm v1"。

2016 年 2 月,核心团队发现了集中式服务的扩展限制后,Swarm 再次在内部重新设计为swarm.v2。这一次,采用了分散式集群设计。2016 年 6 月,SwarmKit 作为分布式服务的编排工具包发布。Docker 宣布 SwarmKit 已合并到 Docker Engine 中,该消息是在 DockerCon 2016 上宣布的。我们将称这个版本的 Swarm 为“Swarm v2”或“Swarm 模式”。

正如我们将在后面看到的那样,这三位大侠(Docker Swarm,Docker Machine 和 Docker Compose)在一起运作时效果最佳,它们之间紧密地交织在一起,几乎不可能将它们视为单独的部分。

然而,即使如此,Machine 和 Compose 在其目标上确实非常直接,易于使用和理解,Swarm 是一个确实值得一本书的工具。

使用 Docker Machine,您可以在多个云平台上以及裸机上提供虚拟和物理机器来运行 Docker 容器。使用 Docker Compose,您可以通过使用 YAML 的简单而强大的语法描述行为,并通过“组合”这些文件来启动应用程序。Swarm 是一个强大的集群工具,需要更深入地研究。

在本章中,我们将看一下以下主题:

  • 什么是容器编排

  • Docker Swarm 的基本原理和架构

  • 与其他开源编排器的区别

  • “旧”Swarm,v1

  • “新”Swarm,Swarm 模式

集群工具和容器管理器

集群工具是一种软件,允许操作员与单个端点通信,并命令和编排一组资源,在我们的情况下是容器。与手动在集群上分发工作负载(容器)不同,集群工具用于自动化这一过程以及许多其他任务。集群工具将决定何时启动作业(容器),如何存储它们,何时最终重新启动它们等等。操作员只需配置一些行为,决定集群拓扑和大小,调整设置,并启用或禁用高级功能。Docker Swarm 是一个用于容器的集群工具的示例。

除了集群工具之外,还有容器管理平台的选择。它们不提供容器托管,但与一个或多个现有系统进行交互;这种软件通常提供良好的 Web 界面、监控工具和其他视觉或更高级的功能。容器管理平台的示例包括 Rancher 或 Tutum(在 2015 年被 Docker Inc.收购)。

Swarm 目标

Swarm 被 Docker 本身描述为:

Docker Swarm 是 Docker 的本地集群。它将一组 Docker 主机转换为单个虚拟 Docker 主机。

Swarm 是一个工具,它让您产生管理一个由许多 Docker 主机组成的单一巨大 Docker 主机的幻觉,就好像它们是一个,并且有一个命令入口点。它允许您使用常规的 Docker 工具,在这些主机上编排和操作一定数量的容器,无论是使用 Docker 本机还是 python-docker 客户端,甚至是使用 Docker 远程 API 的 curl。

这是一个在生产中看起来类似的最小的 Swarm 集群:

Swarm goals

为什么使用 Swarm

使用容器的集群解决方案有许多原因。随着您的应用程序增长,您将面临新的强制性要求,如可扩展性、可管理性和高可用性。

有许多可用的工具;选择 Docker Swarm 给我们带来了一些即时的优势:

  • 本地集群:Swarm 是 Docker 的本地工具,由 Docker 团队和社区制作。其原始创作者是 Andrea Luzzardi 和 Victor Vieux,他们是 Docker Engine Remote API 的早期实施者。Swarm 与 Machine、Compose 和生态系统中的其他工具集成,无需额外要求。

  • 生产级:Swarm v1 在 2015 年 11 月被宣布成熟,并准备投入生产使用。团队已经证明 Swarm 可以扩展到控制多达 1,000 个节点的引擎。Swarm v2 允许形成具有数千个节点的集群,因为它使用了分散式发现。

  • 开箱即用:Swarm 不需要您重新设计您的应用程序以适应另一个编排工具。您可以使用您的 Docker 镜像和配置,无需更改即可进行规模部署。

  • 易于设置和使用:Swarm 易于操作。只需向 Machine 命令添加一些标志或使用 Docker 命令,就可以进行有效的部署,因为 Docker 1.12 版已经集成了发现服务到 Swarm 模式中,使得安装变得迅速:无需设置外部的 Consul、Etcd 或 Zookeeper 集群。

  • 活跃的社区:Swarm 是一个充满活力的项目,拥有一个非常活跃的社区,并且正在积极开发中。

  • 在 Hub 上可用:你不需要安装 Swarm,它作为一个 Docker 镜像(Swarm v1)已经准备好了,所以你只需从 Hub 上拉取并运行它,或者集成到 Docker Engine 中。而 Swarm Mode 已经集成到 Docker 1.12+中。就是这样。

真实世界的用例示例

Docker Swarm 是几个项目的选择,例如:

  • Rackspace Carina 是建立在 Docker Swarm 之上的:Rackspace 提供托管的容器环境,内部基于 Docker Swarm

  • Zenly 正在使用 Swarm 在 Google Cloud Platform 和裸金属服务器上

  • ADP 使用 Docker 和 Swarm 来加速他们的传统部署

  • Swarm 可以直接在亚马逊 AWS 和微软 Azure 模板上部署到它们的公共云上

宠物与牲畜模型

在创建和利用基础设施时有两种相反的方法:宠物与牲畜。

宠物模型中,管理员部署服务器或虚拟机,或者在我们的情况下,容器,并对它们进行管理。她或他登录,安装软件,配置它,并确保一切正常运行。因此,这就是她或他的宠物。

相比之下,管理员并不真正关心基础设施组件的命运,当把它们看作牲畜时。她或他不会登录到每个单元或手动处理它,而是使用批量方法,部署、配置和管理都是通过自动化工具完成的。如果服务器或容器死掉,它会自动复活,或者生成另一个来替代已经失效的。因此,操作员正在处理牲畜。

在本书中,我们将在第一章中使用宠物模型来向读者介绍一些基本概念。但是在进行严肃的事情时,我们将在后面采用牲畜模式。

Swarm 特性

Swarm 的主要目的已经定义好了,但它是如何实现其目标的呢?以下是它的关键特性:

  • Swarm v1 支持 Docker Engine 1.6.0 或更高版本。Swarm v2 已经内置到 Docker Engine 自 1.12 版以来。

  • 每个 Swarm 版本的 API 都与相同版本的 Docker API 兼容。API 兼容性向后维护一个版本。

  • 在 Swarm v1 中,领导选举机制是使用领导库为多个 Swarm 主节点实现的(只有在部署带有发现服务的 Swarm 时才支持,例如 Etcd、Consul 或 Zookeeper)。

  • 在 Swarm v2 中,领导者选举已经使用了分散机制进行构建。Swarm v2 不再需要专门的一组发现服务,因为它集成了 Etcd,这是 Raft 共识算法的实现(参见第二章,“发现发现服务”)。

  • 在 Swarm v1 的术语中,领导 Swarm 主节点称为主节点,其他节点称为副本。在 Swarm v2 中,有主节点和工作节点的概念。领导节点由 Raft 集群自动管理。

  • 基本和高级调度选项。调度器是一个决定容器必须物理放置在哪些主机上的算法。Swarm 带有一组内置调度器。

  • 约束和亲和性让操作员对调度做出决策;例如,有人想要保持数据库容器在地理上靠近,并建议调度器这样做。约束和亲和性使用 Docker Swarm 标签。

  • 在 Swarm v2 中,集群内负载平衡是通过内置的 DNS 轮询实现的,同时它支持通过路由网格机制实现的外部负载平衡,该机制是基于 IPVS 实现的。

  • 高可用性和故障转移机制意味着您可以创建一个具有多个主节点的 Swarm;因此,如果它们宕机,将有其他主节点准备好接管。当我们形成至少 3 个节点的集群时,默认情况下可用 Swarm v2。所有节点都可以是主节点。此外,Swarm v2 包括健康指示器信息。

类似的项目

我们不仅有 Docker Swarm 来对容器进行集群化。为了完整起见,我们将简要介绍最广为人知的开源替代方案,然后完全深入到 Swarm 中。

Kubernetes

Kubernetes (kubernetes.io),也被称为k8s,旨在实现与 Docker Swarm 相同的目标;它是一个容器集群的管理器。最初作为 Google 实验室的 Borg 项目开始,后来以稳定版本的形式开源并于 2015 年发布,支持Google Cloud PlatformCoreOSAzurevSphere

到目前为止,Kubernetes 在 Docker 中运行容器,通过所谓的 Kubelet 通过 API 命令,这是一个注册和管理 Pods 的服务。从架构上讲,Kubernetes 将其集群逻辑上划分为 Pods,而不是裸容器。Pod 是最小的可部署单元,物理上是一个由一个或多个容器组成的应用程序的表示,通常是共同部署的,共享存储和网络等资源(用户可以使用 Compose 在 Docker 中模拟 Pods,并且从 Docker 1.12 开始创建 Docker DABs分布式应用程序包))。

Kubernetes 包括一些预期的基本集群功能,如标签、健康检查器、Pods 注册表,具有可配置的调度程序,以及服务,如大使或负载均衡器。

在实践中,Kubernetes 用户利用 kubectl 客户端与 Kubernetes 主控制单元(命令 Kubernetes 节点执行工作的单元,称为 minions)进行交互。Minions 运行 Pods,所有内容都由 Etcd 粘合在一起。

在 Kubernetes 节点上,您会发现一个正在运行的 Docker 引擎,它运行一个 kube-api 容器,以及一个名为kubelet.service的系统服务。

有许多直观的 kubectl 命令,比如

  • kubectl cluster-infokubectl get podskubectl get nodes用于检索有关集群及其健康状况的信息

  • kubectl create -f cassandra.yaml和任何派生的 Pod 命令,用于创建、管理和销毁 Pods

  • kubectl scale rc cassandra --replicas=2用于扩展 Pods 和应用程序

  • kubectl label pods cassandra env=prod用于配置 Pod 标签

这只是对 Kubernetes 的一个高层次全景。Kubernetes 和 Docker Swarm 之间的主要区别是:

  • Swarm 的架构更直观易懂。Kubernetes 需要更多的关注,只是为了掌握其基本原理。但学习总是好的!

  • 再谈架构:Kubernetes 基于 Pods,Swarm 基于容器和 DABs。

  • 您需要安装 Kubernetes。无论是在 GCE 上部署,使用 CoreOS,还是在 OpenStack 上,您都必须小心处理。您必须部署和配置一个 Kubernetes 集群,这需要额外的努力。Swarm 已集成到 Docker 中,无需额外安装。

  • Kubernetes 有一个额外的概念,叫做复制控制器,这是一种技术,确保某些模板描述的所有 Pod 在给定时间内都在运行。

  • Kubernetes 和 Swarm 都使用 Etcd。但在 Kubernetes 中,它被视为外部设施服务,而在 Swarm 中,它是集成的,并在管理节点上运行。

Kubernetes 和 Swarm 之间的性能比较可能会引发争论,我们希望避免这种做法。有一些基准测试显示 Swarm 启动容器的速度有多快,还有一些基准测试显示 Kubernetes 运行其工作负载的速度有多快。我们认为基准测试结果必须始终带有一定的保留态度。也就是说,Kubernetes 和 Swarm 都适合运行大型、快速和可扩展的容器集群。

CoreOS Fleet

Fleet (github.com/coreos/fleet)是容器编排器中的另一种可能选择。它来自 CoreOS 容器产品系列(包括 CoreOS、Rocket 和 Flannel),与 Swarm、Kubernetes 和 Mesos 基本不同,因为它被设计为系统的扩展。Fleet 通过调度程序在集群节点之间分配资源和任务。因此,它的目标不仅是提供纯粹的容器集群化,而是成为一个分布式的更一般的处理系统。例如,可以在 Fleet 上运行 Kubernetes。

Fleet 集群由负责调度作业、其他管理操作和代理的引擎组成,这些代理在每个主机上运行,负责执行它们被分配的作业并持续向引擎报告状态。Etcd 是保持一切连接的发现服务。

您可以通过 Fleet 集群的主要命令fleetctl与其交互,使用列表、启动和停止容器和服务选项。

因此,总结一下,Fleet 与 Docker Swarm 不同:

  • 这是一个更高级的抽象,用于分发任务,而不仅仅是一个容器编排器。

  • 将 Fleet 视为集群的分布式初始化系统。Systemd 适用于一个主机,而 Fleet 适用于一组主机。

  • Fleet 专门将一堆 CoreOS 节点集群化。

  • 您可以在 Fleet 的顶部运行 Kubernetes,以利用 Fleet 的弹性和高可用性功能。

  • 目前没有已知的稳定和强大的方法可以自动集成 Fleet 和 Swarm v1。

  • 目前,Fleet 尚未经过测试,无法运行超过 100 个节点和 1000 个容器的集群(github.com/coreos/fleet/blob/master/Documentation/fleet-scaling.md),而我们能够运行具有 2300 个和后来 4500 个节点的 Swarm。

Apache Mesos

无论您将 Fleet 视为集群的分布式初始化系统,您都可以将 Mesos(mesos.apache.org/)视为分布式内核。使用 Mesos,您可以将所有节点的资源都作为一个节点提供,并且在本书的范围内,在它们上运行容器集群。

Mesos 最初于 2009 年在伯克利大学开始,是一个成熟的项目,并且已经成功地在生产中使用,例如 Twitter。

它甚至比 Fleet 更通用,是多平台的(可以在 Linux、OS X 或 Windows 节点上运行),并且能够运行异构作业。您通常可以在 Mesos 上运行容器集群,旁边是纯粹的大数据作业(Hadoop 或 Spark)以及其他作业,包括持续集成、实时处理、Web 应用程序、数据存储,甚至更多。

Mesos 集群由一个 Master、从属和框架组成。正如您所期望的那样,主节点在从属上分配资源和任务,负责系统通信并运行发现服务(ZooKeeper)。但是框架是什么?框架是应用程序。框架由调度程序和执行程序组成,前者分发任务,后者执行任务。

对于我们的兴趣,通常通过一个名为 Marathon 的框架在 Mesos 上运行容器(mesosphere.github.io/marathon/docs/native-docker.html)。

在这里比较 Mesos 和 Docker Swarm 是没有意义的,因为它们很可能可以互补运行,即 Docker Swarm v1 可以在 Mesos 上运行,而 Swarm 的一部分源代码专门用于此目的。相反,Swarm Mode 和 SwarmKit 与 Mesos 非常相似,因为它们将作业抽象为任务,并将它们分组为服务,以在集群上分配负载。我们将在第三章中更好地讨论 SwarmKit 的功能,了解 Docker Swarm Mode

Kubernetes 与 Fleet 与 Mesos

Kubernetes,Fleet 和 Mesos 试图解决类似的问题;它们为您的资源提供了一个抽象层,并允许您与集群管理器进行接口。然后,您可以启动作业和任务,您选择的项目将对其进行排序。区别在于提供的开箱即用功能以及您可以自定义资源和作业分配和扩展精度的程度。在这三者中,Kubernetes 更加自动化,Mesos 更加可定制,因此从某种角度来看,更加强大(当然,如果您需要所有这些功能)。

Kubernetes 和 Fleet 对许多细节进行了抽象和默认设置,而这些对于 Mesos 来说是需要配置的,例如调度程序。在 Mesos 上,您可以使用 Marathon 或 Chronos 调度程序,甚至编写自己的调度程序。如果您不需要,不想或甚至无法深入研究这些技术细节,您可以选择 Kubernetes 或 Fleet。这取决于您当前和/或预测的工作负载。

Swarm 与所有

那么,您应该采用哪种解决方案?像往常一样,您有一个问题,开源技术慷慨地提供了许多可以相互交叉帮助您成功实现目标的技术。问题在于如何选择解决问题的方式和方法。Kubernetes,Fleet 和 Mesos 都是强大且有趣的项目,Docker Swarm 也是如此。

在这四个项目中,如果考虑它们的自动化程度和易于理解程度,Swarm 是赢家。这并不总是一个优势,但在本书中,我们将展示 Docker Swarm 如何帮助您使真实的事情运转,要记住,在 DockerCon 的一个主题演讲中,Docker 的 CTO 和创始人 Solomon Hykes 建议Swarm 将成为一个可以为许多编排和调度框架提供共同接口的层

Swarm v1 架构

本节讨论了 Docker Swarm 的概述架构。Swarm 的内部结构在图 3 中描述。

Swarm v1 架构

Docker Swarm v1 的内部结构

管理器部分开始,你会看到图表左侧标有Docker Swarm API的块。如前所述,Swarm 暴露了一组类似于 Docker 的远程 API,这允许您使用任何 Docker 客户端连接到 Swarm。然而,Swarm 的 API 与标准的 Docker 远程 API 略有不同,因为 Swarm 的 API 还包含了与集群相关的信息。例如,对 Docker 引擎运行docker info将给出单个引擎的信息,但当我们对 Swarm 集群调用docker info时,我们还将得到集群中节点的数量以及每个节点的信息和健康状况。

紧邻 Docker Swarm API 的块是Cluster Abstraction。它是一个抽象层,允许不同类型的集群作为 Swarm 的后端实现,并共享相同的 Docker 远程 API 集。目前我们有两种集群后端,内置的 Swarm 集群实现和 Mesos 集群实现。Swarm 集群内置调度器块代表内置的 Swarm 集群实现,而由Mesos 集群表示的块是 Mesos 集群实现。

Swarm 后端的内置调度器配备了多种调度策略。其中两种策略是SpreadBinPack,这将在后面的章节中解释。如果您熟悉 Swarm,您会注意到这里缺少了随机策略。随机策略被排除在解释之外,因为它仅用于测试目的。

除了调度策略,Swarm 还使用一组Scheduling Filters来帮助筛选未满足标准的节点。目前有六种过滤器,分别是HealthPortContainer SlotsDependencyAffinityConstraint。它们按照这个顺序应用于筛选,当有人在调度新创建的容器时。

代理部分,有 Swarm 代理试图将其引擎的地址注册到发现服务中。

最后,集中式部分DISCOVERY是协调代理和管理器之间的引擎地址的。基于代理的 Discovery 服务目前使用 LibKV,它将发现功能委托给您选择的键值存储,如 Consul、Etcd 或 ZooKeeper。相比之下,我们也可以只使用 Docker Swarm 管理器而不使用任何键值存储。这种模式称为无代理发现它是文件和节点(在命令行上指定地址)。

我们将在本章后面使用无代理模型来创建一个最小的本地 Swarm 集群。我们将在第二章中遇到其他发现服务,发现发现服务,以及第三章中的 Swarm Mode 架构,了解 Docker Swarm Mode

术语

在继续其他部分之前,我们回顾一些与 Docker 相关的术语,以回顾 Docker 概念并介绍 Swarm 关键字。

  • Docker Engine 是在主机上运行的 Docker 守护程序。有时在本书中,我们会简称为 Engine。我们通常通过调用docker daemon来启动 Engine,通过 systemd 或其他启动服务。

  • Docker Compose 是一种工具,用于以 YAML 描述多容器服务的架构方式。

  • Docker 堆栈是创建多容器应用程序的镜像的二进制结果(由 Compose 描述),而不是单个容器。

  • Docker 守护程序是与 Docker Engine 可互换的术语。

  • Docker 客户端是打包在同一个 docker 可执行文件中的客户端程序。例如,当我们运行docker run时,我们正在使用 Docker 客户端。

  • Docker 网络是将同一网络中的一组容器连接在一起的软件定义网络。默认情况下,我们将使用与 Docker Engine 一起提供的 libnetwork(github.com/docker/libnetwork)实现。但是,您可以选择使用插件部署您选择的第三方网络驱动程序。

  • Docker Machine 是用于创建能够运行 Docker Engine 的主机的工具,称为machines.

  • Swarm v1 中的 Swarm 节点是预先安装了 Docker Engine 并且在其旁边运行 Swarm 代理程序的机器。Swarm 节点将自己注册到 Discovery 服务中。

  • Swarm v1 中的 Swarm 主节点是运行 Swarm 管理程序的机器。Swarm 主节点从其 Discovery 服务中读取 Swarm 节点的地址。

  • 发现服务是由 Docker 或自托管的基于令牌的服务。对于自托管的服务,您可以运行 HashiCorp Consul、CoreOS Etcd 或 Apache ZooKeeper 作为键值存储,用作发现服务。

  • 领导者选举是由 Swarm Master 执行的机制,用于找到主节点。其他主节点将处于复制角色,直到主节点宕机,然后领导者选举过程将重新开始。正如我们将看到的,Swarm 主节点的数量应该是奇数。

  • SwarmKit 是 Docker 发布的一个新的 Kit,用于抽象编排。理论上,它应该能够运行任何类型的服务,但实际上到目前为止它只编排容器和容器集。

  • Swarm Mode 是自 Docker 1.12 以来提供的新 Swarm,它将 SwarmKit 集成到 Docker Engine 中。

  • Swarm Master(在 Swarm Mode 中)是管理集群的节点:它调度服务,保持集群配置(节点、角色和标签),并确保有一个集群领导者。

  • Swarm Worker(在 Swarm Mode 中)是运行任务的节点,例如,托管容器。

  • 服务是工作负载的抽象。例如,我们可以有一个名为"nginx"的服务,复制 10 次,这意味着您将在集群上分布并由 Swarm 本身负载均衡的 10 个任务(10 个 nginx 容器)。

  • 任务是 Swarm 的工作单位。一个任务就是一个容器。

开始使用 Swarm

我们现在将继续安装两个小型的 Swarm v1 和 v2 概念验证集群,第一个在本地,第二个在 Digital Ocean 上。为了执行这些步骤,请检查配料清单,确保您已经准备好一切,然后开始。

要跟随示例,您将需要:

  • Windows、Mac OS X 或 Linux 桌面

  • Bash 或兼容 Bash 的 shell。在 Windows 上,您可以使用 Cygwin 或 Git Bash。

  • 安装最新版本的 VirtualBox,用于本地示例

  • 至少需要 4GB 内存,用于本地示例的 4 个 VirtualBox 实例,每个实例 1G 内存

  • 至少需要 Docker 客户端 1.6.0 版本用于 Swarm v1 和 1.12 版本用于 Swarm v2

  • 当前版本的 Docker Machine,目前为 0.8.1

Docker for Mac

Docker 在 2016 年初宣布推出了 Docker for Mac 和 Docker for Windows 的桌面版本。它比 Docker Toolbox 更好,因为它包括您期望的 Docker CLI 工具,但不再使用 boot2docker 和 VirtualBox(它改用 unikernels,我们将在第十一章中介绍,下一步是什么?),并且完全集成到操作系统中(Mac OS X Sierra 或启用 Hyper-V 的 Windows 10)。

您可以从www.docker.com/products/overview#/install_the_platform下载 Docker 桌面版并轻松安装。

Docker for Mac

如果您使用的是 Mac OS X,只需将 Docker beta 图标拖放到应用程序文件夹中。输入您的 beta 注册码(如果有),就完成了。

Docker for Mac

在 OS X 上,您将在系统托盘中看到 Docker 鲸鱼,您可以打开它并配置您的设置。Docker 主机将在您的桌面上本地运行。

Docker for Mac

Docker for Windows

对于 Docker for Windows,它需要启用 Hyper-V 的 Windows 10。基本上,Hyper-V 随 Windows 10 专业版或更高版本一起提供。双击安装程序后,您将看到第一个屏幕,显示许可协议,看起来类似于以下截图。安装程序将要求您输入与 Docker for Mac 类似的密钥。

Docker for Windows

如果安装过程顺利进行,您将看到完成屏幕已准备好启动 Docker for Windows,如下所示:

Docker for Windows

在启动时,Docker 将初始化为 Hyper-V。一旦过程完成,您只需打开 PowerShell 并开始使用 Docker。

Docker for Windows

如果出现问题,您可以从托盘图标的菜单中打开日志窗口,并使用 Hyper-V 管理器进行检查。

Docker for Windows

准备好使用 Linux

我们将在本书中广泛使用 Machine,因此请确保您已经通过 Docker for Mac 或 Windows 或 Docker Toolbox 安装了它。如果您在桌面上使用 Linux,请使用您的软件包系统(apt 或 rpm)安装 Docker 客户端。您还需要下载裸机二进制文件,只需使用 curl 并为其分配执行权限;请按照docs.docker.com/machine/install-machine/上的说明进行操作。当前稳定版本为 0.8.1。

$ curl -L 
https://github.com/docker/machine/releases/download/v0.8.1/docker-
machine-uname -s-uname -m > /usr/local/bin/docker-machine
$ chmod +x /usr/local/bin/docker-machine`

检查 Docker Machine 是否可用-所有系统

您可以通过命令行检查机器是否准备好使用以下命令:

$ docker-machine --version
docker-machine version 0.8.1, build 41b3b25

如果您遇到问题,请检查系统路径或为您的架构下载正确的二进制文件。

昨天的 Swarm

对于第一个示例,我们将在本地运行 Swarm v1 集群的最简单配置,以了解“旧”Swarm 是如何工作的(仍然有效)。这个小集群将具有以下特点:

  • 由每个 1CPU,1GB 内存的四个节点组成,总共将包括四个 CPU 和 4GB 内存的基础设施

  • 每个节点将在 VirtualBox 上运行

  • 每个节点都连接到本地 VirtualBox 网络上的其他节点

  • 不涉及发现服务:将使用静态的nodes://机制

  • 没有配置安全性,换句话说 TLS 已禁用

我们的集群将类似于以下图表。四个引擎将通过端口3376在网格中相互连接。实际上,除了 Docker 引擎之外,它们每个都将运行一个在主机上暴露端口3376(Swarm)并将其重定向到自身的 Docker 容器。我们,操作员,将能够通过将环境变量DOCKER_HOST设置为IP:3376来连接到(任何一个)主机。如果您一步一步地跟随示例,一切都会变得更清晰。

昨天的 Swarm

首先,我们必须使用 Docker Machine 创建四个 Docker 主机。Docker Machine 可以自动化这些步骤,而不是手动创建 Linux 虚拟机,生成和上传证书,通过 SSH 登录到它,并安装和配置 Docker 守护程序。

机器将执行以下步骤:

  1. 从 boot2docker 镜像启动一个 VirtualBox 虚拟机。

  2. 为虚拟机分配一个 IP 地址在 VirtualBox 内部网络上。

  3. 上传和配置证书和密钥。

  4. 在此虚拟机上安装 Docker 守护程序。

  5. 配置 Docker 守护程序并公开它,以便可以远程访问。

结果,我们将有一个运行 Docker 并准备好被访问以运行容器的虚拟机。

Boot2Docker

使用 Tiny Core Linux 构建的Boot2Docker是一个轻量级发行版,专为运行 Docker 容器而设计。它完全运行在 RAM 上,启动时间非常快,从启动到控制台大约五秒。启动引擎时,Boot2Docker 默认在安全端口 2376 上启动 Docker 引擎。

Boot2Docker 绝不适用于生产工作负载。它仅用于开发和测试目的。我们将从使用 boot2docker 开始,然后在后续章节中转向生产。在撰写本文时,Boot2Docker 支持 Docker 1.12.3 并使用 Linux Kernel 4.4。它使用 AUFS 4 作为 Docker 引擎的默认存储驱动程序。

使用 Docker Machine 创建 4 个集群节点

如果我们执行:

$ docker-machine ls

在我们的新安装中列出可用的机器时,我们看到没有正在运行的机器。

因此,让我们从创建一个开始,使用以下命令:

$ docker-machine create --driver virtualbox node0

此命令明确要求使用 VirtualBox 驱动程序(-d 简称)并将机器命名为 node0。Docker Machines 可以在数十个不同的公共和私人提供商上提供机器,例如 AWS,DigitalOcean,Azure,OpenStack,并且有很多选项。现在,我们使用标准设置。第一个集群节点将在一段时间后准备就绪。

在这一点上,发出以下命令以控制此主机(以便远程访问):

$ docker-machine env node0

这将打印一些 shell 变量。只需复制最后一行,即带有 eval 的那一行,粘贴并按 Enter。配置了这些变量后,您将不再操作本地守护程序(如果有的话),而是操作node0的 Docker 守护程序。

使用 Docker Machine 创建 4 个集群节点

如果再次检查机器列表,您将看到图像名称旁边有一个*,表示它是当前正在使用的机器。或者,您可以输入以下命令以打印当前活动的机器:

$ docker-machine active

使用 Docker Machine 创建 4 个集群节点

守护程序正在此机器上运行,并具有一些标准设置(例如在端口tcp/2376上启用了 TLS)。您可以通过 SSH 到节点并验证运行的进程来确保这一点:

$ docker-machine ssh node0 ps aux | grep docker
1320 root  /usr/local/bin/docker daemon -D -g /var/lib/docker -H 
    unix:// -H tcp://0.0.0.0:2376 --label provider=virtualbox --
    tlsverify --tlscacert=/var/lib/boot2docker/ca.pem -- 
    tlscert=/var/lib/boot2docker/server.pem -- 
    tlskey=/var/lib/boot2docker/server-key.pem -s aufs

因此,您可以通过立即启动容器并检查 Docker 状态来启动 Docker 守护程序:

使用 Docker Machine 创建 4 个集群节点

完美!现在我们以完全相同的方式为其他三个主机进行配置,将它们命名为node1node2node3

$ docker-machine create --driver virtualbox node1
$ docker-machine create --driver virtualbox node2
$ docker-machine create --driver virtualbox node3

当它们完成时,您将有四个可用的 Docker 主机。使用 Docker Machine 检查。

使用 Docker Machine 创建 4 个集群节点

现在我们准备启动一个 Swarm 集群。但是,在此之前,为了使这个非常简单的第一个示例尽可能简单,我们将禁用运行引擎的 TLS。我们的计划是:在端口2375上运行 Docker 守护程序,没有 TLS。

让我们稍微整理一下,并详细解释所有端口组合。

不安全 安全
引擎:2375 引擎:2376
Swarm: 3375 Swarm: 3376
Swarm v2 使用 2377 进行节点之间的发现

端口2377用于 Swarm v2 节点在集群中发现彼此的节点。

配置 Docker 主机

为了了解 TLS 配置在哪里,我们将通过关闭所有 Docker 主机的 TLS 来进行一些练习。在这里关闭它也是为了激励读者学习如何通过自己调用swarm manage命令来工作。

我们有四个主机在端口tcp/2376上运行 Docker,并且使用 TLS,因为 Docker Machine 默认创建它们。我们必须重新配置它们以将守护程序端口更改为tls/2375并删除 TLS。因此,我们登录到每个主机,使用以下命令:

$ docker-machine ssh node0

然后,我们获得了 root 权限:

$ sudo su -

并通过修改文件/var/lib/boot2docker/profile来配置boot2docker

# cp /var/lib/boot2docker/profile /var/lib/boot2docker/profile-bak
# vi /var/lib/boot2docker/profile

我们删除了具有 CACERT、SERVERKEY 和 SERVERCERT 的行,并将守护程序端口配置为tcp/2375,将DOCKER_TLS配置为no。实际上,这将是我们的配置:

配置 Docker 主机

完成后退出 SSH 会话并重新启动机器:

$ docker-machine restart node0

Docker 现在在端口tcp/2375上运行,没有安全性。您可以使用以下命令检查:

$ docker-machine ssh node0 ps aux | grep docker
1127 root  /usr/local/bin/docker daemon -D -g /var/lib/docker -H 
     unix:// -H tcp://0.0.0.0:2375 --label provider=virtualbox -s aufs

最后,在您的本地桌面计算机上,取消设置DOCKER_TLS_VERIFY并重新导出DOCKER_HOST,以便使用在tcp/2375上监听且没有 TLS 的守护程序:

$ unset DOCKER_TLS_VERIFY
$ export DOCKER_HOST="tcp://192.168.99.103:2375"

我们必须为我们的第一个 Swarm 中的每个四个节点重复这些步骤。

启动 Docker Swarm

要开始使用 Swarm v1(毫不意外),必须从 Docker hub 拉取swarm镜像。打开四个终端,在第一个终端中为每台机器的环境变量设置环境变量,在第一个终端中设置 node0(docker-machine env node0,并将env变量复制并粘贴到 shell 中),在第二个终端中设置node1,依此类推 - ,并在完成更改标准端口和禁用 TLS 的步骤后,对每个终端执行以下操作:

$ docker pull swarm

启动 Docker Swarm

我们将在第一个示例中不使用发现服务,而是使用最简单的机制,例如nodes://。使用nodes://,Swarm 集群节点是手动连接的,以形成对等网格。操作员所需做的就是简单地定义一个节点 IP 和守护进程端口的列表,用逗号分隔,如下所示:

nodes://192.168.99.101:2375,192.168.99.102:2375,192.168.99.103:2375,192.168.99.107:2375

要使用 Swarm,您只需使用一些参数运行 swarm 容器。要在线显示帮助,您可以输入:

$ docker run swarm --help

启动 Docker Swarm

如您所见,Swarm 基本上有四个命令:

  • Create用于使用发现服务创建集群,例如token://

  • List显示集群节点的列表

  • Manage允许您操作 Swarm 集群

  • Join与发现服务结合使用,用于将新节点加入现有集群

现在,我们将使用manage命令。这是具有大多数选项的命令(您可以通过发出docker run swarm manage --help来进行调查)。我们现在限制连接节点。以下是每个节点的策略:

  1. 通过 swarm 容器公开 Swarm 服务。

  2. daemon-d)模式运行此容器。

  3. 将标准 Swarm 端口tcp/3376转发到内部(容器上)端口tcp/2375

  4. 使用nodes://指定集群中的主机列表 - 每个主机都必须是IP:port对,其中端口是 Docker 引擎端口(tcp/2375)。

因此,在每个终端上,您连接到每台机器,执行以下操作:

$ docker run \
-d \
-p 3376:2375 \
swarm manage \
nodes://192.168.99.101:2375,192.168.99.102:2375,
    192.168.99.103:2375,192.168.99.107:2375

提示

当使用nodes://机制时,您可以使用类似 Ansible 的主机范围模式,因此可以使用三个连续 IP 的紧凑语法,例如 nodes://192.168.99.101:2375,192.168.99.102:2375,192.168.99.103:2375 在 nodes://192.168.99.[101:103]:2375

现在,作为下一步,我们将连接到它并在开始运行容器之前检查其信息。为了方便起见,打开一个新的终端。我们现在连接的不再是我们的一个节点上的 Docker 引擎,而是 Docker Swarm。因此,我们将连接到tcp/3376而不再是tcp/2375。为了详细展示我们正在做什么,让我们从node0变量开始:

$ docker-machine env node0

复制并粘贴 eval 行,正如您已经知道的那样,并使用以下命令检查导出的 shell 变量:

$ export | grep DOCKER_

我们现在需要做以下事情:

  1. DOCKER_HOST更改为连接到 Swarm 端口tcp/3376,而不是引擎tcp/2375

  2. 禁用DOCKER_TLS_VERIFY

  3. 禁用DOCKER_CERT_PATH启动 Docker Swarm

您应该有类似于这样的配置:

启动 Docker Swarm

如果我们现在连接到3376的 Docker Swarm,并显示一些信息,我们会看到我们正在运行 Swarm:

启动 Docker Swarm

恭喜!您刚刚启动了您的第一个带有 Swarm 的 Docker 集群。我们可以看到除了四个 Swarm 之外,我们的集群上还没有运行任何容器,但服务器版本是 swarm/1.2.3,调度策略是 spread,最重要的是,我们的 Swarm 中有四个健康节点(每个 Swarm 节点的详细信息如下)。

此外,您可以获取有关此 Swarm 集群调度程序行为的一些额外信息:

Strategy: spread
Filters: health, port, containerslots, dependency, affinity, 
    constraint

spread 调度策略意味着 Swarm 将尝试将容器放置在使用较少的主机上,并且在创建容器时提供了列出的过滤器,因此允许您决定手动建议一些选项。例如,您可能希望使您的 Galera 集群容器在地理上靠近但位于不同的主机上。

但是,这个 Swarm 的大小是多少?您可以在输出的最后看到它:

启动 Docker Swarm

这意味着在这个小小的 Swarm 上,您拥有这些资源的总可用性:四个 CPU 和 4GB 的内存。这正是我们预期的,通过合并每个具有 1GB 内存的 CPU 的 4 个 VirtualBox 主机的计算资源。

测试您的 Swarm 集群

现在我们有了一个 Swarm 集群,是时候开始使用它了。我们将展示扩展策略算法将决定将容器放置在负载较轻的主机上。在这个例子中,这很容易,因为我们从四个空节点开始。所以,我们连接到 Swarm,Swarm 将在主机上放置容器。我们启动一个 nginx 容器,将其端口 tcp/80 映射到主机(机器)端口tcp/80

$ docker run -d -p 80:80 nginx
2c049db55f9b093d19d575704c28ff57c4a7a1fb1937bd1c20a40cb538d7b75c

在这个例子中,我们看到 Swarm 调度程序决定将这个容器放到node1上:

测试您的 Swarm 集群

由于我们必须将端口tcp/80绑定到任何主机,我们只有四次机会,四个不同主机上的四个容器。让我们创建新的 nginx 容器,看看会发生什么:

$ docker run -d -p 80:80 nginx
577b06d592196c34ebff76072642135266f773010402ad3c1c724a0908a6997f
$ docker run -d -p 80:80 nginx
9fabe94b05f59d01dd1b6b417f48155fc2aab66d278a722855d3facc5fd7f831
$ docker run -d -p 80:80 nginx
38b44d8df70f4375eb6b76a37096f207986f325cc7a4577109ed59a771e6a66d

现在我们有 4 个 nginx 容器放置在我们的 4 个 Swarm 主机上:

测试您的 Swarm 集群

现在我们尝试创建一个新的 nginx:

$ docker run -d -p 80:80 nginx
docker: Error response from daemon: Unable to find a node that 
    satisfies the following conditions
[port 80 (Bridge mode)].
See 'docker run --help'.

发生的事情只是 Swarm 无法找到一个合适的主机来放置一个新的容器,因为在所有主机上,端口tcp/80都被占用。在运行了这 4 个 nginx 容器之后,再加上四个 Swarm 容器(用于基础设施管理),正如我们所预期的那样,我们在这个 Swarm 集群上有八个正在运行的容器:

测试您的 Swarm 集群

这就是 Swarm v1 的预期工作方式(它仍然在工作)。

Swarm,今天

在本节中,我们将使用内置在 Docker Engine 1.12 或更高版本中的新 Swarm 模式设置一个小集群。

在 DockerCon16 上,大量的公告中,有两个关于容器编排引起了很大的关注:

  • 引擎和 Swarm 之间的集成,称为 Docker Swarm 模式。

  • SwarmKit

实际上,Docker 守护程序从 1.12 版本开始增加了运行所谓的 Swarm Mode 的可能性。docker 客户端添加了新的 CLI 命令,如nodeservicestackdeploy,当然还有swarm

我们将从第三章开始更详细地介绍 Swarm Mode 和 SwarmKit,但现在我们完成了 Swarm v1 的示例,我们将让读者体验一下 Swarm v2 比 v1 具有更简单的用户体验。使用 Swarm v2 的唯一要求是至少有 1.12-rc1 版本的守护程序版本。但是,使用 Docker Machine 0.8.0-rc1+,您可以使用通常的程序满足这一要求来提供 Docker 主机。

Docker 还在 DockerCon 2016 上宣布了 Docker for AWS 和 Docker for Azure。不仅仅是 AWS 和 Azure,实际上我们也是 DigitalOcean 的粉丝,所以我们创建了一个新工具,它包装了 DigitalOcean 命令行界面的doctl,以帮助以新的大规模方式提供 Docker 集群。该工具称为belt,现在可以从github.com/chanwit/belt获取。您可以使用以下命令拉取 belt:

go get github.com/chanwit/belt

或者从项目的Release标签中下载二进制文件。

首先,我们将为在 DigitalOcean 上进行配置准备一个模板文件。您的.belt.yaml将如下所示:

$ cat .belt.yaml
---
digitalocean:
region: sgp1
image: 18153887
ssh_user: root
ssh_key_fingerprint: 816630

请注意,我的镜像编号18153887是包含 Docker 1.12 的快照。DigitalOcean 通常会在每次发布后提供最新的 Docker 镜像。为了让您能够控制您的集群,需要有 SSH 密钥。对于字段ssh_key_fingerprint,您可以放置指纹以及密钥 ID。

不要忘记设置您的DIGITALOCEAN_ACCESS_TOKEN环境变量。此外,Belt 也识别相同的一组 Docker Machine shell 变量。如果您熟悉 Docker Machine,您将知道如何设置它们。刷新一下,这些是我们在上一节介绍的 shell 变量:

  • export DOCKER_TLS_VERIFY="1"

  • export DOCKER_HOST="tcp://<IP ADDRESS>:2376"

  • export DOCKER_CERT_PATH="/Users/user/.docker/machine/machines/machine"

  • export DOCKER_MACHINE_NAME="machine"

所以,现在让我们看看如何使用 Belt:

$ export DIGITALOCEAN_ACCESS_TOKEN=1b207 .. snip .. b6581c

现在我们创建一个包含 512M 内存的四个节点的 Swarm:

$ belt create 512mb node[1:4]
ID              Name    Public IPv4     Memory  VCPUs   Disk
18511682        node1                   512     1       20
18511683        node4                   512     1       20
18511684        node3                   512     1       20
18511681        node2                   512     1       20

您可以看到,我们可以使用类似的语法 node[1:4]指定一组节点。此命令在 DigitalOcean 上创建了四个节点。请等待大约 55 秒,直到所有节点都被配置。然后您可以列出它们:

$ belt ls
ID              Name    Public IPv4       Status  Tags
18511681        node2   128.199.105.119   active
18511682        node1   188.166.183.86    active
18511683        node4   188.166.183.103   active
18511684        node3   188.166.183.157   active

它们的状态现在已从“新”更改为“活动”。所有 IP 地址都已分配。目前一切都进行得很顺利。

现在我们可以启动 Swarm。

在此之前,请确保我们正在运行 Docker 1.12。我们在node1上检查这一点。

$ belt active node1
node1
$ belt docker version
Client:
Version:      1.12.0-rc2
API version:  1.24
Go version:   go1.6.2
Git commit:   906eacd
Built:        Fri Jun 17 21:02:41 2016
OS/Arch:      linux/amd64
Experimental: true
Server:
Version:      1.12.0-rc2
API version:  1.24
Go version:   go1.6.2
Git commit:   906eacd
Built:        Fri Jun 17 21:02:41 2016
OS/Arch:      linux/amd64
Experimental: true

belt docker命令只是一个薄包装命令,它将整个命令行通过 SSH 发送到您的 Docker 主机。因此,这个工具不会妨碍您的 Docker 引擎始终处于控制状态。

现在我们将使用 Swarm Mode 初始化第一个节点。

$ belt docker swarm init
Swarm initialized: current node (c0llmsc5t1tsbtcblrx6ji1ty) is now 
    a manager.

然后我们将其他三个节点加入到这个新形成的集群中。加入一个大集群是一项繁琐的任务。我们将让belt代替我们手动执行此操作,而不是逐个节点进行 docker swarm join:

$ belt swarm join node1 node[2:4]
node3: This node joined a Swarm as a worker.
node2: This node joined a Swarm as a worker.
node4: This node joined a Swarm as a worker.

提示

当然,您可以运行:belt --host node2 docker swarm join <node1's IP>:2377,手动将 node2 加入到您的集群中。

然后您将看到集群的这个视图:

$ belt docker node ls
ID          NAME   MEMBERSHIP  STATUS  AVAILABILITY  MANAGER STATUS
4m5479vud9qc6qs7wuy3krr4u    node2  Accepted    Ready   Active
4mkw7ccwep8pez1jfeok6su2o    node4  Accepted    Ready   Active
a395rnht2p754w1beh74bf7fl    node3  Accepted    Ready   Active
c0llmsc5t1tsbtcblrx6ji1ty *  node1  Accepted    Ready   Active        Leader

恭喜!您刚在 DigitalOcean 上安装了一个 Swarm 集群。

我们现在为nginx创建一个服务。这个命令将创建一个 Nginx 服务,其中包含 2 个容器实例,发布在 80 端口。

$ belt docker service create --name nginx --replicas 2 -p 80:80 
    nginx
d5qmntf1tvvztw9r9bhx1hokd

我们开始吧:

$ belt docker service ls
ID            NAME   REPLICAS  IMAGE  COMMAND
d5qmntf1tvvz  nginx  2/2       nginx

现在让我们将其扩展到 4 个节点。

$ belt docker service scale nginx=4
nginx scaled to 4
$ belt docker service ls
ID            NAME   REPLICAS  IMAGE  COMMAND
d5qmntf1tvvz  nginx  4/4       nginx

类似于 Docker Swarm,您现在可以使用belt ip来查看节点的运行位置。您可以使用任何 IP 地址来浏览 NGINX 服务。它在每个节点上都可用。

$ belt ip node2
128.199.105.119

这就是 Docker 1.12 开始的 Swarm 模式的样子。

总结

在本章中,我们了解了 Docker Swarm,定义了其目标、特性和架构。我们还回顾了一些其他可能的开源替代方案,并介绍了它们与 Swarm 的关系。最后,我们通过在 Virtualbox 和 Digital Ocean 上创建一个由四个主机组成的简单本地集群来安装和开始使用 Swarm。

使用 Swarm 对容器进行集群化将是整本书的主题,但在我们开始在生产环境中使用 Swarm 之前,我们将先了解一些理论知识,首先是发现服务的主题,即第二章发现发现服务

第二章:发现发现服务

在第一章欢迎来到 Docker Swarm中,我们使用nodes://机制创建了一个简单但功能良好的本地 Docker Swarm 集群。这个系统对于学习 Swarm 的基本原理来说并不是很实用。

事实上,这只是一个扁平的模型,没有考虑任何真正的主从架构,更不用说高级服务,比如节点发现和自动配置、韧性、领导者选举和故障转移(高可用性)。实际上,它并不适合生产环境。

除了nodes://,Swarm v1 正式支持四种发现服务;然而,其中一种 Token,是一个微不足道的非生产级服务。基本上,使用 Swarm v1,你需要手动集成一个发现服务,而使用 Swarm Mode(从 Docker 1.12 开始),一个发现服务 Etcd 已经集成。在本章中,我们将涵盖:

  • 发现服务

  • 一个测试级别的发现服务:Token

  • Raft 理论和 Etcd

  • Zookeeper 和 Consul

在深入探讨这些服务之前,让我们讨论一下什么是发现服务?

一个发现服务

想象一下,你正在运行一个静态配置的 Swarm 集群,类似于第一章欢迎来到 Docker Swarm中的配置,网络是扁平的,每个容器都被分配了一个特定的任务,例如一个 MySQL 数据库。很容易找到 MySQL 容器,因为你为它分配了一个定义的 IP 地址,或者你运行了一些 DNS 服务器。很容易通知这个单独的容器是否工作,而且我们知道它不会改变它的端口(tcp/3336)。此外,我们的 MySQL 容器并不需要宣布它的可用性作为一个带有 IP 和端口的数据库容器:我们当然已经知道了。

这是一个宠物模型,由系统管理员手动模拟。然而,由于我们是更高级的运营者,我们想要驱动一头牛。

所以,想象一下,你正在运行一个由数百个节点组成的 Swarm,托管着运行一定数量服务的几个应用程序(Web 服务器、数据库、键值存储、缓存和队列)。这些应用程序运行在大量的容器上,这些容器可能会动态地改变它们的 IP 地址,要么是因为你重新启动它们,要么是因为你创建了新的容器,要么是因为你复制了它们,或者是因为一些高可用性机制为你启动了新的容器。

您如何找到您的 Acme 应用程序的 MySQL 服务?如何确保负载均衡器知道您的 100 个 Nginx 前端的地址,以便它们的功能不会中断?如果服务已经移动并具有不同的配置,您如何通知?

您使用发现服务。

所谓的发现服务是一个具有许多特性的机制。有不同的服务可供选择,具有更多或更少相似的特性,有其优点和缺点,但基本上所有的发现服务都针对分布式系统,因此它们必须分布在所有集群节点上,具有可伸缩性和容错性。发现服务的主要目标是帮助服务找到并相互通信。为了做到这一点,它们需要保存(注册)与每个服务的位置相关的信息,通过宣布自己来做到这一点,它们通常通过充当键值存储来实现。发现服务在 Docker 兴起之前就已经存在,但随着容器和容器编排的出现,问题变得更加困难。

再次总结,通过发现服务:

  • 您可以定位基础设施中的单个服务

  • 您可以通知服务配置更改

  • 服务注册其可用性

  • 等等

通常,发现服务是作为键值存储创建的。Docker Swarm v1 官方支持以下发现服务。但是,您可以使用libkv抽象接口集成自己的发现服务,如下所示:

github.com/docker/docker/tree/master/pkg/discovery

  • Token

  • Consul 0.5.1+

  • Etcd 2.0+

  • ZooKeeper 3.4.5+

然而,Etcd 库已经集成到 Swarm 模式中作为其内置的发现服务。

Token

Docker Swarm v1 包括一个开箱即用的发现服务,称为 Token。Token 已集成到 Docker Hub 中;因此,它要求所有 Swarm 节点连接到互联网并能够访问 Docker Hub。这是 Token 的主要限制,但很快您将看到,Token 将允许我们在处理集群时进行一些实践。

简而言之,Token 要求您生成一个称为 token 的 UUID。有了这个 UUID,您可以创建一个管理器,充当主节点,并将从节点加入集群。

使用 token 重新设计第一章的示例

如果我们想保持实用性,现在是时候看一个例子了。我们将使用令牌来重新设计第一章的示例,欢迎来到 Docker Swarm。作为新功能,集群将不再是扁平的,而是由 1 个主节点和 3 个从节点组成,并且每个节点将默认启用安全性。

主节点将是暴露 Swarm 端口3376的节点。我们将专门连接到它,以便能够驱动整个集群。

使用令牌重新设计第一章的示例

我们可以使用以下命令创建 4 个节点:

$ for i in `seq 0 3`; do docker-machine create -d virtualbox 
    node$i; 
    done

现在,我们有四台运行最新版本引擎的机器,启用了 TLS。这意味着,正如你记得的那样,引擎正在暴露端口2376而不是2375

使用令牌重新设计第一章的示例

我们现在将创建集群,从主节点开始。选择其中一个节点,例如node0,并获取其变量:

$ eval $(docker-machine env node0)

现在我们生成集群令牌和唯一 ID。为此,我们使用swarm create命令:

$ docker run swarm create
3b905f46fef903800d51513d51acbbbe

使用令牌重新设计第一章的示例

结果,集群容器输出了令牌,并且在本示例中将调用所示的协议:token://3b905f46fef903800d51513d51acbbbe

注意这个令牌 ID,例如将其分配给一个 shell 变量:

$ TOKEN=3b905f46fef903800d51513d51acbbbe

现在我们创建一个主节点,并尝试满足至少一些基本的标准安全要求,也就是说,我们将启用 TLS 加密。正如我们将在一会儿看到的,swarm命令接受 TLS 选项作为参数。但是我们如何将密钥和证书传递给容器呢?为此,我们将使用 Docker Machine 生成的证书,并将其放置在主机上的/var/lib/boot2docker中。

实际上,我们将从 Docker 主机挂载一个卷到 Docker 主机上的容器。所有远程控制都依赖于环境变量。

已经获取了node0变量,我们使用以下命令启动 Swarm 主节点:

$ docker run -ti -v /var/lib/boot2docker:/certs -p 3376:3376 swarm 
    manage -H 0.0.0.0:3376 -tls --tlscacert=/certs/ca.pem --
    tlscert=/certs/server.pem --tlskey=/certs/server-key.pem 
    token://$TOKEN

首先,我们以交互模式运行容器以观察 Swarm 输出。然后,我们将节点/var/lib/boot2docker目录挂载到 Swarm 容器内部的/certs目录。我们将3376 Swarm 安全端口从 node0 重定向到 Swarm 容器。我们通过将其绑定到0.0.0.0:3376来以管理模式执行swarm命令。然后,我们指定一些证书选项和文件路径,并最后描述使用的发现服务是令牌,带有我们的令牌。

使用令牌重新设计第一章的示例

有了这个节点运行,让我们打开另一个终端并加入一个节点到这个 Swarm。让我们首先源node1变量。现在,我们需要让 Swarm 使用join命令,以加入其主节点为node0的集群:

$ docker run -d swarm join --addr=192.168.99.101:2376 
    token://$TOKEN

在这里,我们指定主机(自身)的地址为192.168.99.101以加入集群。

使用令牌重新设计第一章的示例

如果我们跳回第一个终端,我们会看到主节点已经注意到一个节点已经加入了集群。因此,此时我们有一个由一个主节点和一个从节点组成的 Swarm 集群。

使用令牌重新设计第一章的示例

由于我们现在理解了机制,我们可以在终端中停止docker命令,并使用-d选项重新运行它们。因此,要在守护程序模式下运行容器:

主节点:

$ docker run -t-d -v /var/lib/boot2docker:/certs -p 3376:3376 swarm 
    manage -H 0.0.0.0:3376 -tls --tlscacert=/certs/ca.pem --
    tlscert=/certs/server.pem --tlskey=/certs/server-key.pem 
    token://$TOKEN

节点:

$ docker run -d swarm join --addr=192.168.99.101:2376  
    token://$TOKEN

我们现在将继续将其他两个节点加入集群,源其变量,并重复上述命令,如下所示:

$ eval $(docker-machine env node2)
$ docker run -d swarm join --addr=192.168.99.102:2376 
    token://$TOKEN
$ eval $(docker-machine env node3)
$ docker run -d swarm join --addr=192.168.99.103:2376 
    token://$TOKEN

例如,如果我们打开第三个终端,源node0变量,并且特别连接到端口3376(Swarm)而不是2376(Docker Engine),我们可以看到来自docker info命令的一些花哨的输出。例如,集群中有三个节点:

使用令牌重新设计第一章的示例

因此,我们已经创建了一个具有一个主节点、三个从节点的集群,并启用了 TLS,准备接受容器。

我们可以从主节点确保并列出集群中的节点。我们现在将使用swarm list命令:

$ docker run swarm list token://$TOKEN

使用令牌重新设计第一章的示例

令牌限制

Token 尚未被弃用,但很可能很快就会被弃用。Swarm 中的每个节点都需要具有互联网连接的标准要求并不是很方便。此外,对 Docker Hub 的访问使得这种技术依赖于 Hub 的可用性。实际上,它将 Hub 作为单点故障。然而,使用 token,我们能够更好地了解幕后情况,并且我们遇到了 Swarm v1 命令:createmanagejoinlist

现在是时候继续前进,熟悉真正的发现服务和共识算法,这是容错系统中的一个基本原则。

Raft

共识是分布式系统中的一种算法,它强制系统中的代理就一致的值达成一致意见并选举领导者。

一些著名的共识算法是 Paxos 和 Raft。Paxos 和 Raft 提供了类似的性能,但 Raft 更简单,更易于理解,因此在分布式存储实现中变得非常流行。

作为共识算法,Consul 和 Etcd 实现了 Raft,而 ZooKeeper 实现了 Paxos。CoreOS Etcd Go 库作为 SwarmKit 和 Swarm Mode 的依赖项(在vendor/中),因此在本书中我们将更多地关注它。

Raft 在 Ongaro、Ousterhout 的论文中有详细描述,可在ramcloud.stanford.edu/raft.pdf上找到。在接下来的部分中,我们将总结其基本概念。

Raft 理论

Raft 的设计初衷是简单,与 Paxos 相比,它确实实现了这一目标(甚至有学术出版物证明了这一点)。对于我们的目的,Raft 和 Paxos 的主要区别在于,在 Raft 中,消息和日志只由集群领导者发送给其同行,使得算法更易于理解和实现。我们将在理论部分使用的示例库是由 CoreOS Etcd 提供的 Go 库,可在github.com/coreos/etcd/tree/master/raft上找到。

Raft 集群由节点组成,这些节点必须以一致的方式维护复制状态机,无论如何:新节点可以加入,旧节点可以崩溃或变得不可用,但这个状态机必须保持同步。

为了实现这个具有容错能力的目标,通常 Raft 集群由奇数个节点组成,例如三个或五个,以避免分裂脑。当剩下的节点分裂成无法就领导者选举达成一致的组时,就会发生分裂脑。如果节点数是奇数,它们最终可以以多数同意的方式选出领导者。而如果节点数是偶数,选举可能以 50%-50%的结果结束,这是不应该发生的。

回到 Raft,Raft 集群被定义为raft.go中的一种类型 raft 结构,并包括领导者 UUID、当前任期、指向日志的指针以及用于检查法定人数和选举状态的实用程序。让我们通过逐步分解集群组件 Node 的定义来阐明所有这些概念。Node 在node.go中被定义为一个接口,在这个库中被规范地实现为type node struct

type Node interface {
Tick()
Campaign(ctx context.Context) error
Propose(ctx context.Context, data []byte) error
ProposeConfChange(ctx context.Context, cc pb.ConfChange) error
Step(ctx context.Context, msg pb.Message) error
Ready() <-chan Ready
Advance()
ApplyConfChange(cc pb.ConfChange) *pb.ConfState
Status() Status
ReportUnreachable(id uint64)
ReportSnapshot(id uint64, status SnapshotStatus)
Stop()
}

每个节点都保持一个滴答(通过Tick()递增),表示任意长度的当前运行时期或时间段或时代。在每个时期,一个节点可以处于以下 StateType 之一:

  • 领导者

  • 候选者

  • 追随者

在正常情况下,只有一个领导者,所有其他节点都是跟随者。领导者为了让我们尊重其权威,定期向其追随者发送心跳消息。当追随者注意到心跳消息不再到达时,他们会意识到领导者不再可用,因此他们会增加自己的值,成为候选者,然后尝试通过运行Campaign()来成为领导者。他们从为自己投票开始,试图达成选举法定人数。当一个节点实现了这一点,就会选举出一个新的领导者。

Propose()是一种向日志附加数据的提案方法。日志是 Raft 中用于同步集群状态的数据结构,也是 Etcd 中的另一个关键概念。它保存在稳定存储(内存)中,当日志变得庞大时具有压缩日志以节省空间(快照)的能力。领导者确保日志始终处于一致状态,并且只有在确定信息已经通过大多数追随者复制时,才会提交新数据以附加到其日志(主日志)上,因此存在一致性。有一个Step()方法,它将状态机推进到下一步。

ProposeConfChange()是一个允许我们在运行时更改集群配置的方法。由于其两阶段机制,它被证明在任何情况下都是安全的,确保每个可能的多数都同意这一变更。ApplyConfChange()将此变更应用到当前节点。

然后是Ready()。在 Node 接口中,此函数返回一个只读通道,返回准备好被读取、保存到存储并提交的消息的封装规范。通常,在调用 Ready 并应用其条目后,客户端必须调用Advance(),以通知 Ready 已经取得进展。在实践中,Ready()Advance()是 Raft 保持高一致性水平的方法的一部分,通过避免日志、内容和状态同步中的不一致性。

这就是 CoreOS' Etcd 中 Raft 实现的样子。

Raft 的实践

如果您想要亲自尝试 Raft,一个好主意是使用 Etcd 中的raftexample并启动一个三成员集群。

由于 Docker Compose YAML 文件是自描述的,以下示例是一个准备运行的组合文件:

version: '2'
services:
raftexample1:
image: fsoppelsa/raftexample
command: --id 1 --cluster 
          http://127.0.0.1:9021,http://127.0.0.1:9022,
          http://127.0.0.1:9023 --port 9121
ports:
- "9021:9021"
- "9121:9121"
raftexample2:
image: fsoppelsa/raftexample
command: --id 2 --cluster    
          http://127.0.0.1:9021,http://127.0.0.1:9022,
          http://127.0.0.1:9023 --port 9122
ports:
- "9022:9022"
- "9122:9122"
raftexample3:
image: fsoppelsa/raftexample
command: --id 3 --cluster 
          http://127.0.0.1:9021,http://127.0.0.1:9022,
          http://127.0.0.1:9023 --port 9123
ports:
- "9023:9023"
- "9123:9123"

此模板创建了三个 Raft 服务(raftexample1raftexample2raftexample3)。每个都运行一个 raftexample 实例,通过--port公开 API,并使用--cluster进行静态集群配置。

您可以在 Docker 主机上启动它:

docker-compose -f raftexample.yaml up

现在您可以玩了,例如杀死领导者,观察新的选举,通过 API 向一个容器设置一些值,移除容器,更新该值,重新启动容器,检索该值,并注意到它已经正确升级。

与 API 的交互可以通过 curl 完成,如github.com/coreos/etcd/tree/master/contrib/raftexample中所述:

curl -L http://127.0.0.1:9121/testkey -XPUT -d value
curl -L http://127.0.0.1:9121/testkey

我们将这个练习留给更热心的读者。

提示

当您尝试采用 Raft 实现时,选择 Etcd 的 Raft 库以获得最高性能,并选择 Consul(来自 Serf 库)以获得即插即用和更容易的实现。

Etcd

Etcd 是一个高可用、分布式和一致的键值存储,用于共享配置和服务发现。一些使用 Etcd 的知名项目包括 SwarmKit、Kubernetes 和 Fleet。

Etcd 可以在网络分裂的情况下优雅地管理主节点选举,并且可以容忍节点故障,包括主节点。应用程序,例如 Docker 容器和 Swarm 节点,可以读取和写入 Etcd 的键值存储中的数据,例如服务的位置。

重新设计第一章的示例,使用 Etcd

我们再次通过演示 Etcd 来创建一个管理器和三个节点的示例。

这次,我们将需要一个真正的发现服务。我们可以通过在 Docker 内部运行 Etcd 服务器来模拟非 HA 系统。我们创建了一个由四个主机组成的集群,名称如下:

  • etcd-m将是 Swarm 主节点,也将托管 Etcd 服务器

  • etcd-1:第一个 Swarm 节点

  • etcd-2:第二个 Swarm 节点

  • etcd-3:第三个 Swarm 节点

操作员通过连接到etcd-m:3376,将像往常一样在三个节点上操作 Swarm。

让我们从使用 Machine 创建主机开始:

for i in m `seq 1 3`; do docker-machine create -d virtualbox etcd-$i; 
done

现在我们将在etcd-m上运行 Etcd 主节点。我们使用来自 CoreOS 的quay.io/coreos/etcd官方镜像,遵循github.com/coreos/etcd/blob/master/Documentation/op-guide/clustering.md上可用的文档。

首先,在终端中,我们设置etcd-m shell 变量:

term0$ eval $(docker-machine env etcd-m)

然后,我们以单主机模式运行 Etcd 主节点(即,没有容错等):

docker run -d -p 2379:2379 -p 2380:2380 -p 4001:4001 \
--name etcd quay.io/coreos/etcd \
-name etcd-m -initial-advertise-peer-urls http://$(docker-machine 
    ip etcd-m):2380 \
-listen-peer-urls http://0.0.0.0:2380 \
-listen-client-urls http://0.0.0.0:2379,http://0.0.0.0:4001 \
-advertise-client-urls http://$(docker-machine ip etcd-m):2379 \
-initial-cluster-token etcd-cluster-1 \
-initial-cluster etcd-m=http://$(docker-machine ip etcd-m):2380
-initial-cluster-state new

我们在这里做的是以守护进程(-d)模式启动 Etcd 镜像,并暴露端口2379(Etcd 客户端通信)、2380(Etcd 服务器通信)、4001(),并指定以下 Etcd 选项:

  • name:节点的名称,在这种情况下,我们选择 etcd-m 作为托管此容器的节点的名称

  • 在这个静态配置中,initial-advertise-peer-urls是集群的地址:端口

  • listen-peer-urls

  • listen-client-urls

  • advertise-client-urls

  • initial-cluster-token

  • initial-cluster

  • initial-cluster-state

我们可以使用etcdctl cluster-health命令行实用程序确保这个单节点 Etcd 集群是健康的:

term0$ docker run fsoppelsa/etcdctl -C $(dm ip etcd-m):2379 
    cluster-health

重新设计第一章的示例,使用 Etcd

这表明 Etcd 至少已经启动运行,因此我们可以使用它来设置 Swarm v1 集群。

我们在同一台etcd-m主机上创建 Swarm 管理器:

term0$ docker run -d -p 3376:3376 swarm manage \
-H tcp://0.0.0.0:3376 \`
etcd://$(docker-machine ip etcd-m)/swarm

这将从主机到容器暴露通常的3376端口,但这次使用etcd:// URL 启动管理器以进行发现服务。

现在我们加入节点,etcd-1etcd-2etcd-3

像往常一样,我们可以为每个终端提供源和命令机器:

term1$ eval $(docker-machine env etcd-1)
term1$ docker run -d swarm join --advertise \
$(docker-machine ip etcd-1):2379 \
etcd://$(docker-machine ip etcd-m):2379
term2$ eval $(docker-machine env etcd-2)
term1$ docker run -d swarm join --advertise \
$(docker-machine ip etcd-2):2379 \
etcd://$(docker-machine ip etcd-m):2379
term3$ eval $(docker-machine env etcd-3)
term3$ docker run -d swarm join --advertise \
$(docker-machine ip etcd-3):2379 \
etcd://$(docker-machine ip etcd-m):2379

通过使用-advertise加入本地节点到 Swarm 集群,使用运行并暴露在etcd-m上的 Etcd 服务。

现在我们转到etcd-m并通过调用 Etcd 发现服务来查看我们集群的节点:

使用 Etcd 重新架构第一章的示例

我们已经如预期那样将三个主机加入了集群。

ZooKeeper

ZooKeeper 是另一个广泛使用且高性能的分布式应用协调服务。Apache ZooKeeper 最初是 Hadoop 的一个子项目,但现在是一个顶级项目。它是一个高度一致、可扩展和可靠的键值存储,可用作 Docker Swarm v1 集群的发现服务。如前所述,ZooKeeper 使用 Paxos 而不是 Raft。

与 Etcd 类似,当 ZooKeeper 与法定人数形成节点集群时,它有一个领导者和其余的节点是跟随者。在内部,ZooKeeper 使用自己的 ZAB,即 ZooKeeper 广播协议,来维护一致性和完整性。

Consul

我们将在这里看到的最后一个发现服务是 Consul,这是一个用于发现和配置服务的工具。它提供了一个 API,允许客户端注册和发现服务。与 Etcd 和 ZooKeeper 类似,Consul 是一个带有 REST API 的键值存储。它可以执行健康检查以确定服务的可用性,并通过 Serf 库使用 Raft 一致性算法。当然,与 Etcd 和 ZooKeeper 类似,Consul 可以形成具有领导者选举的高可用性法定人数。其成员管理系统基于memberlist,这是一个高效的 Gossip 协议实现。

使用 Consul 重新架构第一章的示例

现在我们将创建另一个 Swarm v1,但在本节中,我们将在云提供商 DigitalOcean 上创建机器。为此,您需要一个访问令牌。但是,如果您没有 DigitalOcean 帐户,可以将--driver digitalocean替换为--driver virtualbox并在本地运行此示例。

让我们从创建 Consul 主节点开始:

$ docker-machine create --driver digitalocean consul-m
$ eval $(docker-machine env consul-m)

我们在这里启动第一个代理。虽然我们称它为代理,但实际上我们将以服务器模式运行它。我们使用服务器模式(-server)并将其设置为引导节点(-bootstrap)。使用这些选项,Consul 将不执行领导者选择,因为它将强制自己成为领导者。

$ docker run -d --name=consul --net=host \
consul agent \
-client=$(docker-machine ip consul-m) \
-bind=$(docker-machine ip consul-m) \
-server -bootstrap

在 HA 的情况下,第二个和第三个节点必须以-botstrap-expect 3开头,以允许它们形成一个高可用性集群。

现在,我们可以使用curl命令来测试我们的 Consul quorum 是否成功启动。

$ curl -X GET http://$(docker-machine ip consul-m):8500/v1/kv/

如果没有显示任何错误,那么 Consul 就正常工作了。

接下来,我们将在 DigitalOcean 上创建另外三个节点。

$ for i in `seq 1 3`; do docker-machine create -d digitalocean 
    consul-$i; 
    done

让我们启动主节点并使用 Consul 作为发现机制:

$ eval $(docker-machine env consul-m)
$ docker run -d -p 3376:3376 swarm manage \
-H tcp://0.0.0.0:3376 \
consul://$(docker-machine ip consul-m):8500/swarm
$ eval $(docker-machine env consul-1)
$ docker run -d swarm join \
--advertise $(docker-machine ip consul-1):2376 \
consul://$(docker-machine ip consul-m):8500/swarm
$ eval $(docker-machine env consul-2)
$ docker run -d swarm join \
--advertise $(docker-machine ip consul-2):2376 \
consul://$(docker-machine ip consul-m):8500/swarm
$ eval $(docker-machine env consul-3)
$ docker run -d swarm join \
--advertise $(docker-machine ip consul-3):2376 \
consul://$(docker-machine ip consul-m):8500/swarm

运行swarm list命令时,我们得到的结果是:所有节点都加入了 Swarm,所以示例正在运行。

$ docker run swarm list consul://$(docker-machine ip consul-m):8500/swarm                                       time="2016-07-01T21:45:18Z" level=info msg="Initializing discovery without TLS"
104.131.101.173:2376
104.131.63.75:2376
104.236.56.53:2376

走向去中心化的发现服务

Swarm v1 架构的局限性在于它使用了集中式和外部的发现服务。这种方法使每个代理都要与外部发现服务进行通信,而发现服务服务器可能会看到它们的负载呈指数级增长。根据我们的实验,对于一个 500 节点的集群,我们建议至少使用三台中高规格的机器来形成一个 HA 发现服务,比如 8 核 8GB 的内存。

为了正确解决这个问题,SwarmKit 和 Swarm Mode 使用的发现服务是以去中心化为设计理念的。Swarm Mode 在所有节点上都使用相同的发现服务代码库 Etcd,没有单点故障。

总结

在本章中,我们熟悉了共识和发现服务的概念。我们了解到它们在编排集群中扮演着至关重要的角色,因为它们提供了容错和安全配置等服务。在详细分析了 Raft 等共识算法之后,我们看了两种具体的 Raft 发现服务实现,Etcd 和 Consul,并将它们应用到基本示例中进行了重新架构。在下一章中,我们将开始探索使用嵌入式 Etcd 库的 SwarmKit 和 Swarm。

第三章:了解 Docker Swarm 模式

在 Dockercon 16 上,Docker 团队提出了一种操作 Swarm 集群的新方式,称为 Swarm 模式。这一宣布略有预期,因为引入了一套新的工具,被称为在任何规模上操作分布式系统Swarmkit

在本章中,我们将:

  • 介绍 Swarmkit

  • 介绍 Swarm 模式

  • 比较 Swarm v1、Swarmkit 和 Swarm 模式

  • 创建一个测试 Swarmkit 集群,并在其上启动服务。

不要跳过阅读 Swarmkit 部分,因为 Swarmkit 作为 Swarm 模式的基础。看到 Swarmkit 是我们选择介绍 Swarm 模式概念的方式,比如节点、服务、任务。

我们将展示如何在第四章中创建生产级别的大型 Swarm 模式集群,创建生产级别的 Swarm

Swarmkit

除了 Swarm 模式,Docker 团队在 DockerCon16 发布了 Swarmkit,被定义为:

“用于在任何规模上编排分布式系统的工具包。它包括节点发现、基于 raft 的共识、任务调度等基元。”

Swarms集群由活动节点组成,可以充当管理者或工作者。

管理者通过 Raft 进行协调(也就是说,当达成法定人数时,他们会选举领导者,如第二章中所述,发现发现服务),负责分配资源、编排服务和在集群中分发任务。工作者运行任务。

集群的目标是执行服务,因此需要在高层次上定义要运行的内容。例如,一个服务可以是“web”。分配给节点的工作单元称为任务。分配给“web”服务的任务可能是运行 nginx 容器的容器,可能被命名为 web.5。

非常重要的是要注意我们正在谈论服务,而服务可能是容器。可能不是必要的。在本书中,我们的重点当然是容器,但 Swarmkit 的意图理论上是抽象编排任何对象。

版本和支持

关于版本的说明。我们将在接下来的章节中介绍的 Docker Swarm 模式,只与 Docker 1.12+兼容。而 Swarmkit 可以编排甚至是以前版本的 Docker 引擎,例如 1.11 或 1.10。

Swarmkit 架构

Swarmkit是发布的编排机制,用于处理任何规模的服务集群。

在 Swarmkit 集群中,节点可以是管理者(集群的管理者)或工作节点(集群的工作马,执行计算操作的节点)。

最好有奇数个管理者,最好是 3 或 5 个,这样就不会出现分裂的情况(如第二章中所解释的,发现发现服务),并且大多数管理者将驱动集群。Raft 一致性算法始终需要法定人数。

Swarmkit 集群可以承载任意数量的工作节点:1、10、100 或 2000。

在管理者上,服务可以被定义和负载平衡。例如,一个服务可以是“web”。一个“web”服务将由运行在集群节点上的多个任务组成,包括管理者,例如,一个任务可以是一个单独的 nginx Docker 容器。

Swarmkit 架构

在 Swarmkit 中,操作员使用Swarmctl二进制文件远程与系统交互,在领导主节点上调用操作。运行名为Swarmd的主节点通过 Raft 同意领导者,保持服务和任务的状态,并在工作节点上调度作业。

工作节点运行 Docker 引擎,并将作业作为单独的容器运行。

Swarmkit 架构可能会被重新绘制,但核心组件(主节点和工作节点)将保持不变。相反,可能会通过插件添加新对象,用于分配资源,如网络和卷。

管理者如何选择最佳节点执行任务

Swarmkit 在集群上生成任务的方式称为调度。调度程序是一个使用诸如过滤器之类的标准来决定在哪里物理启动任务的算法。

管理者如何选择最佳节点执行任务

SwarmKit 的核心:swarmd

启动 SwarmKit 服务的核心二进制文件称为swarmd,这是创建主节点和加入从节点的守护程序。

它可以绑定到本地 UNIX 套接字和 TCP 套接字,但无论哪种情况,都可以通过连接到(另一个)专用的本地 UNIX 套接字来由swarmctl实用程序管理。

在接下来的部分中,我们将使用swarmd在端口4242/tcp上创建一个第一个管理者,并再次使用swarmd在其他工作节点上,使它们加入管理者,最后我们将使用swarmctl来检查我们集群的一些情况。

这些二进制文件被封装到fsoppelsa/swarmkit镜像中,该镜像可在 Docker Hub 上获得,并且我们将在这里使用它来简化说明并避免 Go 代码编译。

这是 swarmd 的在线帮助。它在其可调整项中相当自解释,因此我们不会详细介绍所有选项。对于我们的实际目的,最重要的选项是--listen-remote-api,定义swarmd绑定的address:port,以及--join-addr,用于其他节点加入集群。

SwarmKit 的核心:swarmd

SwarmKit 的控制器:swarmctl

swarmctl是 SwarmKit 的客户端部分。这是用于操作 SwarmKit 集群的工具,它能够显示已加入节点的列表、服务和任务的列表,以及其他信息。这里,再次来自fsoppelsa/swarmkitswarmctl的在线帮助:

SwarmKit 的控制器:swarmctl

使用 Ansible 创建 SwarmKit 集群

在这一部分,我们将首先创建一个由单个管理节点和任意数量的从节点组成的 SwarmKit 集群。

为了创建这样的设置,我们将使用 Ansible 来使操作可重复和更加健壮,并且除了说明命令,我们还将通过检查 playbooks 结构来进行。您可以轻松地调整这些 playbooks 以在您的提供商或本地运行,但在这里我们将在 Amazon EC2 上进行。

为了使这个示例运行,有一些基本要求。

如果您想在 AWS 上跟随示例,当然您必须拥有 AWS 账户并配置访问密钥。密钥可从 AWS 控制台中的账户名称 | 安全凭据下检索。您需要复制以下密钥的值:

  • 访问密钥 ID

  • 秘密访问密钥

我使用awsctl来设置这些密钥。只需从brew(Mac)安装它,或者如果您使用 Linux 或 Windows,则从您的打包系统安装它,并进行配置:

aws configure

在需要时通过粘贴密钥来回答提示问题。配置中,您可以指定例如一个喜爱的 AWS 区域(如us-west-1)存储在~/.aws/config中,而凭据存储在~/.aws/credentials中。这样,密钥会被 Docker Machine 自动配置和读取。

如果您想运行 Ansible 示例而不是命令,这些是软件要求:

  • Ansible 2.2+

  • 与 docker-machine 将在 EC2 上安装的镜像兼容的 Docker 客户端(在我们的情况下,默认的是 Ubuntu 15.04 LTS)一起使用,写作时,Docker Client 1.11.2

  • Docker-machine

  • Docker-py 客户端(由 Ansible 使用)可以通过pip install docker-py安装

此外,示例使用标准端口4242/tcp,以使集群节点相互交互。因此,需要在安全组中打开该端口。

克隆存储库github.com/fsoppelsa/ansible-swarmkit,并开始设置 SwarmKit Manager 节点:

ansible-playbook aws_provision_master.yml

使用 Ansible 配置 SwarmKit 集群

经过一些 docker-machine 的设置后,playbook 将在 Manager 主机上启动一个容器,充当 SwarmKit Manager。以下是 play 片段:

- name: Run the Swarmkit Master 
  docker: 
  name: swarmkit-master 
  image: "fsoppelsa/swarmkit" 
  command: swarmd --listen-remote-api 0.0.0.0:4242 
  expose: 
    - "4242" 
  ports: 
    - "0.0.0.0:4242:4242/tcp" 
  volumes: 
    - "/var/run/docker.sock:/var/run/docker.sock" 
  detach: yes 
  docker_url: "{{ dhost }}" 
  use_tls: encrypt 
  tls_ca_cert: "{{ dcert }}/ca.pem" 
  tls_client_cert: "{{ dcert }}/cert.pem" 
  tls_client_key: "{{ dcert }}/key.pem" 

在主机上,名为swarmkit-master的容器从图像fsoppelsa/swarmkit中运行swarmd以管理模式运行(它在0.0.0.0:4242处监听)。swarmd二进制文件直接使用主机上的 Docker Engine,因此 Engine 的套接字被挂载到容器内。容器将端口4242映射到主机端口4242,以便从属节点可以通过连接到主机4242端口直接访问swarmd

实际上,这相当于以下 Docker 命令:

docker run -d -v /var/run/docker.sock:/var/run/docker.sock -p 
    4242:4242 fsoppelsa/swarmkit swarmd --listen-remote-api  
    0.0.0.0:4242

此命令以分离模式(-d)运行,通过卷(-v)将 Docker 机器 Docker 套接字传递到容器内部,将容器中的端口4242暴露到主机(-p),并通过将容器本身放在任何地址上的端口4242上运行swarmd,使其处于监听模式。

一旦 playbook 完成,您可以获取swarmkit-master机器的凭据并检查我们的容器是否正常运行:

使用 Ansible 配置 SwarmKit 集群

现在是加入一些从属节点的时候了。要启动一个从属节点,您可以,猜猜看,只需运行:

ansible-playbook aws_provision_slave.yml

但由于我们希望至少加入几个节点到 SwarmKit 集群中,我们使用一点 shell 脚本:

for i in $(seq 5); do ansible-playbook aws_provision_slave.yml; 
    done

此命令运行五次 playbook,从而创建五个工作节点。playbook 在创建名为swarmkit-RANDOM的机器后,将启动一个fsoppelsa/swarmkit容器,执行以下操作:

- name: Join the slave to the Swarmkit cluster
  docker:
    name: "{{machine_uuid}}"
    image: "fsoppelsa/swarmkit"
    command: swarmd --join-addr "{{ masterip }}":4242
    volumes:
      - "/var/run/docker.sock:/var/run/docker.sock"
    detach: yes
    docker_url: "{{ shost }}"

在这里,swarmd 以加入模式运行,并通过连接到端口4242/tcp加入在 Master 上启动的集群。这相当于以下 docker 命令:

docker run -d -v /var/run/docker.sock:/var/run/docker.sock 
    fsoppelsa/swarmkit swarmd --join-addr $(docker-machine ip swarmkit- 
    master):4242

ansible 的loop命令将需要一些时间来完成,这取决于有多少工作节点正在启动。当 playbook 完成后,我们可以使用swarmctl来控制集群是否正确创建。如果您还没有提供swarmkit-master机器凭据,现在是时候了:

eval $(docker-machine env swarmkit-master)

现在我们使用 exec 来调用运行 swarmd 主节点的容器:

docker exec -ti 79d9be555dab swarmctl -s /swarmkitstate/swarmd.sock 
    node ls

使用 Ansible 配置 SwarmKit 集群

所以,这里列出了已加入主节点的工作节点。

在 SwarmKit 上创建服务

使用通常的swarmctl二进制文件,我们现在可以创建一个服务(web),由 nginx 容器制成。

我们首先检查一下,确保这个全新的集群上没有活动服务:

在 SwarmKit 上创建服务

所以我们准备好开始了,使用这个命令:

docker exec -ti 79d9be555dab swarmctl service create --name web --
    image nginx --replicas 5

在 SwarmKit 上创建服务

该命令指定创建一个名为web的服务,由nginx容器镜像制成,并且使用因子5进行复制,以在集群中创建 5 个 nginx 容器。这需要一些时间生效,因为在集群的每个节点上,Swarm 将拉取并启动 nginx 镜像,但最终:

在 SwarmKit 上创建服务

5/5表示在 5 个期望的副本中,有 5 个正在运行。我们可以使用swarmctl task ls来详细查看这些容器生成的位置:

在 SwarmKit 上创建服务

但是,等等,manager 节点上是否正在运行 nginx 服务(web.5)?是的。默认情况下,SwarmKit 和 Swarm 模式管理者被允许运行任务,并且调度程序可以将作业分派给它们。

在真实的生产配置中,如果您想要保留管理者不运行作业,您需要应用带有标签和约束的配置。这是第五章管理 Swarm 集群的主题。

Swarm 模式

Docker Swarm 模式(适用于版本 1.12 或更新版本的 Docker 引擎)导入了 SwarmKit 库,以便实现在多个主机上进行分布式容器编排,并且操作简单易行。

SwarmKit 和 Swarm 模式的主要区别在于,Swarm 模式集成到了 Docker 本身,从版本 1.12 开始。这意味着 Swarm 模式命令,如swarmnodesservicetask在 Docker 客户端内部可用,并且通过 docker 命令可以初始化和管理 Swarm,以及部署服务和任务:

  • docker swarm init: 这是用来初始化 Swarm 集群的

  • docker node ls: 用于列出可用节点

  • docker service tasks: 用于列出与特定服务相关的任务

旧的 Swarm 与新的 Swarm 与 SwarmKit

在撰写本文时(2016 年 8 月),我们有三个 Docker 编排系统:旧的 Swarm v1,SwarmKit 和集成的新 Swarm 模式。

旧的 Swarm 与新的 Swarm 与 SwarmKit

我们在第一章中展示的原始 Swarm v1,欢迎来到 Docker Swarm,仍在使用,尚未被弃用。这是一种使用(回收利用?)旧基础设施的方式。但是从 Docker 1.12 开始,新的 Swarm 模式是开始新编排项目的推荐方式,特别是如果需要扩展到大规模。

为了简化事情,让我们用一些表格总结这些项目之间的区别。

首先,旧的 Swarm v1 与新的 Swarm 模式:

Swarm standalone Swarm Mode
这是自 Docker 1.8 起可用 这是自 Docker 1.12 起可用
这可用作容器 这集成到 Docker Engine 中
这需要外部发现服务(如 Consul、Etcd 或 Zookeeper) 这不需要外部发现服务,Etcd 集成
这默认不安全 这默认安全
复制和扩展功能不可用 复制和扩展功能可用
没有用于建模微服务的服务和任务概念 有现成的服务、任务、负载均衡和服务发现
没有额外的网络可用 这个集成了 VxLAN(网状网络)

现在,为了澄清想法,让我们比较一下 SwarmKit 和 Swarm 模式:

SwarmKit Swarm mode
这些发布为二进制文件(swarmdswarmctl)-使用 swarmctl 这些集成到 Docker Engine 中-使用 docker
这些是通用任务 这些是容器任务
这些包括服务和任务 这些包括服务和任务
这些不包括服务高级功能,如负载平衡和 VxLAN 网络 这些包括开箱即用的服务高级功能,如负载平衡和 VxLAN 网络

Swarm 模式放大

正如我们在 Swarm 独立与 Swarm 模式比较的前表中已经总结的,Swarm 模式中的主要新功能包括集成到引擎中,无需外部发现服务,以及包括副本、规模、负载平衡和网络。

集成到引擎中

使用 docker 1.12+,docker 客户端添加了一些新命令。我们现在对与本书相关的命令进行调查。

docker swarm 命令

这是管理 Swarm 的当前命令:

docker swarm command

它接受以下选项:

  • init:这将初始化一个 Swarm。在幕后,此命令为当前 Docker 主机创建一个管理者,并生成一个秘密(工作节点将通过 API 传递给密码以获得加入集群的授权)。

  • join:这是工作节点加入集群的命令,必须指定秘密和管理者 IP 端口值列表。

  • join-token:这用于管理join-tokensjoin-tokens是用于使管理者或工作节点加入的特殊令牌秘密(管理者和工作节点具有不同的令牌值)。此命令是使 Swarm 打印加入管理者或工作节点所需命令的便捷方式:

docker swarm join-token worker

要将工作节点添加到此 Swarm,请运行以下命令:

docker swarm join \ --token SWMTKN-1-  
        36gj6glgi3ub2i28ekm1b1er8aa51vltv00760t7umh3wmo1sc- 
        aucj6a94tqhhn2k0iipnc6096 \ 192.168.65.2:2377
docker swarm join-token manager

要将管理者添加到此 Swarm,请运行以下命令:

docker swarm join \ --token SWMTKN-1- 
        36gj6glgi3ub2i28ekm1b1er8aa51vltv00760t7umh3wmo1sc- 
        98glton0ot50j1yn8eci48rvq \ 192.168.65.2:2377

  • update:这将通过更改一些值来更新集群,例如,您可以使用它来指定证书端点的新 URL

  • leave:此命令使当前节点离开集群。如果有什么阻碍了操作,有一个有用的--force选项。

docker 节点

这是处理集群节点的命令。您必须从管理者启动它,因此您需要连接到管理者才能使用它。

docker 节点

  • demotepromote:这些是用于管理节点状态的命令。通过该机制,您可以将节点提升为管理者,或将其降级为工作节点。在实践中,Swarm 将尝试demote/promote。我们将在本章稍后介绍这个概念。

  • inspect:这相当于 docker info,但用于 Swarm 节点。它打印有关节点的信息。

  • ls:这列出了连接到集群的节点。

  • rm:这尝试移除一个 worker。如果你想移除一个 manager,在此之前你需要将其降级为 worker。

  • ps:这显示了在指定节点上运行的任务列表。

  • update:这允许您更改节点的一些配置值,即标签。

docker service

这是管理运行在 Swarm 集群上的服务的命令:

docker service

除了预期的命令,如createinspectpslsrmupdate,还有一个新的有趣的命令:scale

Docker Stack

并不直接需要 Swarm 操作,但在 Docker 1.12 中作为实验引入了stack命令。Stacks 现在是容器的捆绑。例如,一个 nginx + php + mysql 容器设置可以被堆叠在一个自包含的 Docker Stack 中,称为分布式应用程序包DAB),并由一个 JSON 文件描述。

docker stack 的核心命令将是 deploy,通过它将可以创建和更新 DABs。我们稍后会在第六章中遇到 stacks,在 Swarm 上部署真实应用程序

Etcd 的 Raft 已经集成

Docker Swarm Mode 已经通过 CoreOS Etcd Raft 库集成了 RAFT。不再需要集成外部发现服务,如 Zookeeper 或 Consul。Swarm 直接负责基本服务,如 DNS 和负载均衡。

安装 Swarm Mode 集群只是启动 Docker 主机并运行 Docker 命令的问题,使得设置变得非常容易。

负载均衡和 DNS

按设计,集群管理器为 Swarm 中的每个服务分配一个唯一的 DNS 名称,并使用内部 DNS 对运行的容器进行负载均衡。查询和解析可以自动工作。

对于使用--name myservice创建的每个服务,Swarm 中的每个容器都将能够解析服务 IP 地址,就像它们正在解析(dig myservice)内部网络名称一样,使用 Docker 内置的 DNS 服务器。因此,如果你有一个nginx-service(例如由 nginx 容器组成),你可以只需ping nginx-service来到达前端。

此外,在 Swarm 模式下,操作员有可能将服务端口发布到外部负载均衡器。然后,端口在3000032767的范围内暴露到外部。在内部,Swarm 使用 iptables 和 IPVS 来执行数据包过滤和转发,以及负载均衡。

Iptables 是 Linux 默认使用的数据包过滤防火墙,而 IPVS 是在 Linux 内核中定义的经验丰富的 IP 虚拟服务器,可用于负载均衡流量,这正是 Docker Swarm 所使用的。

端口要么在创建新服务时发布,要么在更新时发布,使用--publish-add选项。使用此选项,内部服务被发布,并进行负载均衡。

例如,如果我们有一个包含三个工作节点的集群,每个节点都运行 nginx(在名为nginx-service的服务上),我们可以将它们的目标端口暴露给负载均衡器:

docker service update --port-add 80 nginx-service

这将在集群的任何节点上创建一个映射,将发布端口30000nginx容器(端口 80)关联起来。如果您连接到端口30000的任何节点,您将看到 Nginx 的欢迎页面。

负载均衡和 DNS

但是这是如何工作的呢?正如您在上面的屏幕截图中看到的,有一个关联的虚拟 IP(10.255.0.7/16),或者 VIP,它位于由 Swarm 创建的覆盖网络2xbr2upsr3yl上,用于负载均衡器的入口:

负载均衡和 DNS

从任何主机,您都可以访问nginx-service,因为 DNS 名称解析为 VIP,这里是 10.255.0.7,充当负载均衡器的前端:

在 Swarm 的每个节点上,Swarm 在内核中实现负载均衡,具体来说是在命名空间内部,通过在专用于网络的网络命名空间中的 OUTPUT 链中添加一个 MARK 规则,如下屏幕截图所示:

负载均衡和 DNS

我们将在稍后的第五章 管理 Swarm 集群和第八章 探索 Swarm 的其他功能中更详细地介绍网络概念。

提升和降级

使用docker node命令,集群操作员可以将节点从工作节点提升为管理节点,反之亦然,将它们从管理节点降级为工作节点。

将节点从管理节点降级为工作节点是从集群中删除管理节点(现在是工作节点)的唯一方法。

我们将在第五章中详细介绍晋升和降级操作,管理 Swarm 集群

副本和规模

在 Swarm 集群上部署应用意味着定义和配置服务,启动它们,并等待分布在集群中的 Docker 引擎启动容器。我们将在第六章中在 Swarm 上部署完整的应用程序,在 Swarm 上部署真实应用程序

服务和任务

Swarm 工作负载的核心被划分为服务。服务只是一个将任意数量的任务(这个数量被称为副本因子或者副本)分组的抽象。任务是运行的容器。

docker service scale

使用docker service scale命令,您可以命令 Swarm 确保集群中同时运行一定数量的副本。例如,您可以从运行在集群上的 10 个容器开始执行一些任务,然后当您需要将它们的大小扩展到 30 时,只需执行:

docker service scale myservice=30

Swarm 被命令安排调度 20 个新容器,因此它会做出适当的决策来实现负载平衡、DNS 和网络的一致性。如果一个任务的容器关闭,使副本因子等于 29,Swarm 将在另一个集群节点上重新安排另一个容器(它将具有新的 ID)以保持因子等于 30。

关于副本和新节点添加的说明。人们经常询问 Swarm 的自动能力。如果您有五个运行 30 个任务的工作节点,并添加了五个新节点,您不应该期望 Swarm 自动地在新节点之间平衡 30 个任务,将它们从原始节点移动到新节点。Swarm 调度程序的行为是保守的,直到某个事件(例如,操作员干预)触发了一个新的scale命令。只有在这种情况下,调度程序才会考虑这五个新节点,并可能在 5 个新工作节点上启动新的副本任务。

我们将在第七章中详细介绍scale命令的实际工作,扩展您的平台

总结

在本章中,我们遇到了 Docker 生态系统中的新角色:SwarmKit 和 Swarm Mode。我们通过在 Amazon AWS 上使用 Ansible 对 SwarmKit 集群进行了简单的实现。然后,我们介绍了 Swarm Mode 的基本概念,包括其界面和内部机制,包括 DNS、负载平衡、服务、副本以及晋升/降级机制。现在,是时候深入了解真正的 Swarm Mode 部署了,就像我们将在第四章 创建一个生产级别的 Swarm中看到的那样。

第四章:创建生产级别的 Swarm

在这一章中,您将学习如何创建拥有数千个节点的真实 Swarm 集群;具体来说,我们将涵盖以下主题:

  • 部署大型 Swarm 的工具

  • Swarm2k:有史以来构建的最大 Swarm 模式集群之一,由 2,300 个节点组成

  • Swarm3k:第二个实验,一个拥有 4,700 个节点的集群

  • 如何规划硬件资源

  • HA 集群拓扑

  • Swarm 基础设施管理、网络和安全

  • 监控仪表板

  • 从 Swarm2k 和 Swarm3k 实验中学到的东西

工具

使用 Swarm 模式,我们可以轻松设计生产级别的集群。

我们在这里阐述的原则和架构在一般情况下非常重要,并为如何设计生产安装提供了基础,无论使用何种工具。然而,从实际角度来看,使用的工具也很重要。

在撰写本书时,Docker Machine 并不是用于大规模 Swarm 设置的理想单一工具,因此我们将使用一个与本书同时诞生的工具来演示我们的生产规模部署,该工具已在第一章中介绍过,欢迎来到 Docker Swarm:belt (github.com/chanwit/belt)。我们将与 Docker Machine、Docker Networking 和 DigitalOcean 的doctl命令一起使用它。

在第五章中,管理 Swarm 集群,您将学习如何自动化创建 Swarm;特别是如何通过脚本和其他机制(如 Ansible)快速加入大量的工作节点。

工具

Swarm2k 的 HA 拓扑

Swarm2k 和 Swarm3k 是协作实验。我们通过呼吁参与者以 Docker 主机而不是金钱来筹集资金,结果令人惊讶-Swarm2k 和 Swarm3k 得到了数十个个人和公司地理分布的贡献者的支持。总共,对于 Swarm2k,我们收集了大约 2,300 个节点,而对于 Swarm3k,大约有 4,700 个节点。

让我们讨论Swarm2k的架构。在前面的图中,有三个管理者,分别标记为mg0mg1mg2。我们将使用三个管理者,因为这是 Docker 核心团队建议的最佳管理者数量。管理者在高速网络链路上形成法定人数,并且 Raft 节点需要大量资源来同步它们的活动。因此,我们决定将我们的管理者部署在同一数据中心的 40GB 以太网链路上。

在实验开始时,我们有以下配置:

  • mg0 是集群的管理者领导者

  • mg1 托管了统计收集器

  • mg2 是一个准备(备用)管理者

相反,W节点是 Swarm 工作者。

安装在 mg1 上的统计收集器从本地 Docker Engine 查询信息,并将其发送到远程时间序列数据库InfluxDB中存储。我们选择了 InfluxDB,因为它是Telegraf监控代理的本地支持。为了显示集群的统计信息,我们使用Grafana作为仪表板,稍后我们会看到。

管理者规格

管理者受 CPU 限制而不是内存限制。对于一个 500-1,000 节点的 Swarm 集群,我们经验性地观察到每个管理者具有 8 个虚拟 CPU 足以承担负载。然而,如果超过 2,000 个节点,我们建议每个管理者至少具有 16-20 个虚拟 CPU 以满足可能的 Raft 恢复。

在 Raft 恢复的情况下

下图显示了硬件升级期间的 CPU 使用情况以及大量工作者加入过程中的情况。在将硬件升级到 8 个虚拟 CPU 时(机器的停机时间由线条表示),我们可以看到领导者 mg0 的 CPU 使用率在 mg1和 mg2重新加入集群时飙升至 75-90%。触发此飙升的事件是 Raft 日志的同步和恢复。

在没有必要恢复的正常情况下,每个管理者的 CPU 使用率保持较低,如下图所示。

在 Raft 恢复的情况下

Raft 文件

在管理主机上,Swarm 数据保存在/var/lib/docker/swarm中,称为swarm 目录。具体来说,Raft 数据保存在/var/lib/docker/swarm/raft中,包括预写式日志(WAL)和快照文件。

在这些文件中,有关节点、服务和任务的条目,按照 Protobuf 格式定义。

WAL 和快照文件经常写入磁盘。在 SwarmKit 和 Docker Swarm 模式中,它们每 10,000 个条目写入一次磁盘。根据这种行为,我们将 swarm 目录映射到具有增加吞吐量的快速和专用磁盘,特别是 SSD 驱动器。

我们将在第五章 管理 Swarm 集群中解释在 Swarm 目录损坏的情况下的备份和恢复程序。

运行任务

Swarm 集群的目标是运行服务,例如,由大量容器组成的大规模 Web 应用程序。我们将这种部署类型称为单一模型。在这个模型中,网络端口被视为必须全局发布的资源。在未来版本的 Docker Swarm 模式中,使用命名空间,部署可以是模型,允许我们拥有多个子集群,这些子集群为不同的服务公开相同的端口。

在小型集群中,我们可以决定允许管理者谨慎地托管工作任务。对于更大的设置,管理者使用更多的资源。此外,如果管理者的负载饱和了其资源,集群将变得不稳定和无响应,并且不会执行任何命令。我们称这种状态为狂暴 状态

为了使大型集群,如 Swarm2k 或 Swarm3k 稳定,所有管理者的可用性必须设置为“排水”状态,以便所有任务不会被安排在它们上面,只会在工作节点上,具体为:

 docker node update --availability drain node-name

管理者拓扑结构

我们将在第五章 管理 Swarm 集群中再次讨论这个 HA 属性,但在这里,我们将介绍它来说明一些 Swarm 拓扑理论。HA 理论要求形成一个具有奇数节点数的 HA 集群。以下表格显示了单个数据中心的容错因素。在本章中,我们将称之为 5(1)-3-2 公式,用于 5 个节点在 1 个数据中心上的集群大小,其中 3 个节点法定人数允许 2 个节点故障。

集群大小 法定人数 允许节点故障
3 2 1
5 3 2
7 4 3
9 5 4

然而,在多个数据中心的生产环境中可以设计出几种管理者拓扑结构。例如,3(3)管理者拓扑结构可以分布为 1 + 1 + 1,而 5(3)管理者拓扑结构可以分布为 2 + 2 + 1。以下图片显示了最佳的 5(3)管理者拓扑结构:

管理者拓扑结构

在相同的容错水平下,下一张图片显示了一个替代的 5(4)拓扑,其中包含了 4 个数据中心的 5 个管理者。在数据中心 1 中运行了 2 个管理者 mg0 和 mg1,而剩下的管理者 mg2、mg3 和 mg4 分别在数据中心 2、3 和 4 中运行。mg0 和 mg1 管理者在高速网络上连接,而 mg2、mg3 和 mg4 可以使用较慢的链接。因此,在 3 个数据中心中的 2 + 2 + 1 将被重新排列为在 4 个数据中心中的 2 + 1 + 1 + 1。

管理者拓扑结构

最后,还有另一种分布式拓扑,6(4),它的性能更好,因为在其核心有 3 个节点在高速链接上形成中央仲裁。6 个管理者的集群需要一个 4 的仲裁大小。如果数据中心 1 失败,集群的控制平面将停止工作。在正常情况下,除了主要数据中心外,可以关闭 2 个节点或 2 个数据中心。

管理者拓扑结构

总之,尽可能使用奇数个管理者。如果您想要管理者仲裁的稳定性,请在高速链接上形成它。如果您想要避免单点故障,请尽可能将它们分布开来。

要确认哪种拓扑结构适合您,请尝试形成它,并通过有意将一些管理者关闭然后测量它们恢复的速度来测试管理者的延迟。

对于 Swarm2k 和 Swarm3k,我们选择在单个数据中心上形成拓扑结构,因为我们希望实现最佳性能。

使用 belt 进行基础设施的配置。

首先,我们使用以下命令为 DigitalOcean 创建了一个名为swarm2k的集群模板:

 $ belt cluster new --driver digitalocean swarm2k

上述命令在当前目录中创建了一个名为.belt/swarm2k/config.yml的配置模板文件。这是我们定义其他属性的起点。

我们通过运行以下命令来检查我们的集群是否已定义:

 $ belt cluster ls
 CLUSTER       ACTIVE    LEADER    MASTERS    #NODES
 swarm2k       -         -         -          0 / 0

使用该命令,我们可以切换并使用可用的swarm2k集群,如下所示:

 $ belt use swarm2k
 swarm2k

在这一点上,我们完善了swarm2k模板的属性。

通过发出以下命令将 DigitalOcean 的实例区域设置为sgp1

 $ belt cluster update region=sgp1

Belt 需要使用该命令定义所有必要的值。以下是我们在config.yml中指定的 DigitalOcean 驱动程序所需的模板键列表:

  • image:这是为了指定 DigitalOcean 镜像 ID 或快照 ID

  • region:这是为了指定 DigitalOcean 区域,例如 sgp1 或 nyc3

  • ssh_key_fingerprint:这是为了指定 DigitalOcean SSH 密钥 ID 或指纹

  • ssh_user:这是为了指定镜像使用的用户名,例如 root

  • access_token:这是为了指定 DigitalOcean 的访问令牌;建议不要在这里放任何令牌

提示

每个模板属性都有其对应的环境变量。例如,access_token属性可以通过DIGITALOCEAN_ACCESS_TOKEN来设置。因此,在实践中,我们也可以在继续之前将DIGITALOCEAN_ACCESS_TOKEN导出为一个 shell 变量。

配置就绪后,我们通过运行以下代码验证了当前的模板属性:

 $ belt cluster config
 digitalocean:
 image: "123456"
 region: sgp1
 ssh_key_fingerprint: "800000"
 ssh_user: root

现在,我们使用以下语法创建了一组 3 个 512MB 的管理节点,分别称为 mg0、mg1 和 mg2:

 $ belt create 8192MB mg[0:2]
 NAME   IPv4         MEMORY  REGION  IMAGE       STATUS
 mg2    128.*.*.11   8192     sgp1   Ubuntu docker-1.12.1 new
 mg1    128.*.*.220  8192     sgp1   Ubuntu docker-1.12.1 new
 mg0    128.*.*.21   8192     sgp1   Ubuntu docker-1.12.1 new

所有新节点都被初始化并进入新状态。

我们可以使用以下命令等待所有 3 个节点变为活动状态:

 $ belt status --wait active=3
 STATUS  #NODES  NAMES
 new         3   mg2, mg1, mg0
 STATUS  #NODES  NAMES
 new         3   mg2, mg1, mg0
 STATUS  #NODES  NAMES
 new         3   mg2, mg1, mg0
 STATUS  #NODES  NAMES
 active      3   mg2, mg1, mg0

然后,我们将 node1 设置为活动的管理主机,我们的 Swarm 将准备好形成。通过运行 active 命令可以设置活动主机,如下所示:

 $ belt active mg0
 swarm2k/mg0

在这一点上,我们已经形成了一个 Swarm。我们将 mg0 初始化为管理者领导者,如下所示:

 $ belt docker swarm init --advertise-addr 128.*.*.220
 Swarm initialized: current node (24j7sytbomhshtayt74lf7njo) is now 
    a manager.

前面的命令输出了要复制和粘贴以加入其他管理者和工作者的字符串,例如,看一下以下命令:

 docker swarm join \
 --token SWMTKN-1-1wwyxnfcgqt...fwzc1in3 \
 128.*.*.220:2377

Belt 提供了一个方便的快捷方式来加入节点,使用以下语法,这就是我们用来加入 mg1 和 mg2 到 Swarm 的方法:

 $ belt --host mg[1:2] docker swarm join \
 --token --token SWMTKN-1-1wwyxnfcgqt...fwzc1in3 \
 128.*.*.220:2377

现在,我们已经配置好了 mg0、mg1 和 mg2 管理者,并准备好获取工作者的 Swarm。

使用 Docker Machine 保护管理者

Docker Machine 对于大规模的 Docker Engine 部署不会很好,但事实证明它非常适用于自动保护少量节点。在接下来的部分中,我们将使用 Docker Machine 使用通用驱动程序来保护我们的 Swarm 管理器,这是一种允许我们控制现有主机的驱动程序。

在我们的情况下,我们已经在 mg0 上设置了一个 Docker Swarm 管理器。此外,我们希望通过为其远程端点启用 TLS 连接来保护 Docker Engine。

Docker Machine 如何为我们工作?首先,Docker Machine 通过 SSH 连接到主机;检测 mg0 的操作系统,在我们的情况下是 Ubuntu;以及 provisioner,在我们的情况下是 systemd。

之后,它安装了 Docker Engine;但是,如果已经有一个存在,就像这里一样,它会跳过这一步。

然后,作为最重要的部分,它生成了一个根 CA 证书,以及所有证书,并将它们存储在主机上。它还自动配置 Docker 使用这些证书。最后,它重新启动 Docker。

如果一切顺利,Docker Engine 将再次启动,并启用 TLS。

然后,我们使用 Docker Machine 在 mg0、mg1 和 mg2 上生成了 Engine 的根 CA,并配置了 TLS 连接。然后,我们稍后使用 Docker 客户端进一步控制 Swarm,而无需使用较慢的 SSH。

 $ docker-machine create \
 --driver generic \
 --generic-ip-address=$(belt ip mg0) mg0
 Running pre-create checks...
 Creating machine...
 (mg0) No SSH key specified. Assuming an existing key at the default 
    location.
 Waiting for machine to be running, this may take a few minutes...
 Detecting operating system of created instance...
 Waiting for SSH to be available...
 Detecting the provisioner...
 Provisioning with ubuntu(systemd)...
 Installing Docker...
 Copying certs to the local machine directory...
 Copying certs to the remote machine...
 Setting Docker configuration on the remote daemon...
 Checking connection to Docker...
 Then we can test our working swarm with `docker info`. We grep only 
    15 lines for the brevity.
 $ docker $(docker-machine config mg0) info | grep -A 15 Swarm
 Swarm: active
 NodeID: 24j7sytbomhshtayt74lf7njo
 Is Manager: true
 ClusterID: 8rshkwfq4hsil2tdb3idpqdeg
 Managers: 3
 Nodes: 3
 Orchestration:
 Task History Retention Limit: 5
 Raft:
 Snapshot Interval: 10000
 Heartbeat Tick: 1
 Election Tick: 3
 Dispatcher:
 Heartbeat Period: 5 seconds
 CA Configuration:
 Expiry Duration: 3 months

此外,docker node ls将在这个设置中正常工作。我们现在验证了 3 个管理者组成了初始的 Swarm,并且能够接受一堆工作节点:

 $ docker $(docker-machine config mg0) node ls
 ID                       HOSTNAME  STATUS  AVAILABILITY  MANAGER STATUS
 24j7sytbomhshtayt74lf7njo *  mg0       Ready   Active        Leader
 2a4jcvp32aoa6olaxlelthkws    mg1       Ready   Active        Reachable
 94po1ln0j0g5fgjnjfvm1w02r    mg2       Ready   Active        Reachable

提示

这个集群有多安全?

我们将使用 Docker 客户端连接到配备 TLS 的 Docker Engine;此外,swarm 节点之间还有另一个 TLS 连接,CA 在三个月后到期,将自动轮换。高级安全设置将在第九章中讨论,保护 Swarm 集群和 Docker 软件供应链

理解一些 Swarm 内部机制

此时,我们通过创建一个带有 3 个副本的 nginx 服务来检查 Swarm 是否可操作:

 $ eval $(docker-machine env mg0)
 $ docker service create --name nginx --replicas 3 nginx
 du2luca34cmy

之后,我们找到了运行 Nginx 的 net 命名空间 ID。我们通过 SSH 连接到 mg0。Swarm 的路由网格的网络命名空间是具有与特殊网络命名空间1-5t4znibozx相同时间戳的命名空间。在这个例子中,我们要找的命名空间是fe3714ca42d0

 root@mg0:~# ls /var/run/docker/netns -al
 total 0
 drwxr-xr-x 2 root root 120 Aug 22 15:38 .
 drwx------ 5 root root 100 Aug 22 13:39 ..
 -r--r--r-- 1 root root   0 Aug 22 15:17 1-5t4znibozx
 -r--r--r-- 1 root root   0 Aug 22 15:36 d9ef48834a31
 -r--r--r-- 1 root root   0 Aug 22 15:17 fe3714ca42d0

我们可以使用 ipvsadm 找出我们的 IPVS 条目,并使用 nsenter 工具(github.com/jpetazzo/nsenter)在 net 命名空间内运行它,如下所示:

 root@node1:~# nsenter --net=/var/run/docker/netns/fe3714ca42d0 ipvsadm -L
 IP Virtual Server version 1.2.1 (size=4096)
 Prot LocalAddress:Port Scheduler Flags
 -> RemoteAddress:Port           Forward Weight ActiveConn InActConn
 FWM  259 rr
 -> 10.255.0.8:0                 Masq    1      0          2

在这里,我们可以注意到有一个活动的轮询 IPVS 条目。IPVS 是内核级负载均衡器,与 iptables 一起用于 Swarm 来平衡流量,iptables 用于转发和过滤数据包。

清理 nginx 测试服务(docker service rm nginx)后,我们将设置管理者为 Drain 模式,以避免它们接受任务:

 $ docker node update --availability drain mg0
 $ docker node update --availability drain mg1
 $ docker node update --availability drain mg2

现在,我们准备在 Twitter 和 Github 上宣布我们的管理者的可用性,并开始实验!

加入工作节点

我们的贡献者开始将他们的节点作为工作节点加入到管理者mg0。任何人都可以使用自己喜欢的方法,包括以下方法:

  • 循环docker-machine ssh sudo docker swarm join命令

  • Ansible

  • 自定义脚本和程序

我们将在第五章中涵盖其中一些方法,管理 Swarm 集群

过了一段时间,我们达到了 2,300 个工作节点的配额,并使用了 100,000 个副本因子启动了一个alpine服务:

加入工人

升级管理器

过了一段时间,我们达到了管理器的最大容量,并且不得不增加它们的物理资源。在生产中,实时升级和维护管理器可能是一项预期的操作。以下是我们执行此操作的方法。

实时升级管理器

使用奇数作为法定人数,安全地将管理器降级进行维护。

 $ docker node ls
 ID                  HOSTNAME  STATUS  AVAILABILITY  MANAGER STATUS
 4viybni..h24zxde    mg1       Ready   Active        Reachable
 6xxwumb..j6zvtyg *  mg0       Ready   Active        Leader
 f1vs2e3..abdehnh    mg2       Ready   Active

在这里,我们将 mg1 作为可达的管理器,并使用以下语法将其降级为工作节点:

 $ docker node demote mg1
 Manager mg1 demoted in the swarm.

我们可以看到当 mg1 成为工作节点时,mg1Reachable状态从节点列表输出中消失。

 $ docker node ls
 ID                  HOSTNAME  STATUS  AVAILABILITY  MANAGER STATUS
 4viybni..h24zxde    mg1       Ready   Active
 6xxwumb..j6zvtyg *  mg0       Ready   Active        Leader
 f1vs2e3..abdehnh    mg2       Ready   Active

当节点不再是管理器时,可以安全地关闭它,例如,使用 DigitalOcean CLI,就像我们做的那样:

 $ doctl compute droplet-action shutdown 23362382

列出节点时,我们注意到 mg1 已经宕机了。

 $ docker node ls
 ID                   HOSTNAME  STATUS  AVAILABILITY  MANAGER STATUS
 4viybni0ud2gjpay6ih24zxde    mg1       Down    Active
 6xxwumbdac34bbgh6hj6zvtyg *  mg0       Ready   Active        Leader
 f1vs2e3hjiqjaukmjqabdehnh    mg2       Ready   Active

我们将其资源升级为 16G 内存,然后再次启动该机器:

 $ doctl -c .doctlcfg compute droplet-action power-on 23362382

在列出这个时间时,我们可以预期一些延迟,因为 mg1 正在重新加入集群。

 $ docker node ls
 ID                  HOSTNAME  STATUS  AVAILABILITY  MANAGER STATUS
 4viybni..h24zxde    mg1       Ready   Active
 6xxwumb..j6zvtyg *  mg0       Ready   Active        Leader
 f1vs2e3..abdehnh    mg2       Ready   Active

最后,我们可以将其重新提升为管理器,如下所示:

 $ docker node promote mg1
 Node mg1 promoted to a manager in the swarm.

一旦完成这个操作,集群就正常运行了。所以,我们对 mg0 和 mg2 重复了这个操作。

监控 Swarm2k

对于生产级集群,通常希望设置某种监控。到目前为止,还没有一种特定的方法来监视 Swarm 模式中的 Docker 服务和任务。我们在 Swarm2k 中使用了 Telegraf、InfluxDB 和 Grafana 来实现这一点。

InfluxDB 时间序列数据库

InfluxDB 是一个易于安装的时间序列数据库,因为它没有依赖关系。InfluxDB 对于存储指标、事件信息以及以后用于分析非常有用。对于 Swarm2k,我们使用 InfluxDB 来存储集群、节点、事件以及使用 Telegraf 进行任务的信息。

Telegraf 是可插拔的,并且具有一定数量的输入插件,用于观察系统环境。

Telegraf Swarm 插件

我们为 Telegraf 开发了一个新的插件,可以将统计数据存储到 InfluxDB 中。该插件可以在github.com/chanwit/telegraf找到。数据可能包含标签时间戳。值将根据时间戳进行计算或聚合。此外,标签将允许您根据时间戳将这些值分组在一起。

Telegraf Swarm 插件收集数据并创建以下系列,其中包含我们认为对 Swarmk2 最有趣的值、标签和时间戳到 InfluxDB 中:

  • 系列swarm_node:该系列包含cpu_sharesmemory作为值,并允许按node_idnode_hostname标签进行分组。

  • 系列swarm:该系列包含n_nodes表示节点数量,n_services表示服务数量,n_tasks表示任务数量。该系列不包含标签。

  • 系列swarm_task_status:该系列包含按状态分组的任务数量。该系列的标签是任务状态名称,例如 Started、Running 和 Failed。

要启用 Telegraf Swarm 插件,我们需要通过添加以下配置来调整telegraf.conf

 # Read metrics about swarm tasks and services
 [[inputs.swarm]]
   # Docker Endpoint
   #   To use TCP, set endpoint = "tcp://[ip]:[port]"
 #   To use environment variables (ie, docker-machine), set endpoint =
 "ENV"
   endpoint = "unix:///var/run/docker.sock"
   timeout = “10s”

首先,按以下方式设置 InfluxDB 实例:

 $ docker run -d \
 -p 8083:8083 \
 -p 8086:8086 \
 --expose 8090 \
 --expose 8099 \
 -e PRE_CREATE_DB=telegraf \
 --name influxsrv
 tutum/influxdb

然后,按以下方式设置 Grafana:

 docker run -d \
 -p 80:3000 \
 -e HTTP_USER=admin \
 -e HTTP_PASS=admin \
 -e INFLUXDB_HOST=$(belt ip influxdb) \
 -e INFLUXDB_PORT=8086 \
 -e INFLUXDB_NAME=telegraf \
 -e INFLUXDB_USER=root \
 -e INFLUXDB_PASS=root \
 --name grafana \
 grafana/grafana

在设置 Grafana 实例后,我们可以从以下 JSON 配置创建仪表板:

objects-us-west-1.dream.io/swarm2k/swarm2k_final_grafana_dashboard.json

要将仪表板连接到 InfluxDB,我们将不得不定义默认数据源并将其指向 InfluxDB 主机端口8086。以下是定义数据源的 JSON 配置。将$INFLUX_DB_IP替换为您的 InfluxDB 实例。

 {
 "name":"telegraf",
 "type":"influxdb",
 "access":"proxy",
 "url":"http://$INFLUX_DB_IP:8086",
 "user":"root",
 "password":"root",
 "database":"telegraf",
 "basicAuth":true,
 "basicAuthUser":"admin",
 "basicAuthPassword":"admin",
 "withCredentials":false,
 "isDefault":true
 }

将所有内容链接在一起后,我们将看到一个像这样的仪表板:

Telegraf Swarm 插件

Swarm3k

Swarm3k 是第二个协作项目,试图使用 Swarm 模式形成一个非常大的 Docker 集群。它于 2016 年 10 月 28 日启动,有 50 多个个人和公司加入了这个项目。

Sematext 是最早提供帮助的公司之一,他们提供了他们的 Docker 监控和日志解决方案。他们成为了 Swarm3k 的官方监控系统。Stefan、Otis 和他们的团队从一开始就为我们提供了很好的支持。

Swarm3k

Sematext 仪表板

Sematext 是唯一一家允许我们将监控代理部署为全局 Docker 服务的 Docker 监控公司。这种部署模型大大简化了监控过程。

Swarm3k 设置和工作负载

我们的目标是 3000 个节点,但最终,我们成功地形成了一个工作的、地理分布的 4700 个节点的 Docker Swarm 集群。

经理们的规格要求是在同一数据中心中使用高内存 128GB 的 DigitalOcean 节点,每个节点有 16 个 vCores。

集群初始化配置包括一个未记录的"KeepOldSnapshots",告诉 Swarm 模式不要删除,而是保留所有数据快照以供以后分析。每个经理的 Docker 守护程序都以 DEBUG 模式启动,以便在移动过程中获得更多信息。

我们使用 belt 来设置经理,就像我们在前一节中展示的那样,并等待贡献者加入他们的工作节点。

经理们使用的是 Docker 1.12.3,而工作节点则是 1.12.2 和 1.12.3 的混合。我们在ingressoverlay网络上组织了服务。

我们计划了以下两个工作负载:

  • MySQL 与 Wordpress 集群

  • C1M(Container-1-Million)

打算使用 25 个节点形成一个 MySQL 集群。首先,我们创建了一个 overlay 网络mydb

 $ docker network create -d overlay mydb

然后,我们准备了以下entrypoint.sh脚本:

 #!/bin/bash
 ETCD_SUBNET=${ETCD_SUBNET:-10.0.0.0}
 ETCD_HOST=$(ip route get $ETCD_SUBNET | awk 'NR==1 {print $NF}')
 /usr/local/bin/etcd \
 -name etcd0 \
 -advertise-client-urls 
       http://${ETCD_HOST}:2379,http://${ETCD_HOST}:4001 \
 -listen-client-urls http://0.0.0.0:2379,http://0.0.0.0:4001 \
 -initial-advertise-peer-urls http://${ETCD_HOST}:2380 \
 -listen-peer-urls http://0.0.0.0:2380 \
 -initial-cluster-token etcd-cluster-1 \
 -initial-cluster etcd0=http://${ETCD_HOST}:2380 \
 -initial-cluster-state new

然后,我们将为我们特殊版本的 Etcd 准备一个新的 Dockerfile,如下所示:

 FROM quay.io/coreos/etcd
 COPY entrypoint.sh /usr/local/bin/entrypoint.sh
 RUN  chmod +x /usr/local/bin/entrypoint.sh
 ENTRYPOINT ['/usr/local/bin/entrypoint.sh']

在开始使用之前,不要忘记使用$ docker build -t chanwit/etcd.来构建它。

第三,我们启动了一个 Etcd 节点作为 MySQL 集群的中央发现服务,如下所示:

 $ docker service create --name etcd --network mydb chanwit/etcd

通过检查 Etcd 的虚拟 IP,我们将得到以下服务 VIP:

 $ docker service inspect etcd -f "{{ .Endpoint.VirtualIPs }}"
 [{... 10.0.0.2/24}]

有了这些信息,我们创建了我们的mysql服务,可以在任何程度上进行扩展。看看以下示例:

 docker service create \
 --name mysql \
 -p 3306:3306 \
 --network mydb \
 --env MYSQL_ROOT_PASSWORD=mypassword \
 --env DISCOVERY_SERVICE=10.0.0.2:2379 \
 --env XTRABACKUP_PASSWORD=mypassword \
 --env CLUSTER_NAME=galera \
 --mount "type=bind,src=/var/lib/mysql,dst=/var/lib/mysql" \
 perconalab/percona-xtradb-cluster:5.6

由于 Libnetwork 的一个 bug,我们在 mynet 和 ingress 网络中遇到了一些 IP 地址问题;请查看github.com/docker/docker/issues/24637获取更多信息。我们通过将集群绑定到一个单一overlay 网络mydb来解决了这个 bug。

现在,我们尝试使用复制因子 1 创建一个 WordPress 容器的docker service create。我们故意没有控制 WordPress 容器的调度位置。然而,当我们试图将这个 WordPress 服务与 MySQL 服务连接时,连接一直超时。我们得出结论,对于这种规模的 WordPress + MySQL 组合,最好在集群上加一些约束,使所有服务在同一数据中心中运行。

规模上的 Swarm 性能

从这个问题中我们还学到,覆盖网络的性能在很大程度上取决于每个主机上网络配置的正确调整。正如一位 Docker 工程师建议的那样,当有太多的 ARP 请求(当网络非常大时)并且每个主机无法回复时,我们可能会遇到“邻居表溢出”错误。这些是我们在 Docker 主机上增加的可调整项,以修复以下行为:

 net.ipv4.neigh.default.gc_thresh1 = 30000 
    net.ipv4.neigh.default.gc_thresh2 = 32000    
    net.ipv4.neigh.default.gc_thresh3 = 32768

在这里,gc_thresh1是预期的主机数量,gc_thresh2是软限制,gc_thresh3是硬限制。

因此,当 MySQL + Wordpress 测试失败时,我们改变了计划,尝试在路由网格上实验 NGINX。

入口网络设置为/16 池,因此最多可以容纳 64,000 个 IP 地址。根据 Alex Ellis 的建议,我们在集群上启动了 4,000(四千个!)个 NGINX 容器。在这个测试过程中,节点仍在不断进出。几分钟后,NGINX 服务开始运行,路由网格形成。即使一些节点不断失败,它仍然能够正确提供服务,因此这个测试验证了 1.12.3 版本中的路由网格是非常稳定且可以投入生产使用的。然后我们停止了 NGINX 服务,并开始测试尽可能多地调度容器,目标是 1,000,000 个,一百万个。

因此,我们创建了一个“alpine top”服务,就像我们为 Swarm2k 所做的那样。然而,这次调度速率稍慢。大约 30 分钟内我们达到了 47,000 个容器。因此,我们预计填满集群需要大约 10.6 小时来达到我们的 1,000,000 个容器。

由于预计会花费太多时间,我们决定再次改变计划,转而选择 70,000 个容器。

规模下的 Swarm 性能

调度大量的容器(docker scale alpine=70000)使集群压力山大。这创建了一个巨大的调度队列,直到所有 70,000 个容器完成调度才会提交。因此,当我们决定关闭管理节点时,所有调度任务都消失了,集群变得不稳定,因为 Raft 日志已损坏。

在这个过程中,我们想通过收集 CPU 配置文件信息来检查 Swarm 原语加载集群的情况。

规模下的 Swarm 性能

在这里,我们可以看到只有 0.42%的 CPU 用于调度算法。我们得出一些近似值的结论,即 Docker Swarm 1.12 版本的调度算法非常快。这意味着有机会引入一个更复杂的调度算法,在未来的 Swarm 版本中可能会导致更好的资源利用,只需增加一些可接受的开销。

规模下的 Swarm 性能

此外,我们发现大量的 CPU 周期被用于节点通信。在这里,我们可以看到 Libnetwork 成员列表层。它使用了整体 CPU 的约 12%。

规模下的 Swarm 性能

然而,似乎主要的 CPU 消耗者是 Raft,在这里还显著调用了 Go 垃圾收集器。这使用了整体 CPU 的约 30%。

Swarm2k 和 Swarm3k 的经验教训

以下是从这些实验中学到的总结:

  • 对于大量的工作节点,管理者需要大量的 CPU。每当 Raft 恢复过程启动时,CPU 就会飙升。

  • 如果领先的管理者死了,最好停止该节点上的 Docker,并等待集群再次稳定下来,直到剩下 n-1 个管理者。

  • 尽量保持快照保留尽可能小。默认的 Docker Swarm 配置就可以了。持久化 Raft 快照会额外使用 CPU。

  • 成千上万的节点需要大量的资源来管理,无论是在 CPU 还是网络带宽方面。尽量保持服务和管理者的拓扑地理上紧凑。

  • 数十万个任务需要高内存节点。

  • 现在,稳定的生产设置建议最多 500-1000 个节点。

  • 如果管理者似乎被卡住了,等一等;他们最终会恢复过来。

  • advertise-addr参数对于路由网格的工作是必需的。

  • 将计算节点尽可能靠近数据节点。覆盖网络很好,但需要调整所有主机的 Linux 网络配置,以使其发挥最佳作用。

  • Docker Swarm 模式很强大。即使在将这个庞大的集群连接在一起的不可预测的网络情况下,也没有任务失败。

对于 Swarm3k,我们要感谢所有的英雄:来自 PetalMD 的@FlorianHeigl@jmaitrehenry;来自 Rackspace 的@everett_toews、来自 Demonware 的@squeaky_pl@neverlock@tomwillfixit;来自 Jabil 的@sujaypillai;来自 OVH 的@pilgrimstack;来自 Collabnix 的@ajeetsraina;来自 Aiyara Cluster 的@AorJoa@PNgoenthai;来自 HotelQuickly 的@GroupSprint3r@toughIQ@mrnonaki@zinuzoid@_EthanHunt_;来自 Packet.io 的@packethost;来自 The Conference 的@ContainerizeT-ContainerizeThis;来自 FirePress 的@_pascalandy;来自 TRAXxs 的@lucjuggery;@alexellisuk;来自 Huli 的@svega;@BretFisher;来自 Emerging Technology Advisors 的@voodootikigod@AlexPostID;来自 ThumpFlow 的@gianarb@Rucknar@lherrerabenitez;来自 Nipa Technology 的@abhisak;以及来自 NexwayGroup 的@djalal

我们还要再次感谢 Sematext 提供的最佳 Docker 监控系统;以及 DigitalOcean 提供给我们的所有资源。

总结

在本章中,我们向您展示了如何使用 belt 在 Digital Ocean 上部署了两个庞大的 Swarm 集群。这些故事给了您很多值得学习的东西。我们总结了这些教训,并概述了一些运行庞大生产集群的技巧。同时,我们还介绍了一些 Swarm 的特性,比如服务和安全性,并讨论了管理者的拓扑结构。在下一章中,我们将详细讨论如何管理 Swarm。包括使用 belt、脚本和 Ansible 部署工作节点,管理节点,监控以及图形界面。

第五章:管理 Swarm 集群

现在我们将看到如何管理运行中的 Swarm 集群。我们将详细讨论诸如扩展集群大小(添加和删除节点)、更新集群和节点信息;处理节点状态(晋升和降级)、故障排除和图形界面(UI)等主题。

在本章中,我们将看一下以下主题:

  • Docker Swarm 独立

  • Docker Swarm 模式

  • 集群管理

  • Swarm 健康

  • Swarm 的图形界面

Docker Swarm 独立

在独立模式下,集群操作需要直接在swarm容器内完成。

在本章中,我们不会详细介绍每个选项。Swarm v1 很快将被弃用,因为 Swarm 模式已经被宣布为过时。

Docker Swarm 独立模式

管理 Docker Swarm 独立集群的命令如下:

  • 创建(c):正如我们在第一章中所看到的,欢迎来到 Docker Swarm,这是我们生成 UUID 令牌的方式,以防令牌机制将被使用。通常,在生产环境中,人们使用 Consul 或 Etcd,因此这个命令对生产环境没有相关性。

  • 列表(l):这显示了基于对 Consul 或 Etcd 的迭代的集群节点列表,也就是说,Consul 或 Etcd 必须作为参数传递。

  • 加入(j):将运行 swarm 容器的节点加入到集群中。在这里,我们需要在命令行中传递一个发现机制。

  • 管理(m):这是独立模式的核心。管理集群涉及更改集群属性,例如过滤器、调度程序、外部 CA URL 和超时。当我们在第六章中使用真实应用程序部署时,我们将更多地讨论这些选项在 Swarm 模式中的应用。

Docker Swarm 模式

在本节中,我们将继续探索 Swarm 模式命令,以管理集群。

手动添加节点

您可以选择创建新的 Swarm 节点,即 Docker 主机,无论您喜欢哪种方式。

如果使用 Docker Machine,它很快就会达到极限。在列出机器时,您将不得不非常耐心地等待几秒钟,直到 Machine 获取并打印整个信息。

手动添加节点的方法是使用通用驱动程序的 Machine;因此,将主机配置(操作系统安装、网络和安全组配置等)委托给其他东西(比如 Ansible),然后利用 Machine 以适当的方式安装 Docker。这就是如何做到的:

  1. 手动配置云环境(安全组、网络等)。

  2. 使用第三方工具为 Ubuntu 主机提供支持。

  3. 在这些主机上使用通用驱动程序运行机器,唯一的目标是正确安装 Docker。

  4. 使用第二部分的工具管理主机,甚至其他的。

如果使用 Machine 的通用驱动程序,它将选择最新稳定的 Docker 二进制文件。在撰写本书时,为了使用 Docker 1.12,我们有时通过使用--engine-install-url选项,让 Machine 选择获取最新的不稳定版本的 Docker:

docker-machine create -d DRIVER --engine-install-url 
    https://test.docker.com mymachine

在阅读本书时,对于生产 Swarm(模式),1.12 将是稳定的;因此,除非你需要使用一些最新的 Docker 功能,否则这个技巧将不再必要。

管理者

在规划 Swarm 时,必须牢记一些关于管理者数量的考虑,正如我们在第四章中所看到的,创建生产级 Swarm。高可用性的理论建议管理者数量必须是奇数,并且等于或大于 3。为了在高可用性中获得法定人数,大多数节点必须同意领导操作的部分。

如果有两个管理者,其中一个宕机然后恢复,可能会导致两者都被视为领导者。这会导致集群组织中的逻辑崩溃,这被称为分裂脑。

你拥有的管理者越多,对故障的抵抗比就越高。看一下下表。

管理者数量 法定人数(多数) 最大可能故障数
3 2 1
5 3 2
7 4 3
9 5 4

此外,在 Swarm 模式下,ingress覆盖网络会自动创建,并与节点关联为入口流量。它的目的是与容器一起使用:

管理者

你希望你的容器与内部覆盖(VxLAN meshed)网络关联,以便彼此通信,而不是使用公共或其他外部网络。因此,Swarm 会为您创建这个网络,并且它已经准备好使用。

工作者数量

您可以添加任意数量的工作节点。这是 Swarm 的弹性部分。拥有 5、15、200、2300 或 4700 个运行中的工作节点都是完全可以的。这是最容易处理的部分;您可以随时以任何规模添加和删除工作节点。

脚本化节点添加

如果您计划不超过 100 个节点,最简单的添加节点的方法是使用基本脚本。

在执行docker swarm init时,只需复制粘贴输出的行。

脚本化节点添加

然后,使用循环创建一组特定的工作节点:

#!/bin/bash
for i in `seq 0 9`; do
docker-machine create -d amazonec2 --engine-install-url 
    https://test.docker.com --amazonec2-instance-type "t2.large" swarm-
    worker-$i
done

之后,只需要浏览机器列表,ssh进入它们并join节点即可:

#!/bin/bash
SWARMWORKER="swarm-worker-"
for machine in `docker-machine ls --format {{.Name}} | grep 
    $SWARMWORKER`;
do
docker-machine ssh $machine sudo docker swarm join --token SWMTKN-
    1-5c3mlb7rqytm0nk795th0z0eocmcmt7i743ybsffad5e04yvxt-
    9m54q8xx8m1wa1g68im8srcme \
172.31.10.250:2377
done

此脚本将遍历机器,并对于每个以swarm-worker-开头的名称,它将ssh进入并将节点加入现有的 Swarm 和领导管理者,即172.31.10.250`。

注意

有关更多详细信息或下载一行命令,请参阅github.com/swarm2k/swarm2k/tree/master/amazonec2

Belt

Belt 是用于大规模配置 Docker Engines 的另一种变体。它基本上是一个 SSH 包装器,需要您在go大规模之前准备提供程序特定的映像和配置模板。在本节中,我们将学习如何做到这一点。

您可以通过从 Github 获取其源代码来自行编译 Belt。

# Set $GOPATH here
go get https://github.com/chanwit/belt

目前,Belt 仅支持 DigitalOcean 驱动程序。我们可以在config.yml中准备我们的配置模板。

digitalocean:
image: "docker-1.12-rc4"
region: nyc3
ssh_key_fingerprint: "your SSH ID"
ssh_user: root

然后,我们可以用几个命令创建数百个节点。

首先,我们创建三个管理主机,每个主机有 16GB 内存,分别是mg0mg1mg2

$ belt create 16gb mg[0:2]
NAME      IPv4         MEMORY  REGION         IMAGE           STATUS
mg2   104.236.231.136  16384   nyc3    Ubuntu docker-1.12-rc4  active
mg1   45.55.136.207    16384   nyc3    Ubuntu docker-1.12-rc4  active
mg0   45.55.145.205    16384   nyc3    Ubuntu docker-1.12-rc4  active

然后我们可以使用status命令等待所有节点都处于活动状态:

$ belt status --wait active=3
STATUS  #NODES  NAMES
active      3   mg2, mg1, mg0

我们将再次为 10 个工作节点执行此操作:

$ belt create 512mb node[1:10]
$ belt status --wait active=13

STATUS  #NODES  NAMES
active      3   node10, node9, node8, node7

使用 Ansible

您也可以使用 Ansible(我喜欢,而且它变得非常流行)来使事情更具重复性。我们已经创建了一些 Ansible 模块,可以直接与 Machine 和 Swarm(Mode)一起使用;它还与 Docker 1.12 兼容(github.com/fsoppelsa/ansible-swarm)。它们需要 Ansible 2.2+,这是与二进制模块兼容的第一个 Ansible 版本。

您需要编译这些模块(用go编写),然后将它们传递给ansible-playbook -M参数。

git clone https://github.com/fsoppelsa/ansible-swarm
cd ansible-swarm/library
go build docker-machine.go
go build docker_swarm.go
cd ..

playbooks 中有一些示例 play。Ansible 的 plays 语法非常容易理解,甚至不需要详细解释。

我使用这个命令将 10 个工作节点加入到Swarm2k实验中:

    ---    
name: Join the Swarm2k project
hosts: localhost
connection: local
gather_facts: False
#mg0 104.236.18.183
#mg1 104.236.78.154
#mg2 104.236.87.10
tasks:
name: Load shell variables
shell: >
eval $(docker-machine env "{{ machine_name }}")
echo $DOCKER_TLS_VERIFY &&
echo $DOCKER_HOST &&
echo $DOCKER_CERT_PATH &&
echo $DOCKER_MACHINE_NAME
register: worker
name: Set facts
set_fact:
whost: "{{ worker.stdout_lines[0] }}"
wcert: "{{ worker.stdout_lines[1] }}"
name: Join a worker to Swarm2k
docker_swarm:
role: "worker"
operation: "join"
join_url: ["tcp://104.236.78.154:2377"]
secret: "d0cker_swarm_2k"
docker_url: "{{ whost }}"
tls_path: "{{ wcert }}"
register: swarm_result
name: Print final msg
debug: msg="{{ swarm_result.msg }}"

基本上,它在加载一些主机信息后调用了docker_swarm模块:

  • 操作是join

  • 新节点的角色是worker

  • 新节点加入了tcp://104.236.78.154:2377,这是加入时的领导管理者。这个参数接受一个管理者数组,比如[tcp://104.236.78.154:2377, 104.236.18.183:2377, tcp://104.236.87.10:2377]

  • 它传递了密码(secret)

  • 它指定了一些基本的引擎连接事实,模块将使用tlspath上的证书连接到dockerurl

在库中编译了docker_swarm.go之后,将工作节点加入到 Swarm 就像这样简单:

#!/bin/bash
SWARMWORKER="swarm-worker-"
for machine in `docker-machine ls --format {{.Name}} | grep 
    $SWARMWORKER`;
do
ansible-playbook -M library --extra-vars "{machine_name: $machine}" 
    playbook.yaml
done

使用 Ansible

集群管理

为了更好地说明集群操作,让我们看一个由三个管理者和十个工作节点组成的例子。第一个基本操作是列出节点,使用docker node ls命令:

集群管理

你可以通过主机名(manager1)或者 ID(ctv03nq6cjmbkc4v1tc644fsi)来引用节点。列表中的其他列描述了集群节点的属性。

  • 状态是节点的物理可达性。如果节点正常,它是就绪的,否则是下线的。

  • 可用性是节点的可用性。节点状态可以是活动的(参与集群操作)、暂停的(待机,暂停,不接受任务)或者排空的(等待被排空任务)。

  • 管理状态是管理者的当前状态。如果一个节点不是管理者,这个字段将为空。如果一个节点是管理者,这个字段可以是可达的(保证高可用性的管理者之一)或者领导者(领导所有操作的主机)。集群管理

节点操作

docker node命令有一些可能的选项。

节点操作

如你所见,你有所有可能的节点管理命令,但没有create。我们经常被问到node命令何时会添加create选项,但目前还没有答案。

到目前为止,创建新节点是一个手动操作,是集群操作员的责任。

降级和晋升

工作节点可以晋升为管理节点,而管理节点可以降级为工作节点。

在管理大量管理者和工作者(奇数,大于或等于三)时,始终记住表格以确保高可用性。

使用以下语法将worker0worker1提升为管理者:

docker node promote worker0
docker node promote worker1

幕后没有什么神奇的。只是,Swarm 试图通过即时指令改变节点角色。

降级和晋升

降级是一样的(docker node demote worker1)。但要小心,避免意外降级您正在使用的节点,否则您将被锁定。

最后,如果您尝试降级领导管理者会发生什么?在这种情况下,Raft 算法将开始选举,并且新的领导者将在活动管理者中选择。

给节点打标签

您可能已经注意到,在前面的屏幕截图中,worker9处于排水状态。这意味着该节点正在疏散其任务(如果有的话),这些任务将在集群的其他地方重新安排。

您可以通过使用docker node update命令来更改节点的可用性状态:

给节点打标签

可用性选项可以是活动暂停排水。在这里,我们只是将worker9恢复到了活动状态。

  • 活动状态意味着节点正在运行并准备接受任务

  • 暂停状态意味着节点正在运行,但不接受任务

  • 排水状态意味着节点正在运行并且不接受任务,但它目前正在疏散其任务,这些任务正在被重新安排到其他地方。

另一个强大的更新参数是关于标签。有--label-add--label-rm,分别允许我们向 Swarm 节点添加标签。

Docker Swarm 标签不影响引擎标签。在启动 Docker 引擎时可以指定标签(dockerd [...] --label "staging" --label "dev" [...])。但 Swarm 无权编辑或更改它们。我们在这里看到的标签只影响 Swarm 的行为。

标签对于对节点进行分类很有用。当您启动服务时,您可以使用标签来过滤和决定在哪里物理生成容器。例如,如果您想要将一堆带有 SSD 的节点专门用于托管 MySQL,您实际上可以:

docker node update --label-add type=ssd --label-add type=mysql 
    worker1
docker node update --label-add type=ssd --label-add type=mysql 
    worker2
docker node update --label-add type=ssd --label-add type=mysql 
    worker3

稍后,当您使用副本因子启动服务,比如三个,您可以确保它将在node.type过滤器上准确地在 worker1、worker2 和 worker3 上启动 MySQL 容器:

docker service create --replicas 3 --constraint 'node.type == 
    mysql' --name mysql-service mysql:5.5.

删除节点

节点移除是一个微妙的操作。这不仅仅是排除 Swarm 中的一个节点,还涉及到它的角色和正在运行的任务。

移除工作节点

如果一个工作节点的状态是下线(例如,因为它被物理关闭),那么它目前没有运行任何任务,因此可以安全地移除:

docker node rm worker9

如果一个工作节点的状态是就绪,那么先前的命令将会引发错误,拒绝移除它。节点的可用性(活跃、暂停或排空)并不重要,因为它仍然可能在运行任务,或者在恢复时运行任务。

因此,在这种情况下,操作员必须手动排空节点。这意味着强制释放节点上的任务,这些任务将被重新调度并移动到其他工作节点:

docker node update --availability drain worker9

排空后,节点可以关闭,然后在其状态为下线时移除。

移除管理者

管理者不能被移除。在移除管理者节点之前,必须将其适当地降级为工作节点,最终排空,然后关闭:

docker node demote manager3
docker node update --availability drain manager3
# Node shutdown
docker node rm manager3

当必须移除一个管理者时,应该确定另一个工作节点作为新的管理者,并在以后提升,以保持管理者的奇数数量。

提示

使用以下命令移除docker node rm --force

--force标志会移除一个节点,无论如何。这个选项必须非常小心地使用,通常是在节点卡住的情况下才会使用。

Swarm 健康状况

Swarm 的健康状况基本上取决于集群中节点的可用性以及管理者的可靠性(奇数个、可用、运行中)。

节点可以用通常的方式列出:

docker node ls

这可以使用--filter选项来过滤输出。例如:

docker node ls --filter name=manager # prints nodes named *manager*
docker node ls --filter "type=mysql" # prints nodes with a label 
    type tagged "mysql"

要获取有关特定节点的详细信息,请使用 inspect 命令,如下所示:

docker inspect worker1

此外,过滤选项可用于从输出的 JSON 中提取特定数据:

docker node inspect --format '{{ .Description.Resources }}' worker2
{1000000000 1044140032}

输出核心数量(一个)和分配内存的数量(1044140032字节,或 995M)。

备份集群配置

管理者的重要数据存储在/var/lib/docker/swarm中。这里有:

  • certificates/中的证书

  • raft/中的 Raft 状态与 Etcd 日志和快照

  • worker/中的任务数据库

  • 其他不太关键的信息,比如当前管理者状态、当前连接套接字等。

最好设置定期备份这些数据,以防需要恢复。

Raft 日志使用的空间取决于集群上生成的任务数量以及它们的状态变化频率。对于 20 万个容器,Raft 日志可以在大约三个小时内增长约 1GB 的磁盘空间。每个任务的日志条目占用约 5KB。因此,Raft 日志目录/var/lib/docker/swarm/raft的日志轮换策略应该更或多或少地根据可用磁盘空间进行校准。

灾难恢复

如果管理器上的 swarm 目录内容丢失或损坏,则需要立即使用docker node remove nodeID命令将该管理器从集群中移除(如果暂时卡住,则使用--force)。

集群管理员不应该使用过时的 swarm 目录启动管理器或加入集群。使用过时的 swarm 目录加入集群会导致集群处于不一致的状态,因为在此过程中所有管理器都会尝试同步错误的数据。

在删除具有损坏目录的管理器后,需要删除/var/lib/docker/swarm/raft/wal/var/lib/docker/swarm/raft/snap目录。只有在此步骤之后,管理器才能安全地重新加入集群。

Swarm 的图形界面

在撰写本文时,Swarm 模式还很年轻,现有的 Docker 图形用户界面支持尚未到来或正在进行中。

Shipyard

Shipyard (shipyard-project.com/),它对 Swarm(v1)操作有很好的支持,现在已更新为使用 Swarm 模式。在撰写本文时(2016 年 8 月),Github 上有一个 1.12 分支,使其可行。

在本书出版时,可能已经有一个稳定的版本可用于自动部署。您可以查看shipyard-project.com/docs/deploy/automated/上的说明。

这将类似于通过 SSH 进入领导管理器主机并运行一行命令,例如:

curl -sSL https://shipyard-project.com/deploy | bash -s

如果我们仍然需要安装特定的非稳定分支,请从 Github 下载到领导管理器主机并安装 Docker Compose。

curl -L 
    https://github.com/docker/compose/releases/download/1.8.0/docker-
    compose-`uname -s`-`uname -m` > /usr/local/bin/docker-compose && 
    chmod +x /usr/local/bin/docker-compose

最后从compose开始:

docker-compose up -d < docker-compose.yml

此命令将启动多个容器,最终默认公开端口8080,以便您可以连接到公共管理器 IP 的端口8080以进入 Shipyard UI。

Shipyard

如您在下面的屏幕截图中所见,Docker Swarm 功能已经在 UI 中得到支持(有服务节点等),并且我们在本章中概述的操作,如提升降级等,对每个节点都是可用的。

Shipyard

Portainer

支持 Swarm Mode 的另一种 UI,也是我们的首选,是Portainergithub.com/portainer/portainer/)。

将其部署起来就像在领导管理者上启动容器一样简单:

docker run -d -p 9000:9000 -v /var/run/:/var/run 
    portainer/portainer

Portainer

UI 具有预期的选项,包括一个很好的模板列表,用于快速启动容器,例如 MySQL 或私有注册表,Portainer 在启动时支持 Swarm 服务,使用-s选项。

Portainer,在撰写本文时,即将推出 UI 身份验证功能,这是实现完整基于角色的访问控制的第一步,预计将在 2017 年初实现。随后,RBAC 将扩展支持 Microsoft Active Directory 作为目录源。此外,Portainer 还将在 2016 年底之前支持多集群(或多主机)管理。在 2017 年初添加的其他功能包括 Docker Compose(YAML)支持和私有注册表管理。

摘要

在这一章中,我们通过了典型的 Swarm 管理程序和选项。在展示了如何向集群添加管理者和工作者之后,我们详细解释了如何更新集群和节点属性,如何检查 Swarm 的健康状况,并遇到了 Shipyard 和 Portainer 作为 UI。在此之后,我们将重点放在基础设施上,现在是时候使用我们的 Swarm 了。在下一章中,我们将启动一些真实的应用程序,通过创建真实的服务和任务来实现。

第六章:在 Swarm 上部署真实应用

在 Swarm 基础设施上,我们可以部署各种类型的负载。在本章和下一章中,我们将处理应用程序堆栈。在本章中,我们将:

  • 发现 Swarm 的服务和任务

  • 部署 Nginx 容器

  • 部署一个完整的 WordPress

  • 部署一个小规模的 Apache Spark 架构。

微服务

IT 行业一直热衷于解耦和重用其创造物,无论是源代码还是应用程序。在架构层面对应用程序进行建模也不例外。模块化早期被称为面向服务的架构SOA),并且是基于 XML 的开源协议粘合在一起。然而,随着容器的出现,现在每个人都在谈论微服务。

微服务是小型的、自包含的自治模块,它们共同工作以实现架构目标。

微服务架构的最夸张的例子是 Web 应用程序堆栈,例如 WordPress,其中 Web 服务器可能是一个服务,其他服务包括数据库、缓存引擎和包含应用程序本身的服务。通过 Docker 容器对微服务进行建模可以立即完成,这就是目前行业的发展方向。

微服务

使用微服务有许多优势,如下所示:

  • 可重用性:您只需拉取您想要的服务的镜像(nginx、MySQL),以防需要自定义它们。

  • 异构性:您可以链接现有的模块,包括不同的技术。如果将来某个时候决定从 MySQL 切换到 MariaDB,您可以拔掉 MySQL 并插入 MariaDB

  • 专注于小规模:独立模块易于单独进行故障排除

  • 规模:您可以轻松地将 Web 服务器扩展到 10 个前端,将缓存服务器扩展到 3 个,并在 5 个节点上设计数据库副本,并且可以根据应用程序的负载和需求进行扩展或缩减

  • 弹性:如果你有三个 memcached 服务器,其中一个失败了,你可以有机制来尝试恢复它,或者直接忘记它并立即启动另一个

部署一个复制的 nginx

我们通过一个简单的示例来了解如何在 Swarm 上使用服务:部署和扩展 Nginx。

最小的 Swarm

为了使本章自给自足并对正在阅读它的开发人员有用,让我们快速在本地创建一个最小的 Swarm 模式架构,由一个管理者和三个工作者组成:

  1. 我们启动了四个 Docker 主机:
 for i in seq 3; do docker-machine create -d virtualbox 
      node- $i; done

  1. 然后我们接管了node-1,我们选举它作为我们的静态管理器,并在 Swarm 上初始化它:
 eval $(docker-machine env node-1)
 docker swarm init --advertise-addr 192.168.99.100

  1. Docker 为我们生成一个令牌,以加入我们的三个工作节点。因此,我们只需复制粘贴该输出以迭代其他三个工作节点,将它们加入节点:
 for i in 2 3 4; do
 docker-machine ssh node-$i sudo docker swarm join \
 --token SWMTKN-1-
      4d13l0cf5ipq7e4x5ax2akalds8j1zm6lye8knnb0ba9wftymn-
      9odd9z4gfu4d09z2iu0r2361v \
 192.168.99.100:2377

Swarm 模式架构始终通过 Docker Machine-shell 环境变量连接到node-1,这些变量由先前的eval命令填充。我们需要检查所有节点,包括领导管理器,是否都处于活动状态并成功加入了 Swarm:

一个最小的 Swarm

现在,我们可以使用docker info命令来检查这个 Swarm 集群的状态:

一个最小的 Swarm

这里的重要信息是 Swarm 处于活动状态,然后是一些 Raft 的细节。

Docker 服务

Docker 1.12 中引入的一个新命令是docker service,这就是我们现在要看到的。服务是在 Docker Swarm 模式上操作应用程序的主要方式;这是您将创建、销毁、扩展和滚动更新服务的方式。

服务由任务组成。一个 nginx 服务由 nginx 容器任务组成。服务机制在(通常)工作节点上启动任务。因此,当您创建一个服务时,您必须强制指定服务名称和将成为服务基础的容器等选项。

Docker 服务

创建服务的语法非常直接:只需使用docker service create命令,指定选项,如暴露的端口,并选择要使用的容器。在这里我们执行

 docker service create -p 80:80 --name swarm-nginx --replicas 3
    fsoppelsa/swarm-nginx

Docker 服务

这个命令启动了 nginx,将容器的端口80暴露给主机的端口80,以便可以从外部访问,并指定了三个副本因子。

副本因子是在 Swarm 上扩展容器的方式。如果指定为三个,Swarm 将在三个节点上创建三个 nginx 任务(容器),并尝试保留这个数量,以防其中一个或多个容器死掉,通过在其他可用主机上重新调度 nginx 来实现。

如果没有给出--replicas选项,则默认的副本因子是1

一段时间后,Swarm 需要从 hub 或任何本地注册表中将镜像拉到主机并创建适当的容器(并暴露端口);我们看到三个 nginx 已经在我们的基础设施上就位了,使用命令:

 docker service ls

Docker 服务

这些任务实际上是在三个节点上调度的,如下命令所示:

 docker service ps swarm-nginx

Docker 服务

这里使用的fsoppelsa/swarm-nginx容器是对richarvey/nginx-php-fpm的微小修改,后者是一个由 PHP 增强的 nginx 镜像。我们使用 PHP 在 Nginx 欢迎页面上输出当前服务器的地址,通过添加一个 PHP 命令来显示负载均衡机制的目的。

 <h2>Docker swarm host <?php echo $_SERVER['SERVER_ADDR']; ?></h2>

Docker 服务

现在,如果你将浏览器指向管理器 IP 并多次重新加载,你会发现负载均衡器有时会将你重定向到不同的容器。

第一个加载的页面将类似于以下截图:

Docker 服务

以下截图显示了另一个加载的页面,由负载均衡器选择了不同的节点,即 10.255.0.9:

Docker 服务

以下截图是当负载均衡器重定向到节点 10.255.0.10 时加载的另一个页面:

Docker 服务

覆盖网络

如果你不仅仅是要复制,而是要连接运行在不同主机上的容器到你的 Swarm 基础设施,你必须使用网络。例如,你需要将你的 web 服务器连接到你的数据库容器,以便它们可以通信。

在 Swarm 模式下,解决这个问题的方法是使用覆盖网络。它们是使用 Docker 的 libnetwork 和 libkv 实现的。这些网络是建立在另一个网络之上的 VxLAN 网络(在标准设置中,是物理主机网络)。

VxLAN 是 VLAN 协议的扩展,旨在增加其可扩展性。连接到 Docker VxLAN 网络的不同主机上的容器可以像它们在同一主机上一样进行通信。

Docker Swarm 模式包括一个路由网格表,通过默认情况下称为ingress,实现了这种多主机网络。

集成负载均衡

Swarm Mode 1.12 上的负载平衡是如何工作的?路由有两种不同的方式。首先,它通过虚拟 IP 服务公开的端口工作。对端口的任何请求都会分布在承载服务任务的主机之间。其次,服务被赋予一个仅在 Docker 网络内可路由的虚拟 IP 地址。当对此 VIP 地址进行请求时,它们将分布到底层容器。这个虚拟 IP 被注册在 Docker Swarm 中包含的 DNS 服务器中。当对服务名称进行 DNS 查询时(例如 nslookup mysql),将返回虚拟 IP。

连接服务:WordPress 示例

启动一堆复制和负载平衡的容器已经是一个不错的开始,但是如何处理由不同相互连接的容器组成的更复杂的应用程序堆栈呢?

在这种情况下,您可以通过名称调用容器进行链接。正如我们刚才看到的,内部 Swarm DNS 服务器将保证可靠的名称解析机制。如果您实例化一个名为nginx的服务,您只需将其引用为nginx,其他服务将解析为nginx虚拟 IP(负载平衡),从而访问分布式容器。

为了以示例演示这一点,我们现在将在 Swarm 上部署经典中的经典:WordPress。您可以将 WordPress 作为容器运行,实际上 Docker Hub 上有一个准备好的镜像,但是它需要一个外部数据库(在本例中是 MySQL)来存储其数据。

因此,首先,我们将在 Swarm 上创建一个名为 WordPress 的新专用覆盖网络,并将一个 MySQL 容器作为 Swarm 服务运行在其上,并将三个负载平衡的 WordPress 容器(Web 容器)也作为 Swarm 服务运行。MySQL 将公开端口 3306,而 WordPress 将公开端口80

让我们首先定义我们的覆盖网络。连接到 Swarm 管理器时,我们发出以下命令:

 docker network create --driver overlay wordpress

连接服务:WordPress 示例

那么,幕后发生了什么?该命令使用 libnetwork 创建了一个覆盖网络,在需要时在 Swarm 节点上可用。如果连接到node-2并列出网络,它将始终存在。

我们现在创建一个 MySQL 服务,只由一个容器组成(没有 MySQL 本地副本,也没有 Galera 或其他复制机制),使用以下命令:

 docker service create \
 --name mysql \
 --replicas 1 \
 -p 3306:3306 \
 --network wordpress \
 --env MYSQL_ROOT_PASSWORD=dockerswarm \
 mysql:5.6

我们想要从 hub 上拉取 MySQL 5.6,调用服务(稍后可以通过解析的名称访问其 VIP)mysql,为了清晰起见,将副本设置为 1,暴露端口3306,指定专用网络 WordPress,并指定根密码,在我们的情况下是dockerswarm

连接服务:WordPress 示例

必须从 hub 上拉取 MySQL 镜像,几秒钟后,我们可以检查并看到在我们的情况下,一个mysql容器被下载并放置在node-1上(实际上,如果没有另行指定,主节点也可以运行容器),VIP 是10.255.0.2,在 WordPress 网络上。我们可以使用以下命令获取此信息:

 docker service inspect mysql -f "{{ .Endpoint.VirtualIPs }}"

连接服务:WordPress 示例

我们现在有一个正在运行的 MySQL,我们只需要启动并将其连接到 WordPress。

Swarm 调度策略

碰巧我们启动了一个服务,Swarm 将容器调度到node-1上运行。Swarm 模式(截至目前,在编写 Docker 1.12 和 1.13-dev 时)只有一种可能的策略:spread。Spread 计算每个主机上的容器数量,并尝试将新创建的容器放置在负载较轻的主机上(即,容器较少的主机)。尽管在当天只有一种可用的 spread 策略,但 Swarm 提供了选项,允许我们以很高的精度过滤将启动任务的主机。

这些选项称为约束条件,可以在实例化服务时作为可选参数传递,使用--constraint

现在我们想要启动 WordPress。我们决定要强制在三个工作者上执行三个容器,而不是在主节点上,因此我们指定了一个约束条件。

约束条件的形式为--constraint``node.KEY == VALUE--constraint``node.KEY != VALUE,有几种变体。操作员可以按节点 ID、角色和主机名进行过滤。更有趣的是,正如我们在第五章中看到的那样,管理 Swarm 集群,可以通过使用docker node update --label-add命令将自定义标签添加到节点属性中来指定自定义标签。

含义 示例
node.id 节点 ID node.id == 3tqtddj8wfyd1dl92o1l1bniq
node.role 节点角色(管理器,工作者) node.role != manager
node.hostname 节点主机名 node.hostname == node-1
node.labels 标签 node.labels.type == database

现在,WordPress

在这里,我们希望在所有工作节点上启动wordpress,因此我们说约束条件是node.role != manager(或node.role == worker)。此外,我们将服务命名为wordpress,将副本因子设置为3,暴露端口80,并告诉 WordPress MySQL 位于主机 mysql 上(这在 Swarm 内部解析并指向 MySQL VIP):

 docker service create \
 --constraint 'node.role != manager' \
 --name wordpress \
 --replicas 3 \
 -p 80:80 \
 --network wordpress \
 --env WORDPRESS_DB_HOST=mysql \
 --env WORDPRESS_DB_USER=root \
 --env WORDPRESS_DB_PASSWORD=dockerswarm \
 wordpress

现在,WordPress

经过一段时间,我们需要将 WordPress 图像下载到工作节点,以便我们可以检查一切是否正常运行。

现在,WordPress

现在我们通过端口80连接到主机之一,并受到 WordPress 安装程序的欢迎。

现在,WordPress

WordPress 在浏览器中执行一些步骤后就准备好了,比如选择管理员用户名和密码:

现在,WordPress

Docker Compose 和 Swarm 模式

许多开发人员喜欢使用 Compose 来模拟他们的应用程序,例如类似 WordPress 的应用程序。我们也这样做,并认为这是描述和管理 Docker 上的微服务的一种绝妙方式。然而,在撰写本书时,Compose 尚未支持 Docker Swarm 模式,所有容器都被安排在当前节点上。要在整个 Swarm 上部署应用程序,我们需要使用堆栈的新捆绑功能。

在撰写本文时,堆栈仅以实验性方式提供,但我们在这里展示它们,只是为了让您体验在(不久的)将来在 Docker 上部署微服务的感觉。

介绍 Docker 堆栈

对于 Docker,堆栈将成为打包由多个容器组成的应用程序的标准方式。考虑到庞大的 WordPress 示例:您至少需要一个 Web 服务器和一个数据库。

开发人员通常通过创建一个 YAML 文件来使用 Compose 描述这些应用程序,如下所示:

 version: '2'
 services:
   db:
     image: mysql:5.6
     volumes:
       - "./.data/db:/var/lib/mysql"
     restart: always
     environment:
       MYSQL_ROOT_PASSWORD: dockerswarm
       MYSQL_DATABASE: wordpress
       MYSQL_USER: wordpress
       MYSQL_PASSWORD: wordpress
   wordpress:
     depends_on:
       - db
     image: wordpress:latest
     links:
       - db
     ports:
       - "8000:80"
     restart: always
     environment:
       WORDPRESS_DB_HOST: db:3306
       WORDPRESS_DB_PASSWORD: wordpress

然后,他们使用类似以下的命令启动此应用:

 docker-compose --rm -d --file docker-compose.yml up.

在这里,mysqlwordpress容器被安排、拉取并作为守护进程在开发者连接的主机上启动。从 Docker 1.12 开始(在 1.12 中是实验性的),将有可能将mysql + wordpress打包成一个单一文件包,称为分布式应用程序包DAB)。

分布式应用程序包

因此,您将运行docker-compose up命令,而不是:

 docker-compose --file docker-compose.yml bundle -o wordpress.dab

这个命令将输出另一个名为wordpress.dab的 JSON,它将成为通过 Compose 在 Swarm 上描述为 Swarm 服务的服务部署的起点。

对于这个例子,wordpress.dab的内容看起来类似于:

 {
 "Services": {
 "db": {
 "Env": [
 "MYSQL_ROOT_PASSWORD=dockerswarm",
 "MYSQL_PASSWORD=wordpress",
 "MYSQL_USER=wordpress",
 "MYSQL_DATABASE=wordpress"
 ],
 "Image": 
          "mysql@sha256:e9b0bc4b8f18429479b74b07f4
          d515f2ac14da77c146201a885c5d7619028f4d",
 "Networks": [
 "default"
 ]
 },
 "wordpress": {
 "Env": [
 "WORDPRESS_DB_HOST=db:3306",
 "WORDPRESS_DB_PASSWORD=wordpress"
 ],
 "Image": 
          "wordpress@sha256:10f68e4f1f13655b15a5d0415
          3fe0a454ea5e14bcb38b0695f0b9e3e920a1c97",
 "Networks": [
 "default"
 ],
 "Ports": [
 {
 "Port": 80,
 "Protocol": "tcp"
 }
 ]
 }
 },
 "Version": "0.1"

Docker 部署

从生成的wordpress.dab文件开始,当连接到 Swarm 管理器时,开发者可以使用 deploy 命令启动一个堆栈:

 docker deploy --file wordpress.dab wordpress1

现在你将有两个名为wordpress1_wordpresswordpress1_db的服务,传统上遵循 Compose 的语法传统。

Docker 部署

这只是一个非常原始的演示。作为一个实验性功能,Compose 中的支持功能仍然没有完全定义,但我们期望它会改变(甚至根本改变)以满足开发者、Swarm 和 Compose 的需求。

另一个应用程序:Apache Spark

现在我们已经通过使用服务获得了一些实践经验,我们将迈向下一个级别。我们将在 Swarm 上部署 Apache Spark。Spark 是 Apache 基金会的开源集群计算框架,主要用于数据处理。

Spark 可以用于诸如以下的事情:

  • 大数据分析(Spark Core)

  • 快速可扩展的数据结构化控制台(Spark SQL)

  • 流式分析(Spark Streaming)

  • 图形处理(Spark GraphX)

在这里,我们主要关注 Swarm 的基础设施部分。如果你想详细了解如何编程或使用 Spark,可以阅读 Packt 关于 Spark 的图书选择。我们建议从Fast Data Processing with Spark 2.0 - Third Edition开始。

Spark 是 Hadoop 的一个整洁而清晰的替代方案,它是 Hadoop 复杂性和规模的更敏捷和高效的替代品。

Spark 的理论拓扑是立即的,可以在一个或多个管理器上计算集群操作,并有一定数量的执行实际任务的工作节点。

对于管理器,Spark 可以使用自己的独立管理器(就像我们在这里做的那样),也可以使用 Hadoop YARN,甚至利用 Mesos 的特性。

然后,Spark 可以将存储委托给内部 HDFS(Hadoop 分布式文件系统)或外部存储服务,如 Amazon S3、OpenStack Swift 或 Cassandra。存储由 Spark 用于获取数据进行处理,然后保存处理后的结果。

为什么在 Docker 上使用 Spark

我们将向您展示如何在 Docker Swarm 集群上启动 Spark 集群,作为使用虚拟机启动 Spark 的替代方法。本章中定义的示例可以从容器中获得许多好处:

  • 启动容器更快

  • 在宠物模型中扩展容器更为直接。

  • 您可以获取 Spark 镜像,而无需创建虚拟机,编写自定义脚本,调整 Ansible Playbooks。只需docker pull

  • 您可以使用 Docker Networking 功能创建专用的覆盖网络,而无需在物理上损害或调用网络团队

Spark 独立模式无 Swarm

让我们开始定义一个使用经典 Docker 工具构建的小型 Apache Spark 集群,这些工具基本上是 Docker 主机上的 Docker 命令。在了解整体情况之前,我们需要开始熟悉 Swarm 概念和术语。

在本章中,我们将使用google_container镜像,特别是 Swarm 版本 1.5.2。2.0 版本中包含了许多改进,但这些镜像被证明非常稳定可靠。因此,我们可以从 Google 仓库中开始拉取它们,用于主节点和工作节点:

 docker pull gcr.io/google_containers/spark-master
 docker pull gcr.io/google_containers/spark-worker

Spark 可以在 YARN、Mesos 或 Hadoop 的顶部运行。在接下来的示例和章节中,我们将使用它的独立模式,因为这是最简单的,不需要额外的先决条件。在独立的 Spark 集群模式中,Spark 根据核心分配资源。默认情况下,应用程序将占用集群中的所有核心,因此我们将限制专用于工作节点的资源。

我们的架构将非常简单:一个负责管理集群的主节点,以及三个负责运行 Spark 作业的节点。对于我们的目的,主节点必须发布端口8080(我们将用于方便的 Web UI),我们将其称为 spark-master。默认情况下,工作节点容器尝试连接到 URL spark://spark-master:7077,因此除了将它们链接到主节点外,不需要进一步的定制。

因此,让我们将其传递给实际部分,并使用以下代码初始化 Spark 主节点:

 docker run -d \
 -p 8080:8080 \
 --name spark-master \
 -h spark-master \
 gcr.io/google_containers/spark-master

这在守护程序模式(-d)中运行,从gcr.io/google_containers/spark-master镜像中创建一个容器,将名称(--name)spark-master 分配给容器,并将其主机名(-h)配置为 spark-master。

我们现在可以连接浏览器到 Docker 主机,端口8080,以验证 Spark 是否正在运行。

没有 Swarm 的 Spark 独立运行

它仍然没有活动的工作节点,我们现在要生成。在我们注意到 Spark 主容器的 ID 之前,我们使用以下命令启动工作节点:

 docker run -d \
 --link 7ff683727bbf \
 -m 256 \
 -p 8081:8081 \
 --name worker-1 \
 gcr.io/google_containers/spark-worker

这将以守护进程模式启动一个容器,将其链接到主节点,将内存使用限制为最大 256M,将端口 8081 暴露给 Web(工作节点)管理,并将其分配给容器名称worker-1。类似地,我们启动其他两个工作节点:

 docker run -d --link d3409a18fdc0 -m 256 -p 8082:8082 -m 256m -- 
    name worker-2 gcr.io/google_containers/spark-worker
 docker run -d --link d3409a18fdc0 -m 256 -p 8083:8083 -m 256m --
    name worker-3 gcr.io/google_containers/spark-worker

我们可以在主节点上检查一切是否连接并运行:

没有 Swarm 的 Spark 独立运行

Swarm 上的独立 Spark

到目前为止,我们已经讨论了不那么重要的部分。我们现在将已经讨论的概念转移到 Swarm 架构,所以我们将实例化 Spark 主节点和工作节点作为 Swarm 服务,而不是单个容器。我们将创建一个主节点的副本因子为 1 的架构,以及工作节点的副本因子为 3。

Spark 拓扑

在这个例子中,我们将创建一个由一个主节点和三个工作节点组成的 Spark 集群。

存储

我们将在第七章中定义真实的存储并启动一些真实的 Spark 任务,扩展您的平台。

先决条件

我们首先为 Spark 创建一个新的专用覆盖网络:

 docker network create --driver overlay spark

然后,我们在节点上设置一些标签,以便以后能够过滤。我们希望将 Spark 主节点托管在 Swarm 管理器(node-1)上,将 Spark 工作节点托管在 Swarm 工作节点(节点 2、3 和 4)上:

 docker node update --label-add type=sparkmaster node-1
 docker node update --label-add type=sparkworker node-2
 docker node update --label-add type=sparkworker node-3
 docker node update --label-add type=sparkworker node-4

提示

我们在这里添加了“sparkworker”类型标签以确保极端清晰。实际上,只有两种变体,事实上可以写成相同的约束:

--constraint 'node.labels.type == sparkworker'

或者:

--constraint 'node.labels.type != sparkmaster'

在 Swarm 上启动 Spark

现在,我们将在 Swarm 中定义我们的 Spark 服务,类似于我们在前一节为 Wordpress 所做的操作,但这次我们将通过定义在哪里启动 Spark 主节点和 Spark 工作节点来驱动调度策略,以最大程度地精确地进行。

我们从主节点开始,如下所示:

 docker service create \
 --container-label spark-master \
 --network spark \
 --constraint 'node.labels.type==sparkmaster' \
 --publish 8080:8080 \
 --publish 7077:7077 \
 --publish 6066:6066 \
 --name spark-master \
 --replicas 1 \
 --limit-memory 1024 \
 gcr.io/google_containers/spark-master

Spark 主节点暴露端口8080(Web UI),并且可选地,为了示例的清晰度,这里我们还暴露了端口7077,Spark 工作节点用于连接到主节点的端口,以及端口 6066,Spark API 端口。此外,我们使用--limit-memory 将内存限制为 1G。一旦 Spark 主节点启动,我们可以创建托管工作节点的服务,sparkworker:

 docker service create \
 --constraint 'node.labels.type==sparkworker' \
 --network spark \
 --name spark-worker \
 --publish 8081:8081 \
 --replicas 3 \
 --limit-memory 256 \
 gcr.io/google_containers/spark-worker

同样,我们暴露端口8081(工作节点的 Web UI),但这是可选的。在这里,所有的 Spark 容器都被调度到了之前我们定义的 spark 工作节点上。将镜像拉到主机上需要一些时间。结果,我们有了最小的 Spark 基础设施:

在 Swarm 上启动 Spark

Spark 集群正在运行,即使有一点需要补充的地方:

在 Swarm 上启动 Spark

尽管我们将每个 worker 的内存限制为 256M,但在 UI 中我们仍然看到 Spark 读取了 1024M。这是因为 Spark 内部的默认配置。如果我们连接到任何一个正在运行其中一个 worker 的主机,并使用docker stats a7a2b5bb3024命令检查其统计信息,我们会看到容器实际上是受限制的。

在 Swarm 上启动 Spark

摘要

在本章中,我们开始在应用程序堆栈上工作,并在 Swarm 上部署真实的东西。我们练习了定义 Swarm 服务,并在专用覆盖网络上启动了一组 nginx,以及一个负载均衡的 WordPress。然后,我们转向了更真实的东西:Apache Spark。我们通过定义自己的调度策略,在 Swarm 上以小规模部署了 Spark。在第七章中,我们将扩展 Swarm 并将其扩展到更大规模,具有更多真实的存储和网络选项。

第七章:扩展您的平台

在本章中,我们将扩展我们在第六章中所看到的内容,在 Swarm 上部署真实应用程序。我们的目标是在 Swarm 之上部署一个逼真的生产级别的 Spark 集群,增加存储容量,启动一些 Spark 作业,并为基础架构设置监控。

为了做到这一点,本章主要是面向基础架构的。事实上,我们将看到如何将LibnetworkFlockerPrometheus与 Swarm 结合起来。

对于网络,我们将使用基本的 Docker 网络覆盖系统,基于 Libnetwork。有一些很棒的网络插件,比如 Weave 和其他插件,但它们要么还不兼容新的 Docker Swarm 模式,要么被 Swarm 集成的路由网格机制所淘汰。

存储方面,情况更加繁荣,因为选择更多(参考docs.docker.com/engine/extend/plugins/)。我们将选择 Flocker。Flocker 是 Docker 存储的鼻祖,可以配置各种各样的存储后端,使其成为生产负载的最佳选择之一。对 Flocker 的复杂性感到害怕吗?这是不必要的:我们将看到如何在几分钟内为任何用途设置多节点 Flocker 集群。

最后,对于监控,我们将介绍 Prometheus。它是目前可用于 Docker 的最有前途的监控系统,其 API 可能很快就会集成到 Docker 引擎中。

因此,我们将在这里涵盖什么:

  • 一个准备好运行任何 Spark 作业的 Swarm 上的 Spark 示例

  • 自动化安装 Flocker 以适应规模的基础设施

  • 演示如何在本地使用 Flocker

  • 在 Swarm 模式下使用 Flocker

  • 扩展我们的 Spark 应用程序

  • 使用 Prometheus 监控这个基础架构的健康状况

再次介绍 Spark 示例

我们将重新设计第六章中的示例,在 Swarm 上部署真实应用程序,因此我们将在 Swarm 上部署 Spark,但这次是以逼真的网络和存储设置。

Spark 存储后端通常在 Hadoop 上运行,或者在文件系统上运行 NFS。对于不需要存储的作业,Spark 将在工作节点上创建本地数据,但对于存储计算,您将需要在每个节点上使用共享文件系统,这不能通过 Docker 卷插件自动保证(至少目前是这样)。

在 Swarm 上实现这个目标的一种可能性是在每个 Docker 主机上创建 NFS 共享,然后在服务容器内透明地挂载它们。

我们的重点不是说明 Spark 作业的细节和它们的存储组织,而是为 Docker 引入一种主观的存储选项,并提供如何在 Docker Swarm 上组织和扩展一个相当复杂的服务的想法。

Docker 插件

关于 Docker 插件的详细介绍,我们建议阅读官方文档页面。这是一个起点docs.docker.com/engine/extend/,此外,Docker 可能会发布一个工具,通过一个命令获取插件,请参阅docs.docker.com/engine/reference/commandline/plugin_install/

如果您想探索如何将新功能集成到 Docker 中,我们建议您参考 Packt 的Extending Docker书籍。该书的重点是 Docker 插件、卷插件、网络插件以及如何创建自己的插件。

对于 Flocker,ClusterHQ 提供了一个自动化部署机制,可以使用 CloudForm 模板在 AWS 上部署 Flocker 集群,您可以使用 Volume Hub 安装。要注册和启动这样一个集群,请访问flocker-docs.clusterhq.com/en/latest/docker-integration/cloudformation.html。有关详细过程的逐步解释,请参阅Extending Docker的第三章,Packt。

在这里,我们将手动进行,因为我们必须集成 Flocker 和 Swarm。

实验室

在本教程中,我们将在 AWS 上创建基础架构。理想情况下,对于生产环境,您将设置三个或五个 Swarm 管理器和一些工作节点,并根据负载情况随后添加新的工作节点。

在这里,我们将使用三个 Swarm 管理器、六个 Swarm 工作节点和一个带有 Machine 的 Flocker 控制节点设置一个 Swarm 集群,并不会添加新的工作节点。

安装 Flocker 需要进行几个手动步骤,这些步骤可以自动化(正如我们将看到的)。因此,为了尽可能地减少示例的复杂性,我们将最初按线性顺序运行所有这些命令,而不重复程序以增加系统容量。

如果您不喜欢 Ansible,您可以轻松地将流程调整为您喜欢的工具,无论是 Puppet、Salt、Chef 还是其他工具。

一个独特的关键

为简单起见,我们将使用特定生成的 SSH 密钥安装我们的实验室,并将此密钥复制到authorized_keys中的主机上安装 Docker Machines。目标是拥有一个唯一的密钥来验证后续使用的 Ansible,我们将使用它来自动化许多我们否则应该手动执行的步骤。

因此,我们首先生成一个flocker密钥,并将其放入keys/目录:

ssh-keygen -t rsa -f keys/flocker

Docker Machine

为了配置我们的 Docker 主机,我们将使用 Docker Machine。这是本教程的系统详细信息:

AWS 实例将被命名为 aws-101 到 aws-110。这种标准化的命名在以后生成和创建 Flocker 节点证书时将非常重要:

  • 节点 aws-101、102、103 将成为我们的 Swarm 管理器

  • 节点 aws-104 将成为 Flocker 控制节点

  • 从 aws-105 到 aws-110 的节点将成为我们的 Swarm 工作节点。

实例类型将是t2.medium(2 个 vCPU,4G 内存,EBS 存储)

口味将是 Ubuntu 14.04 Trusty(使用--amazonec2-ami参数指定)

安全组将是标准的docker-machine(我们将在几秒钟内再次总结要求)

Flocker 版本将是 1.15。

要使用的确切 AMI ID 可以在cloud-images.ubuntu.com/locator/ec2/上搜索。

AWS 计算器计算出这个设置的成本大约是每月 380 美元,不包括存储使用。

Docker Machine

因此,我们创建基础设施:

for i in `seq 101 110`; do
docker-machine create -d amazonec2 \
--amazonec2-ami ami-c9580bde \
--amazonec2-ssh-keypath keys/flocker \
--amazonec2-instance-type "t2.medium" \
aws-$i;
done

并运行。

过一段时间,我们将使其运行起来。

安全组

此外,我们还需要在用于此项目的安全组(docker-machine)中在 EC2 控制台中打开三个额外的新端口。这些是 Flocker 服务使用的端口:

  • 端口4523/tcp

  • 端口4524/tcp

此外,以下是 Swarm 使用的端口:

  • 端口2377/tcp安全组

网络配置

我们使用标准配置和额外的覆盖网络,称为Spark。流量数据将通过 spark 网络传递,这样就可以通过新的主机和工作节点扩展实验室配置,甚至在其他提供商(如DigitalOceanOpenStack)上运行。当新的 Swarm 工作节点加入此集群时,此网络将传播到它们,并对 Swarm 服务进行提供。

存储配置和架构

正如前面提到的,我们选择了 Flocker(clusterhq.com/flocker/introduction/),它是顶级的 Docker 存储项目之一。ClusterHQ 将其描述为:

Flocker 是一个为您的 Docker 化应用程序提供容器数据卷管理的开源工具。通过提供数据迁移工具,Flocker 为运维团队提供了在生产环境中运行容器化有状态服务(如数据库)所需的工具。与绑定到单个服务器的 Docker 数据卷不同,称为数据集的 Flocker 数据卷是可移植的,并且可以与集群中的任何容器一起使用。

Flocker 支持非常广泛的存储选项,从 AWS EBS 到 EMC、NetApp、戴尔、华为解决方案,再到 OpenStack Cinder 和 Ceph 等。

它的设计很简单:Flocker 有一个控制节点,它暴露其服务 API 以管理 Flocker 集群和 Flocker 卷,以及一个Flocker 代理,与 Docker 插件一起在集群的每个节点上运行。

存储配置和架构

要使用 Flocker,在命令行上,您需要运行类似以下的 Docker 命令来读取或写入 Flocker myvolume卷上的有状态数据,该卷被挂载为容器内的/data

docker run -v myvolume:/data --volume-driver flocker image command

此外,您可以使用docker volume命令管理卷:

docker volume ls
docker volume create -d flocker

在本教程架构中,我们将在 aws-104 上安装 Flocker 控制节点,因此将专用,以及在所有节点(包括 node-104)上安装 flocker 代理。

此外,我们将安装 Flocker 客户端,用于与 Flocker 控制节点 API 交互,以管理集群状态和卷。为了方便起见,我们还将从 aws-104 上使用它。

安装 Flocker

需要一系列操作才能运行 Flocker 集群:

  1. 安装flocker-ca实用程序以生成证书。

  2. 生成授权证书。

  3. 生成控制节点证书。

  4. 为每个节点生成节点证书。

  5. 生成 flocker 插件证书。

  6. 生成客户端证书。

  7. 从软件包安装一些软件。

  8. 向 Flocker 集群分发证书。

  9. 配置安装,添加主配置文件agent.yml

  10. 在主机上配置数据包过滤器。

  11. 启动和重新启动系统服务。

您可以在小集群上手动执行它们,但它们是重复和乏味的,因此我们将使用一些自解释的 Ansible playbook 来说明该过程,这些 playbook 已发布到github.com/fsoppelsa/ansible-flocker

这些 playbook 可能很简单,可能还不够成熟。还有官方的 ClusterHQ Flocker 角色 playbook(参考github.com/ClusterHQ/ansible-role-flocker),但为了解释的连贯性,我们将使用第一个存储库,所以让我们克隆它:

git clone git@github.com:fsoppelsa/ansible-flocker.git

生成 Flocker 证书

对于证书生成,需要flocker-ca实用程序。有关如何安装它的说明,请访问docs.clusterhq.com/en/latest/flocker-standalone/install-client.html。对于 Linux 发行版,只需安装一个软件包。而在 Mac OS X 上,可以使用 Python 的pip实用程序来获取该工具。

在 Ubuntu 上

sudo apt-get -y install --force-yes clusterhq-flocker-cli

在 Mac OS X 上

pip install https://clusterhq-
    archive.s3.amazonaws.com/python/Flocker-1.15.0-py2-none-any.whl

一旦拥有此工具,我们生成所需的证书。为了简化事情,我们将创建以下证书结构:

包括所有证书和密钥的目录certs/

  • cluster.crt.key是授权证书和密钥

  • control-service.crt.key是控制节点证书和密钥

  • plugin.crt.key是 Docker Flocker 插件证书和密钥

  • client.crt.key是 Flocker 客户端证书和密钥

  • node-aws-101.crt.keynode-aws-110.crt.key是节点证书和密钥,每个节点一个

以下是步骤:

  1. 生成授权证书:flocker-ca initialize cluster

  2. 一旦拥有授权证书和密钥,就在同一目录中生成控制节点证书:flocker-ca create-control-certificate aws-101

  3. 然后生成插件证书:flocker-ca create-api-certificate plugin

  4. 然后生成客户端证书:flocker-ca create-api-certificate client

  5. 最后,生成每个节点的证书:flocker-ca create-node-certificate node-aws-X

当然,我们必须欺骗并使用ansible-flocker存储库中提供的utility/generate_certs.sh脚本,它将为我们完成工作:

cd utils
./generate_certs.sh

在执行此脚本后,我们现在在certs/中有所有我们的证书:

生成 Flocker 证书

安装软件

在每个 Flocker 节点上,我们必须执行以下步骤:

  1. 将 ClusterHQ Ubuntu 存储库添加到 APT 源列表中。

  2. 更新软件包缓存。

  3. 安装这些软件包:

  • clusterhq-python-flocker

  • clusterhq-flocker-node

  • clusterhq-flocker-docker-plugin

  1. 创建目录/etc/flocker

  2. 将 Flocker 配置文件agent.yml复制到/etc/flocker

  3. 将适用于该节点的证书复制到/etc/flocker

  4. 通过启用ufw配置安全性,并打开 TCP 端口2376237745234524

  5. 启动系统服务。

  6. 重新启动 docker 守护程序。

再一次,我们喜欢让机器为我们工作,所以让我们在喝咖啡的时候用 Ansible 设置这个。

但是,在此之前,我们必须指定谁将是 Flocker 控制节点,谁将是裸节点,因此我们在inventory文件中填写节点的主机 IP。该文件采用.ini格式,所需的只是指定节点列表:

安装软件

然后,我们创建一个目录,Ansible 将从中获取文件、证书和配置,然后复制到节点上:

mkdir files/

现在,我们将之前创建的所有证书从certs/目录复制到files/中:

cp certs/* files/

最后,我们在files/agent.yml中定义 Flocker 配置文件,内容如下,调整 AWS 区域并修改hostnameaccess_key_idsecret_access_key

control-service:
hostname: "<Control node IP>"
port: 4524
dataset:
backend: "aws"
region: "us-east-1"
zone: "us-east-1a"
access_key_id: "<AWS-KEY>"
secret_access_key: "<AWS-ACCESS-KEY>"
version: 1

这是核心的 Flocker 配置文件,将在每个节点的/etc/flocker中。在这里,您可以指定和配置所选后端的凭据。在我们的情况下,我们选择基本的 AWS 选项 EBS,因此我们包括我们的 AWS 凭据。

有了清单、agent.yml和所有凭据都准备好在files/中,我们可以继续了。

安装控制节点

安装控制节点的 playbook 是flocker_control_install.yml。此 play 执行软件安装脚本,复制集群证书、控制节点证书和密钥、节点证书和密钥、客户端证书和密钥、插件证书和密钥,配置防火墙打开 SSH、Docker 和 Flocker 端口,并启动这些系统服务:

  • flocker-control

  • flocker-dataset-agent

  • flocker-container-agent

  • flocker-docker-plugin

最后,刷新docker服务,重新启动它。

让我们运行它:

$ export ANSIBLE_HOST_KEY_CHECKING=False
$ ansible-playbook \
-i inventory \
--private-key keys/flocker \
playbooks/flocker_control_install.yml

安装集群节点

类似地,我们使用另一个 playbook flocker_nodes_install.yml 安装其他节点:

$ ansible-playbook \
-i inventory \
--private-key keys/flocker \
playbooks/flocker_nodes_install.yml

步骤与以前大致相同,只是这个 playbook 不复制一些证书,也不启动flocker-control服务。只有 Flocker 代理和 Flocker Docker 插件服务在那里运行。我们等待一段时间直到 Ansible 退出。

安装集群节点

测试一切是否正常运行

为了检查 Flocker 是否正确安装,我们现在登录到控制节点,检查 Flocker 插件是否正在运行(遗憾的是,它有.sock文件),然后我们使用curl命令安装flockerctl实用程序(参考docs.clusterhq.com/en/latest/flocker-features/flockerctl.html):

$ docker-machine ssh aws-104
$ sudo su -
# ls /var/run/docker/plugins/flocker/
flocker.sock  flocker.sock.lock
# curl -sSL https://get.flocker.io |sh

现在我们设置一些flockerctl使用的环境变量:

export FLOCKER_USER=client
export FLOCKER_CONTROL_SERVICE=54.84.176.7
export FLOCKER_CERTS_PATH=/etc/flocker

我们现在可以列出节点和卷(当然,我们还没有卷):

flockerctl status
flockerctl list

测试一切是否正常运行

现在,我们可以转到集群的另一个节点,检查 Flocker 集群的连接性(特别是插件和代理是否能够到达并对控制节点进行身份验证),比如aws-108,创建一个卷并向其中写入一些数据:

$ docker-machine ssh aws-108
$ sudo su -
# docker run -v test:/data --volume-driver flocker \
busybox sh -c "echo example > /data/test.txt"
# docker run -v test:/data --volume-driver flocker \
busybox sh -c "cat /data/test.txt"
example

测试一切是否正常运行

如果我们回到控制节点aws-104,我们可以通过使用 docker 和flockerctl命令列出它们来验证已创建具有持久数据的卷:

docker volume ls
flockerctl list

测试一切是否正常运行

太棒了!现在我们可以删除已退出的容器,从 Flocker 中删除测试卷数据集,然后我们准备安装 Swarm:

# docker rm -v ba7884944577
# docker rm -v 7293a156e199
# flockerctl destroy -d 8577ed21-25a0-4c68-bafa-640f664e774e

安装和配置 Swarm

现在,我们可以使用我们喜欢的方法安装 Swarm,就像前几章中所示。我们将aws-101aws-103作为管理者,除了aws-104之外的其他节点作为工作节点。这个集群甚至可以进一步扩展。对于实际的事情,我们将保持在 10 个节点的规模。

安装和配置 Swarm

现在我们添加一个专用的spark覆盖 VxLAN 网络:

docker network create --driver overlay --subnet 10.0.0.0/24 spark

一个用于 Spark 的卷

现在我们连接到任何 Docker 主机并创建一个75G大小的卷,用于保存一些持久的 Spark 数据:

docker volume create -d flocker -o size=75G -o profile=bronze --
    name=spark

这里讨论的选项是 profile。这是一种存储的类型(主要是速度)。如链接 docs.clusterhq.com/en/latest/flocker-features/aws-configuration.html#aws-dataset-backend 中所解释的,ClusterHQ 维护了三种可用的 AWS EBS 配置文件:

  • 金牌:EBS Provisioned IOPS / API 名称 io1。配置为其大小的最大 IOPS - 30 IOPS/GB,最大为 20,000 IOPS

  • 银牌:EBS 通用 SSD / API 名称 gp2

  • 青铜:EBS 磁盘 / API 名称标准

我们可以在 Flocker 控制节点上检查这个卷是否已生成,使用 flockerctl list

再次部署 Spark

我们选择一个主机来运行 Spark 独立管理器,即 aws-105,并将其标记为这样:

docker node update --label-add type=sparkmaster aws-105

其他节点将托管我们的 Spark 工作节点。

我们在 aws-105 上启动 Spark 主节点:

$ docker service create \
--container-label spark-master \
--network spark \
--constraint 'node.labels.type == sparkmaster' \
--publish 8080:8080 \
--publish 7077:7077 \
--publish 6066:6066 \
--name spark-master \
--replicas 1 \
--env SPARK_MASTER_IP=0.0.0.0 \
--mount type=volume,target=/data,source=spark,volume-driver=flocker 
    \
fsoppelsa/spark-master

首先是镜像。我发现 Google 镜像中包含一些恼人的东西(例如取消设置一些环境变量,因此无法使用 --env 开关从外部进行配置)。因此,我创建了一对 Spark 1.6.2 主节点和工作节点镜像。

然后,--network。在这里,我们告诉这个容器连接到名为 spark 的用户定义的覆盖网络。

最后,存储:--mount,它与 Docker 卷一起使用。我们将其指定为:

  • 使用卷:type=volume

  • 在容器内挂载卷到 /datatarget=/data

  • 使用我们之前创建的 spark 卷:source=spark

  • 使用 Flocker 作为 volume-driver

当您创建一个服务并挂载某个卷时,如果卷不存在,它将被创建。

注意

当前版本的 Flocker 只支持 1 个副本。原因是 iSCSI/块级挂载不能跨多个节点附加。因此,一次只有一个服务可以使用卷,副本因子为 1。这使得 Flocker 更适用于存储和移动数据库数据(这是它的主要用途)。但在这里,我们将用它来展示在 Spark 主节点容器中的持久数据的一个小例子。

因此,根据这个配置,让我们添加三个 Spark 工作节点:

$ docker service create \
--constraint 'node.labels.type != sparkmaster' \
--network spark \
--name spark-worker \
--replicas 3 \
--env SPARK\_MASTER\_IP=10.0.0.3 \
--env SPARK\_WORKER\_CORES=1 \
--env SPARK\_WORKER\_MEMORY=1g \
fsoppelsa/spark-worker

在这里,我们将一些环境变量传递到容器中,以限制每个容器的资源使用量为 1 核心和 1G 内存。

几分钟后,系统启动,我们连接到 aws-105,端口 8080,并看到这个页面:

部署 Spark,再次

测试 Spark

所以,我们访问 Spark shell 并运行一个 Spark 任务来检查是否一切正常。

我们准备一个带有一些 Spark 实用程序的容器,例如fsoppelsa/spark-worker,并运行它来使用 Spark 二进制文件run-example计算 Pi 的值:

docker run -ti fsoppelsa/spark-worker /spark/bin/run-example 
    SparkPi

经过大量输出消息后,Spark 完成计算,给出:

...
Pi is roughly 3.14916
...

如果我们回到 Spark UI,我们可以看到我们惊人的 Pi 应用程序已成功完成。

测试 Spark

更有趣的是运行一个连接到主节点执行 Spark 作业的交互式 Scala shell:

$ docker run -ti fsoppelsa/spark-worker \
/spark/bin/spark-shell --master spark://<aws-105-IP>:7077

测试 Spark

使用 Flocker 存储

仅用于本教程的目的,我们现在使用之前创建的 spark 卷来运行一个示例,从 Spark 中读取和写入一些持久数据。

为了做到这一点,并且由于 Flocker 限制了副本因子,我们终止当前的三个工作节点集,并创建一个只有一个的集合,挂载 spark:

$ docker service rm spark-worker
$ docker service create \
--constraint 'node.labels.type == sparkmaster' \
--network spark \
--name spark-worker \
--replicas 1 \
--env SPARK\_MASTER\_IP=10.0.0.3 \
--mount type=volume,target=/data,source=spark,volume-driver=flocker\
fsoppelsa/spark-worker

我们现在获得了主机aws-105的 Docker 凭据:

$ eval $(docker-machine env aws-105)

我们可以尝试通过连接到 Spark 主容器在/data中写入一些数据。在这个例子中,我们只是将一些文本数据(lorem ipsum 的内容,例如在www.loremipsum.net上可用)保存到/data/file.txt中。

$ docker exec -ti 13ad1e671c8d bash
# echo "the content of lorem ipsum" > /data/file.txt

使用 Flocker 存储

然后,我们连接到 Spark shell 执行一个简单的 Spark 作业:

  1. 加载file.txt

  2. 将其包含的单词映射到它们出现的次数。

  3. 将结果保存在/data/output中:

$ docker exec -ti 13ad1e671c8d /spark/bin/spark-shell
...
scala> val inFile = sc.textFile("file:/data/file.txt")
scala> val counts = inFile.flatMap(line => line.split(" 
        ")).map(word => (word, 1)).reduceByKey(_ + _)
scala> counts.saveAsTextFile("file:/data/output")
scala> ^D

使用 Flocker 存储

现在,让我们在任何 Spark 节点上启动一个busybox容器,并检查spark卷的内容,验证输出是否已写入。我们运行以下代码:

$ docker run -v spark:/data -ti busybox sh
# ls /data
# ls /data/output/
# cat /data/output/part-00000

使用 Flocker 存储

前面的截图显示了预期的输出。关于 Flocker 卷的有趣之处在于它们甚至可以从一个主机移动到另一个主机。许多操作可以以可靠的方式完成。如果一个人正在寻找 Docker 的良好存储解决方案,那么 Flocker 是一个不错的选择。例如,它被 Swisscom Developer cloud(developer.swisscom.com/)在生产中使用,该云平台可以通过 Flocker 技术提供MongoDB等数据库。即将推出的 Flocker 版本将致力于精简 Flocker 代码库,并使其更加精简和耐用。内置 HA、快照、证书分发和容器中易于部署的代理等项目是接下来的计划。因此,前景一片光明!

扩展 Spark

现在我们来说明 Swarm Mode 最令人惊奇的功能--scale命令。我们恢复了在尝试 Flocker 之前的配置,因此我们销毁了spark-worker服务,并以副本因子3重新创建它:

aws-101$ docker service create \
--constraint 'node.labels.type != sparkmaster' \
--network spark \
--name spark-worker \
--replicas 3 \
--env SPARK_MASTER_IP=10.0.0.3 \
--env SPARK\_WORKER\_CORES=1 \
--env SPARK\_WORKER\_MEMORY=1g \
fsoppelsa/spark-worker

现在,我们使用以下代码将服务扩展到30个 Spark 工作节点:

aws-101$ docker service scale spark-worker=30

经过几分钟,必要的时间来拉取镜像,我们再次检查:

扩展 Spark

从 Spark web UI 开始:

扩展 Spark

scale可以用来扩展或缩小副本的大小。到目前为止,仍然没有自动扩展或将负载分配给新添加的节点的自动机制。但可以使用自定义工具来实现,或者甚至可以期待它们很快被集成到 Swarm 中。

监控 Swarm 托管的应用程序

我(Fabrizio)在 2016 年 8 月在 Reddit 上关注了一个帖子(www.reddit.com/r/docker/comments/4zous1/monitoring_containers_under_112_swarm/),用户抱怨新的 Swarm Mode 更难监控。

如果目前还没有官方的 Swarm 监控解决方案,那么最流行的新兴技术组合之一是:Google 的cAdvisor用于收集数据,Grafana用于显示图形,Prometheus作为数据模型。

Prometheus

Prometheus 团队将该产品描述为:

Prometheus 是一个最初在 SoundCloud 构建的开源系统监控和警报工具包。

Prometheus 的主要特点包括:

  • 多维数据模型

  • 灵活的查询语言

  • 不依赖分布式存储

  • 时间序列收集是通过拉模型进行的

  • 通过网关支持推送时间序列

  • 支持多种图形和仪表板模式

prometheus.io/docs/introduction/overview/上有一个很棒的介绍,我们就不在这里重复了。Prometheus 的最大特点,在我们看来,是安装和使用的简单性。Prometheus 本身只包括一个从 Go 代码构建的单个二进制文件,以及一个配置文件。

安装监控系统

事情可能很快就会发生变化,所以我们只是勾勒了一种在 Docker 版本 1.12.3 上尝试设置 Swarm 监控系统的方法。

首先,我们创建一个新的覆盖网络,以免干扰ingressspark网络,称为monitoring

aws-101$ docker network create --driver overlay monitoring

然后,我们以全局模式启动 cAdvisor 服务,这意味着每个 Swarm 节点上都会运行一个 cAdvisor 容器。我们在容器内挂载一些系统路径,以便 cAdvisor 可以访问它们:

aws-101$ docker service create \
--mode global \
--name cadvisor \
--network monitoring \
--mount type=bind,src=/var/lib/docker/,dst=/var/lib/docker \
--mount type=bind,src=/,dst=/rootfs \
--mount type=bind,src=/var/run,dst=/var/run \
--publish 8080 \
google/cadvisor

然后我们使用basi/prometheus-swarm来设置 Prometheus:

aws-101$ docker service create \
--name prometheus \
--network monitoring \
--replicas 1 \
--publish 9090:9090 \
prom/prometheus-swarm

然后我们添加node-exporter服务(再次全局,必须在每个节点上运行):

aws-101$ docker service create \
--mode global \
--name node-exporter \
--network monitoring \
--publish 9100 \
prom/node-exporter

最后,我们以一个副本启动Grafana

aws-101$ docker service create \
--name grafana \
--network monitoring \
--publish 3000:3000 \
--replicas 1 \
-e "GF_SECURITY_ADMIN_PASSWORD=password" \
-e "PROMETHEUS_ENDPOINT=http://prometheus:9090" \
grafana/grafana

在 Grafana 中导入 Prometheus

当 Grafana 可用时,为了获得 Swarm 健康状况的令人印象深刻的图表,我们使用这些凭据登录 Grafana 运行的节点,端口为3000

"admin":"password"

作为管理员,我们点击 Grafana 标志,转到数据源,并添加Prometheus

在 Grafana 中导入 Prometheus

会出现一些选项,但映射已经存在,所以只需保存并测试

在 Grafana 中导入 Prometheus

现在我们可以返回仪表板,点击Prometheus,这样我们就会看到 Grafana 的主面板:

在 Grafana 中导入 Prometheus

我们再次利用了开源社区发布的内容,并用一些简单的命令将不同的技术粘合在一起,以获得期望的结果。监控 Docker Swarm 及其应用程序是一个完全开放的研究领域,因此我们也可以期待那里的惊人进化。

总结

在本章中,我们使用 Flocker 为 Swarm 基础架构增加了存储容量,并设置了专用的覆盖网络,使我们的示例应用程序(一个 Spark 集群)能够在其上运行,并通过添加新节点(也可以是在新的提供者,如 DigitalOcean 上)轻松扩展。在使用了我们的 Spark 安装和 Flocker 之后,我们最终引入了 Prometheus 和 Grafana 来监控 Swarm 的健康和状态。在接下来的两章中,我们将看到可以插入 Swarm 的新附加功能,以及如何保护 Swarm 基础架构。

第八章:探索 Swarm 的其他功能

在本章中,我们将讨论并加深我们对 Docker 和编排系统的两个非常重要的主题的了解:网络和共识。特别是,我们将看到如何:

  • Libnetwork 的基础。

  • Libnetwork 的基本安全性

  • 路由网格

  • 覆盖网络

  • 网络控制平面

  • Libkv

Libnetwork

Libnetwork 是从头开始设计的网络堆栈,可以与 Docker 一起使用,无论平台、环境、操作系统还是基础设施如何。Libnetwork 不仅是网络驱动程序的接口。它不仅是一个管理 VLAN 或 VXLAN 网络的库,它做得更多。

Libnetwork 是一个完整的网络堆栈,由三个平面组成,即管理平面控制平面数据平面,如下图所示:

Libnetwork

  • 管理平面允许用户、运营商或工具管理网络基础设施。这些操作包括网络监控。管理平面代表 Docker 网络用户体验,提供 API。它还可以通过管理插件进行扩展,例如 IPAM 插件,例如,允许我们控制如何为每个容器分配 IP 地址。

  • 控制平面是在-scoped gossip 协议中实现的,直接添加了服务发现、加密密钥分发。

  • 简而言之,数据平面负责在两个端点之间传输网络数据包。网络插件适用于每个数据平面。默认情况下,有一些内置驱动程序。例如,我们在前几章中遇到的覆盖驱动程序直接使用 Linux 和 Windows 内核中的功能,因此对于这种类型的网络,没有驱动程序代码。这也适用于桥接、IPVLAN 和 MacVLAN 驱动程序。相比之下,其他第三方网络需要以插件的形式进行自己的实现。

遵循通常的 Docker UX,即组件应该在任何环境下都能正常工作,网络堆栈也必须是可移植的。为了使 Docker 的网络堆栈可移植,其设计和实现必须是牢固的。例如,管理平面不能被任何其他组件控制。同样,控制平面也不能被其他组件替换。如果允许这样做,当我们将应用程序环境从一个环境更改为另一个环境时,网络堆栈将会崩溃。

网络插件

数据平面设计为可插拔的。事实上,它只能由内置或外部插件管理。例如,MacVLAN 被实现为 Docker 1.12 中的一个插件,而不会影响系统的其他部分。

最值得注意的是,我们可以在同一网络堆栈上拥有多个驱动程序和插件,它们可以在不相互干扰的情况下工作。因此,在 Swarm 中,我们通常可以在同一集群上运行覆盖网络、桥接网络以及主机驱动程序。

容器网络模型

Libnetwork 的设计和实现是为了满足 Docker Swarm 运行 Docker 分布式应用程序的要求。也就是说,Libnetwork 实际上是 Docker 网络基础架构。Libnetwork 的基础是一个称为容器网络模型CNM)的模型。这是一个明确定义的基本模型,描述了容器如何连接到给定的网络。CNM 由三个组件组成:

  • 沙盒:这是一个包含容器网络堆栈配置的隔离。

  • 端点:这是一个仅属于网络和沙盒的连接点。

  • 网络:这是一组允许彼此自由通信的端点。一个网络由一个或多个端点组成。

驱动程序代表数据平面。每个驱动程序,无论是覆盖、桥接还是 MacVLAN,都以插件的形式存在。每个插件都在特定于其的数据平面上工作。

在系统中,默认情况下有一个内置的 IPAM。这是一个重要问题,因为每个容器必须有一个附加的 IP 地址。因此,有必要内置一个 IPAM 系统,允许每个容器能够像我们以传统方式那样连接到彼此,并且我们需要一个 IP 地址让其他人与容器通信。我们还需要定义子网以及 IP 地址范围。此外,该系统设计为 IPAM 可插拔。这意味着它允许我们拥有自己的 DHCP 驱动程序或允许将系统连接到现有的 DHCP 服务器。

如前所述,Libnetwork 支持开箱即用的多主机网络。值得讨论的多主机网络的组件是其数据平面和控制平面。

Docker 1.12 中的控制平面目前使用八卦机制作为节点的一般发现系统。这种基于八卦协议的网络在 Raft 一致性系统的另一层上同时工作。基本上,我们有两种不同的成员机制同时工作。Libnetwork 允许其他插件的驱动程序共同使用控制平面。

这些是 Libnetwork 控制平面的特点:

  • 它是安全和加密的

  • 每个数据平面都可以使用它

  • 它提供了原生的服务发现和负载均衡功能

Docker 1.12 在 Swarm 中实现了基于 VIP 的服务发现。这项服务通过将容器的虚拟 IP 地址映射到 DNS 记录来工作。然后所有的 DNS 记录都通过八卦进行共享。在 Docker 1.12 中,随着服务概念的引入,这个概念直接适用于发现的概念。

在 Docker 1.11 和之前的版本中,需要使用容器名称和别名来“模拟”服务发现,并进行 DNS 轮询来执行某种原始的负载均衡。

Libnetwork 延续了“电池内置但可拆卸”的原则,这是作为插件系统实现的。在未来,Libnetwork 将逐渐扩展插件系统,以涵盖其他网络部分,例如负载均衡。

加密和路由网格

正如之前提到的,Libnetwork 的核心模型是 CNM。在 Swarm 模式下,libnetwork 以集群感知模式构建,并支持多主机网络而无需外部键值存储。覆盖网络自然适应于这种模型。同时引入了数据平面和控制平面加密。通过加密的控制平面,例如 VXLAN 上的路由信息,容器具有哪个 MAC 地址和哪个 IP 地址,将自动得到保护。此外,通过路由网格,CNM 提供了一种分散的机制,允许您从集群的任何 IP 访问服务。当请求来自外部并命中集群的任何节点时,流量将被路由到一个工作容器。

MacVLAN

1.12 版本中的新驱动程序是 MacVLAN。MacVLAN 是一个高性能的驱动程序,旨在允许 Docker 网络连接到现有的 VLAN,例如公司的一个 VLAN,让一切继续工作。有一种情况是我们将逐渐将工作负载从原始 VLAN 迁移到 Docker,MacVLAN 将帮助将 Docker 集群连接到原始 VLAN。这将使 Docker 网络与底层网络集成,容器将能够在相同的 VLAN 中工作。

我们可以使用 MacVLAN 驱动程序创建一个网络,并将真实子网指定给该网络。我们还可以为容器指定一系列 IP 地址。此外,我们可以使用--aux-address排除一些 IP 地址,例如网关,不分配给容器。MacVLAN 驱动程序的父接口是我们希望将该网络连接到的接口。如前所述,MacVLAN 驱动程序的性能最佳。其 Linux 实现非常轻量级。它们只是强制执行网络之间的分离和连接到物理父网络,而不是作为传统的 Linux 桥接实现网络隔离。MacVLAN 驱动程序的使用需要 Linux 内核 3.9-3.19 或 4.x。

覆盖网络

由于 Swarm 集群现在是内置在 Docker Engine 中的本机功能,这使得创建覆盖网络非常容易,而无需使用外部键值存储。

管理节点负责管理网络的状态。所有网络状态都保存在 Raft 日志中。Swarm 模式中 Raft 实现与外部键值存储的主要区别在于,嵌入式 Raft 的性能远高于外部存储。我们自己的实验证实,外部键值存储将保持在 100-250 个节点左右,而嵌入式 Raft 帮助我们将系统扩展到了 Swarm3k 事件中的 4,700 个节点。这是因为外部 Raft 存储基本上具有很高的网络延迟。当我们需要就某些状态达成一致时,我们将从网络往返中产生开销,而嵌入式 Raft 存储只是存在于内存中。

过去,当我们想要执行任何与网络相关的操作,例如为容器分配 IP 地址时,由于我们总是与外部存储进行通信,会发生显着的网络延迟。对于嵌入式 Raft,当我们希望就某些值达成共识时,我们可以立即在内存存储中进行。

覆盖网络

当我们使用覆盖驱动程序创建网络时,如下所示:

$ docker network create --driver overlay --subnet 10.9.0.0/24 mh_net

命令将与分配器交谈。然后将进行子网保留,例如10.9.0.0/24,并在分配后立即在管理主机的内存中同意相关值。之后我们想要创建一个服务。然后我们稍后将该服务连接到网络。当我们创建一个服务时,如下所示:

$ docker service create --network mh_net nginx

编排器为该服务创建了一些任务(容器)。然后为每个创建的任务分配一个 IP 地址。在此分配过程中,分配将再次起作用。

任务创建完成后:

  • 任务获得一个 IP 地址

  • 其与网络相关的信息将被提交到 Raft 日志存储中

  • 在分配完成后,调度程序将任务移动到另一个状态

  • 调度程序将每个任务分派给工作节点之一

  • 最后,与该任务关联的容器将在 Docker Engine 上运行

如果任务无法分配其网络资源,它将停留在分配状态,并且不会被调度。这是与以前版本的 Docker 不同的重要差异,在 Swarm 模式的网络系统中,分配状态的概念是明显的。通过这种方式,它大大改善了系统的整体分配周期。当我们谈论分配时,我们不仅指 IP 地址的分配,还包括相关的驱动程序工件。对于覆盖网络,需要保留一个 VXLAN 标识符,这是每个 VXLAN 的一组全局标识符。这个标识符的保留是由网络分配器完成的。

将来,要实现相同的分配机制,插件只需实现一些接口,并使状态自动由 Libnetwork 管理并存储到 Raft 日志中。通过这种方式,资源分配是以集中方式进行的,因此我们可以实现一致性和共识。有了共识,我们需要一个高效的共识协议。

网络控制平面

网络控制平面是 Libnetwork 的一个子系统,用于管理路由信息,我们需要一个能够快速收敛的协议来完成这项工作。例如,Libnetwork 不使用 BGP 作为协议(尽管 BGP 在支持非常大数量的端点方面非常出色),因为点对点的 BGP 收敛速度不够快,无法在诸如软件容器环境这样高度动态的环境中使用。

在以容器为中心的世界中,网络系统预计会发生非常快速的变化,特别是对于新的 Docker 服务模型,它需要大规模且快速的 IP 分配。我们也希望路由信息在大规模情况下能够非常快速地收敛,例如对于超过 10,000 个容器。在 Swarm2k 和 Swarm3k 的实验中,我们确实一次启动了 10,000 个容器。特别是在 Swarm3k 中,我们在入口负载均衡网络上启动了 4,000 个 NGINX 容器。如果没有良好的实现,这种规模的数量将无法正常工作。

为了解决这个问题,Libnetwork 团队选择在网络控制平面中包含八卦协议。协议的内部算法工作方式如下:它选择 3 个邻居,然后传播相同的信息;在 Libnetwork 的情况下,是路由和其他与网络相关的信息。八卦协议将重复执行此过程,直到每个节点共享相同的信息。通过这种技术,整个集群将在几秒钟内非常快速地接收到信息。

网络控制平面

无论如何,整个集群并不需要始终具有相同的信息。集群中的每个节点并不需要知道所有网络的信息。只有特定网络中的节点需要知道自己的网络信息。为了优化 Libnetwork,团队实现了两个范围,集群范围的八卦通信网络范围的八卦通信。到目前为止,我们所解释的是集群范围的八卦通信,而网络范围的八卦通信将限制特定网络内的网络信息。当网络扩展以覆盖额外节点时,其八卦范围广播也将覆盖它们。

这项活动是建立在 Docker 的 CNM 之上的,因此依赖于网络抽象。从图中可以看出,左侧网络中有节点w1w2w3,右侧网络中也有w3w4w5。左侧网络执行八卦,只有w1w2w3才会知道其路由信息。您可能会注意到 w3 同时存在于两个网络中。因此,它将接收所有左侧和右侧网络的路由信息。

Libkv

libkv是一个统一的库,用于与不同的键值存储后端进行交互。libkv最初是 Docker Swarm v1 的一部分,在最初的开发版本中。后来,所有与键值存储发现服务相关的代码都经过了重构,并移至www.github.com/docker/libkv

libkv允许您执行 CRUD 操作,还可以从不同的后端观察键值条目,因此我们可以使用相同的代码与所有 HA 分布式键值存储一起工作,这些存储包括ConsulEtcdZooKeeper,如下图所示。在撰写本文时,libkv 还支持使用BoltDB实现的本地存储。

Libkv

如何使用 libkv

要开始使用libkv,我们首先需要了解如何调用其 API。以下是 Go 中的libkv Store接口,适用于每个存储实现:

type Store interface {
Put(key string, value []byte, options *WriteOptions) error
Get(key string) (*KVPair, error)
Delete(key string) error
Exists(key string) (bool, error)
Watch(key string, stopCh <-chan struct{}) (<-chan *KVPair, error)
WatchTree(directory string, stopCh <-chan struct{}) (<-chan  
       []*KVPair, 
       error)
NewLock(key string, options *LockOptions) (Locker, error)
List(directory string) ([]*KVPair, error)
DeleteTree(directory string) error
AtomicPut(key string, value []byte, previous *KVPair, options 
       *WriteOptions) (bool, *KVPair, error)
AtomicDelete(key string, previous *KVPair) (bool, error)
Close()
}

我们需要知道如何PutGetDeleteWatch来基本地与存储进行交互。

确保您的计算机上还安装了 Go 和 Git,并且 Git 可执行文件位于您的 PATH 上。然后,我们需要执行一些 go get 来安装我们程序的依赖项:

$ go get github.com/docker/libkv
$ go get github.com/davecgh/go-spew/spew
$ go get github.com/hashicorp/consul/api

这里我们提供了一个框架。在尝试运行以下程序之前,您需要启动一个单节点的Consul

# Delete all keys in Consul
$ curl -X DELETE http://localhost:8500/v1/kv/?recurse
# Compile the program
$ go build main.go
# Run it
$ ./main
# Spew is dumping the result for us in details
([]*store.KVPair) (len=1 cap=2) {
(*store.KVPair)(0x10e00de0)({
Key: (string) (len=27) "docker/nodes/127.0.0.1:2375",
Value: ([]uint8) (len=14 cap=15) {
00000000  31 32 37 2e 30 2e 30 2e  31 3a 32 33 37 35        
      |127.0.0.1:2375|
},
LastIndex: (uint64) 736745
})
}

您还可以使用 curl 测试获取您的值。您放置的值应该在那里。我们应该继续使用libkv的 API,即GetDelete。这留给读者作为练习。

总结

本章介绍了 Libnetwork,这是 Docker Swarm 中最重要的部分之一。我们已经讨论了其管理平面,控制平面和数据平面。本章还包括一些关于如何使用libkv的技术,这是一个键值抽象,用于实现您自己的服务发现系统。在下一章中,我们将专注于安全性。在下一章中,我们将学习如何保护一个集群。

第九章:保护 Swarm 集群和 Docker 软件供应链

本章主要讨论 Swarm 集群安全性。特别是,我们将讨论以下主题:

  • 使用 Docker 的软件供应链

  • 如何保护 Swarm 集群的建议

  • 使用 Docker Notary 来保护软件供应链

软件供应链

软件供应链

Docker 编排只是更大的软件供应链的一个组成部分。我们基本上从源代码作为原材料开始。我们的源代码与库和依赖包进行编译和链接。我们使用构建服务来持续集成我们的源代码和其依赖项,并最终将它们组装成一个产品。然后,我们将产品发布到互联网上,将其存储在其他地方。我们通常称这个仓库为应用程序存储库或简称存储库。最后,我们将产品发送到客户的环境中,例如云或物理数据中心。

Docker 非常适合这种工作流程。开发人员在本地使用 Docker 来编译和测试应用程序,系统管理员使用 Docker 在构建服务器上部署这些应用程序,并且 Docker 在持续集成的过程中也可能发挥重要作用。

安全性从这里开始。我们需要一种安全的方式在将产品推送到应用程序存储库之前对其进行签名。在我们以 Docker 为中心的世界中,我们将准备好的产品存储在一个称为Docker Registry的仓库中。然后,每次在将签名产品部署到我们运行 Docker Swarm 模式集群的生产系统之前,都会对其进行验证。

在本章的其余部分,我们将讨论安全的以下两个方面:

  • 如何通过最佳实践来保护生产 Swarm 集群

  • 如何通过 Docker Notary 来保护软件供应链

保护 Swarm 集群

回想一下来自第四章创建生产级别的 Swarm的安全的 Swarm 集群图片;我们将解释 Docker Swarm 模型集群中发现的安全方面。

保护 Swarm 集群

编排器是 Docker Swarm 管理器的主要部分之一。Docker 安全团队成员 Diogo Monica 在 2016 年柏林的编排最低特权演示中提到,编排中的每个组件都必须有其能够做的限制。

  • 节点管理:集群操作员可以指示编排器为一组节点执行操作

  • 任务分配:编排器还负责为每个节点分配任务

  • 集群状态协调:编排器通过将每个状态与期望状态协调来维护集群的状态

  • 资源管理:编排器为提交的任务提供和撤销资源

具有最小权限的编排器将使系统更安全,最小权限的编排器是基于这些功能定义的。遵循最小权限原则,管理者以及工作节点必须能够访问“执行给定任务所必需的信息和资源”。

此外,Diogo 提出了可以应用于 Docker 的五种不同攻击模型的列表。它们从最低风险到最高风险列出。

  • 外部攻击者:试图破坏集群的防火墙之外的人。

  • 内部攻击者:不拥有交换机但可以访问交换机。它可以发送数据包与集群中的节点进行通信。

  • 中间人攻击:可以监听网络中的所有内容并进行主动攻击的攻击者。在这种模型中,存在一个 Swarm 集群,并且拦截了工作节点与管理节点的通信。

  • 恶意工作节点:工作节点拥有的资源实际上被攻击者拥有。

  • 恶意管理节点:管理者是一个攻击者,可以控制完整的编排器并访问所有可用资源。这是最糟糕的情况。如果我们能够实现最小权限,那么恶意管理节点只能攻击与其关联的工作节点。

保护 Swarm:最佳实践

我们现在将总结保护 Swarm 集群的检查表。Swarm 团队正在努力实现防止对整个堆栈的攻击的目标,但无论如何,以下规则都适用。

认证机构

确保安全性的第一个重要步骤是决定如何使用 CA。当您使用第一个节点形成集群时,它将自动为整个集群创建一个自签名的 CA。在启动后,它创建 CA,签署自己的证书,为管理器添加证书,即自身,并成为准备运行的单节点集群。当新节点加入时,它通过提供正确的令牌来获取证书。每个节点都有自己的身份,经过加密签名。此外,系统为每个规则、工作节点或管理器都有一个证书。角色信息在身份信息中,以告知节点的身份。如果管理器泄露了根 CA,整个集群就会受到威胁。Docker Swarm 模式支持外部 CA 来维护管理器的身份。管理器可以简单地将 CSR 转发给外部 CA,因此不需要维护自己的 CA。请注意,目前仅支持cfssl协议。以下命令是使用外部 CA 初始化集群。

$ docker swarm init --external-ca \  
    protocol=cfssl,url=https://ca.example.com

证书和双向 TLS

网络控制平面上的每个端点通信必须具有双向 TLS,并且默认情况下是加密和授权的。这意味着工作节点不能伪装成管理器,也没有外部攻击者可以连接到端点并成功完成 TLS 握手,因为攻击者没有密钥来进行相互认证。这意味着每个节点必须提供有效的 CA 签名证书,其中 OU 字段与集群的每个规则匹配。如果工作节点连接到管理器端点,将被拒绝访问。

Swarm 会自动执行证书轮换。在 SwarmKit 和 Docker Swarm 模式中,证书轮换可以设置为短至一小时。以下是调整证书到期时间的命令。

$ docker swarm update --cert-expiry 1h

加入令牌

每个节点用于加入集群的令牌具有以下四个组件:

  • SWMTKN,Swarm 前缀,用于在令牌泄露时查找或搜索

  • 令牌版本,目前为 1

  • CA 根证书的加密哈希值,用于允许引导

  • 一个随机生成的秘密。

以下是一个令牌的示例:

SWMTKN-1-11lo1xx5bau6nmv5jox26rc5mr7l1mj5wi7b84w27v774frtko-e82x3ti068m9eec9w7q2zp9fe

要访问集群,需要发送一个令牌作为凭证。这就像集群密码。

好消息是,如果令牌被 compromise,令牌可以使用以下命令之一简单地旋转

$ docker swarm join-token --rotate worker
$ docker swarm join-token --rotate manager

使用 Docker Machine 添加 TLS

另一个良好的实践是使用 Docker Machine 为所有管理节点提供额外的 TLS 层,自动设置,以便每个管理节点都可以以安全的方式被远程 Docker 客户端访问。这可以通过以下命令简单地完成,类似于我们在上一章中所做的方式:

$ docker-machine create \
      --driver generic \
      --generic-ip-address=<IP> \
    mg0

在私有网络上形成一个集群

如果形成混合集群不是一个要求,最佳实践之一是我们应该在本地私有网络上形成一个集群,所有节点都在本地私有网络上。这样,覆盖网络的数据就不需要加密,集群的性能会很快。

在形成这种类型的集群时,路由网格允许我们将任何工作节点(不一定是管理节点)暴露给公共网络接口。下图显示了集群配置。您可以看到,通过这种配置和 Docker 服务在入口网络上发布端口 80。路由网格形成了一个星形网格,但我们简化了它,并只显示了从大 W 节点连接到其他节点的一侧。大 W 节点有两个网络接口。其公共接口允许节点充当整个集群的前端节点。通过这种架构,我们可以通过不暴露任何管理节点到公共网络来实现一定级别的安全性。

在私有网络上形成一个集群

Docker Notary

Docker Content Trust 机制是使用 Docker Notary (github.com/docker/notary) 实现的,它位于 The Update Framework (github.com/theupdateframework/tuf) 上。TUF 是一个安全的框架,允许我们一次交付一系列受信任的内容。Notary 通过使发布和验证内容变得更容易,允许客户端和服务器形成一个受信任的集合。如果我们有一个 Docker 镜像,我们可以使用高度安全的离线密钥对其进行离线签名。然后,当我们发布该镜像时,我们可以将其推送到一个可以用于交付受信任镜像的 Notary 服务器。Notary 是使用 Docker 为企业启用安全软件供应链的方法。

我们演示了如何设置我们自己的 Notary 服务器,并在将 Docker 镜像内容推送到 Docker 注册表之前使用它进行签名。先决条件是安装了最新版本的 Docker Compose。

第一步是克隆 Notary(在这个例子中,我们将其版本固定为 0.4.2):

git clone https://github.com/docker/notary.git
cd notary
git checkout v0.4.2
cd notary

打开docker-compose.yml并添加图像选项以指定签名者和服务器的图像名称和标签。在这个例子中,我使用 Docker Hub 来存储构建图像。所以是chanwit/server:v042chanwit/signer:v042。根据您的本地配置进行更改。

Docker Notary

然后开始

$ docker-compose up -d

我们现在在127.0.0.1:4443上运行一个 Notary 服务器。为了使 Docker 客户端能够与 Notary 进行握手,我们需要将 Notary 服务器证书复制为这个受信任地址(127.0.0.4443)的 CA。

$ mkdir -p ~/.docker/tls/127.0.0.1:4443/
$ cp ./fixtures/notary-server.crt 
    ~/.docker/tls/127.0.0.1:4443/ca.crt

之后,我们启用 Docker 内容信任,并将 Docker 内容信任服务器指向我们自己的 Notary,地址为https://127.0.0.1:4443

$ export DOCKER_CONTENT_TRUST=1
$ export DOCKER_CONTENT_TRUST_SERVER=https://127.0.0.1:4443

然后我们将图像标记为新图像,并在启用 Docker 内容信任的同时推送图像:

$ docker tag busybox chanwit/busybox:signed
$ docker push chanwit/busybox:signed

如果设置正确完成,我们将看到 Docker 客户端要求新的根密钥和新的存储库密钥。然后它将确认chanwit/busybox:signed已成功签名。

The push refers to a repository [docker.io/chanwit/busybox]
e88b3f82283b: Layer already exists
signed: digest: 
sha256:29f5d56d12684887bdfa50dcd29fc31eea4aaf4ad3bec43daf19026a7ce69912 size: 527
Signing and pushing trust metadata
You are about to create a new root signing key passphrase. This passphrase
will be used to protect the most sensitive key in your signing system. Please
choose a long, complex passphrase and be careful to keep the password and the
key file itself secure and backed up. It is highly recommended that you use a
password manager to generate the passphrase and keep it safe. There will be no
way to recover this key. You can find the key in your config directory.
Enter passphrase for new root key with ID 1bec0c1:
Repeat passphrase for new root key with ID 1bec0c1:
Enter passphrase for new repository key with ID ee73739 (docker.io/chanwit/busybox):
Repeat passphrase for new repository key with ID ee73739 (docker.io/chanwit/busybox):
Finished initializing "docker.io/chanwit/busybox"
Successfully signed "docker.io/chanwit/busybox":signed

现在,我们可以尝试拉取相同的镜像:

$ docker pull chanwit/busybox:signed
Pull (1 of 1): chanwit/busybox:signed@sha256:29f5d56d12684887bdfa50dcd29fc31eea4aaf4ad3bec43daf19026a7ce69912
sha256:29f5d56d12684887bdfa50dcd29fc31eea4aaf4ad3bec43daf19026a7ce69912: Pulling from chanwit/busybox
Digest: sha256:29f5d56d12684887bdfa50dcd29fc31eea4aaf4ad3bec43daf19026a7ce69912
Status: Image is up to date for chanwit/busybox@sha256:29f5d56d12684887bdfa50dcd29fc31eea4aaf4ad3bec43daf19026a7ce69912
Tagging chanwit/busybox@sha256:29f5d56d12684887bdfa50dcd29fc31eea4aaf4ad3bec43daf19026a7ce69912 as chanwit/busybox:signed

当我们拉取一个未签名的镜像时,这时会显示没有受信任的数据:

$ docker pull busybox:latest
Error: remote trust data does not exist for docker.io/library/busybox: 127.0.0.1:4443 does not have trust data for docker.io/library/busybox

介绍 Docker 秘密

Docker 1.13 在 Swarm 中包含了新的秘密管理概念。

我们知道,我们需要 Swarm 模式来使用秘密。当我们初始化一个 Swarm 时,Swarm 会为我们生成一些秘密:

$ docker swarm init

Docker 1.13 添加了新的命令secret来管理秘密,目的是有效地处理它们。秘密子命令被创建,ls,用于检查和 rm。

让我们创建我们的第一个秘密。secret create子命令从标准输入中获取一个秘密。因此,我们需要输入我们的秘密,然后按Ctrl+D保存内容。小心不要按Enter键。例如,我们只需要1234而不是1234\n作为我们的密码:

$ docker secret create password
1234

然后按两次Ctrl+D关闭标准输入。

我们可以检查是否有一个名为 password 的秘密:

$ docker secret ls
ID                      NAME                CREATED             UPDATED
16blafexuvrv2hgznrjitj93s  password  25 seconds ago      25 seconds ago
uxep4enknneoevvqatstouec2  test-pass 18 minutes ago      18 minutes ago

这是如何工作的?秘密的内容可以通过在创建新服务时传递秘密选项来绑定到服务。秘密将是/run/secrets/目录中的一个文件。在我们的情况下,我们将有/run/secrets/password包含字符串1234

秘密旨在取代环境变量的滥用。例如,在 MySQL 或 MariaDB 容器的情况下,其根密码应该设置为一个秘密,而不是通过环境变量以明文传递。

我们将展示一个小技巧,使 MariaDB 支持新的 Swarm 秘密,从以下的entrypoint.sh开始:

$ wget https://raw.githubusercontent.com/docker-
library/mariadb/2538af1bad7f05ac2c23dc6eb35e8cba6356fc43/10.1/docker-entrypoint.sh

我们将这行放入这个脚本中,大约在第 56 行之前,然后检查MYSQL_ROOT_PASSWORD

# check secret file. if exist, override
if [ -f "/run/secrets/mysql-root-password" ]; then
MYSQL_ROOT_PASSWORD=$(cat /run/secrets/mysql-root-password)
fi

此代码检查是否存在/run/secrets/mysql-root-password。如果是,则将密钥分配给环境变量MYSQL_ROOT_PASSWORD

之后,我们可以准备一个 Dockerfile 来覆盖 MariaDB 的默认docker-entrypoint.sh

FROM mariadb:10.1.19
RUN  unlink /docker-entrypoint.sh
COPY docker-entrypoint.sh /usr/local/bin/
RUN  chmod +x /usr/local/bin/docker-entrypoint.sh
RUN  ln -s usr/local/bin/docker-entrypoint.sh /

然后我们构建新的镜像。

$ docker build -t chanwit/mariadb:10.1.19 .

回想一下,我们有一个名为 password 的秘密,我们有一个允许我们从秘密文件/run/secrets/mysql-root-password设置根密码的镜像。因此,该镜像期望在/run/secrets下有一个不同的文件名。有了这个,我们可以使用完整选项的秘密(source=passwordtarget=mysql-root-password)来使 Swarm 服务工作。例如,我们现在可以从这个 MariaDB 镜像启动一个新的mysql Swarm 服务:

$ docker network create -d overlay dbnet
lsc7prijmvg7sj6412b1jnsot
$ docker service create --name mysql \
--secret source=password,target=mysql-root-password \
--network dbnet \
chanwit/mariadb:10.1.19

要查看我们的秘密是否有效,我们可以在相同的覆盖网络上启动一个 PHPMyAdmin 实例。不要忘记通过向myadmin服务传递-e PMA_HOST=mysql来将这些服务链接在一起。

$ docker service create --name myadmin \
--network dbnet --publish 8080:80 \
-e PMA_HOST=mysql \
phpmyadmin/phpmyadmin

然后,您可以在浏览器中打开http://127.0.0.1:8080,并使用我们通过 Docker 秘密提供的密码1234作为 root 登录PHPMyAdmin,这样我们就可以使用秘密。

摘要

在本章中,我们学习了如何保护 Swarm Mode 和 Docker 软件供应链。我们谈到了一些在生产中如何保护 Docker Swarm 集群的最佳实践。然后我们继续介绍了 Notary,这是一种安全的交付机制,允许 Docker 内容信任。本章以 Docker 1.13 中的一个新功能概述结束:秘密管理。我们展示了如何使用 Docker Secret 来安全地部署 MySQL MariaDB 服务器,而不是通过环境传递根密码。在下一章中,我们将发现如何在一些公共云提供商和 OpenStack 上部署 Swarm。

第十章:Swarm 和云

在本书中,我们使用了 Docker Swarm 在一系列不同的底层技术上,到目前为止,我们还没有深入探讨这一含义:我们在 AWS、DigitalOcean 以及我们的本地工作站上运行了 Swarm。对于测试和分期目的,我们运行 Swarm 的平台可能是次要的(让我们用 Docker Machine 启动一些 AWS 实例并以这种方式工作),但对于生产环境来说,了解利弊、原因、评估和跟随趋势是必不可少的。

在本章中,我们将审查几种公共和私有云选项和技术以及它们可能的交集。最后,我们将在第十一章中讨论CaaS(容器即服务)和IaaC(基础设施即代码)这两个全新的热词,接下来是什么?

主要我们将关注:

  • Docker for AWS 和 Azure

  • Docker 数据中心

  • 在 OpenStack 上的 Swarm

Docker for AWS 和 Azure

与 Docker For Mac 和 Windows 一样,Docker 团队开始着手开发新一代的运维工具集:Docker for AWS 和 Docker for Windows。这些工具旨在为部署 Docker 基础架构提供自动化支持,特别是适用于 Swarm 的基础架构。

目标是提供一种标准的做事方式,将底层基础设施与 Docker 工具集集成,并让人们毫不费力地在他们喜爱的平台上运行最新的软件版本。最终目标实际上是让开发人员将东西从他们的笔记本电脑上移动到云端,使用 Docker for Mac/Windows 和 Docker for AWS/Azure。

Docker for AWS

用户体验在 Docker 生态系统中一如既往地出色。要求如下:

  • 一个 AWS ID

  • 导入到 AWS 密钥环中的 SSH 密钥

  • 准备好的安全组

基本上,Docker for AWS 是 CloudForms 的可点击模板。CloudForms 是 AWS 的编排系统,允许创建复杂系统的模板,例如,您可以指定由三个 Web 服务器、一个数据库和一个负载均衡器组成的 Web 基础架构。

Docker for AWS 当然具备创建 Docker Swarm(模式)基础架构的能力,它会根据您的指定创建尽可能多的主节点和工作节点,放置一个负载均衡器,并相应地配置所有网络。

这是欢迎界面:

Docker for AWS

然后,您可以指定一些基本和高级选项:

Docker for AWS

如您所见,您可以选择管理者和工作节点的数量,以及要启动的实例的类型。到目前为止,支持最多 1000 个工作节点。之后,您只需在下一步中点击创建堆栈,并等待几分钟让 CloudForms 启动基础架构。

模板的作用是:

  1. 在您的 AWS 帐户中创建一个新的虚拟私有云,包括网络和子网。

  2. 创建两个自动扩展组,一个用于管理者,一个用于工作节点。

  3. 启动管理者并确保它们与 Raft quorum 达成健康状态。

  4. 逐个启动和注册工作节点到 Swarm。

  5. 创建弹性负载均衡器ELBs)来路由流量。

  6. 完成

一旦 CloudFormation 完成,它将提示一个绿色的确认。

Docker for AWS

现在,我们准备好进入我们的新 Docker Swarm 基础架构。只需选择一个管理者的公共 IP 并使用在第一步中指定的 SSH 密钥连接到它:

 ssh docker@ec2-52-91-75-252.compute-1.amazonaws.com

Docker for AWS

Docker for Azure

由于与微软的协议,Azure 也可以作为一键式体验(或几乎是)自动部署 Swarm。

在 Azure 上部署 Swarm 的先决条件是:

  • 拥有有效的 Azure 帐户

  • 将此帐户 ID 与 Docker for Azure 关联。

  • 活动目录主体应用程序 ID

要生成最后一个,您可以方便地使用一个 docker 镜像,并使用以下命令启动它:

 docker run -it docker4x/create-sp-azure docker-swarm

在过程中,您将需要通过浏览器登录到指定的 URL。最后,将为您提供一对 ID/密钥,供您在 Azure 向导表单中输入。

Docker for Azure

一切就绪后,您只需点击OKCreate

Docker for Azure

将创建一组经典虚拟机来运行指定数量的管理者(这里是 1)和工作节点(这里是 4),以及适当的内部网络、负载均衡器和路由器。就像在 Docker for AWS 中一样,您可以通过 SSH 连接到一个管理者的公共 IP 来开始使用部署的 Swarm:

 ssh docker@52.169.125.191

Docker for Azure

目前 Azure 模板有一个限制,它只支持一个管理者。然而,很快就会有添加新管理者的可能性。

Docker 数据中心

Docker 数据中心,以前是 Tutum,被 Docker 收购,是 Docker 提供的单击部署解决方案,用于使用 UCP,Universal Control Panel,Docker 的商业和企业产品。

Docker 数据中心包括:

在 Dockercon 16 上,团队发布了对在 AWS 和 Azure 上运行 Docker 数据中心的支持(目前处于 Beta 阶段)。要尝试 Docker 数据中心,你需要将许可证与你的公司/项目的 AWS 或 Azure ID 关联起来。

对于 AWS 的数据中心,就像对于 AWS 的 Docker 一样,有一个 CloudFormation 模板,可以立即启动一个 Docker 数据中心。要求是:

你需要做的就是从你的许可证链接进入创建堆栈页面。在这里,你只需输入HostedZone ID 和 Docker 数据中心许可证,然后开始创建堆栈。在内部,Docker 数据中心在私有网络(节点)上放置一些虚拟机,并且一些通过弹性负载均衡器(ELBs,用于控制器)进行负载平衡,上面安装了商业支持的引擎版本。当前版本的 Docker 数据中心虚拟机在内部运行 Swarm 独立和发现机制,以相互连接。我们可以预期数据中心的稳定版本很快就会发布。

Docker 数据中心和 Docker for AWS 之间的主要区别在于前者旨在成为一体化的企业就绪解决方案。而后者是部署 Swarm 集群的最快方式,前者是更完整的解决方案,具有时尚的 UI,Notary 和来自生态系统的可选服务。

在 OpenStack 上的 Swarm

说到私有云,最受欢迎的 IaaS 开源解决方案是 OpenStack。OpenStack 是一个由程序(以前称为项目)组成的庞大生态系统,旨在提供所谓的云操作系统。核心 OpenStack 程序包括:

  • Keystone:身份和授权系统

  • Nova:虚拟机抽象层。Nova 可以与虚拟化模块(如 Libvirt、VMware)进行插接

  • Neutron:处理租户网络、实例端口、路由和流量的网络模块

  • Cinder:负责处理卷的存储模块

  • Glance:镜像存储

一切都由额外的参与者粘合在一起:

  • 一个数据库系统,比如 MySQL,保存配置。

  • 一个 AMQP 代理,比如 Rabbit,用于排队和传递操作

  • 一个代理系统,比如 HAproxy,用来代理 HTTP API 请求

在 OpenStack 中典型的 VM 创建中,发生以下情况:

  1. 用户可以从 UI(Horizon)或 CLI 决定生成一个 VM。

  2. 他/她点击一个按钮或输入一个命令,比如nova boot ...

  3. Keystone 检查用户在他/她的租户中的授权和认证,通过在用户的数据库或 LDAP 中检查(取决于 OpenStack 的配置),并生成一个将在整个会话中使用的令牌:“这是你的令牌:gAAAAABX78ldEiY2”.

  4. 如果认证成功并且用户被授权生成 VM,Nova 将使用授权令牌调用:“我们正在启动一个 VM,你能找到一个合适的物理主机吗?”

  5. 如果这样的主机存在,Nova 从 Glance 获取用户选择的镜像:“Glance,请给我一个 Ubuntu Xenial 可启动的 qcow2 文件”

  6. 在物理启动 VM 的计算主机上,一个nova-compute进程,它与配置的插件进行通信,例如,对 Libvirt 说:“我们正在这个主机上启动一个 VM”

  7. Neutron 为 VM 分配私有(如果需要还有公共)网络端口:“请在指定的网络上创建这些端口,在这些子网池中”

  8. 如果用户愿意,Cinder 会在其调度程序设计的主机上分配卷。也就是说。让我们创建额外的卷,并将它们附加到 VM。

  9. 如果使用 KVM,将生成一个包含上述所有信息的适当 XML,并且 Libvirt 在计算主机上启动 VM

  10. 当 VM 启动时,通过 cloud-init 注入一些变量,例如,允许无密码 SSH 登录的 SSH 密钥

这(除了 Cinder 上的第 8 步)正是 Docker Machine 的 OpenStack 驱动程序的行为方式:当你使用-d openstack在 Machine 上创建一个 Docker 主机时,你必须指定一个现有的 glance 镜像,一个现有的私有(和可选的公共)网络,并且(可选的,否则会自动生成)指定一个存储在 Nova 数据库中的 SSH 镜像。当然,你必须将授权变量传递给你的 OpenStack 环境,或者作为导出的 shell 变量源它们。

在 OpenStack 上创建一个 Docker 主机的 Machine 命令将如下所示:

 docker-machine create \
 --driver openstack \
 --openstack-image-id 98011e9a-fc46-45b6-ab2c-cf6c43263a22 \
 --openstack-flavor-id 3 \
 --openstack-floatingip-pool public \
 --openstack-net-id 44ead515-da4b-443b-85cc-a5d13e06ddc85 \
 --openstack-sec-groups machine \
 --openstack-ssh-user ubuntu \
 ubuntu1

OpenStack Nova

因此,在 OpenStack 上进行 Docker Swarm 的经典方式将是开始创建实例,比如从 Ubuntu 16.04 镜像中创建 10 个 VMs,放在一个专用网络中:

  • 从 Web UI 中,指定 10 作为实例的数量

  • 或者从 CLI 中,使用nova boot ... --max-count 10 machine-

  • 或者使用 Docker Machine

最后一种方法更有前途,因为 Machine 会自动安装 Docker,而不必在新创建的实例上进行后续的黑客攻击或使用其他工具(例如使用通用驱动程序的 Machine,Belt,Ansible,Salt 或其他脚本)。但在撰写本文时(Machine 0.8.2),Machine 不支持批量主机创建,因此您将不得不使用一些基本的 shell 逻辑循环docker-machine命令:

 #!/bin/bash
 for i in `seq 0 9`; do
 docker-machine create -d openstack ... openstack-machine-$i
 done

这根本不是一个好的用户体验,因为当我们谈论数十个主机时,机器的扩展性仍然非常糟糕。

(不推荐使用的)nova-docker 驱动程序

曾经,有一个用于 Nova 的驱动程序,可以将 Docker 容器作为 Nova 的最终目的地(而不是创建 KVM 或 VmWare 虚拟机,例如,这些驱动程序允许从 Nova 创建和管理 Docker 容器)。如果对于的 Swarm 来说使用这样的工具是有意义的(因为一切都被编排为容器),那么对于 Swarm Mode 来说就没有兴趣了,它需要的是 Docker 主机而不是裸露的容器。

现实 - OpenStack 友好的方式

幸运的是,OpenStack 是一个非常充满活力的项目,现在它已经发布了OOcata),它通过许多可选模块得到了丰富。从 Docker Swarm 的角度来看,最有趣的是:

  • Heat: 这是一个编排系统,可以从模板创建 VMs 配置。

  • Murano: 这是一个应用程序目录,可以从由开源社区维护的目录中运行应用程序,包括 Docker 和 Kubernetes 容器。

  • Magnum: 这是来自 Rackspace 的容器即服务解决方案。

  • Kuryr: 这是网络抽象层。使用 Kuryr,您可以将 Neutron 租户网络和使用 Docker Libnetwork 创建的 Docker 网络(比如 Swarm 网络)连接起来,并将 OpenStack 实例与 Docker 容器连接起来,就好像它们连接到同一个网络一样。

OpenStack Heat

OpenStack Heat 有点类似于 Docker Compose,允许您通过模板启动系统,但它更加强大:您不仅可以从镜像启动一组实例,比如 Ubuntu 16.04,还可以对它们进行编排,这意味着创建网络,将 VM 接口连接到网络,放置负载均衡器,并在实例上执行后续任务,比如安装 Docker。粗略地说,Heat 相当于 OpenStack 的 Amazon CloudFormation。

在 Heat 中,一切都始于 YAML 模板,借助它,您可以在启动之前对基础架构进行建模,就像您使用 Compose 一样。例如,您可以创建一个这样的模板文件:

 ...
 resources:
   dockerhosts_group:
     type: OS::Heat::ResourceGroup
     properties:
       count: 10
       resource_def:
         type: OS::Nova::Server
         properties:
           # create a unique name for each server
           # using its index in the group
           name: docker_host_%index%
           image: Ubuntu 16.04
           flavor: m.large
 ...

然后,您可以从中启动一个堆栈(heat stack-create -f configuration.hot dockerhosts)。Heat 将调用 Nova、Neutron、Cinder 和所有必要的 OpenStack 服务来编排资源并使其可用。

在这里,我们不会展示如何通过 Heat 启动 Docker Swarm 基础架构,而是会看到 Magnum,它在底层使用 Heat 来操作 OpenStack 对象。

OpenStack Magnum

Magnum 于 2015 年底宣布,并由 OpenStack 容器团队开发,旨在将容器编排引擎COEs)如 Docker Swarm 和Kubernetes作为 OpenStack 中的一流资源。在 OpenStack 领域内有许多项目专注于提供容器支持,但 Magnum 走得更远,因为它旨在支持容器编排,而不仅仅是裸露的容器管理。

OpenStack Magnum

到目前为止,重点特别放在 Kubernetes 上,但我们在这里谈论Magnum,因为它是提供在私有云上运行 CaaS 编排的最有前途的开源技术。在撰写本文时,Magnum 尚不支持最新的 Swarm Mode:这个功能必须得到解决。作者已经在 Launchpad 蓝图上开放了一个问题,可能会在书出版后开始着手处理:blueprints.launchpad.net/magnum/+spec/swarm-mode-support

架构和核心概念

Magnum 有两个主要组件,运行在控制节点上:

 magnum-api
 magnum-conductor

第一个进程magnum-api是典型的 OpenStack API 提供者,由 magnum Python 客户端或其他进程调用进行操作,例如创建集群。后者magnum-conductormagnum-api(或多或少具有nova-conductor的相同功能)通过 AMQP 服务器(如 Rabbit)调用,其目标是与 Kubernetes 或 Docker API 进行接口。实际上,这两个二进制文件一起工作,提供一种编排抽象。

架构和核心概念

在 OpenStack 集群计算节点上,除了nova-compute进程外,不需要运行任何特殊的东西:Magnum conductor 直接利用 Heat 创建堆栈,这些堆栈创建网络并在 Nova 中实例化 VM。

Magnum 术语随着项目的发展而不断演变。但这些是主要概念:

  • 容器是 Docker 容器。

  • 集群(以前是 Bay)是一组调度工作的节点对象的集合,例如 Swarm 节点。

  • ClusterTemplate(以前是 BayModel)是存储有关集群类型信息的模板。例如,ClusterTemplate 定义了具有 3 个管理节点和 5 个工作节点的 Swarm 集群

  • Pods是在同一物理或虚拟机上运行的一组容器。

至于高级选项,如存储、新的 COE 支持和扩展,Magnum 是一个非常活跃的项目,我们建议您在docs.openstack.org/developer/magnum/上关注其发展。

在 Mirantis OpenStack 上安装 HA Magnum

安装 Magnum 并不那么简单,特别是如果您想要保证 OpenStack HA 部署的一些故障转移。互联网上有许多关于如何在 DevStack(开发者的 1 节点暂存设置)中配置 Magnum 的教程,但没有一个显示如何在具有多个控制器的真实生产系统上工作。在这里,我们展示了如何在真实设置中安装 Magnum。

通常,生产 OpenStack 安装会有一些专门用于不同目标的节点。在最小的 HA 部署中,通常有:

  • 三个或更多(为了仲裁原因是奇数)控制节点,负责托管 OpenStack 程序的 API 和配置服务,如 Rabbit、MySQL 和 HAproxy

  • 任意数量的计算节点,在这里工作负载在物理上运行(VM 托管的地方)

还可以选择专用存储、监控、数据库、网络和其他节点。

在我们的设置中,基于运行 Newton 的Mirantis OpenStack,安装了 Heat,我们有三个控制器和三个计算加存储节点。使用 Pacemaker 配置了 HA,它将 MySQL、Rabbitmq 和 HAproxy 等资源保持高可用性。API 由 HAproxy 代理。这是一个显示配置到 Pacemaker 中的资源的屏幕截图。它们都已启动并正常工作:

在 Mirantis OpenStack 上安装 HA Magnum

集群中的所有节点都运行 Ubuntu 16.04(Xenial),稳定的 Magnum 2.0 软件包存在,因此只需从上游使用它们并使用apt-get install进行安装。

然而,在安装 Magnum 之前,有必要准备环境。首先需要一个数据库。只需在任何控制器上输入 MySQL 控制台:

 node-1# mysql

在 MySQL 中,创建 magnum 数据库和用户,并授予正确的权限:

 CREATE DATABASE magnum;
 GRANT ALL PRIVILEGES ON magnum.* TO 'magnum'@'controller' \
   IDENTIFIED BY 'password';
 GRANT ALL PRIVILEGES ON magnum.* TO 'magnum'@'%' \
   IDENTIFIED BY 'password';

现在,有必要在 Keystone 中创建服务凭据,首先要定义一个 magnum OpenStack 用户,必须将其添加到服务组中。服务组是一个特殊的组,其中包括在集群中运行的 OpenStack 服务,如 Nova、Neutron 等。

 openstack user create --domain default --password-prompt magnum
 openstack role add --project services --user magnum admin

之后,必须创建一个新的服务:

 openstack service create --name magnum \   --description "OpenStack 
    Container Infrastructure" \   container-infra

OpenStack 程序通过其 API 调用并进行通信。API 通过端点访问,这是一个 URL 和端口的配对,在 HA 设置中由 HAproxy 代理。在我们的设置中,HAproxy 在10.21.22.2上接收 HTTP 请求,并在控制器 IP 之间进行负载均衡,即10.21.22.4, 56

在 Mirantis OpenStack 上安装 HA Magnum

我们必须为 Magnum 创建这样的端点,默认情况下监听端口 9511,对于每个区域(公共、内部和管理员):

 openstack endpoint create --region RegionOne \
   container-infra public http://10.21.22.2:9511/v1
 openstack endpoint create --region RegionOne \
   container-infra internal http://10.21.22.2:9511/v1
 openstack endpoint create --region RegionOne \
   container-infra admin http://10.21.22.2:9511/v1

此外,Magnum 需要额外的配置来在域内部组织其工作负载,因此必须添加一个专用域和域用户:

 openstack domain create --description "Magnum" magnum
 openstack user create --domain magnum --password-prompt 
    magnum_domain_admin
 openstack role add --domain magnum --user magnum_domain_admin admin

现在一切就绪,最终运行apt-get。在所有三个控制器上运行以下命令,并在 ncurses 界面中,始终选择 No,以不更改环境,或保持默认配置:

 apt-get install magnum-api magnum-conductor

配置 HA Magnum 安装

Magnum 的配置非常简单。使其处于运行状态所需的操作是:

  1. 通过magnum.conf文件进行配置。

  2. 重新启动 magnum 二进制文件。

  3. 打开端口tcp/9511

  4. 配置 HAproxy 以接受和平衡 magnum APIs。

  5. 重新加载 HAproxy。

必须在每个控制器上进行的关键配置如下。首先,在每个控制器上,主机参数应该是管理网络上接口的 IP:

 [api]
 host = 10.21.22.6

如果未安装Barbican(专门用于管理密码等秘密的 OpenStack 项目),则必须由**x509keypair**插件处理证书:

 [certificates]
 cert_manager_type = x509keypair

然后,需要一个数据库连接字符串。在这个 HA 设置中,MySQL 在 VIP10.21.22.2上响应:

 [database]
 connection=mysql://magnum:password@10.21.22.2/magnum

Keystone 身份验证配置如下(选项相当不言自明):

 [keystone_authtoken]
 auth_uri=http://10.21.22.2:5000/
 memcached_servers=10.21.22.4:11211,
    10.21.22.5:11211,10.21.22.6:11211
 auth_type=password
 username=magnum
 project_name=services
 auth_url=http://10.21.22.2:35357/
 password=password
 user_domain_id = default
 project_domain_id = default
 auth_host = 127.0.0.1
 auth_protocol = http
 admin_user = admin
 admin_password =
 admin_tenant_name = admin

必须配置 Oslo(消息代理)以进行消息传递:

 [oslo_messaging_notifications]
 driver = messaging

Rabbitmq 的配置是这样的,指定 Rabbit 集群主机(因为 Rabbit 在控制器上运行,所以所有控制器的管理网络的 IP):

 [oslo_messaging_rabbit]
 rabbit_hosts=10.21.22.6:5673, 10.21.22.4:5673, 10.21.22.5:5673
 rabbit_ha_queues=True
 heartbeat_timeout_threshold=60
 heartbeat_rate=2
 rabbit_userid=magnum
 rabbit_password=A3elbTUIqOcqRihB6XE3MWzN

最后,受托人的额外配置如下:

 [trust]
 trustee_domain_name = magnum
 trustee_domain_admin_name = magnum_domain_admin
 trustee_domain_admin_password = magnum

在进行此重新配置后,必须重新启动 Magnum 服务:

 service magnum-api restart
 service magnum-conductor restart

Magnum 默认使用端口tcp/9511,因此必须在 iptables 中接受到该端口的流量:修改 iptables 以添加此规则:

 -A INPUT -s 10.21.22.0/24 -p tcp -m multiport --dports 9511 -m 
    comment --comment "117 magnum-api from 10.21.22.0/24" -j ACCEPT

就在其他 OpenStack 服务之后,在116 openvswitch db之后。

现在,是时候配置 HAproxy 来接受 magnum 了。在所有控制器的/etc/haproxy/conf.d中添加一个180-magnum.cfg文件,内容如下:

 listen magnum-api
 bind 10.21.22.2:9511
 http-request  set-header X-Forwarded-Proto https if { ssl_fc }
 option  httpchk
 option  httplog
 option  httpclose
 option  http-buffer-request
 timeout  server 600s
 timeout  http-request 10s
 server node-1 10.21.22.6:9511  check inter 10s fastinter 2s 
      downinter 3s rise 3 fall 3
 server node-2 10.21.22.5:9511  check inter 10s fastinter 2s 
      downinter 3s rise 3 fall 3
 server node-3 10.21.22.4:9511  check inter 10s fastinter 2s 
      downinter 3s rise 3 fall 3

这将配置 magnum-api 侦听 VIP10.21.22.2:9511,支持三个控制器。

紧接着,必须从 Pacemaker 重新启动 HAproxy。从任何控制器上运行:

 pcs resource disable p_haproxy

等待直到所有控制器上没有运行 HAproxy 进程(您可以使用ps aux进行检查),但这应该非常快,不到 1 秒,然后:

 pcs resource enable p_haproxy

之后,Magnum 将可用并且服务已启动:

 source openrc
 magnum service-list

配置 HA Magnum 安装

在 Magnum 上创建一个 Swarm 集群

创建一个 Swarm 集群,当 COE 被添加到 Magnum 时,将需要执行以下步骤:

  1. 创建一个 Swarm 模板。

  2. 从模板启动一个集群。

我们不会深入研究尚不存在的东西,但命令可能是这样的:

 magnum cluster-template-create \
 --name swarm-mode-cluster-template \
 --image-id ubuntu_xenial \
 --keypair-id fuel \
 --fixed-network private \
 --external-network-id public \
 --dns-nameserver 8.8.8.8 \
 --flavor-id m1.medium \
 --docker-volume-size 5 \
 --coe swarm-mode

在这里,定义了一个基于 Ubuntu Xenial 的m1.medium flavors 的 swarm-mode 类型的集群模板:VM 将注入 fuel 密钥对,将具有额外的外部公共 IP。基于这样一个模板创建集群的 UX 可能会是:

 magnum cluster-create --name swarm-mode-cluster \
       --cluster-template swarm-mode-cluster-template \
       --manager-count 3 \
       --node-count 8

在这里,使用三个管理节点和五个工作节点实例化了一个 Swarm 集群。

Magnum 是一个很棒的项目,在 OpenStack 上以最高级别的抽象层次运行容器编排。它在 Rackspace 云上运行,并且通过 Carina 可以供公众使用,参见blog.rackspace.com/carina-by-rackspace-simplifies-containers-with-easy-to-use-instant-on-native-container-environment

总结

在本章中,我们探讨了可以运行 Docker Swarm 集群的替代平台。我们使用了最新的 Docker 工具--Docker for AWS 和 Docker for Azure--并用它们来演示如何以新的方式安装 Swarm。在介绍了 Docker Datacenter 之后,我们转向了私有云部分。我们在 OpenStack 上工作,展示了如何在其上运行 Docker 主机,如何安装 OpenStack Magnum,以及如何在其上创建 Swarm 对象。我们的旅程即将结束。

下一章也是最后一章将勾勒出 Docker 编排的未来。

第十一章:接下来是什么?

Docker 生态系统正在朝着更大的方向发展,其中 Swarm 将是核心组件之一。让我们假设一个路线图。

供应的挑战

目前还没有官方工具可以在规模上创建一个大型的 Swarm。目前,运营商使用内部脚本,临时工具(如 Belt),配置管理器(如 Puppet 或 Ansible),或编排模板(如 AWS 的 CloudFormation 或 OpenStack 的 Heat),正如我们在前几章中所看到的。最近,Docker For AWS 和 Azure 成为了替代方案。

但这种用例可能会以软件定义基础设施工具包的统一方式来解决。

软件定义基础设施

从容器作为构建模块开始,然后创建系统来设计、编排、扩展、保护和部署不仅仅是应用程序,还有基础设施,长期目标可能是可编程互联网

在 SwarmKit 之后,Docker 于 2016 年 10 月开源了Infrakit,这是用于基础设施的工具包。

Infrakit

虽然 Docker Engine 的重点是容器,Docker Swarm 的重点是编排,但 Infrakit 的重点是作为基元。组意味着任何对象:宠物、牲畜、unikernels 和 Swarm 集群。

Infrakit 是解决在不同基础设施中管理 Docker 的问题的答案。在 Infrakit 之前,这是困难的,而且不可移植。其想法是提供一致的用户体验,从设计数据中心到运行裸容器。Infrakit 是由 Docker 创建可编程基础设施的当前最高级抽象,并且它自己描述为:

"InfraKit 是一个用于创建和管理声明式、自愈基础设施的工具包。它将基础设施自动化分解为简单的、可插拔的组件。这些组件共同工作,积极确保基础设施状态与用户的规格说明相匹配。"

在堆栈中的 Infrakit 靠在容器引擎的侧面。

Infrakit

组织是按组来划分的。有一个用于 Infrakit 自身结构的组,由保持配置的管理器组成。每次只有一个领导者,例如,有两个追随者。每个管理器都包括一些组声明。组可以是牛群、宠物、蜂群、unikernels 等。每个组都用实例(例如容器这样的真实资源)和 flavor(资源类型,例如 Ubuntu Xenial 或 MySQL Docker 镜像)来定义。

Infrakit 是声明性的。它依赖于 JSON 配置,并在内部使用封装和组合的众所周知的模式,以使配置成为处理和使基础设施收敛到特定配置的输入。

Infrakit 的目标是:

  • 提供统一的工具包来管理组

  • 可插拔

  • 提供自我修复

  • 发布滚动更新

组抽象了对象的概念。它们可以是任何大小和规模的组,并且可以扩展和缩小,它们可以是具有命名宠物的组、无名牛群、Infrakit 管理器本身和/或所有上述内容的组。目前,在 Infrakit 中只有一个默认的组配置(默认插件),但以后可能会出现新的组定义。默认组是一个接口,公开了诸如观察/取消观察(启动和停止组)、执行/停止更新、更改组大小等操作。

组由实例组成。它们可以是诸如 VM 或容器之类的物理资源,也可以是其他服务的接口,例如 Terraform。

在实例上可以运行 flavor,例如 Zookeeper、MySQL 或 Ubuntu Xenial。

组、实例和 flavor 是可插拔的:它们实际上作为可以用任何语言编写的插件运行。目前,Infrakit 提供了一些 Go 代码,编译后会生成一组二进制文件,例如 cli,可用于控制、检查和执行组、实例和 flavor 的操作,以及插件二进制文件,例如 terraform、swarm 或 zookeeper。

Infrakit 被认为能够管理不一致性,通过持续监控、检测异常并触发操作。这种特性称为自我修复,可以用来创建更健壮的系统。

Infrakit 支持的主要操作之一将是发布滚动更新以更新实例。例如,更新容器中的软件包、更新容器镜像,或者可能通过使用TUFThe Update Framework)来实现,这是下一节中描述的一个项目。

Infrakit 在撰写时还处于早期和年轻阶段,我们无法展示任何不是 Hello World 的示例。在互联网上,很快就会充满 Infrakit Hello World,Infrakit 团队本身发布了一份逐步教程,以便使用文件或 Terraform 插件。我们可以将其描述为 Docker 生态系统中的架构层,并期望它能够部署甚至是 Swarms,为主机提供服务并相互连接。

预计 Infrakit 将被包含在 Engine 中,可能作为版本 1.14 中的实验性功能。

TUF - 更新框架

在柏林的 Docker Summit 16 上,还讨论了另一个话题,TUF (theupdateframework.github.io/),这是一个旨在提供安全的更新方式的工具包。

有许多可用的更新工具,可以在实践中进行更新,但 TUF 更多。从项目主页:

“TUF 帮助开发人员保护新的或现有的软件更新系统,这些系统经常容易受到许多已知攻击的影响。TUF 通过提供一个全面、灵活的安全框架来解决这个普遍问题,开发人员可以将其与任何软件更新系统集成。”

TUF 已经集成到 Docker 中,该工具称为 Notary,正如我们在第九章中看到的,保护 Swarm 集群和 Docker 软件供应链,可以使用 Notary。Notary 可用于验证内容并简化密钥管理。使用 Notary,开发人员可以使用密钥离线签署其内容,然后通过将其签名的可信集合推送到 Notary 服务器来使内容可用。

TUF 是否会作为滚动更新机制合并到 Docker Infrakit 中?那将是另一个惊人的进步。

Docker stacks 和 Compose

另一个可供开发人员使用但仍处于实验阶段的 Docker 功能是 Stacks。我们在第六章中介绍了 Stacks,在 Swarm 上部署真实应用。它们将成为在 Swarms 上部署应用程序的默认方法。与启动容器不同,想法是将容器组打包成捆绑包,而不是单独启动。

此外,还可以预期 Compose 与新的 Swarm 之间的新集成。

CaaS - 容器即服务

在 XaaS 领域,一切都被视为软件,不仅容器是一流公民,编排系统和基础设施也是如此。所有这些抽象将导致以云定义的方式运行这些工具生态系统:容器即服务。

CaaS 的一个例子是 Docker Datacenter。

Unikernels

SwarmKit 作为一个工具包,不仅可以运行容器集群,还可以运行 unikernels。

unikernels 是什么,为什么它们如此奇妙?

如果你使用 Docker For Mac,你已经在使用 unikernels。它们是这些系统的核心。在 Mac 上,xhyve,一个 FreeBSD 虚拟化系统(bhyve)的端口,在 unikernel 模式下运行 Docker 主机。

我们都喜欢容器,因为它们小巧快速,但是有一个机制来抽象内核并使其组件(容器)共享系统资源、库、二进制文件的安全隐患确实令人担忧。只需在任何搜索引擎上查找有关容器安全性的 CVE 公告。这是一个严重的问题。

unikernels 承诺对软件架构进行最高级别的重新评估。这里很快解释了这一点。有一种有效的方法来保证最大的安全性,由于它们的性质,它们以非常非常小的尺寸运行。在我们谈论 Terabytes、Petabytes 甚至更大的世界中,你会惊讶地知道,类似 ukvm 的 KVM 的 unikernel 实现可以适应 67Kb(千字节),Web 服务器二进制文件可以达到 300Kb,或者操作系统镜像可以达到几兆字节的数量级。

这是可能的,因为 unikernels 基本上不会将所有系统调用暴露给堆栈,而是将这些调用包含在二进制文件中。一个ping二进制文件不需要任何系统调用来访问磁盘,使用加密函数或管理系统进程。那么为什么不切断 ping 的这些调用,并为其提供它所需的最小功能呢?这就是 unikernels 背后的主要思想。ping 命令将与一些网络 I/O、原始套接字一起编译在内部,仅此而已。

使用 unikernels 时,内核和用户空间之间没有区别,因为地址表是统一的。这意味着地址表是连续的。正如前面解释的那样,这是可能的,因为 unikernel 二进制文件是通过嵌入它们需要的系统功能(如 I/O 操作、内存管理或共享库)在二进制内部编译的。在传统的操作系统模型中,应用程序在运行时查看和使用系统调用,而使用 unikernels 时,这些系统调用在编译时静态链接。

Unikernels

乍一看这可能看起来很奇怪,但这在进程隔离和安全性方面是一项巨大的进步。即使有人能够欺诈性地介入运行 unikernel 内容的某个系统中,她几乎不可能找到任何安全漏洞。攻击面是如此之小,以至于几乎不可能存在任何可利用的未使用的系统调用或功能,除了已经加固的可能正在使用的功能。没有 shell 可调用,没有外部实用程序库或脚本,没有配置或密码文件,没有额外的端口绑定。

那么 unikernels 和 Docker 呢?

在巴塞罗那的 DockerConEU 15 上,一些人跳上舞台展示如何将 Docker 与 unikernels 集成,最终 Docker Inc.收购了该公司,签署了 Docker For Mac 的诞生等其他事项。

在柏林的 Docker Summit 16 上,有人提到 unikernels 可以与 SwarmKit 中的容器一起运行。集成的未来即将到来。

为 Docker 做贡献

Docker 中的所有这些创新都是可能的,因为这些项目依赖于一个非常广泛的社区。Docker 是一个非常密集和活跃的项目,分成几个 Github 存储库,其中最值得注意的是:

但是,这些项目也无法在没有它们的库的情况下运行,比如 Libcontainer、Libnetwork、Libcompose(等待与 Compose 合并)等等。

所有这些代码都不会存在,没有 Docker 团队和 Docker 社区的承诺。

Github

鼓励任何公司或个人为项目做出贡献。在github.com/docker/docker/blob/master/CONTRIBUTING.md上有一些指南。

文件问题

一个很好的开始是通过在相关项目的 GitHub 空间上开放问题来报告异常、错误或提交想法。

代码

另一个受到赞赏的帮助方式是提交拉取请求,修复问题或提出新功能。这些 PR 应遵循并参考记录在 Issues 页面上的一些问题,符合指南。

Belt 和其他项目

此外,除了这本书,还有许多小的并行项目开始了:

  • Swarm2k 和 Swarm3k,作为社区导向的实验,旨在创建规模化的 Swarm。一些代码、说明和结果可在www.github.com/swarmzilla的相应存储库中找到。

  • Belt 作为 Docker 主机供应商。目前,它只包括 DigitalOcean 驱动程序,但可以进一步扩展。

  • 用于 Swarm、Machine 和 Docker 证书的 Ansible 模块,可用于 Ansible play books。

  • 容器推送到 Docker Hub 以说明特定组件(如fsoppelsa/etcd)或引入新功能(如fsoppelsa/swarmkit)。

  • 其他次要的拉取请求、黑客和代码部分。

秉承开源精神,上述所有内容都是免费软件,非常感谢任何贡献、改进或批评。

总结

最后,简要介绍一下这本书的历史,以及关于 Docker 开发速度的惊人之处。

当编写有关 Docker Swarm 的书籍项目刚起草时,当天只有旧的 Docker Swarm 独立模式,其中 Swarm 容器负责编排容器基础设施,必须依赖外部发现系统,如 Etcd、Consul 或 Zookeeper。

回顾这些时期,就在几个月前,就像在思考史前时期。就在 6 月,当 SwarmKit 作为编排工具开源并被包含到引擎中作为 Swarm Mode 时,Docker 在编排方面迈出了重要的一步。一个完整的、可扩展的、默认安全的、以及本地编排 Docker 的简单方式被发布。然后,结果证明最好的 Docker 编排方式就是 Docker 本身。

但是当 Infrakit 在 2016 年 10 月开源时,基础设施方面迈出了一大步:现在不仅编排和容器组是基本元素,而且其他对象的组,甚至混合在原始 Infrakit 意图中:容器、虚拟机、unikernels,甚至裸金属。

在(不久的)未来,我们可以期待所有这些项目被粘合在一起,Infrakit 作为基础设施管理器,能够提供 Swarm(任何东西),在其中容器或其他对象被编排、互联、存储(完全状态)、滚动更新、通过覆盖网络互联,并得到保护。

Swarm 只是这个大局生态系统的开始。

posted @ 2024-05-06 18:32  绝不原创的飞龙  阅读(27)  评论(0编辑  收藏  举报