干货 | 容器成本降低50%,携程在AWS Spot上的实践

2021-12-31 16:04:35 浏览数 (1)

一、背景

AWS Spot实例,即竞价实例,是AWS把用户未购买的空闲计算资源以低于按需价格的方式出售给用户,以期带来收益。通常,AWS Spot实例的价格是按需实例价格的30%,对于AWS使用者来说,如果合理使用,可以大大节省云上费用的支出,是节省成本的一大利器。

在企业的实践中,由于Spot实例会随时被回收,不合理的使用会对系统的稳定性造成冲击。如何在节省成本的同时,保证系统的稳定性和可靠性,是一个值得投入的课题。携程集团各业务(机票、酒店等)有大量应用长期运行在AWS上,我们通过Spot实例的大规模使用,成功将业务的容器使用成本降低了50%,以下将分享我们的经验。

二、携程使用Spot实例的实践

2.1 Spot实例特性分析

携程内部使用Spot实例的应用场景,是引发我们思考在引入Spot实例之后如何采取措施,更好地保证系统稳定可靠的出发点。

Spot实例的优点

  • 成本优势显著:通常价格仅是按需实例的30%
  • 计费方式灵活友好:相比预留实例(RI)或SavingPlan等其他成本节省方式,仅按使用时长付费,无需容量预留或预付,没有因过量购买而造成浪费的负担

Spot实例的缺点

  • 回收终止对程序造成的影响:实例随时会被AWS回收终止,导致程序中断
  • 不确定性:回收不受用户自己控制,无法预估,无法确定下一刻会发生什么、目前的Spot实例是否很快会被回收,处于被动状态
  • 容量在可用区间不均衡:各可用区的容量容易出现不均衡现象,即使打开AutoScaling Group的容量自动均衡功能,也无法避免。对于需强制多可用区部署的应用需要特别注意

2.2 Spot实例的应用场景

我们看到Spot实例具有随时会被AWS回收终止的特点,所以Spot实例比较适合灵活性较高或具有容错性的应用程序。同时,实例被回收后又如何自动保证应用的容量,K8s天然地解决了这一问题,所以,我们在K8s的无状态业务负载节点大量使用了Spot实例,容器的单价成本节省了50%。当然,一些对稳定性要求非常高或者有状态程序,如K8s核心组件Scheduler webhook、HPA、Cluster Autoscaler、Metrics Server等,应避免部署在Spot节点上。否则,Spot实例回收过程中需进行容器迁移,这些组件会因重启造成抖动进而影响其他Pod正常启动,或者造成状态丢失,影响系统的可用性。

2.3 Spot实例中断事件的处理

AWS回收Spot实例时,将在执行回收动作前两分钟发出一个事件,这需要高弹性架构的支持以及处理实例突发中断的技术措施来应对。有两种方式可以检测到该事件:

1)CloudWatch Events:CloudWatch Events会发出类型为“EC2 Spot Instance Interruption Warning“的事件,通过配置事件规则对事件进行匹配,触发对应的动作。

2)实例元数据(Metadata)服务:通过在实例内 curl http://169.254.169.254/latest/meta-data/spot/instance-action 可以查看该事件相关信息:

代码语言:javascript复制
   {"action": "terminate", "time": "2021-10-10T10:10:00Z"}

在K8s中,Spot实例被回收,其本质来说,是个node驱逐的过程,执行的操作图如下:

通过监测EC2 Spot Instance Interruption Warning的CloudWatch事件,配置CloudWatch Events规则,触发Lambda。出于安全的考虑,调用K8s apiserver所需要的如认证等信息存储在Parameter Store上,给Lambda赋具有AmazonSSMReadOnlyAccess权限的IAM角色,调用K8s Apiserver对Node进行驱逐,对Pod进行迁移。

我们采用CloudWatch Events而非检测实例元数据服务的方式,一方面原因在于开销少,无需在机器上部署,包括对应日志收集的程序; 更重要的原因在于考虑到对实例回收事件引发的故障的排障需求。由于实例会在两分钟内被释放,没有机器现场,后续排障只能依赖推送到日志系统的日志。若使用实例元数据服务,极有可能丢失事件现场的日志:实例元数据未能准确送入到元数据服务上、实例上程序异常退出、实例网络问题、日志链路不可用等。而Lambda运行的日志都保存在CloudWatch Logs中,CloudWatch Event的方式是与EC2实例不交叉的链路,不存在上述的问题。

