
近几年,OPPO 构建并不断完善基于 kubernetes 的混合云平台,统一了之前公有云、自有机房裸机部署的方式,积极推动业务迁移上云,使得在简化运维、降低成本等方面取得了一定的成果。但是初尝喜悦之后,我们发现不(ke)合(you)理(hua)的地方:
-
宏观上看:整个云看起来像个“会跳舞的胖子”,所有的服务单纯堆在云上,显得很臃肿; -
微观上看:应对突发、峰谷等状况没有为业务提供很好的弹性手段;
从平台的角度看,要做的就是一件事:“把资源盘活、盘好、盘细”,我们内部把他们分成不同的方向在探索,比如统一算力、智能调度,而我们这里要谈的就是弹性伸缩,希望通过它做到的是节省空余资源,提高分时利用率。
我们从不同的维度,挖掘了OPPO云实际业务的一些配对特征(下图所示):
按实时性分:
在线业务,实时要求高,不可重试,不太能接受被抢占;
离线业务,可以接受排队,被抢占重试任务即可;
按职能分:
基础设施,包括云上的一些paas产品,或者保护平台基本运作的设施;
对外服务,主要是对外业务的服务;
按部署形态分:
单集群部署,不具备跨集群部署能力的业务;
可用区部署,跨集群业务,但这里想表达的是包括但不限于serverless化;
按状态分:
有状态业务:各种rds、中间件、甚至大数据处理的集群等;
无状态业务:一些高并发服务;
按实现方式分:
传统方式实现:传统技术实现的服务直接上云;
-
云原生方式实现:operator 化的业务,有些本身实现了k8s scale能力,有些则没有;
其实有很多是具备“逻辑可弹”的属性(即逻辑上可以设置多副本,组成集群或者组的概念),比如“持续部署”是一个 PAAS 产品,他里面的“分组”就是用来管理业务节点副本的,同时又包装了诸多比如rds授权、服务发现、对接监控等能力,我们把持续部署打造为一款弹性资源,那只要接入了持续部署的所有业务,就变成了弹性资源的实例化对象。我们这里希望做的不仅仅是通过“弹性伸缩”解决典型的场景问题,更想要统一的管理和挖掘弹性资源,输出标准。
综上所述,我们打算做个统一管理弹性资源的平台,提供弹性伸缩能力。
▎2.1 原则
在上述背景下,对于弹性平台的设计,我们设立了一些基本原则:
丰富:提供丰富的弹性策略,支持多种触发能力;
安全:伸缩执行时要有严格的兜底管控能力;同时具备详细的观测、审计能力可以追溯;
统一:设定资源弹性化标准,需要弹性化的资源方只需做一些对接,即可自动化接入;
复用:公司内部已经有非常完备的监控、告警、调用链等系统,我们的弹性产品希望能够做良好的集成,把整个产品链起来;
-
框架:通用能力尽量下沉,同时又具备充分的扩展能力。比如对接公司产品,我们不希望做强耦合,而是通过实现接口将产品能力安置在扩展包中,保留框架的抽象层。
▎2.2 选型
基于 kubernetes 的弹性开源方案并不太多,像原生的 HPA 或者 keda,本质都是利用了 hpa controller 对各种metrics server做轮询。
而又鉴于以下几点,我们觉得 kubernetes 已有的方案并不适合我们:

我们决定自己去实现一款弹性平台,吸收了现有方案的优势,择善而从,在包含了 hpa 能力的同时又增加了一些实用的功能,项目代号为“burrfish”。
本节主要介绍在平台开发中的一些设计理念和架构组件。
▎3.1 理念设计
对于弹性平台的设计:
首先得具备丰富的弹性功能,这是本职工作;
-
其次我们想要实现的是相对通用的、又能结合公司产品的框架,所以在主流程的关键步骤上都是由抽象的接口层实现,接口层之上又分为标准层和扩展层,对于标准层我们制定协议、结构,让有需要的调用方按照标准接入;对于扩展层,则是我们主动适配对方接口来实现功能,但又因为扩展层的规划,我们在适配时不需要敛手束脚地担心对框架的侵入,用直白的话说,就是“直接拿掉扩展层(蓝色阴影),框架照样可以运行”。
对于弹性资源的设计:
前文提到可以设置多副本、多worker、多成员的对象,其实都具备逻辑可弹性,一般实现来说,即使没有用 kubernetes 里原生的工作负载比如 deployment、statefulset,也会去构造“组”的概念,对组设置副本。而我们则做了个取(chou)巧(xiang),设计了弹性资源的概念,这样我在控制面上不需要关心你用什么去承载多副本,你在我这就是注册的一种弹性资源。
-
然后我们在弹性资源的设计上严格遵守框架的理念,推出资源弹性化标准。其中弹性化标准层是让弹性资源方来适配框架,而http接口标准则是为了让传统服务更简单的接入框架;弹性化扩展层则是框架去主动适配某一种弹性资源。

