[翻译] Autopilot: Workload Autoscaling at Google Scale
之前还在做容器方向时,利用业余时间人肉翻译的论文,仅供参考。
摘要
在很多公有云和私有云系统中,用户需要指定任务的资源(CPU 和 Memory)使用量的上限,资源使用超出限制的任务可能会被节流或终⽌运行,进而导致终端用户的请求延迟或丢弃,因此管理员针对这种问题出于谨慎考虑,会申请大于任务自身需要的资源上限。在规模很大的时候,将会导致大量的资源浪费。
为了解决这个问题,Google 使⽤ Autopilot ⾃动配置资源,调整并发任务的数量(⽔平伸缩)和单个任务的 CPU/Memory 上限(垂直伸缩)。 Autopilot 和人工操作有着相同的目标:主要是减少 slack(闲置资源:申请的资源与实际使用资源之间的差值),同时最大程度地降低任务因内存不足错误(OOM)被终止或由于 CPU 节流导致的性能下降的风险。Autopilot 使⽤机器学习算法和⼀组经过微调的启发式算法应⽤到任务之前的历史数据上来保持这种平衡。在实践中,使用了 Autopilot 的作业 slack 仅为 23%,⽽⼿动管理的作业 slack 为 46%。此外,Autopilot 将受 OOM 严重影响的作业数量降低了 10 倍。
尽管优势很明显,但确保 Autopilot 被⼴泛采⽤仍需要付出巨⼤的努力,包括让尚未加⼊使用的用户很容易的看到推荐的潜力、⾃动迁移某些种类的任务,以及增加对⾃定义推荐器的⽀持。在撰写本⽂时,使用了 Autopilot 的作业占 Google 容器集群资源使⽤量的 48%以上。
1. 简介
许多公有云和私有云系统要求用户声明他们的作业在执行期间需要多少个实例,以及每个实例所需的资源:在公有云平台中,用户需要选择他们租用的虚拟机(VM)类型和数量;在 Kubernetes 集群中,用户可以设置 pod 的副本数量和单个 pod 资源限制上限;在 Google,我们要求用户指定他们需要的容器数量以及每个容器的资源限制上限。使用云基础架构能够提供⾜够的性能隔离,这些隔离限制使云计算成为可能。
但是这些限制(⼤部分)对用户来说是⼀种麻烦,很难估计⼀个作业需要多少资源才能以最佳⽅式运行: CPU、内存和同时运行的副本数量的正确组合。负载测试可以帮助找到⼀个初步预估,但这些推荐值将随着资源需求的变化⽽变得不适用,因为许多服务终端用户任务具有每日或每周的负载模式,并且随着服务访问量的变化,流量也会在很长的时间内不断变化。
最后,处理给定负载所需的资源会随着底层软件或硬件堆栈的新功能、优化和更新⽽变化。如果 CPU 使用超过申请的资源可能会导致性能下降,或者由于内存用尽而导致任务被终止,这两者都不是我们想要看到的。
因此,理性的用户会故意⾼估他们的作业所需的资源,从而导致物理资源的利⽤率很低。⼀项在 Google 内部⼀个集群 [27] 上执行的为期⼀个⽉的作业跟踪分析 [26] 显⽰平均内存利⽤率为 50%;对 Alibaba 的 YARN 集群的另⼀项分析 [23] 显⽰,任务的峰值内存利⽤率从未超过 80%。
为了应对配置资源的困难,常⻅的模式是采⽤⽔平⾃动伸缩器,它通过添加或删除副本来响应最终用户流量或平均 CPU 利⽤率的变化来扩展 Jobs。所有主要的云提供商(AWS、Azure 和 GCP)都提供⽔平⾃动伸缩功能;在⼀些云上的中间件也可以使⽤,例如:Kubernetes。还有⼀种不太常⻅的模式是使⽤垂直⾃动伸缩来调整每个副本可⽤的资源量,这两种技术也可以结合使用。
Autopilot 是 Google 在其内部云上使⽤的主要⾃动伸缩器,它提供⽔平和垂直⾃动伸缩。这篇论文聚焦在 Autopilot 的内存垂直伸缩,因为这方面的报告较少。
本论文:
- 描述 Autopilot 以及它⽤于垂直⾃动伸缩的两种主要算法:第⼀种依赖于历史使⽤率的指数平滑的滑动窗⼝;另⼀种是元算法(meta-algorithm)基于从强化学习中借鉴的的思想,它可以运行很多滑动窗⼝算法的变体,并为每个作业选择⼀个能够带来最佳表现的算法
- 评估 Autopilot 算法对 Google 内部 Workload 有代表性样本上的有效性
- 讨论为使我们的集群广泛采用 Autopilot 而采取的步骤
2. 使用 Borg 管理云资源
Autopilot 的目标和限制约束来自于 Google 的 Borg 基础架构,并且针对 Google 的工作负载进行了调整。我们在此提供了两者的简要概述:有关 Borg 的更多详细信息请参见 [34],有关工作负载的更多详细信息请参见 [26、27、31、35]。
2.1 机器、作业和任务
Google 计算架构由分布在多个地理位置的集群组成,一个中等规模的集群大约有 10000 台物理机,并同时运行许多不同类型的工作负载。一台物理计算机可能会同时运行着大量消耗 CPU 和大量消耗内存的批处理计算,以及对内存数据库的切片提供存储和查询服务,同时也对延迟敏感的终端用户请求提供服务。
我们将工作负载的特定实例称为作业,作业由一个或多个任务组成,任务在单个物理计算机上执行,一台机器可以同时执行多个任务。作业是具有某些功能(例如:文件系统或身份验证服务)的服务并且与这个服务相关的逻辑实体;任务执行实际工作,例如:为最终用户或文件访问请求提供服务。作业运行数月的情况并不罕见,尽管在这段时间内我们可能会多次对该任务的二进制进行发版,在作业发布时,新的版本逐渐替代老的版本。
我们运行的工作负载可以分为两类:服务型作业和批处理作业。服务型作业通常对查询的响应时间有严格的要求(例如:请求延迟服务等级目标(SLO) 在 95 分位处小于等于 50 毫秒)。这种严格的延迟要求排除了操作系统内核之外的任何 inband 资源分配的决策,因此服务型作业要求他们申请的资源应该被明确保留。相比之下,批处理作业旨在”快速“完成并退出,但通常没有严格的完成期限。服务型的作业是我们基础设施容量的主要驱动力,而批处理作业通常会填充剩余或暂时未使用的容量,如 [4]所示。
内存不⾜(OOM)事件会终⽌单个任务,有一些作业可以适当地容忍 OOM 事件,而有些则完全不能容忍,还有一些介于容忍和不能容忍之间。总体⽽⾔,由更多任务组成且状态较少的作业在单个任务终⽌时经历的服务降级也会越小,因此更能容忍 OOM。有些作业需要持续的低延迟来服务请求,有些则不需要。Autopilot 根据作业声明的⼤⼩、优先级和类别选择默认值,但允许我们的用户覆盖这些默认值。 Borg 驱逐任务是为了执行安全和操作系统的更新、为更⾼优先级的任务腾出空间,以及让我们更有效地将工作负载安排到机器上。我们有意识地通过计算集群和应用程序之间提供服务的弹性能力来分担想要通过运行更多任务的程序来解决驱逐和硬件故障的压力。Borg 发布了预期驱逐的最⼤速率,以及与观察到的驱逐率之间的差异,这使我们可以轻易地使⽤任务资源配置进行实验,当我们了解任务需要什么时,偶尔出现 OOM 也是能接受的。(VM 实时迁移等工具用于向外部云虚机隐藏这些内部优化)
⼀个典型的服务型作业有许多任务和⼀个负载均衡器,它将流量转发到可⽤的任务上,因此⼀个任务不能工作了只会导致其负载被分散到其他任务上,这是正常的⽽不是灾难性的故障,它使我们能够更激进地利⽤基础设施以及优雅地处理偶尔的硬件故障。我们的批处理作业中内置了类似的弹性能力,使用了类似 MapReduce [6] 中的技术。
2.2 Borg 调度器架构
每个集群由经过定制化的 Borg 集群调度器实例管理。下面我们简要介绍下 Borg 架构和功能,因为它会直接影响 Autopilot 设计,有关完整说明请参阅 [34]。
Borg 包含了多个副本的 Borgmaster 负责调度决策,和运行在集群中每台计算机上叫做 Borglet 的代理程序。一台机器可以同时执行 Borglet 管控的数十个任务,Borglet 依次将这些任务的状态和资源使用情况报告给 Borgmaster。当新作业提交给 Borgmaster 时,它会选择一台或多台机器,这些机器上有足够的空闲资源来执行新提交的作业 – 或通过驱逐优先级较低的作业来腾出空间来满足高优先级的任务。在 Borgmaster 决定将作业的任务放置在哪里之后,它将任务的启动和运行任委派给所选计算机上的 Borglet。
2.3 通过任务限制实现资源管理
为了达到预期的性能,在⼀台机器上运行的任务必须相互隔离。与 Kubernetes ⼀样,Borg 也是在单独的 Linux 容器中运行每个任务,通过本地 agent 设置容器的资源限制,以及使⽤ cgroups 实现性能隔离。与传统的操作系统级别公平共享机制不同,这种方式可以确保任务在执行同⼀个⼆进制⽂件时的性能是一致的,即便是在不同机器(只要硬件相同)和拥有不同的邻居(被调度到同一台机器上的任务)[38]。
在我们的基础架构中,CPU 和 RAM 是要管理的关键资源。我们使⽤术语 ”limit“ 来指每种资源通常可以消耗的最⼤允许量。由于 Borg 通常将作业的任务视为同质的,因此所有任务通常具有相同的限制。
作业以标准化毫核 [31] 来描述 CPU 的限制单位,并且此限制由标准 Linux 内核 cgroups 机制强制执行。如果在宿主机上争抢很少发生(通过整体 CPU 利⽤率来衡量),则允许任务使⽤超出其 CPU 的限制。但是,⼀旦存在争抢就会强制对其执行限制,并且某些任务可能会被压制在其限制范围内运行。
作业以字节描述内存的限制单位,与标准的 Linux cgroups ⼀样,作业 RAM 的限制可以是硬限制也可以是软限制,并且作业的拥有者在提交作业时声明限制类型。使⽤硬限制 RAM 的任务一旦使用超过限制,就会因内存不⾜ (OOM) 错误⽽被终⽌,并将失败报告给 Borgmaster。使⽤软限制 RAM 的任务允许超过其声明的内存限制,但如果机器上的整体 RAM 利⽤率过⾼, Borglet 会开始终⽌超过其自身限制的任务(带有 OOM 错误),直到机器被认为不再处于风险状态(标准的 cgroup 强制措施是防止超过上限的容器保留更多内存)
Borg 允许作业在运行时修改其资源需求,在水平伸缩时作业可以动态添加或删除任务。在垂直伸缩时,作业可以更改其任务的 RAM 和 CPU 限制,增加作业的 RAM 和 CPU 限制可能是代价⾼昂的操作,因为某些任务可能不再适合现在的机器。在这种情况下,这些机器上的 Borglet 会终⽌⼀些低优先级的任务,这些任务将被依次重新调度到其他机器上,并可能触发更低优先级任务的终⽌。(⼏年前,我们大幅度地降低了优先级层级的数量用来减少这种级联的影响)
尽管通常会过度设置作业限制上限,但也有⼀些反压力:我们向服务用户收取他们保留的资源(而不是他们使用的资源)费用,并且请求的资源会消耗用户的配额,配额是一种硬限制,是一个集群所有作业能够获取的资源总量,这类似于公共云对 VM 使用的定价和配额。收费和配额在解决这个问题上都是有帮助的,但实际上效果有限。供应不足的弊端通常远远超过通过请求较少资源获得的收益。我们发现这是一个反复出现的话题:理论上可得到的效率提升在实践中通常很难达成,因为手动执行所需的努力或风险更高,所以我们需要一种自动进行权衡的方法,这就是 Autopilot 做的事情。
3. 使用 Autopilot 自动调整上限
Autopilot 使⽤垂直伸缩对 CPU 和 RAM 上限进行微调,用来减少 slack,同时确保任务不会耗尽资源,它还使⽤⽔平伸缩(更改作业的任务数量)来适应更⼤规模的⼯作负载。
3.1 架构
Autopilot 的功能架构是三合一的闭环控制系统,一个用于作业级别的水平伸缩,另外两个用于任务级别资源(CPU 和内存)的垂直伸缩,算法(在后续章节中详细描述)充当控制器,Autopilot 将作业分开考虑 - 没有跨作业学习的能力。
Autopilot 的实现(图 1)采用我们基础设施上标准作业集合的部署形式:每个集群都有自己的 Autopilot,每个 Autopilot 的资源推荐器(根据其历史使用情况调整作业大小)作为单独的任务运行,具有三个任务副本以确保可靠性。多副本的 Autopilot 服务选举出一个主节点,来负责为作业选择推荐器,并通过 Actuator 服务将推荐信息(过滤后的)发送给 Borgmaster。如果请求是改变任务的数量,Borgmaster 会相应地创建或终止任务。如果请求是更改资源限制,Borgmaster 首先做出任何需要的调度决策以适应它们,然后将变更通知到有关的 Borglet agents 以应用更改,一个独立的监控系统跟踪每个任务使用了多少资源,Autopilot 只是订阅它的更新。
现在我们的用户使用简单的标记明确指出他们的作业使用 Autopilot,我们正在将此设置为默认值,并允许明确选择取消。用户还可以配置 Autopilot 行为的几个方面,例如:(1) 强制所有副本具有相同的上限,这对于只有一个活跃 master 节点的容错程序很有用; (2) 增加上限值,以便来自另一个集群中的副本作业的负载能够立即故障转移到该集群。
当使用了 Autopilot 的作业提交给 Borgmaster 时它会暂时排队,直到 Autopilot 有机会为其提供初始资源推荐为止,然后它会继续进行正常的调度过程。
3.2 垂直伸缩(任务)
Autopilot 服务根据被自动伸缩的资源是内存还是 CPU 来选择用于作业的推荐器,作业对资源不足事件的容忍度(延迟容忍与否,OOM 敏感与 OOM 容忍)和可选的用户输入,可选的用户输入包括明确的选择推荐器,或用于控制 Autopilot 行为的其他参数,例如:可以设置限制的上限和下限。
3.2.1 预处理:聚合输入信号
推荐器使用预处理的资源使用率信号,大部分预处理由我们的监控系统完成以减少对历史数据的存储需求,聚合信号的格式类似于 [35] 中提供的数据。
任务监控数据记录一个原始信号,该信号是作业的每个任务监控指标的的时间序列(例如:CPU 或 RAM 使用情况,或接收到的查询数量),我们将监控系统在 时刻从任务 记录的值表示为 ,此时间序列通常每 1 秒包含一个样本。
为了在设置作业上限时减少存储和处理的数据量,我们的监控系统将 预处理为聚合信号 ,该聚合信号通常是 5 分钟窗口聚合值,聚合信号的单个样本 是一个直方图,总结了这 5 分钟内作业所有任务的资源使用情况。
更正式地说,对于每个窗口 ,聚合的每个任务信号 是一个向量,它包含原始信号 的直方图,其中 。对于 CPU 信号,该向量的元素 计算原始信号样本的数量 ,它们落入大约 400 个使用率 bucket 中: ,其中 是第 桶的边界值(这些值由监控系统固定)。对于一个 Memory 信号,我们在直方图中只记录了 5 分钟窗口期间任务的峰值(即每个任务的直方图 只有一个非零值)。内存信号使用峰值而不是使用整个分布数据,因为我们通常希望提供(接近)峰值内存使用量:任务对内存不足相较于 CPU 更敏感,内存不足会通过 OOM 终止,而 CPU 不足可能只会导致节流。
然后,我们通过简单地求和 将每个任务的直方图 聚合为单独的作业直方图 。我们没有明显地考虑单个任务 — 例如:检查极端的数值。由于大多数 Borg 作业中的单个任务都是可互换的副本,因此除少数特殊情况外,我们对所有其他任务使用相同的限制。
表 1 用于描述推荐器的符号
任务的原始 CPU/MEM 时间序列(1秒精度)
聚合值,每个任务聚合的 CPU/MEM 时间序列(直方图,5分钟精度)
聚合值,每个作业聚合的 CPU/MEM 时间序列值(直方图,5分钟精度)
每个作业负载(调整后)的直方图
直方图的第 k 个边界值
衰减老化年龄为 的样本权重
在 t 时刻的滑动窗口推荐值
模型(参数化的 arg min 算法)
模型 使用的衰减率
模型 使用的安全边界
通过推荐器测试出的上限值
通过模型 推荐的上限
ML 推荐器最终推荐值
对于上限 的超出成本
对于上限 的未超出成本
超出成本的权重
未超出成本的权重
改变上限的惩罚权重
改变模型的惩罚权重
用于计算模型成本的衰减率
模型 m 的历史成本
3.2.2 滑动窗口推荐器
滑动窗口推荐器通过使用聚合信号 的统计数据来计算上限,我们希望上限随着使用量的增加而迅速增加,但在负载下降后缓慢减少,以避免对临时下降的工作负载波动做出过快的响应,为了平滑应对负载峰值,我们通过指数衰减权重 对信号进行加权:
(1)
其中 是样本年龄, 是半衰期:权重下降一半的时间。 Autopilot 针对长时间运行的作业进行了调整:CPU 信号半衰期使用 12 小时 和内存半衰期使用 48 小时。
在 上使用以下统计数据之一用于计算在时间 的推荐值 :
- 返回最近样本的最大值,,即最后 N 个样本中非空桶的最大值,其中 N 是一个固定的系统参数。
- 加权平均值 () 计算时间加权平均的平均值:
(2)
其中 是直方图 的平均使用率,例如:
。
-
使用率的 j 分位置 () 首先计算负载调整后的衰减直方图 ,其第 元素 将桶 中的衰减样本数乘以负载量 :
(3)
然后返回这个直方图的某个百分位值 , 和标准直方图 之间的区别是在 中每个样本都有相同的权重,而在桶 中的样本的权重等于负载 。
请注意,给定百分位 可能会随着时间的推移得到不同的值。在大多数情况下,我们希望确保设置的上限能够适应已经给出的负载,而不是简单地计算瞬时观察到的负载 — 即:我们想要按负载加权计算,而不是样本统计。这种差异如图 2 所示:如果上限设置为 1(按时间计算的 90 分位值),那么最后时刻的 9/19 负载单位将高于上限(下虚线)。在这种情况下,负载调整后直方图 h 计算如下,负载调整直方图 h 计数的单个观察值可以解释为在某个负载(信号的当前电平)下处理的信号面积的单位。 等于 (在 9 个时间单位内负载为 1)和 (在 1 个时间单位内负载为 10)。因此, 的 90 分位值或要求 90% 的信号区域在该限制或以下处理的限制为 10 - 在这种情况下,意味着可以在上限内处理整个信号。
Autopilot 根据信号和作业类别使用以下统计信息,对于 CPU 上限,我们使用:
- 批处理作业: 平均值,因为如果作业容忍 CPU 节流,则对基础设施最有效的限制是作业的平均负载,这允许作业在不堆积延迟的情况下继续进行。
- 服务型作业:根据作业对延迟的敏感程度,使用负载使用率的 (95 分位值)或 (90 分位值)
对于内存,Autopilot 使用不同的统计方法作为作业 OOM 容忍度的函数。对于大多数大型作业,默认设置为 “low”,对于小型作业则设置为 ”minimal“(最严格),但可以由用户覆盖:
- 适用于 OOM 容忍度为 ”low“ 的作业
- 适用于 OOM 容忍度为 ”minimal“ 的作业
- 对于具有中等 OOM 容忍度的作业,我们通过使用 (加权后的 60 分位值)和 (峰值的一半)中的最大值来覆盖短暂的负载尖峰
最后,这些原始推荐值在应用之前还需要二次处理。首先,建议增加 10-15% 的安全余量(对于较大的上限则可以减少),然后我们采用过去一小时内看到的最大推荐值来减少波动。
3.2.3 基于机器学习的推荐器
原则上,Autopilot 解决了一个机器学习问题:对于作业,根据其过去的使用情况,找到一个上限以优化表达作业和基础设施目标的函数。上一节中描述的方法(基于滑动窗口上的简单统计来设置上限)指定了一种算法来解决这个问题。相比之下,Autopilot 的 ML 推荐器从成本函数开始 - 期望方案的规范 - 然后为每个作业选择适当的模型参数以优化此成本函数。 这种自动化允许 Autopilot 为每个作业优化先前固定的参数,例如:衰减率 、安全边界或缩容稳定期。
在内部,ML 推荐器由大量模型组成。对于每个作业,推荐器会定期选择性能最佳的模型(根据下面定义的成本函数计算历史使用情况),然后被选择的模型负责设置上限。 每个模型都是最小化成本的简单 arg min 类型算法 - 模型的不同之处在于分配给 arg min 各个元素的权重。ML 方法经常遇到的问题是其结果的可解释性 [8]:在 Autopilot 中,推荐器设置的上限必须能够向作业所有者可解释。拥有许多简单模型有助于解释 ML 推荐器的行为:单个模型大致对应于作业的推断特征(例如:具有长时间稳定的模型对应于利用率快速变化的作业)。 然后,给出所选模型施加的权重,其决策很容易解释。
更正式地说,对于信号 在时间 ,ML 推荐器从模型集合 中选择用于推荐上限的单个模型 。模型是一种参数化的 arg min 算法,它基于历史信号数据计算上限,模型 由衰减率 和安全界限 参数化。
在每个时刻 ,模型测试所有可能的上限值 (可能的上限值对应于直方图桶的后续边界, )。对于每个上限值 ,模型根据最近使用率的直方图 计算当前没有超出和超出的成本,然后使用历史值对其进行指数平滑,超出成本 是统计最近的直方图中超过上限 的桶中样本数:
(4)
同样地,没有超出的成本 统计桶中低于上限 的样本数,
(5)
然后,模型选择一个上限 ,以最小化的超出上限、未超出和惩罚 的加权总和,以应对可能的上限变化:
(6)
如果 ,则 ,否则为 0。(使用 Kronecker delta,)该函数刻画了在大规模系统中做出资源分配决策的三个关键成本。 ”超出上限“ 代表失去机会的成本 — 在服务型作业中,当超出上限发生时查询会延迟,这意味着一些终端用户可能不太愿意继续使用系统。 “未超上限” 表示基础设施的成本:作业保留的资源越多,需要的电力、机器和人员就越多。 惩罚项 有助于避免过于频繁地更改上限,因为这可能导致任务不再适合其当前机器,从而导致它(或其他任务)被驱逐。
最后,上限增加了安全余量 ,即
(7)
为了在运行时选择一个模型(并因此优化特定作业的衰减率 和安全余量 ),ML 推荐器为每个模型维护其成本 (指数平滑),这是针对上限的 ”超出上限“、”未超出上限“和惩罚的加权和。
(8)
由于历史成本包含在 中,因此给定模型的”未超出上限“ 成本和”超出上限“ 成本仅考虑了最近的成本,即最后一个样本中超出上限的直方图样本数量,因此, 和 。
最后,推荐器选择使成本最小化的模型,但会因切换上限和模型而产生额外的惩罚:
(9)
总的来说,该方法类似于多臂老虎机问题(Multi-armed bandit problem, K-armed bandit problem, MAB),老虎机的“手臂”对应于上限值。 然而,多臂老虎机的关键特性是,一旦选择了一个臂,我们就无法观察到其他臂的结果。 相比之下,一旦知道下一个时间段的信号,Autopilot 就可以计算所有可能的上限值的成本函数 — 除了在极少数情况,当上限太小和任务以 OOM 终止时(在 4.3 节中我们看到的 OOM 很少),这种全面的可观测性使我们的问题处理变得相当容易。
整体上有五个超参数:上面定义的成本函数中的权重 。这些权重大致对应于机会成本和基础设施成本。我们在离线实验中调整这些超参数,在实验中我们从有代表性的作业中提取已保存的 trace 数据来模拟 Autopilot 的行为。这种调优的目标是生成一种配置,该配置在大部分样本上支配替代算法(例如:滑动窗口推荐器),具有相似(或略低)数量的超限和上限调整,并显著提高利用率。这种调优是迭代和半自动的:我们对可能的权重值执行参数扫描(穷举搜索),然后手动分析异常值(性能异常糟糕的作业)。如果我们认为这种行为不可接受,那么在下一次参数扫描迭代中聚合结果时,我们将手动增加相应作业的权重。
这些离线实验使用原始(未调整的)使用率数据,即:它们不会尝试根据新设置的上限值来调整信号(例如:在 OOM 之后应该终止任务然后重新启动)。但是,根据特定的作业,OOM 或 CPU 节流的影响可能会有所不同 — 对于某些作业,OOM 可能会进一步增加负载(因为已终止任务的负载被其他任务接管),而对于另外一些作业 ,它可能会导致下降(当服务质量下降时终端用户会下降)。 在实践中这不是问题,因为利用率调整的事件相当少见,而且我们会持续监控生产中的 Autopilot,其中很容易发现诸如过于频繁的 OOM 之类的问题。
3.3 水平伸缩
对于大多数作业来说,只有垂直自动伸缩是不够的,单个任务不能比运行它的机器大。为了解决这个问题,Autopilot 水平伸缩会根据作业负载动态更改作业中任务(副本)的数量 𝑛。水平和垂直扩缩机制相辅相成,垂直自动伸缩确定单个任务的最佳资源分配,而水平自动伸缩会随着服务的使用越来越多和负载的变化而添加或删除副本。
水平自动伸缩使用以下信号源之一在时刻 推导出原始推荐 :
- CPU 使用率:作业拥有者指定(1)CPU 使用率信号的平均窗口(默认为 5 分钟); (2) 一个时间长度 (默认是72小时); (3) 统计 或 (即 95 分位值); (4) 目标平均利用率 。 Autopilot 根据最近 时间段利用率样本的 值计算时刻 的副本数,,原始推荐的副本数量是 。
- 目标大小:作业拥有者指定一个函数 用于计算任务数量,即 。该函数使用来自作业监控指标的数据。例如:使用队列系统管理请求的作业可以按请求处理时间的 95 分位值扩容;文件系统服务器可能会根据它管理的文件空间量进行扩容。
水平自动伸缩比垂直自动伸缩需要更多的配置,而垂直自动伸缩对绝大多数作业来说不需要进行配置。即使在标准 CPU 利用率算法中,作业拥有者也必须至少设置目标平均利用率 (这类似于公有云中的水平自动伸缩配置)。 在我们的基础架构中,水平自动伸缩主要用于大型作业,他们的拥有者通常更喜欢调整自动伸缩器以优化特定于作业的性能指标,例如:延迟 95 分位值(直接通过指定目标大小或间接通过实验更改目标平均利用率并观察对指标的影响)。
然后对原始推荐 进行处理后以产生稳定的推荐 ,其目的是减少任务数量的突然变化。 Autopilot 为作业所有者提供以下平滑策略选择(对于大多数作业具有合理的默认值):
- 延迟缩容 从 开始的最近推荐中返回最大推荐值:。因此,缩容被推迟到用户指定的时间 ,而扩容是立即的,我们的大部分作业都使用很长的 :大约 40% 使用 2 天,35% 使用 3 天。
- 缓慢衰减 避免同时终止太多任务,如果当前任务数量 超过稳定推荐 ,则每 5 分钟会终止一些任务,选择每次终止的任务数量是用于在一段时间内将任务数减少一半(98% 的作业使用一小时的默认值)。
- 延迟较小的变更 在某种程度上与缓慢衰减相反,当推荐值与当前任务数量之间的差异很小时,它会忽略更改。
- 限制增长 允许作业所有者指定对正在初始化的任务比例限制(即:尚未完成健康检查状态的任务),从而限制添加任务的速率。
4. 推荐质量
本部分使用来自我们生产环境的工作负载的样本来探讨 Autopilot 在 Google 的有效性,我们聚焦在 RAM 的垂直伸缩,因为 OOM 具有直接可衡量的效果,关于 CPU 伸缩的效果请参考 [31]。
4.1 方法论
我们的结果基于监控系统观察到的结果,该监控系统使用我们的基础设施监控所有作业。大规模地操作为我们提供了对 Autopilot 实际收益的良好统计评估,但任何纯粹的观察性研究的缺点是它不能控制作业接受的处理(Autopilot 或 手动设置上限),所以我们需要尽可能弥补这一点。 观察性研究的另一种选择是 A/B 实验,在该实验中我们将 Autopilot 应用于随机选择的一半作业样本,尽管我们对一组较少数量的作业进行了此类 A/B 研究,但迁移高优先级、大型生产作业需要得到作业所有者的明确同意,因此在统计意义上说是不实用的。
另一种选择是使用记录下来的 trace 进行模拟研究,但这些都有自己的偏差,我们没有可靠的方法来预测真实作业将如何响应模拟事件,例如:CPU 节流(终端用户观察到延迟增加可能会断开连接降低 CPU 使用率,或重新发出查询增加 CPU 使用率)或 OOM 事件(如果问题是临时过载任务可能会重新启动并成功,如果是内存泄露引起的可能会再次报错)。
为了缓解可观察性研究的问题,我们使用了从几个不同作业组中抽取的结果。
第一组是在我们的集群里随机选择的 20000 个作业(有偏差的)样本,我们从以下四个类别中分别抽取了 5000 个作业:使用手动设置上限的硬性 RAM 的作业、使用手动设置上限的具有软性 RAM 的作业、使用 Autopilot 滑动窗口推荐器的作业、以及使用 Autopilot ML 推荐器的作业。在控制一些潜在问题的前提下,这组作业可以提供给我们全集群范围内的 Autopilot 效果指标:
- 大多数具有手动设置 RAM 上限的作业使用硬限制,而 Autopilot 将所有这样的作业切换到软 RAM 限制,此开关本身可能会减少 OOM 的数量,我们通过对具有硬和软 RAM 限制的相同数量的作业进行采样来缓解这个问题带来的干扰。
- 作业可能被迫使用手动上限,因为 Autopilot 无法正确设置其上限。我们通过使用第二组作业来解决这个问题,该组由 500 个在特定时间开始使用 Autopilot 的作业样本组成。我们在更改前后的两个月内报告它们的表现,以降低二进制文件或负载变化带来的干扰。这组作业也可能存在偏差,因为我们只能对成功迁移的作业进行采样,但由于迁移成功率很高,因此我们认为这不是一个大问题。
4.1.1 指标
我们报告的性能指标基于 5 分钟窗口的聚合值(我们的监控系统默认值)、跨自然日的样本(与我们对资源分配的收费方式保持一致),通常使用 95 分位值来实现利用率和 OOM 率之间的适当平衡。指标如下:
占用的资源量(footprint):一个作业在自然日占用的资源量是所有任务平均上限的总和(每个任务在该天的运行时间加权),占用的资源量直接对应于作业的基础设施成本,一旦任务请求资源其他高优先级任务无法回收它们,占用的资源量以字节表示。然而,我们通过将以字节为单位的原始值除以单个(较大)机器的内存量来对其进行标准化。因此,如果一个作业占用 10 台机器,这意味着它分配的 RAM 量等于 10 台机器的 RAM 量(这并不意味着它被分配在专门用于该作业的 10 台机器上)。
相对未使用量(relative slack):一个自然日内作业的相对未使用资源量(上限减去使用部分)除以上限 — 即未使用的资源的比例。在这里,使用量是一个自然日内作业任务报告的所有 5 分钟平均使用量的 95% 分位(也就是先将任务的 5 分钟内使用量求平均,然后再取 P95 分位值),上限是 24 小时区间内的平均值。
**绝对未使用量(absolute slack):**一个自然日内作业的绝对未使用量(以字节为单位)直接衡量浪费情况,它是作业所有任务的 limit-seconds 减去 usage-seconds 的总和并除以 24 × 3600(一天)。这种聚合更加强调较大、成本更高的作业,这里 limit-seconds 是任务运行时间内请求的内存上限的积分,使用 5 分钟的平均值。我们在归一化 “占用的资源量” 同时也归一化了“绝对未使用量”,因此如果算法总的绝对未使用量为 50,那么我们浪费的 RAM 量等于 50 台机器的量。使 “绝对未使用量” 尽可能小是一个有挑战的目标,它需要所有任务的上限几乎完全等于它们在任何时候的使用量。
**相对 OOM 率(relative OOM rate):**是作业在一天中经历的内存不足 (OOM) 事件的数量除以该作业在当天运行的平均任务数,它与用户向作业添加多少任务以容忍 Autopilot 给作业带来的不可靠性直接相关,由于 OOM 很少见我们还跟踪作业完全没有 OOM 的天数。
这些指标是针对作业每天报告的(即:每个作业将在一个自然月中报告 30 或 31 个此类值),并计算所有作业的所有报告日的统计数据(例如:相对未使用量的中位数)。
Autopilot 在尝试增加作业的上限时可能会达到伸缩上限(例如:任务变得超过了可用配额,或者用户指定的边界,甚至是单台机器的大小)。我们没有过滤掉此类 OOM,因为尚不清楚该作业的未来行为是什么,并且此类事件的影响应该与所使用的算法无关。
4.1.2 作业采样和过滤
我们展示了单个自然月(或 4 个月,已迁移的作业)在基础设施上运行的作业样本表现,虽然我们的基础设施运行着多种类型的作业,但种类规模是由高优先级、服务类作业驱动的,因为这些作业可以保证使用到声明里描述的资源量。 因此,更严格的上限直接转化为更多的资源容量并且降低了未来基础设施扩展的速度,我们的分析集中在这些作业上。 除非另有说明,否则我们只考虑长期运行的作业(至少是提供完整日历月的服务),因为这些作业对我们的基础设施影响最大 [26]。 我们还过滤掉了一些具有特殊用途、非常规 SLO 的作业类别以及使用自定义推荐器的作业,从而将讨论重点放在默认算法的质量上。
4.2 未使用资源量下降
开启了 Autopilot 的作业”未使用资源量“部分明显低于没有开启 Autopilot 的作业,图 3a 显示了每个作业每天的未使用资源量的累积分布函数(CDF)。没有开启 Autopilot 的作业平均相对未使用量范围为 60%(硬限制)到 46%(软限制),而开启了 Autopilot 的作业平均相对未使用量从 31%(滑动窗口)到 23%(ML)不等。
未开启 Autopilot 的作业会浪费大量的资源,图 3b 显示了我们样本中作业的绝对未使用资源量的累积分布函数,在我们的 10000 个未开启 Autopilot 的作业样本(过去一个月平均值)上,总的绝对未使用资源量总和大于等于 12000 台机器,而开启 Autopilot 的作业样本的绝对未使用资源量少于 500 台机器,差额相当于数千万美元的机器成本。
然而,这些比较可能存在偏差,因为在构建这些样本时我们控制了每个类别的作业数量,而不是资源总量。如果所有的作业中开启 Autopilot 的作业数量很少,而没有开启 Autopilot 的作业的数量很多。不管每组的限制质量如何,我们最终可能会得到类似的绝对资源未使用量的节省(但是相对未使用量的比较仍然有效)。为了解决这个问题,我们在图 3c 中展示了作业占用资源量的 CDF,该图证实与没开启 Autopilot 的作业相比,开启了 Autopilot 的作业在资源占用量方面确实更小。然而,正如我们在分析迁移到 Autopilot 的作业时将看到的那样,这种较小的资源占用量至少部分是 Autopilot 减少作业上限导致的。此外,小型作业默认开启 Autopilot(第 5 节)。
最后,我们分析了最近开始使用 Autopilot 的作业未使用资源量减少情况(图 4),迁移前几乎所有作业都使用硬性内存限制,并且几乎所有迁移后都使用 ML 推荐器。图表显示了超过 4 个月的作业生命周期的结果,所有作业都在同一自然月开始使用 Autopilot,表示为 0 月 (m0)。我们展示了这些作业前两个月手动设置上限时的表现,分别使用 m-1 和 m-2 来表示,以及迁移后两个月使用了 Autopilot 的表现,分别使用 m+1 和 m+2 来表示。
图 4a 显示了每个作业每天相对未使用量的 CDF,在迁移前一个月平均相对未使用量为 75%,中位数为 84%。在迁移后的一个月里,平均相对未使用量下降到 20%,中位数下降到 17%。在迁移后的两个月内未使用量的分布没有变化,这表明收益是持续的。
绝对未使用量(图 4b)能看得出节省很明显,在迁移之前这些作业浪费了相当于 1870 台机器容量的 RAM,迁移后作业只浪费了 162 台机器,通过迁移这些作业,我们节省了 1708 台机器的容量。
迁移的作业占用的资源量的 CDF(图 4c)显示出作业的占用资源量随时间增加,表明流量的自然增长。迁移前两个月的作业总占用的资源量小于迁移前一个月;同样地,迁移后一个月的总占用资源量小于迁移后两个月的占用资源量。在 m0 处迁移扭转了这一趋势,虽然占用资源量逐月资源增长,但 m+1 的占用资源量明显小于 m-1 的占用资源量。迁移后资源占用量增长的速度也降低了,因为 m+2 的 CDF 比 m-2 的 CDF 更接近 m+1。
500 个迁移作业的占用资源量分布(图 4c)与我们全局群采样的 20000 个作业的占用资源量分布(图 3c)不同,这 500 个迁移的作业比整个集群采样的作业的占用资源量更大,这是因为许多小型作业在 m0 之前自动迁移了,我们选择该月份作为该样本的参考月份(有关详细信息,请参阅第 5 节)。
4.3 可靠性
上一节展示了 Autopilot 可以明显减少资源的浪费,然而将上限设置为 0,能够产生更好的结果 — 以频繁的 OOM 为代价!在本节中,我们展示了开启 Autopilot 的作业比没开启 Autopilot 的作业具有更高的可靠性。
图 5 显示了作业天级别的相关 OOM 的累积分布函数 (CDF),OOM 很少见:超过 99.5% 的 Autopilot 作业没有出现 OOM。虽然使用 ML 推荐器比使用滑动窗口推荐器的作业每天产生的 OOM 略少,但它也导致相对 OOM 略多(每个任务-天 0.013 vs 0.002)。 两种算法都明显比手动限度设置有优势,在 RAM 硬限制下,大约 98.7% 的作业是无 OOM 的,相对 OOM 率为0.069/任务-天。 软 RAM 限制作业的相对 OOM 率更好,为 0.019,但没有 OOM 的作业-天略少(97.5%)。
OOM 的数量自然取决于相对未使用的资源量(slack) — 更多的未使用资源意味着更多的内存可用,因此任务应该更少 OOM。 图 6 中的线斜率表示 OOM 率与未使用资源量的关联程度,而截距则反映了 OOM 的总数。使用软上限并没有使用 Autopilot 的作业回归线低于具有硬限制的未使用 Autopilot 的作业; 在使用滑动窗口算法的 Autopilot 作业和使用软上限的未使用 Autopilot 作业之间存在类似的优势。 然而 ML 回归算法与使用手动指定软上限的作业线和使用滑动窗口 Autopilot 的作业的线条相交,这表明剩余可用资源较少的作业有更多的 OOM(利用率偏高),但对于有更多资源可使用的作业 OOM 数量也会更少(利用率偏低)。
由于 ML 推荐器导致的平均相对 OOM 率高于滑动窗口推荐器,因此可能是 ML 推荐器过于激进地降低了作业的限度。但是,ML 推荐器的就是为那些偶尔发生 OOM 但不会受到太大影响的作业而设计的。正如我们在第 2 节中解释的那样,我们的作业是能够容忍偶尔的故障 — 只要那些没有发生故障的任务能够接管发生故障任务的流量。这是按预期工作的,好处是更大的资源节省。然而,用户可能仍然有理由担心推荐器过于激进。
为了探究这一问题,我们将受到 OOM 严重影响的作业每天归类,当它在一天中经历的 OOM 多于其阈值数(例 4)或占其作业下总任务数的大比例时(如 1/7)执行归类统计。表 2 显示了在从更宽松的(顶部)到更保守的(底部)阈值设置,OOM 严重影响的每天的作业数量,尽管绝对数字略有不同,但方法的相对顺序保持不变。
在没有使用 Autopilot 的作业中,我们惊奇地发现使用手动指定 RAM 上限的作业虽然有更多的相对 OOM(如上所述),但受 OOM 的影响较小。我们假设用户可能会为具有不稳定内存使用规律的作业指定软限制,这些不稳定的内存使用模式特别难以提供推荐值。在使用 Autopilot 的作业中,正如预期的那样,虽然使用滑动窗口算法的作业相对 OOM 较少,但与使用 ML 算法的作业相比,它们更容易受到 OOM 的带来的严重影响。
我们还更详细地研究了 OOM 在作业中的集中度,如果大多数 OOM 仅发生在少数几个作业中,这可能表明自动伸缩算法存在系统性问题 — 我们不希望作业发生频繁地 OOM。我们分析了当月至少有一个 OOM 的作业,对于每个这样的作业,我们计算至少发生过一次 OOM 的天数(我们计算 OOM 天数而不是简单的 OOM 数量或相对的 OOM 数量,以关注 OOM 发生的规律)。对于 Autopilot ML,46% 的此类作业恰好 OOM 一次; 80% 的作业在 4 天或更短的时间内 OOM。相比之下,在使用Autopilot 滑动窗口推荐器的作业中,只有 28% 的作业 OOM 恰好一次; 80% 的作业在 21 天或更短的时间内 OOM。
在我们的第二个样本组(迁移到 Autopilot 的作业)中,OOM 的数量很少,无法对相对 OOM 和严重 OOM 进行有意义的评估。在迁移前的一个月,总共有 348 个作业每日至少有一个 OOM,迁移后这个数字减少到只有 48 个作业,对这些作业来说迁移到 Autopilot 是有收益的。
4.4 上限变化数量
手动控制的作业很少改变其上限:在我们的 10000 个使用手动限度的作业样本中,我们观察到一个月内有 334 次变化,每个作业每天大约有 0.001 次变化。图 7 显示了 Autopilot 更改 10000 个作业样本上限的频率:每个作业更改的频率是用户操作的几百倍。然而,它仍然相当稳定,大约 70% 的作业天级没有变化;并且作业在一天统计中 99 分位处只有 6 个(滑动窗口)到 7 个(ML)上限的更改。 考虑到即使任务被驱逐,通常只需要几十秒就可以为任务找到一个新位置,对于可以显著节约成本来说这似乎是一个能接受的代价。
有人可能会认为,上一节中报告的 OOM(严重的 OOM 数量)的减少仅仅来自于比人工操作员更频繁地更改上限。在图 7 中,Autopilot ML 和滑动窗口算法有相似的变更次数;然而如表 2 所示,Autopilot ML 更有效地使用了这种 ”中断预算”(类似于 Kubernetes 中的 PDB)。
4.5 及时行为
在前面的章节中,我们专注于长期运行的作业:我们的作业至少连续服务了一个月。在本节中,我们将 Autopilot 表现与作业运行时长的函数进行分析,图 8a 显示了每个不同运行时长范围的 1000 个作业的相对未使用资源量的 CDF。
与运行时间较长的作业相比,运行时间不到一天的作业具有明显更高的 slack(未使用资源量):这是 Autopilot 针对长时间运行的作业进行调整的直接结果 — 可用的历史数据越多,未使用的资源量就越少,但即使在 14 天之后,未使用资源量仍然高于上一节中分析的稳态。
相对 OOM 率的分析(图 8b)表明,Autopilot 对存活期较短作业持谨慎态度。对于持续时间少于 24 小时的作业,几乎没有 OOM:但是,如果我们过滤掉时间较短的作业(总任务持续时间少于 1.5 小时的作业),则 OOM 比稳定状态下要多。一旦我们考虑 7 天或更长时间前开始的作业,相对 OOM 率与稳态行为相当。
5 赢得用户的信任:增加采用率的关键
我们的基础架构为成千上万的内部用户提供服务,他们具有不同的角色、经验和期望,较小或较新的服务通常由最初创建它们的软件工程师在生产环境中运行,而较大、更成熟的服务则有专门的 dev/ops 团队。为了提高 Autopilot 的采用率,我们不仅要确保我们算法的质量是可以接受的,而且还要识别和回答工程师对基础设施的需求。本节讨论这些定性方面,我们的经验巩固了 [5] 中描述的许多经验教训。
5.1 评估过程
与 Autopilot 一起,我们开发了一个评估潜在推荐器的流程,推荐器首先在离线模拟中进行评估,使用具有代表性的作业样本资源使用的 Trace 数据。虽然这样的评估还不够(我们在第 4.1 节中详细介绍了问题),但足以确定是否值得在推荐器上投入更多精力。如果这样没问题的话,我们继续使推荐器空跑(dryrun),同时将该推荐器作为生产 Autopilot 服务的一部分与其他推荐器一起运行 - 它的推荐被记录,但没有被执行。在这两个阶段中,我们都会分析通常的统计聚合,例如平均值和高百分位数,但也会特别注意异常值 - 推荐器表现特别差的作业,这些异常值有助于捕捉实现中的 Bug 和我们算法没有覆盖到的地方。
之后,我们执行 A/B 测试,其中新推荐器为选定集群中的一小部分用户执行推荐上限操作,即使在这个阶段出现一个完整的算法失败也不太可能是灾难性的:如果一个集群中的作业失败,服务的负载均衡器会将其流量切换到其他集群,这些集群应该有足够的容量来处理激增。 最后,当新推荐器在 A/B 测试中获得较好的效果时,我们逐渐将其在整个集群部署。为了降低可能发生故障的风险,发布是逐个集群执行的,它们之间有几个小时的间隔,如果检测到异常可以被回滚。
5.2 作业所有者可以轻松访问 Autopilot 的上限值
我们用于资源监控的标准仪表盘(图 9)显示作业 CPU 和内存使用率的分布以及 Autopilot 计算的上限值 - 即使对于没有使用 Autopilot 的作业(对于这些作业运行在 Autopilot 模拟模式下)。 仪表盘可帮助用户了解 Autopilot 的操作,并建立对 Autopilot 的信任(也就是在没有开启 Autopilot 作业上启用 Autopilot 会发生什么):用户可以看到 Autopilot 如何响应每日和每周周期、新版本的二进制文件或突然变化的负载。
5.3 自动迁移
一旦我们从大规模离线模拟研究和小规模 A/B 实验中对 Autopilot 的行为有足够的信任后,我们就可以将其作为所有现有小型作业的默认设置(总计限制为大约 10 台机器)和所有新创建的作业。用户提前得到通知,他们可以选择退出。这种自动迁移略微地增加了采用率,几乎没有用户强烈反对。
5.4 用自定义推荐器替代已有推荐器
Autopilot 的算法依靠历史 CPU/Memory 使用情况来设置将来的上限或任务数量。但是,对于某些作业,其他指标维度可以更好地预测上限:例如,我们的文件系统服务器负载几乎是线性的,而它的负载取决于服务器控制的文件总大小。此外,一些长期运行的服务在 Autopilot 之前已经开发了自己的水平自动伸缩器,其中一些包含经过多年复杂的、细致的调优逻辑(尽管它们通常只是 Autopilot 功能的一个子集,并且它们并不总是跟得上 Borg 的变化)。Autopilot 的自定义推荐器允许用户保留此类算法的关键部分 - 计算任务数量或单个任务资源限度 - 同时将诸如驱动(基于不同维度指标的驱动)等支持功能委托给 Autopilot 生态系统。
定制推荐器被证明很受欢迎:在提供 3 个月后,定制推荐器管理着我们整个集群 13% 的资源。
6 释放劳动力
Google 遵循减少劳动量的 dev/ops 原则:繁琐,重复的工作应该由机器而不是工程师来完成,因此我们在自动化上进行了投资,Autopilot 就是一个例子。
随着作业负载的增加,作业的资源限度也需要增加。热门的服务即使不包括最初的快速增长阶段,可能也应该每两周或每月调整一次,并且每次有新的版本发布可能都需要调整资源上限。假设这些是手动完成的,并且假设手动调整平均需要 30 分钟的工作量:更改配置文件,将更改提交到版本控制系统,审查提议的更改,启动发布并监视其结果。对于 10000 个需要手动调整上限的作业样本,即使是 334 次手动的调整也代表了大约一个人月的工作量 - 这还远低于预期需要调整的次数。
Autopilot 的水平伸缩 - 将任务添加到正在运行的作业中 - 自动处理自然的负载增长。Autopilot 的垂直伸缩可以处理每个任务的负载变化和新版本发布的影响,两者都能够显著地释放劳动力。
我们询问了迁移到 Autopilot 大型作业的几位拥有者,目的是估算他们所经历的工作量的减少。一个大型服务(由多个作业组成)报告说,在迁移到 Autopilot 之前他们每月大约执行 8 次手动调整大小,另一个服务估算 Autopilot 每月为他们节省 2 小时的手动调整所需的工作量,还有一个服务的负载在集群之间还有时间上变化很大,每月需要大约 12 次手动调整大小。
另一个好处是减少了必须由 oncall dev/ops 工程师处理的阻断情况(页面),随着可靠性的提高,任务失败的率降低了,报告系统发出的报警也减少了,这种减少对于负载在集群中有着显著变化的作业尤其明显:设置不同的手动上限是问题的常见来源,甚至使监控复杂化。 一项服务报告说,服务在迁移后 Autopilot 增加了某些集群的内存上限,导致 OOM 数量从每天大约 2000 个减少到可以忽略不计的数量。 另一项服务在迁移后近一年没有报告任何 OOM,oncall 页的数量从每周 3 个减少到不足 1 个(其余页用于处理无关的问题)。
Autopilot 比较严格的资源上限可能会暴露作业的一些 Bug,而这些 Bug 在更大的资源限度下会被忽视。 众所周知,罕见的内存泄漏或越界访问很难找到,尽管 Autopilot 在大多数情况下运行良好,但它可能仍需要针对一些作业进行自定义配置。因此,当作业迁移到 Autopilot 后开始频繁 OOM 时,很难区分是由于 Autopilot 错误配置导致还是真正的 Bug 导致。一个小组将此类问题归咎于 Autopilot 的内存上限设置算法,几周后才发现根本原因:很少触发的内存越界写入。 最后,Autopilot 被批处理作业大量使用(88% 的此类作业通过 CPU 启用),我们推测这是因为 Autopilot 使用户甚至不必为此类作业指定资源上限。
7 相关工作
虽然 Autopilot 的作用、用户体验以及一些定制化的内容是 Borg 特有的,但 Autopilot 解决的问题在云资源管理中几乎是普遍存在的。
期望的副本数量和资源需求将由许多云资源管理系统中的用户提供,因为调度程序会按照用户期望将任务调度到机器上 [14]。 Borg [34]、Omega [29] 和 Kubernetes [3] 都要求用户在提交作业(Borg、Omega)或 Pod(Kubernetes)时设置此类上限,YARN [32] 要求应用程序(作业)声明容器(任务)的数量以及每个容器的 CPU 和 RAM 资源需求。 在有些情况下,用于高性能计算( HPC)系统的调度程序,例如:Slurm [37] 要求每个批处理作业指定机器的数量。
其他研究证实当手动设置作业上限时,我们在私有云的基础架构中观察到的利用率较低。[30] 分析了阿里巴巴 10000 台机器 YARN 集群 5 天的使用情况,同样观察到 80% 的时间 RAM 使用率低于 55%。 [23] 分析了一个短时间的(12 小时)阿里巴巴提供的 trace 信息,显示几乎所有实例(任务)的峰值内存利用率为 80% 或更低。 [26] 分析 30 天的 Google 集群 trace 信息 [27],结果表明尽管平均请求内存几乎等于总可用内存,但实际使用量(超过一小时窗口的平均值)低于 50%。
对作业设置更精确的上限的另一种方法是超额订阅资源(即超售),即有意地给机器分配任务,使得任务请求资源总和高于本地可用的物理资源量,[30] 展示了一个在 YARN 集群中超额订阅资源的系统。尽管超额订阅可以用于偶尔容忍速度下降的批处理工作负载,但它可能导致服务工作负载的尾部等待时间明显增加 - 这需要仔细的有区别的对待[2, 21]。
水平和垂直自动伸缩需要作业具有弹性能力。通常,许多类别的应用程序都很难扩展。例如:JVM在其默认配置下不愿意释放堆内存。幸运的是,对于 Autopilot 而言,Google 的绝大多数工作都是在考虑扩展的基础上进行构建的。
自动伸缩是一个成熟的研究领域,最近的调查包括 [11, 16, 22] 大多数研究涉及水平自动伸缩。[17]通过实验分析了一些针对工作流水平自动伸缩算法的性能。[10] 在 AWS 和 Azure 中构建水平自动伸缩器的概率性能模型。[24] 测量了 AWS、Azure 和 GCE 中水平自动伸缩器的性能。虽然 Autopilot 也有一个反应式水平自动伸缩器,但本论文主要关注垂直伸缩(在 [16] 中也称为调整大小或 VM 适配)。Kubernetes 垂直 Pod 自动伸缩器(VPA,[15])使用滑动窗口上的统计数据设置容器的上限(例如:对于 RAM 使用过去 24 小时的 99 百分位值)。 Kubernetes 的方法直接受到我们滑动窗口推荐器的启发(第 3.2.2 节)。 [25] 提出了一个使用了窗口样本的中位数和标准差之和的预估器。
我们描述了两个推荐器:一个基于从滑动窗口计算的统计数据,具有窗口参数,例如:长度,由作业的所有者设置(第 3.2.2 节);另一种是根据成本函数自动选择滑动窗口参数(第 3.2.3 节)。这些简单统计的替代方法是使用更高级的时间序列预测方法,例如:自回归滑动平均 (ARMA)(如 [28] 中也使用作业性能模型)、神经网络(如 [18])、递归神经网络(如 [9, 20])或自定义预测(如 [13]),这表明基于马尔可夫链的预测比基于自回归(autoregression)或自相关(autocorrelation)的方法表现更好。我们使用此类方法的初步实验表明,对于绝大多数 Borg 案例,ARMA 的额外复杂性是没必要的:作业倾向于使用长窗口(例如:滑动窗口推荐器中内存的默认半衰期为 48 小时,第 3.2.2 节);并且日间趋势足够小,以至于一个简单的滑动窗口反应足够快。对于可由用户配置的推荐器,我们认为参数具有简单的语义并且推荐器能够根据预测调整是更为重要的。
Autopilot 没有构建作业性能模型:它不会尝试优化批处理作业的完成时间或服务型作业的终端用户响应延迟。我们发现控制上限可以让作业所有者对作业性能(和性能问题)进行推理,并将作业和基础设施之间的关注点分开,这种关注点分离部分依赖于集群和节点级调度程序。例如:Autopilot 不需要考虑来自所谓的吵闹邻居的性能问题,因为 Borg 通过一种特殊的机制来处理它们 [38]。为了涵盖剩下的少数特殊情况,Autopilot 提供了水平伸缩的 Hook 点,以允许团队使用自定义指标甚至自定义推荐器。相比之下,许多研究旨在直接优化作业的性能指标,而不仅仅是分配资源的数量。例如:在 Quasar [7] 中,用户指定性能约束而不是资源上限,调度程序负责满足它们。在公有云中,作业被分配给具有严格限制的 VM,Paris [36] 建议在具有代表性任务及其性能指标的情况下配置 VM。 D2C [12] 是一种水平自动伸缩器,它通过学习每一层的性能参数(用排队论建模)来伸缩由多层应用程序组成的每一层中的应用副本数量,学习过程是在线进行的 —— 应用程序不必提前进行基准测试。
如果作业类别足够少则可以进行更具体的优化。Ernest [33] 专注于批处理,机器学习作业,而CherryPick [1] 也考虑分析作业。尽管本论文聚焦于服务类型的作业,但在 Google 内部 88% 批处理作业使用了 Autopilot(以 CPU 消耗来衡量)。[19] 使用强化学习(RL)来驱动服务类型作业的水平伸缩(使用考虑副本的数量,吞吐量和任何违反 SLO 响应时间的工具函数)。 Autopilot 的 ML 推荐器借鉴了 RL 的一些想法,例如基于历史性能来选择模型和上限值。
8 结论
自动伸缩对于云效率、可靠性、减少工作量至关重要,手动设置上限不仅浪费资源(大部分作业的上限都很高),而且随着负载增加或服务的新版本推出时,会导致频繁违反上限。
Autopilot 是 Google 使用的垂直和水平自动缩放工具,通过自动提高上限设置的准确度,减少了资源浪费并提高了可靠性:内存不足的错误变得不那么常见和严重,同时影响的作业也更少。其中一些收益可以通过简单的时间加权滑动窗口算法获得,如果切换到受强化学习启发的更复杂的算法可以实现更大的收益。
现在,使用了 Autopilot 的作业占我们所有集群使用量的 48% 以上,实现如此高的采用率需要系统上有显著的进步和建立信任的措施才能够达到,即使在可靠性和效率方面都有明显的提升的情况下也是如此。然而,我们证明了这项额外的工作已经得到了回报,因为用户不再需要手动调整其作业的大小,并且可靠性的提高导致更少的 oncall 告警,我们强烈推荐这种方法给其他大规模计算集群的用户。