在 6 月 6 日召开的 Systems @Scale 大会上,我们展示了名为 Tupperware 的集群管理系统。它可以在数百万台服务器上编排容器,足以运行我们几乎全部的服务。自 2011 年 Tupperware 首次开始部署以来,我们的基础架构已从一个数据中心扩展到了 15 个分布在各地的数据中心上。这些年来 Tupperware 也在迅速发展,紧跟我们前进的步伐。本文将介绍 Tupperware 在集群管理领域的最新技术进展,包括对有状态服务的无缝支持、跨数据中心的统一控制面,以及实时在服务之间迁移容量的能力。我们还会回顾一些在 Tupperware 发展至今的历程中总结到的经验和教训。
Tupperware 服务于多个利益相关方。应用开发者使用 Tupperware 来部署和管理应用。它将应用的代码和依赖项打包到镜像中,并将其作为容器部署到服务器上。容器在同一服务器上运行的多个应用之间提供隔离,使开发者可以专注于应用逻辑,而无需操心获取服务器或应用升级等事项。此外,Tupperware 还能监控服务器运行状况,并在检测到故障时将容器移离受影响的服务器。
容量工程师使用 Tupperware 根据预算约束和业务优先级来跨团队分配服务器容量。他们还利用它来进一步提升服务器利用率。数据中心运营商依靠 Tupperware 在整个数据中心内正确传播容器,并在维护期间停止或迁移容器。Tupperware 能帮助数据中心运营商尽可能自动化维护服务器、网络和设施。
Tupperware 架构
Tupperware 架构。PRN 是我们其中的一个数据中心区域。每个区域由彼此相邻的多栋数据中心建筑(PRN1 和 PRN2)组成。我们计划在每个区域只用一个控制面来管理区域内的所有服务器。
应用开发者将服务部署为 Tupperware 作业,每个作业由多个容器组成,通常都运行相同的应用二进制文件。Tupperware 负责容器分配和容器生命周期管理。它由以下组件组成:
Tupperware 前端,负责为 UI、CLI 和其他自动化工具提供 API,以便与 Tupperware 交互。它对作业所有者隐藏了 Tupperware 的内部细节。
Tupperware 调度器,是负责管理作业和容器生命周期的控制面。它可以在区域和全局作用域上部署,其中区域调度器管理同一区域的服务器,全局调度器管理多个区域的服务器。调度器是分片的,每个分片管理其作用域中的作业子集。
Tupperware 调度器代理,它隐藏了调度器分片的内部细节,为 Tupperware 用户提供了单一控制面的抽象和可用性。
Tupperware 分配器,负责将容器分配给服务器。容器启动、停止、更新和故障转移的编排则由调度器完成。现在一个分配器就足以处理整个区域而无需分片了,扩展能力很出色。(注意这里的术语与其他系统的一些差异。例如,Tupperware 调度器对应的是 Kubernetes 控制面,而 Tupperware 分配器对应 Kubernetes 的调度器。)
Resource Broker(资源代理),为服务器信息和维护事件存储数据源。我们为每个数据中心设置一个 Resource Broker,它会存储数据中心内所有服务器的信息。Resource Broker 和一个名为资源审批系统的容量管理系统协同工作,动态决定由哪个调度器部署管理哪些服务器。一个健康状况检查服务会监控服务器,并将服务器的运行状况信息存储在 Resource Broker 中。如果服务器运行状况不佳或需要维护,Resource Broker 会通知分配器和调度器停止容器,或将容器迁移到其他服务器上。
Tupperware 代理,是在所有服务器上运行的守护程序,负责设置和移除容器。应用在容器内运行,以提供更好的隔离性和可复用性。我们在去年的Systems @Scale大会上介绍了如何使用 images、btrfs、cgroupv2 和 systemd 设置单个 Tupperware 容器。
Tupperware 的特色
虽然 Tupperware 与其他集群管理系统(如 Kubernetes 和Mesos)有许多类似的功能,但前者也有自己的许多特色:
无缝支持有状态服务。
单个控制面即可跨数据中心管理服务器,以自动执行基于意图的容器部署、集群淘汰和维护等工作。
控制面可分片,分片过程完全透明,提供良好的扩展能力。
使用一种弹性计算方法,用于实时在服务之间迁移容量。
这些高级功能是为了支持在大型全局共享的服务器群中运行的各种无状态和有状态应用需求而诞生的。
无缝支持有状态服务
Tupperware 正在运行许多重要的有状态服务,为 Facebook、Instagram、Messenger 和 WhatsApp 的产品持久存储数据。相关案例包括大键值存储(例如ZippyDB)和监控数据存储(例如ODS Gorilla和Scuba)。支持有状态服务是一项挑战,因为系统需要确保容器部署能够抵御大规模故障(诸如网络分区和断电等事件)。虽然常见策略(例如跨容错域传播容器)很适合无状态服务,但有状态服务需要额外的支持手段。
例如,如果服务器故障导致数据库的一个副本不可用,我们是否应该允许自动维护策略对 10,000 台服务器池中的 50 台服务器升级内核?具体情况要具体分析。如果这 50 台服务器中恰好有一台托管了这个数据库的另一个副本,那么最好等一等,避免同时丢失两个副本。控制维护和系统健康状况管理系统要做出动态决策,就需要了解每个有状态服务的内部数据复制和放置逻辑。
一个名为 TaskControl(任务控制)的接口允许有状态服务权衡可能影响数据可用性的决策。调度器利用该接口对外部应用通知容器生命周期操作信息,例如重新启动、更新、迁移和维护事件。还有一个有状态服务实现了一个控制器,该控制器指示 Tupperware 何时可以安全执行这些操作,可能还会根据需要重新排序或暂时延迟操作。在上面的示例中,数据库的控制器会通知 Tupperware 可以升级 50 台服务器中的 49 台,但暂时只留一台指定的服务器(X)。最后,如果到了内核升级截止日期后数据库仍然无法恢复失败的副本,Tupperware 就会继续升级服务器 X.
在 Tupperware 上运行的许多有状态服务通过 ShardManager(分片管理器)间接使用 TaskControl,ShardManager 是一种广泛应用的编程框架,用来在 Facebook 上构建有状态服务。Tupperware 允许开发者指定自己的容器如何在数据中心中传播的意图;ShardManager 允许开发者指定自己的数据分片如何跨容器传播的意图。ShardManager 知道其应用的数据存储和复制情况,还通过 TaskControl 与 Tupperware 协同工作来规划容器的生命周期操作,而无需应用直接参与。这种集成极大地简化了有状态服务的管理工作,但并非所有 TaskControl 都支持它。例如,我们的大型 Web 层是无状态的,并使用 TaskControl 动态调整跨容器的更新速度。因此,Web 层可以每天快速发布多个软件版本,而不会影响可用性。
跨数据中心管理服务器
回到 2011 年,Tupperware 刚刚投入使用时每个服务器集群都由一个独立的专用调度器管理。那时,Facebook 的一个集群是一组连接到公共网络交换机的服务器机架,一个数据中心托管多个集群。每个调度器只能管理一个集群的服务器,意味着作业无法跨越集群。随着我们基础设施的发展,集群淘汰也愈加频繁。由于 Tupperware 无法将作业从待淘汰集群中透明地迁移到其他集群上,因此淘汰任务需要大量的手动操作,还要求应用开发者和数据中心操作员之间谨慎配合。这一过程往往需要服务器闲置数月之久,因此导致了大量的资源浪费。
我们引入了 Resource Broker 来解决这个集群淘汰问题,并协调其他所有类型的维护事件。Resource Broker 跟踪与服务器关联的所有物理信息,并动态决定由哪个调度器管理哪台服务器。将服务器动态绑定到调度器后,调度器就具备了跨数据中心管理服务器的灵活性。由于 Tupperware 作业不再局限于单个集群,因此 Tupperware 用户可以声明他们对容器如何跨容错域传播的意图。例如,开发者可以声明她的意图(比如在 PRN 区域中的两个容错域中运行我的作业)而不指定她要使用哪片可用区域。即使在集群停用或维护操作的情况下,Tupperware 也会负责寻找合适的服务器来满足这一意图。
支持大型全局共享服务器群的扩展能力
在过去,我们的基础架构被划分为数百个专用服务器池,由众多独立团队管理。碎片化和非标准化推高了运营开销,也让空闲服务器更难复用。在去年的 Systems @Scale 上我们公布了基础架构即服务,它将我们的基础架构整合到一个庞大的全局共享服务器群中。但这个共享群也面临着许多复杂的需求和全新的挑战。
可扩展性:随着我们在每个区域内添加更多的数据中心,基础设施架构也随之不断扩展。此外,我们转向使用更小和更节能服务器的硬件变革使每个区域需要托管更多的服务器。结果,每个区域只部署一个调度器就不足以编排区域中数十万台服务器上运行的海量容器了。
可靠性:即使调度器具备高度可扩展能力,但每个调度器的作用域越大也意味着出现软件错误的风险越大,整个容器区域也就更难管理。
容错能力:如果发生大规模基础架构故障,例如网络分区或运行调度器的服务器出现断电故障,我们希望将负面影响控制在区域服务器群的一小块范围中。
可用性:上述需求可能要求我们在每个区域部署多个独立的调度器。但从可用性的角度来看,为每个区域的共享池设置单个入口点又能简化许多容量管理和作业管理工作流。
于是我们引入了调度器分片来解决支持大型共享池的挑战。每个调度器分片管理该区域中的一个作业子集,降低了与每个部署相关的风险。随着共享池不断扩大,我们可以根据需要添加更多调度器分片。Tupperware 用户将调度器分片和代理视为单个控制面,而无需与编排其作业的众多调度器分片交互。请注意,调度器分片与我们以往的集群调度器本质上是不同的,因为前者是对控制面分片,而不会通过网络拓扑静态分片共享服务器池。
通过弹性计算提高利用率
随着我们的基础设施架构不断扩张,提升服务器群的利用率也愈加重要,提升利用率可以降低基础设施成本并减轻运营负担。提高服务器利用率有两大手段:
弹性计算方法,在非高峰时段缩减在线服务的规模,并将释放的服务器提供给离线工作负载,例如机器学习和 MapReduce 作业。
资源过载方法,是在同一服务器上堆叠在线服务和批处理工作负载,并以较低优先级运行批处理工作负载。
我们数据中心的一项有限资源就是电力。因此我们更倾向于使用小型而节能的服务器,从而提供更多的计算能力。大量使用装备较少 CPU 和内存的小型服务器会产生一个副作用,就是降低了资源过载方法的效率。虽然我们可以在单台服务器上堆叠多个对 CPU 和内存需求较低的小型服务容器,但大型服务堆叠在小型服务器上的性能表现就不好了。因此,我们鼓励大型服务的开发者高度优化他们的服务以利用所有服务器。
我们主要通过弹性计算实现高利用率。一些规模最大的服务——例如新闻推送、消息传递和我们的前端 Web 层——都明显表现出一种日常模式,也就是在非高峰时段中利用率显著下降。所以在非高峰时段,我们减少了支持这些在线系统的服务器数量,并将释放的服务器提供给离线工作负载,例如机器学习和 MapReduce 作业。
我们的经验是,最好将整台服务器作为弹性容量的基本调度单位,因为大型服务既是弹性容量的最大提供者和消费者,又对每台服务器做了深度优化。当非高峰时段某台服务器从在线服务中释放时,Resource Broker 会将它借给调度器来运行离线工作负载。如果在线服务又碰上了负载高峰,Resource Broker 会快速调回借用的服务器,并与调度器一起将服务器交还给在线服务。
经验教训和未来工作
在过去的八年中 Tupperware 不断进化发展,跟上了 Facebook 快速增长的步伐。我们正在分享我们学到的一些经验教训,希望能够为运营快速发展的基础设施的同行提供帮助:
控制面与其管理的服务器之间最好使用弹性映射。这种灵活性使一个控制面能够跨数据中心管理服务器,自动化停用和维护集群,并通过弹性计算实现动态容量迁移。
每个区域只有一个控制面抽象极大地提高了作业所有者的可用性,并简化了大型共享机群的管理工作。就算控制面需要为可扩展性和容错能力在内部分片,对外也可以保持单个入口点的抽象。
控制面可以利用插件模型向外部应用通知即将到来的容器生命周期操作。此外,有状态服务可以利用插件接口来定制容器管理。这种插件模型简化了控制面,同时高效地提供多种不同的有状态服务。
我们发现弹性计算——也就是将整台服务器从在线服务中释放出来以供批处理、机器学习和其他容忍高延迟服务使用的过程——是一种能有效提高服务器利用率的方法,尤其适合大批量使用小型、节能的服务器的情况。
我们实现大型全局共享机群的目标还有很长的路要走。目前我们的共享池中有大约 20%的服务器。要达到 100%共享的目标还需要解决许多挑战,包括为存储系统创建共享池支持、自动化维护、添加多租户需求控制、提高服务器利用率以及增强对机器学习工作负载的支持等等。我们希望将来能解决这些问题并分享我们的进展。
原文链接:
https://code.fb.com/data-center-engineering/tupperware/
评论