王玉君,腾讯云后台高级开发工程师,负责腾讯云原生系统开发及建设。
晏子怡,腾讯云容器产品经理,在K8s弹性伸缩、资源管理领域有丰富的实战经验。
导语
Kubernetes 作为 IaaS 和 PaaS 中间的一层,通过声明式 API/控制器模式、以应用服务为中心、并且从API到运行时都提供了高度灵活的可扩展机制,为云厂商、各企业构建应用托管服务甚至云原生服务提供了统一的标准和基础设施管理的各项能力。
随着企业上云进入稳定期,成本控制就是永远逃不开的话题。本文通过 Kubernetes 的扩展机制 Admission Webhook、Scheduler Framework 和CRD+Operator,结合云上资源的特异性,介绍如何基于Kubernetes和云上环境构建成本控制系统。
TKE的头部客户S(离线计算场景),面对增长的客户月活以及相应剧增的计算需求,强烈希望腾讯云容器服务能提供更低单价的算力和更弹性的架构。S通过构建合适的成本控制系统,将月账单降低了接近80%(业务计算量相同)。结合S的业务场景,我们主要建议S做了如下低成本改造动作:
- 通过 Spot Controller 配置了较高的竞价实例比例(接近90%),同时配置了10%左右的包年包月实例作为稳定资源的 buffer 池。由于 Spot 比例设置较高,为了增强资源供应的稳定性,S配置了多种备选机型来扩大资源池的供应范围,保持回收频率在极低水平。
- 为了进一步保障业务的高可用,S设置了多可用区平衡分布的扩容策略,来保障实例在多可用区打散分布。
- 为了保证弹性以及架构的容错性,S同时使用了业务弹性伸缩(HPA)+资源弹性伸缩(节点池)来完成应用层弹性伸缩到资源层的弹性伸缩平滑过渡,同时也可以在业务低谷时自动释放闲置资源,进一步节成本。
- 为了应对极端情况下的回收情况,S安装了 Spot Agent 来保障业务的优雅终止以及计算过程的断点续传。
WHY SPOT ? 我们需要一种云原生的低成本算力
TKE 能力的底座是集群以及节点能力(底层依赖腾讯云的CVM实现),而TKE本身是不计费的,那么您购买节点时选择的计费方式将极大程度上决定您的总体使用成本。
根据不同的使用场景,用户最常使用的计费方式为按量计费或者包年包月:
- 按量计费是一种符合云计算理念的计费方式 —— 按需使用,弹性供应,秒级计费。按量计费的这种按需使用的特性,和云上的弹性伸缩能力天然适配,很多用户也会搭配使用。
- 包年包月是一种更低成本的计费方式 —— 提前付费,降低成本 您在十分明确使用期限的情况下,可以以低折扣按月、按年、按周的方式购买实例。
那么,有没有一种计费类型,既具备云原生按量计费的特性、能够匹配弹性伸缩能力,又能够像包年包月实例一样提供较低折扣呢?竞价实例,就是这个问题的答案。
竞价实例(Spot)是云服务器 CVM 的一种新实例运作模式,它最核心的特点是折扣售卖和系统中断机制。
竞价实例是三种计费模式中成本最低的一种使用方式(低至两折), 可以极大程度降低您的云资源支出。
但正如它的名字一样,您和其他同时使用竞价实例的用户存在一定的竞争关系:在特定场景下,实例可能会被回收,我们官方将这种回收定义为系统主动中断(库存波动):当前阶段,在腾讯云的竞价实例模型下,仅会因为竞价实例资源池库存不足而产生中断。资源管控系统会自动根据实时库存变化回收这些折扣售卖的实例。当您成功购买一个竞价实例后,它的使用和按量计费的 CVM 实例基本毫无区别,包括控制台操作、远程登录、服务部署、关联 VPC 等。
在丰富的实践与探索中,我们发现,Spot 非常适合容器、无状态服务、 CI/CD 、强化学习、离线转码、大数据分析等具有容错能力的业务应用,尤其是基于云原生框架构建的应用,在这些场景下可以在巨幅降低成本(80%以上)的前提下,保证业务的稳定性。
不惧中断 — 云原生框架保证业务稳定性
上面我们已经介绍了竞价实例的核心特性,可能您看到系统主动中断这个概念,心里会浮现一些顾虑以及疑惑:
- Q1: 回收机制是怎么样的?会提前通知吗?
- Q2: 我想要节约成本,我可以做些什么来降低甚至消除回收带来的潜在风险吗?
- Q3: 是否有自动化的方式可以抵消回收代替来的对业务的潜在影响?
- Q4: 怎么判断我的业务是否适合使用竞价实例?
这几个问题,本文都会一一解答。我们依托于丰富的实战经验,基于 Kubenetes 构建了云上成本控制系统,利用弹性供应能力以及云原生的维持业务期望状态的能力,定义且实现了一种低成本且稳定的算力交付形式。
- 竞价实例在被系统回收前的2-5分钟(不同云服务商配置的时间不一致),都会发出回收信号、或者以虚拟机元数据信息的方式体现出来,TKE 基于云原生的优雅终止以及 Workload Controller 维持应用期望状态的特性,有效降低了回收的风险,在感知回收信号后,可以优雅终止竞价实例上运行的应用副本,同时自动创建新的副本来满足业务期望状态。
- TKE将维持期望状态的能力从应用层扩展到了资源层,用户无需关心资源购买过程,只需定义期望资源的状态(规格、可用区、计费类型)等,spot-controller 会自动供应资源直至满足客户期望。
成本控制系统概述
上图是整个成本控制系统的架构图。
在介绍系统各组件前,先提一下位于架构图最下面的资源池这一层,其中最主要的一种资源是竞价实例(Spot),各个主流云厂商都有提供,所谓竞价实例(Spot)。
竞价实例是用户可以直接购买的, 而碎片资源,则是指一些长期闲置的资源碎片,由于规格原因一直未被申请到,这些资源往往需要具有IaaS层操作权限,所以比较适合云厂商来使用,作为普通用户,使用竞价实例池即可。
整个系统由三部分组成,tke-spot-agent、cost-wehbhook+ cost-scheduler,以及 spot controller,这三部分是完全松耦合的,比如部分业务在前期只使用了 tke-spot-agent.
- the-spot-agent 以 deamonset 的方式运行在每台 Node 节点,用于监听竞价实例的回收信号,从而实将节点Disable以禁止调度新的 POD,优雅驱逐 POD 等。
- cost-webhook+cost-scheduler 是中心化的,每个集群只需部署一套,用于拦截用户请求,将用户请求通过自定义调度器调度,满足指定比例的 Pod 被调度到竞价实例。
- spot-controller 也是中心化的,每个集群一套,用于处理用户配置的CRD资源,调用云厂商提供的购买机器的云API进行机型的购买,用户得以按照一个简单的描述文件声明集群所需竞价实例的配比,从而控制成本。
通过以上三个组件,分别实现了竞价实例被回收前的优雅处理、用户对不同业务场景下将Pod按比例调度到竞价实例上的成本感知调度、对用户的成本声明进行协调控制。
tke-spot-agent - 业务的优雅终止以及平滑迁移
上文提到,竞价实例在被系统回收前的2-5分钟(不同云服务商配置的时间不一致),都会发出回收信号、或者以虚拟机元数据信息的方式体现出来,针对这个云厂商普遍存在的友好预警机制,我们可以提供一种守护服务,时刻监听这个来自IaaS层的预警信息,提前做一些处理,将业务应用容器无损的迁移到其他虚拟机上。
从架构上来讲,这种守护服务,最优的方式是以中心化的形式运行在集群中,也就是一个 Kubernetes 集群只需运行一个这样的 Pod,最多通过选举机制启动一个 standby 容器做高可用,然而这样的前提是,IaaS 层的预警机制能够以统一的消息发送过来,目前各大云厂商也只有极少数提供了这样的发生消息的机制,只是在虚拟机元数据信息中做了体现,而且该信息只能在虚拟机的节点上查询,考虑到当前阶段的普遍适用性,我们目前将该服务以 Kubernetes daemonsets 的方式部署在每一台spot机型上。
云厂商虚拟机的元数据信息,可以在虚拟机上以HTTP的方式获取,该守护服务启动后,不断的监听该spot虚拟机的元数据信息,当发现回收信息后,首先调用 Kubernetes API 将该虚拟机的调度状态设置为不可调度,防止新的Pod被调度到这台即将被回收的虚拟机上。紧接着,守护服务通过 Kubernetes API 获取到当前节点上所有的Pod,对这些Pod发起驱逐命令,Kubernetes 为每个Pod配置了默认优雅退出时间,这个值是30s,有些业务应用的场景可能在30秒内难以处理完手头的事情,守护服务在向 Kubernetes API server 发起请求时,可以携带一个叫做 DeleteOptions 的 Kubernetes 资源属性,可以将优雅退出的时间进行用户自定义,当然,这个时间,设置为大于IaaS层对Spot虚拟机回收时间(2-5分钟)就没有意义了,Pod还没有优雅退出,虚拟机可能就已经被回收了。
这里需要额外提到的是,业务应用如何感知到spot实例即将被回收呢?这就要从 Kubernetes 的设计说起,Kubelet 在真正删除Pod之前,会在Pod上设置一个删除标记的起始时间,表示这个时刻收到了删除该Pod的请求,并会给Pod的容器进程发送一个 SIGTerm 信号,告诉业务进程该Pod即将被删除,而业务进程要做的,就是实现一行代码,去监听这个信号,这也算是云原生应用的基本要求了,对于云原生应用而言,云资源是不稳定的。
cost webhook + cost scheduler - 灵活的业务调度形式
cost webhook 和 cost scheduler 两个组件,本质上是为了实现成本感知调度,也就是将指定比例的Pod调度到spot虚拟机上,指定比例的Pod调度到按量付费\包年包月的虚拟机上,达到既节约成本,又能平衡可用性。
从实现角度来说,最简单的方式就是 CRD+operator 模式,用户使用时声明总共的副本数和 spot 实例的副本数,operator即可按照用户的期望将固定比例Pod的 node selector 选择为spot虚拟机。
然而,Kubernetes 及其声明式API模式的原则之一是,“牺牲小我,完成大你”, 也就是说将负责的事情全部由 Kubernetes 来做,用户只需简单的声明即可。本着这样的原则再来思考,这种 CRD+operator 的模式虽然简单易行,但是对于用户而言,就不那么友好了,用户是平台开发者甚至是应用开发者,学习并掌握了Kubenretes 的副本编排控制器如 Deployment、Statefulsets 等的使用配置参数,已经撸起袖子开始使用了,突然告诉用户一种新的配置,对于PaaS平台开发者或者应用开发者,都是不那么友好的。因此,思考一种对用户更优雅的实现,还是很有必要的。
cost webhook 和 cost scheduler 正是基于Kubernetes 原生的 Deployment 而设计,在使用时,用户只需要将 Deployment 打上熟悉的 annotation,如 pod-on-spot-instance=70%,cost webhook 和 cost scheduler 即可完成将70%的Pod调度到 spot 实例,将30%的Pod调度到按量付费\包年包月实例上。
How Cost webhook works
具体来说,cost webhook 是基于 Kubernetes 提供的动态准入控制机制实现的一个webhook,用户请求到达API Server后,以此经过路由、认证、审计、鉴权等流程,而审计包括 Mutating 和 Validating 两个阶段,前者用于对API资源继续修改,后者主要用于校验,如下图。
(图片来源:https://kubernetes.io/blog/2019/03/21/a-guide-to-kubernetes-admission-controllers/)
cost webhook 正是 Mulating 阶段的 webhook,在流程走到 Mutating 阶段被调用执行,cost webhook 监听 Deployment 这种资源类型,判断 annotation 中是否包含上述提到的 pod-on-spot-instance=70% 信息,如果有,则将该 Deployment 所属的 Pod 的 Scheduler Name 修改为 cost scheduler,对于这些 Pod 的调度,就交给 cost scheduler 来完成。
How Cost Scheduler works
cost scheduker 也是基于 Kubernetes 的 scheduker framework 扩展机制实现的自定义调度器。
(图片来源:https://kubernetes.io/docs/concepts/scheduling-eviction/scheduling-framework/)如上图所示,Schedule Framework 为我们提供了多个扩展点,比如 preFilter、Filter、Score 等等,可以在调度的各个环节实现自定义扩展。cost scheduler主要基于 Filter 进行扩展,将Pod分为适合调度到 spot 实例和适合调度到非 spot 实例。
spot-controller - 持续稳定的算力交付
看到这里,可能您还有个疑问还没有得到解答:是否有自动化的方式可以抵消回收带来的对业务的潜在影响?Spot-controller 可以回答这个问题,它定义了一种算力交付的方式,将维持期望状态的能力从应用层扩展到了资源层。
用户无需关心资源购买过程,只需定义期望资源的状态(规格、可用区、计费类型)等,spot-controller 会自动供应资源直至满足客户期望。
spot-controller 在实现上采用了 CRD+operator 的模式,用户只需填写一份CR并提交到 Kubernetes 集群,spot-controller 则监听该CR,负责集群node资源的新增和删除,当然,这需要拥有 IaaS 层虚拟机的创建和删除权限。
从功能模块来看,Spot-Controller 的功能模块分为以下几个部分:
- 混合算力供应 —— 抵消竞价实例可能回收/库存不足带来的算力不足的风险
- 用户可指定部分按量算力作为竞价实例的算力补充,即稳定的算力buffer
- 用户可指定多种机型规格,降低某种机型售罄的潜在风险
- 用户可指定多可用区,作用和配置多机型规格类似
- 灵活的供应策略 —— 满足不同的本质需求
- 多可用区打散策略 (在您配置的多可用区内平均供应算力,达到高可用)
- 容量高可用策略(优先扩容资源库存最高的实例规格,达到高可用)
- 成本优化策略 (优先扩容成本最低的实例规格,具有最优的成本优化效果)
- 释放闲置资源 —— 极致的弹性伸缩
- 结合TKE提供的全套弹性伸缩解决方案: HPA/VPA + Cluster Autoscaler, 自动释放闲置资源,即可利用弹性能力进一步助力成本缩减。
使用成本控制系统的最佳实践
文章最后,通过沉淀我们在服务不同行业场景客户的实战经验,我们给出了一些使用本系统以及竞价实例的最佳实践。从业务场景来看,如果您的业务是无状态业务,比如可横向伸缩的Web站点服务、图像渲染、大数据分析、并行计算、强化学习、AI等,都非常适合使用这套成本控制系统。此外,我们有一些Tips供您参考,以获得更佳的使用体验:
大数据/强化学习场景 - 切分任务粒度
- 长时间作业拆成细粒度的作业,减少被中断可能性(结合容器场景下的Workload能力)
- 强化学习场景
在线业务场景 - 通过负载均衡在保证服务的稳定性
- 利用Kubernetes原生的Service能力,配合负载均衡,保障业务的高可用。
- 通过合理配置Spot-controller中的不同资源配比,保证负载均衡后端资源的稳定供应。
- 通过tke-spot-agent 监听竞价实例中断情况,优雅终止并迁移业务副本。
离线计算场景 - 支持断点续算的计算调度模式
- 将计算中间结果放到 COS/CFS/NAS 等持久存储产品上。
- 通过tke-spot-agent 监听竞价实例中断情况,在应用(Pod)中定义优雅退出钩子,保存中间计算结果。
- 定义应用(Pod)中定义启动钩子,当新业务副本成功启动后自动从持久存储中拉取中间结果,继续计算。
在腾讯云容器服务使用竞价实例
当前TKE已经通过节点池集成了竞价实例,您可以直接通过TKE直接创建竞价实例节点池。详细可查看创建节点池[1]。并且可以通过TKE应用市场部署上述Spot Agent应用助力业务优雅终止和平滑迁移。同时弹性容器服务EKS即将推出竞价类型Pod, 届时您也可以通过弹性容器服务使用更低成本的计算资源。
总结
多数企业上云核心目的之一就是降低成本, 且容器化让成本具备了非常大的优化空间,而真正降低成本需要深度利用云和容器化的弹性能力, 并且容器能够让弹性和稳定性得到了权衡。腾讯云容器团队将陆续提供上述成本控制系统套件, 如您有任何建议或诉求欢迎通过小助手与我们联系。
参考资料
[1] 创建节点池: ( https://cloud.tencent.com/document/product/457/43735)
往期降本增效指南精选
① 容器化计算资源利用现象剖析
② 资源利用率提升工具大全
持续更新中。。。