-
进一步的,我们把公司的多个 PAAS 产品对接成了弹性资源。这种多样化的对接方式是有必要的,同时我们又想让这个过程变得平滑,不强求资源方为了接入弹性框架,而让他们按照统一云原生的方式改造自己。
梳理完思路后,我们提出了一个分层构想:
▎3.2 架构设计
如下图所示,我们架构包括5个组件,guarder、trigger、scaler、hooker、tracer。其中:
guarder:守卫模块,职能包括:
保存弹性策略完整的伸缩配置;
根据触发器的不同类型,决定是否要到指标源进行必要注册;
-
管理策略的状态,只有正常的且打开的才能顺利流转;
trigger:触发器模块,他是一类可以扩展的组件。只要实现了逻辑语义“拿到一个指标,格式化发给后续组件”都可以认为是触发器,即使有业务逻辑比较重的触发规则,也可以归类到扩展层中。它的分类如下:
定时触发:基本需求;
k8s metrics server:主要就是一些开源的指标采集方式,可以无缝支持,但实际对我们来说不是很用得上;
云监控告警:绝大部分策略采用的触发方式。我们有完备的监控体系,他集成了系统负载(cpu/memory等)、消息中间件、rds、log日志、自定义业务指标等监控,它同时支持轮询(分钟级)、流式(秒级)告警,所以我们并不需要造太多的触发器轮子;
-
sidecar触发:这是 FAAS 对接衍生出的一种触发方式,可以满足其需求;
支持原生的 hpa controller 能力;
-
增加了生效规则、兜底校验、多重策略优先级锁等功能
hooker:hooker设计的目的如下:
形成统一的方式扩缩容。因为不同的弹性资源,实现方式不同,副本设置逻辑也有差异,比如:
k8s scale resource:原生方式,面向一些比较新的云原生弹性资源;
http scale resource:调用弹性标准接口扩缩容,他主要面向了两类弹性资源:
传统服务包装成弹性资源,不方便改造为云原生方式;
做业务侧的扩缩容决策,不希望直接提交给原生的controller;
可以增加其他逻辑比如前后钩子等;
-
“结果校验”的逻辑是按需配置的,这个功能可以使扩缩容变成串行化,这在我们内部有实际的场景,比如持续部署业务发布时有上线锁,框架不断检查上线结果再触发下一次弹性发布,虽然这些逻辑可以做在各自的模块里,但我们的标准可以让这个事变得简单;
tracer:追踪器,是为了可观测、可审计单独设置的一个模块。我们在框架的几个关键部位进行埋点,就可以实现:
输出完整的副本变化指标;
输出关键的event事件;
-
记录每一次伸缩的详细过程,甚至包括被忽略执行的(不重要的审计会进行合并);
这里说的是指整个集群的稳定性,因为在没有弹性伸缩的时候,业务一般都是维持了一个固定的副本数在运行,当用上弹性之后,副本是在动态变化的,这里面其实暴露了一些问题,比如:
资源不足:尤其是一些规格大的、规格比例不太友好的业务,缩了之后扩不出来;
-
缩容异常:会由于docker卡主、lxcfs卡主、volume占用等各种问题导致缩容失败;
对于这些,我们总的规划是“用不同的专项去解决,弹性伸缩专注在实现副本扩缩容”,这里列举以下几个主要方面和我们的应对措施,些许笔墨道不清其中逻辑,可以等待后续专题:
资源问题:智能调度;
-
异常处理:弹性自愈;
本文为上篇,主要描述怎么实现一个弹性框架,因为我们的希望始终都是“功能上通用、设计上解耦”。在“下篇-运用实践”中,我们会通过不同场景的案例来描述弹性平台如何在 OPPO 内运用的。

Jia Zhu
OPPO高级后端工程师
负责OPPO云弹性伸缩的整体架构、云原生化的设计与开发。
本文分享自微信公众号 - 安第斯智能云(OPPO_tech)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。