2.4 Spot实例的应用高可用思考和设计

首先我们需要了解AWS Spot计算容量池的后台设计理念。Spot容量池是一组未使用的EC2实例,它们具有相同的实例类型、操作系统、可用区和网络类型(EC2-Classic或EC2-VPC)。每个Spot容量池的价格都不同,具体取决于供需情况。我们都是VPC场景,Linux平台,那么重点考虑下实例类型和可用区,如eu-central-1a r4.4xlarge是作为一个Spot容量池进行提供的。当某一个容量池资源紧张时,那我们账号内该容量池的Spot实例很有可能将会被回收,而且用户不可控制。

图引用自 AWS Container Day 2019 Barcelona – Amazon EC2 Spot Instances[1]

为了保证高可用,降低同时段Spot实例回收对应用的影响,我们对应用的部署和调度需要考虑容量池和可用区的因素,也就是我们需要重新设计Spot实例下应用的部署架构。

2.4.1 高可用部署架构

设计跨可用区的高可用架构,而且,需要把同应用分散到不同的Spot容量池。

1)从高可用的角度出发,应用必须跨可用区进行部署。首先,多可用区的设计避免在单可用区故障时,应用实例同时遭受影响导致服务不可用。同时,在平时Spot实例频繁回收的场景下,往往某个可用区的实例会相对其他可用区的实例的紧张度更突出,保证同个应用至少会使用两个Spot容量池,降低了某个Spot容量池紧张该应用下所有Pod实例同时受影响的可能性。

2)对于实例数不多(少于一定数量),但核心的应用,或者更高稳定性的要求,除了满足多可用区分散的调度策略外,还可以考虑混合使用Spot/OnDemand实例,降低同应用所在的宿主机实例同时被回收的风险。在我们的实践中,我们还添加了按应用开启该策略的能力。

3)对于K8s核心组件及有状态应用,仅部署到OnDemand实例上。

2.4.2 Pod调度策略

我们使用调度器的TopologySpreadConstraints功能,来达到同个K8s集群内容器应用跨越多个故障域的高可用部署架构。

1)可用区分散的调度策略

代码语言:javascript复制
topologySpreadConstraints:
- maxSkew: 1
  topologyKey: topology.kubernetes.io/zone
  whenUnsatisfiable: DoNotSchedule
  labelSelector:
    matchLabels:
      GroupidKey: "%s"

2)可用区分散 Spot/OnDemand混部的调度策略

代码语言:javascript复制
topologySpreadConstraints:
- maxSkew: 1
  topologyKey: topology.kubernetes.io/zone
  whenUnsatisfiable: DoNotSchedule
  labelSelector:
    matchLabels:
      GroupidKey: "%s"
- maxSkew: 1
  topologyKey: purchasetypeKey
  whenUnsatisfiable: DoNotSchedule
  labelSelector:
    matchLabels:
      GroupidKey: "%s"

2.4.3 NodeGroup与Autoscaling Group的设计

基于上述的应用部署架构和调度需求分析,我们在调度上需要感知可用区和Spot/OnDemand。基于Cluster Autoscaler的原理,系统中作为Pod调度策略的Labels/Taints键值对集合都需要作为一个单独资源池,才能精确触发对应的Autoscaling Group的扩缩容。所以在特定用途内,从实例配置(目前是核数和内存)、可用区、Spot/OnDemand这几个维度完成资源池的结构设计:

2.4.4 异常处理策略

在可用区故障时,多可用区的部署架构首先使得服务不会整体挂,但是进入故障可用区的流量会受到影响,服务可用性降低。而且,在故障未完全解除前,故障可用区的部分云服务可能会出现在健康与不健康间抖动的状态,大大影响服务的正常工作。这种异常情况下,我们的策略是使用一切手段使得流量往正常可用区转移,实现故障恢复。这种策略基于原则:恢复优先、容量优先及固化最佳实践为可重复的SOP。具体的:

1)临时解除Pod可用区分散的调度策略:当可用区故障时,该可用区的K8s Node很可能会遭遇实例故障,如Pod的网络不通、Kubelet自动重建Pod;或故障处理过程执行迁移Pod时,当前的K8s Node资源很可能是不充足的状态,此时Pending事件触发Cluster Autoscaler进行扩容。如果还是保持可用区分散的调度策略,仍会扩容出故障可用区的实例。所以我们需要关闭可用区分散的调度策略。

