尽管复杂,Kubernetes 仍然是目前最流行的编排器,但 HashiCorp 在 Nomad 上的成功也表明,Kubernetes 的替代方案还有发展空间。
有些用户仍然热衷于 Docker Swarm 的简单性,但它的未来存在不确定性,其他替代方案看上去已经基本被放弃了。现在的生态似乎主要围绕着 Swarm、Kubernetes 和 Nomad 这三个玩家,但容器编排仍然是一个相对不那么成熟的领域。十年前,这种技术几乎还不存在,现在仍在快速发展。容器编排领域可能还会出现许多令人兴奋的新想法和新发展。
本文最初发布于 LWN.net。
Docker及其他容器引擎可以从许多方面简化服务端应用程序的部署,但许多应用程序不只包含一个容器。随着部署的应用程序和服务增加,管理一组容器的难度越来越大,一类名为容器编排器的工具由此发展了起来,截至目前最著名的是Kubernetes,容器编排的历史以它为界也分成了前后两段。
在享受容器带来的便利的同时,我们也要做好一些权衡取舍。如果一个人严格遵守 Docker“每个服务都应有自己的容器”的理念,那么最终他将运行海量的容器。即使是一个访问数据库的简单 Web 界面也可能需要为数据库服务器和应用程序运行单独的容器,它可能还包括一个单独的 Web 服务器容器用于提供静态文件服务、一个单独的代理服务器容器用于终止 SSL/TLS 连接、一个键/值存储容器充当缓存,或者一个辅助应用程序容器用于处理后台作业及计划任务。
一位管理员如果负责了数个这样的系统,很快就会发现需要一个工具来简化自己的工作,这就是容器编排器的用途所在了。
容器编排器是一个工具,它可以将一组容器当成一个单元来管理。编排器让你可以将多台服务器合并成一个集群,并自动在集群节点之间分配容器工作负载,而不是单独在一台服务器操作。
Docker Compose 和 Swarm
Docker Compose 称不上是一个不完全的编排器,但这是 Docker 第一次尝试创建一个工具来简化多容器应用程序的管理。Compose 读取一个 YAML 文件,通常命名为 docker-compose.yml,并使用 Docker API 创建文件中声明的资源;Compose 还会为所有资源添加标签,以便在创建完成后把它们当成一个组来管理。实际上,它是 Docker 命令行接口(CLI,可操作容器组)的一个替代方案。Compose 文件可以定义三类资源:
服务(services):声明要启动的容器,其中每一条都相当于一个 docker run 命令。
网络(networks):声明可以附加到容器(Compose 文件中定义的)的网络,其中每一条相当于一个 docker network create 命令。
卷(volumes): 定义可以附加到容器的命名卷。在 Docker 术语中,卷是可以挂在到容器的持久存储。命名卷由 Docker daemon 管理。其中每一条相当于一个 docker volume create 命令。
网络和卷可以直接连接到 Docker 所在主机的网络和文件系统,也可以通过插件提供。网络插件可以帮我们实现像将容器连接到 VPN 这样的事情;卷插件可以帮我们将卷存储在一台 NFS 服务器或一个对象存储服务上。
在管理多容器应用程序方面,Compose 提供了一种方便许多的方式,但在最初的版本中,它只能工作在单台主机上,创建的所有容器也运行在相同的机器上。为了让它能够覆盖多台主机,Docker 在 2016 年推出了 Swarm mode。实际上,这是 Docker 第二个带有“Swarm”字样的产品——2014 年的一款产品实现了一种完全不同的、跨多台主机运行容器的方式,但 Docker 已经不再维护,它被 SwarmKit 所取代,后者是 Docker Swarm 当前版本的基础。
Swarm mode 包含在 Docker 中,无需安装其他软件。创建集群很简单,只需在初始节点上运行 docker swarm init,然后在每个要添加的节点上运行 docker swarm join。Swarm 集群包含两种类型的节点。管理节点提供了一个 API,用于启动集群中的容器,并使用基于Raft一致性算法的协议互相通信,在所有管理节点间同步集群状态。工作节点完成运行容器的具体工作。这些集群可以到多大我们并不是很确定,Docker 的文档说一个集群的管理节点不应超过 7 个,但并没有说明工作节点的数量限制。跨节点桥接容器网络功能是自带的,但跨节点共享存储不是,需要借助第三方卷插件来提供跨节点的共享持久存储。
服务使用 Compose 文件部署到 Swarm 上。Swarm 扩展了 Compose 模式,为每个服务添加了一个 deploy 键,用于指定该服务应该运行多少实例以及应该在哪些节点上运行。遗憾的是,这导致了 Compose 和 Swarm 的分化,进而导致了一些混乱,因为对于像 CPU 和内存配额这样的选项,它们提供了不同的指定方式。
在这段时期内,为了避免歧义,用于 Swarm 的文件被称作“栈文件”,而不是 Compose 文件;所幸,在 Swarm 和 Compose 的当前版本中,这些差异似乎已经被抹平。如果引用的栈文件和 Compose 文件截然不同,那多半是从网上搜来的。现在,Compose 格式已经有了一个开放的规范,并且它自己的 GitHub 社区也提供了参考实现。
Swarm 的未来还存在一定的不确定性。它曾是 Docker Cloud 服务的基础,但该服务2018年突然关闭了;它也曾被认为是 Docker 企业版的关键特性,但那个产品自此卖给了另外一家公司,它现在的名称为 Mirantis Kubernetes Engine。与此同时,Compose 的最新版本已经具备向亚马逊和微软托管服务部署容器的能力。一直没有弃用声明,但记忆中也没有任何其他类型的公告。在 Docker 的网站上搜索“Swarm”这个词,也只能够搜到过去一些曾提及它的信息。
Kubernetes
Kubernetes (有时候称为 k8s)项目的灵感来自谷歌内部的一个工具Borg。Kubernetes 可以在多达数千个节点的集群上管理资源并协调工作负载的运行;它在容器编排领域的统治地位就像谷歌在搜索领域的地位一样。2014 年,谷歌希望与 Docker 合作开发 Kubernetes,但 Docker 决定走自己的路,继续开发 Swarm。然而,Kubernetes 在云原生计算基金会(CNCF)的主持下发展壮大。截至 2017 年,Kubernetes 已经变得如此流行,以至于 Docker 也宣布将其集成到自己的产品中。
除了受欢迎之外,Kubernetes 主要以其复杂性而闻名。手动配置一个新集群是一项非常复杂的任务,除了 Kubernetes 之外,管理员还需要选择并配置多个第三方组件。和 Linux 内核需要结合额外的软件形成完整的操作系统一样,Kubernetes 只是一个编排器,它需要结合其他的软件才能组成一个完整的集群。它需要一个容器引擎来运行容器;它还需要网络插件以及持久卷插件。
Kubernetes发行版的存在填补了这一空白。和 Linux 发行版一样,Kubernetes 发行版也将 Kubernetes 与安装程序以及精心挑选的第三方组件捆绑在一起。不同发行版的存在是为了满足不同的细分市场;似乎每个具有一定规模的公司都有自己的发行版和/或托管服务,以迎合企业的需求。minikube 项目让开发人员可以轻松地在本地搭建一个试验环境。和 Linux 发行版不同,Kubernetes 发行版经过了 CNCF 的一致性认证;为了得到认证,每个发行版都必须实现基本的功能,这样它们才能使用“Certified Kubernetes”标识。
一个 Kubernetes 集群包含多个软件组件。集群中的每个节点都运行着一个名为 kubelet 的代理,用于维护集群的成员关系并从它接收工作。它还运行着一个容器引擎和 kube-proxy,后者负责与在其他节点上运行的容器进行网络通信。
维护集群状态的组件以及决定资源分配的组件合称为控制平面——这包括一个名为etcd的分布式键值存储,一个给集群节点分配工作的调度器,一个或多个响应集群状态变化的控制器进程,负责触发所需的操作,使集群的状态符合预期。用户和集群节点通过 Kubernetes API Server 与控制平面交互。要实现修改,用户会通过 API Server 设定期望的集群状态,而 kubelet 会向控制器进程报告每个集群节点的实际状态。
Kubernetes 在一个名为 pod 的抽象中运行容器,其中可以包含一个或多个容器,不过,并不建议在一个 pod 中运行多个服务的容器。相反,一个 pod 通常只有一个提供服务的主容器,可能会有一个或多个“边”容器负责从运行服务的主容器中收集指标或日志。一个 pod 中的所有容器都将调度到同一台机器上,共享同一个网络命名空间——在同一 pod 中运行的容器可以通过 loopback 接口相互通信。每个 pod 在集群中都有自己独一无二的 IP 地址。运行在不同 pod 中的容器可以使用它们的集群 IP 地址相互通信。
Pod 指定一组要运行的容器,但 pod 的定义并没有说明在哪里运行这些容器,或者是运行多长时间——没有这些信息,Kubernetes 就会在集群上的随便什么地方启动容器,但不会在它们退出时重启,而且,如果控制平面判断其他工作负载需要它们占用的资源,Kubernetes 可能会终止它们。为此,pod 很少单独使用;取而代之,pod 的定义通常是封装在用于定义持久服务的 Deployment 对象中。和 Compose 及 Swarm 一样,由 Kubernetes 管理的对象是在 YAML 中声明的;对于 Kubernetes,YAML 声明是通过 kubectl 工具提交到集群的。
除了 pod 和 Deployment 之外,Kubernetes 还可以管理许多其他类型的对象,如负载均衡器和授权策略。它支持的 API 清单还在不断增长,根据 Kubernetes 的版本以及集群运行的哪个发行版会有所不同。自定义资源可以用于向集群添加 API,用于管理额外的对象类型。例如,KubeVirt添加的 API 使 Kubernetes 可以运行虚拟机。可以通过kubectl api-versions命令查看特定集群支持的完整 API 清单。
和 Compose 不同,这些对象中的每一种都是在单独的 YAML 文档中声明的,虽然多个 YAML 文档可以内联到一个文件中,并用“---”隔开(参加Kubernetes文档)。一个复杂的应用程序可能包含许多对象,它们的定义分散在多个文件中;在维护这样一个应用程序时,保持所有这些定义彼此同步会非常繁琐。为了简化这项工作,有些 Kubernetes 管理员转而采用像Jsonnet这样的模板工具。
Helm让模板工具更上一层楼。和 Kubernetes 类似,Helm 也是在 CNCF 的主持下开发的;它号称是“Kubernetes 包管理器”。Helm 从一套名为 chart 的模板和变量声明生成 Kubernetes 的 YAML 配置。它使用的模板语言不同于 Ansible 使用的Jinja模板,但看上去非常相似;熟悉Ansible Roles的人看到 Helm Charts 也会感觉很熟悉。
Helm Charts 集可以发布到Helm存储库;Artifact Hub提供了一个很大的公共 Helm 存储库目录。管理员可以将这些存储库添加到他们的 Helm 配置中,使用已经编制好的 Heml chart 将流行应用的预打包版本部署到集群上。Helm 的最新版本还支持向容器注册中心推送或从注册中心拉取 chart,为管理员提供了一个选型,让他们可以将 chart 和容器镜像存储在一起。
Kubernetes 的增长势头还没有任何减缓的趋势。按照设计,它可以管理任何类型的资源;这种灵活性(已为 KubeVirt 虚拟机控制器所证明)使得,即使容器化工作负载最终不再流行,Kubernetes 仍然有继续存在的价值。开发有序进行,新的主版本定期发布。版本支持周期为 1 年;似乎没有长期支持版本。集群可以升级,但有人喜欢新建一个集群,并将服务迁移过去。
Nomad
Nomad是 HashiCorp 推出的一个编排器,号称是一个比 Kubernetes 更简单的替代方案。和 Docker 及 Kubernetes 类似,Nomad 是一个开源项目。它包含一个名为 nomad 的二进制文件,可以用于启动一个名为代理的守护进程。它还提供了一个 CLI,用于和代理通信。根据配置方式不同,代理进程可以在两种模式下运行。在服务器模式下运行的代理可以接受作业,并为它们分配集群资源。在客户端模式下运行的代理会接收作业,运行它们,并将作业状态报告给服务器。代理还可以在开发模式下运行,同时承担客户端和服务器的角色,成为一个用于测试目的的单节点集群。
创建 Nomad 集群相当简单。在 Nomad 最基本的操作模式中,必须启动初始服务器代理,然后使用 nomad server join 命令向集群添加额外的节点。HashiCorp 还提供了 Consul,这是一个通用的服务网格和发现工具。虽然 Nomad 可以单独使用,但最好是和 Consul 搭配使用。Nomad 代理可以使用 Consul 自动发现并加入一个集群,它还可以执行健康检查,提供 DNS 记录,并为集群上运行的服务提供 HTTPS 代理。
Nomad 支持复杂的集群拓扑。每个集群被划分成一个或多个“数据中心”。和 Swarm 类似,同一数据中心中的服务器代理使用一种基于 Raft 的协议相互通信;这种协议有严格的延迟要求,但多个数据中心可以用 gossip 协议连接起来,从而使信息可以在集群中传播,而又不需要每个服务器与其他服务器保持直接连接。从用户的角度来看,以这种方式连接起来的数据中心就和一个集群一样。这种架构让 Nomad 在扩展到大量集群时颇有优势。按照官方说法,Kubernetes 最多支持 5000 个节点和 3 万个容器,而 Nomad 的文档中提到了一个有 1 万多节点的集群示例和一个有 20 万容器的集群示例。
和 Kubernetes 类似,Nomad 并没有包含一个容器引擎或运行时,它使用任务驱动器来运行作业。它包含使用 Docker 和 Podman 来运行容器的任务驱动器;社区提供了面向其他容器引擎的驱动器。Nomad 的野心也不限于容器,这点也和 Kubernetes 类似。它还提供了面向其他工作负载类型的任务驱动器,包括在主机上运行命令的 fork/exec 驱动器,运行虚拟机的 QEMU 驱动器,启动 Java 应用程序的 Java 驱动器。社区支持的任务驱动器可以将 Nomad 连接到其他类型的工作负载。
与 Docker 或 Kubernetes 不同,Nomad 不使用 YAML,而是使用HashiCorp配置语言(HCL)。HCL 最初是为 HashiCorp 的另一个项目创建的,用于配置名为Terraform的云资源。虽然在其他地方的应用有限,但 HashiCorp 的整个产品线都在使用 HCL。用 HCL 编写的文档很容易转换成 JSON,但它的目标是提供一种比 JSON 更便捷、比 YAML 更不容易出错的语法。
HashiCorp 提供的相当于 Helm 的产品是 Nomad Pack。和 Helm 类似,Nomad Pack 会处理一个满是模板和变量声明的目录,生成作业配置。Nomad 还有一个预打包应用程序的社区注册中心,但其选择空间比 Helm 的 Artifact Hub 要小很多。
Nomad 不像 Kubernetes 那么受欢迎。和 Swarm 一样,它的开发似乎主要是由其创建者推动的;尽管有许多大公司部署了 HashiCorp,但 HashiCorp 仍然是 Nomad 相关社区的中心。目前看来,该项目似乎不太可能获得足够的发展势头,进而从母公司独立出来。对用户来说,与 Docker 在 Swarm 上做的工作相比,HashiCorp 对 Nomad 的开发和推广或许更有保证。
小结
Swarm、Kubernetes 和 Nomad 并不是仅有的容器编排器,但它们是其中最有生命力的三个。Apache Mesos也可以用来运行容器,但它在 2021 年就几乎被封存了;DC/OS基于 Mesos,但很像 Docker 企业版,支持其开发的公司现在也专注于 Kubernetes。其他大多数容器编排项目,如 OpenShift 和 Rancher,实际上只是增强(和认证)的 Kubernetes 发行版,即使它们的名字中没有 Kubernetes。
尽管 Kubernetes 非常复杂,它仍然是目前最流行的编排器,但 HashiCorp 在 Nomad 上的成功表明,替代方案也还有它的发展空间。有些用户仍然热衷于 Docker Swarm 的简单性,但它的未来存在不确定性。至此,其他替代方案看上去已经基本被放弃了。现在的生态似乎主要围绕着这三个玩家,但容器编排仍然是一个相对不那么成熟的领域。十年前,这种技术几乎还不存在,而它现在仍在快速发展。容器编排领域可能还会出现许多令人兴奋的新想法和新发展。
原文链接:
评论