2)临时关闭Cluster Autoscaler对故障可用区的NodeGroup纳管:去除可用区分散的调度策略后,Cluster Autoscaler会随机挑选匹配的各可用区NodeGroup,此时需要剔除故障可用区的NodeGroup。具体方式是把故障可用区的Autoscaling Group的k8s.io/cluster-autoscaler/enabled=true标签都去除。

3)临时关闭Cluster Autoscaler对Spot的NodeGroup的扩容:在平时监控到自己账号内的Spot回收频率异常高时,需要及时联动进行Spot比例的调整,避免新扩容Spot实例。可用区故障时,非故障可用区资源也很有可能出现紧张、踩踏的情况,此时应避免开启Spot。因为在容量池紧张的情况下,即使Spot竞价成功生命周期也不长,会很快被回收以优先满足按需实例购买者的用量,此时应避免引入更多的不稳定和波动因素,造成应用实例频繁地被迁移而导致故障影响时间被延长。

4)临时关闭自动弹性缩容:避免上游链路因故障致流量下降后资源使用降低而自动缩容,流量恢复后还得重新进行扩容。同时也给系统减轻负担,避免上下波动,往快速恢复方向用力。

上述就是在使用Spot实例时需要考虑的可用区故障、平时Spot回收状态异常高等场景需要采取的处理措施。这需要系统具备如下的能力:

1)调度策略动态调整的能力:受益于携程K8s容器平台的统一调度体系[2],我们把默认可用区分散调度的策略和针对部分应用Spot/OnDemand分散调度的策略作为两份PolicyTemplate,由sched-webhook根据绑定的Policy实现动态地配置Pod的调度行为。

2)SOP处理流程规范:经反复验证实践出的SOP流程文档手册。

2.4.5 K8s Cluster Autoscaler对TopologySpreadConstraints的支持

目前社区的K8s Cluster Autoscaler尚未支持TopologySpreadConstraints,在我们的场景下会造成非预期NodeGroup的扩容或缩容。所以我们进行了扩展,新增对Spec.TopologySpreadConstraints Metadata的支持,目前已稳定运行在生产环境中。

2.5 Spot实例集群的长期治理

虽然AWS提供了Spot Advisor工具帮助用户根据折扣及中断概率进行实例类型的选型,但是该工具的数据粒度较粗,也无直接可观测的工具可以查看自己账号内的Spot实例中断次数、频率等统计情况。从长期维护治理的角度出发,我们自己收集记录每次Spot实例回收的事件及对应实例的信息,特别关注可用区、实例类型、存活时长等属性,把Spot中断历史数据保留下来,便于后续数据分析,进行持续性的治理。

可观测性是大规模长期运营的前提,可以在以下场景发挥作用:

1)观测及排障:实际查询实例由于何原因被终止,是否因为Spot实例回收导致实例中断。

2)策略优化:回收经数据汇总分析后,可以查看当前各可用区各实例类型的中断频率,对中断频率过高的Spot容量池进行替换;以及直观了解现阶段Spot实例整体的波动状况,为当前Spot比例的控制提供依据;把Spot数据作为影响系统稳定性输入的一个因子,结合稳定性数据,计算出稳定性保证范围内的Spot比例阈值指标。

3)容量规划:比例阈值指标经数据分析后,得出如季节性或节日性规律,便于做好下一周期的Spot实例容量规划和控制管理。

可观测性

通过监测Amazon EventBridge中的EC2 Spot Instance Interruption Warning的CloudWatch事件,配置CloudWatch规则,触发Lambda。Lambda通过EC2 API获取该实例的详细信息,存入到存储系统。基于数据,可以制作监控的告警看板,以及进行后续的治理分析。

三、总结

本文介绍了携程在AWS上使用Spot实例的实践,重点介绍了使用的场景,引入Spot实例后面向恢复的高可用服务的思考和设计,不断优化保证稳定性与可靠性。Spot实例的使用使得AWS上的容器成本降低了50%。未来,会持续对Spot实例集群系统进行治理,在管理上更自动化智能化,在成本和稳定性的矛盾中继续探索,保证系统稳定性输出的同时,最大程度地降低成本。

四、引用

【1】 AWS Container Day - Amazon EC2 Spot Instances https://www.youtube.com/watch?v=tPdv1wAG6o4

【2】10W K8s容器数量下,携程如何打造统一弹性调度体系 https://mp.weixin.qq.com/s/PZIPl6OJz26XnkAho3dY9g

0 人点赞