背景
近期公司开始做成本治理, 降本增效. 通过观察以往各业务线的资源规格配置, 不难发现有大量服务资源超配情况. 即服务实际资源开销远小于其声明的资源开销. 而这部分的超配直接导致集群的资源使用率较低. 通常形如下图
在过去的集群资源使用实践过程中,我们也遇到了如下问题:
服务本身资源使用率较低, 但申请资源规格时使用了较高的规格
服务仅在特定时段(流量波峰)有较高资源使用, 其余时段资源使用率较低,没有申请合理的资源规格
开发者不知道如何评估及配置自身服务规格
在这篇文章的过程中,我们核心介绍的是:
如何建设合理的服务资源画像, 对外提供画像数据查询能力
依据资源画像数据在调度系统提供一定资源超售能力, 从而提高集群资源使用率
目标
如背景中提到的资源分配值与资源使用值之间的差值是带来整体资源利用率较低的直接原因, 故有效缩短这部分差值能带来直接的资源利用率提升.
故我们抽象了以下公式:
gap = abs(resource request - resource usage)
基于上述背景,我们细化出以下目标:
降低服务实际使用资源和服务声明使用资源的差异, 即尽可能减少上述公式中提到的 gap
根据在 PaaS 平台配置的服务类型提供梯度的资源超卖机制
对于指标中周期性抓取的资源特征数据进行持久化, 目前只考虑 cpu, memory
对于开发者在 PaaS 平台配置规格时给出推荐值
本次方案将降低用户配置服务资源规格成本,在集群维度避免过度超卖导致的节点内存掉底导致的宕机现象或频繁换页带来的性能影响.
本次讨论不关注 k8s 节点资源超卖, 仅关注服务粒度资源超卖
系统架构
名词解释:
console: 服务平台, 存储服务元信息
plume: 调度系统, 包装 k8s api, 提供 k8s workload 操作能力
portrait data service: 服务画像服务, 提供服务维度画像数据
VPA 结构上主要分为三个组件:
-
Recommender:
定期(Interval 默认为1天)从 Console 获取全量部署到 kubernetes 上的服务列表
-
向 Prometheus 查询服务过去 N 天的资源使用情况 (默认 N=1 以覆盖每日业务峰值)
对于 CPU, 获取 TP50/90/95/99 的 cpu 利用率作为业务容器平均 cpu 利用率 (即从小到大排序有第 50/90/95/99 位的容器 cpu 使用率)
对于 Memory, 获取特定时间窗口的峰值
-
根据推荐算法⑴计算服务资源的推荐值, 并根据 Recommender 策略进行服务元信息配置更新
策略1 – Auto: 将自动将推荐值刷入 console 服务元数据数据库中 (默认 Auto, 使用项目维度黑名单机制开启 Off)
策略2 – Off: 将推荐值记录到 db 中用于 console 界面展示规格推荐值
Recommender-Updater:
Recommender 的子模块, 用于周期性更新 k8s 资源, 存储特征数据
-
根据配置策略更新 k8s deployment/pod
策略1 – Auto: 将自动更新 k8s pod spec 中的资源 requests/limit 值, 且服务将发生重调度 (业务有感知)
策略2 – Off: 不进行自动更新, 靠服务重新在 console 部署触发资源更新 (默认 Off, 使用项目维度白名单机制开启 Auto)
往 db 中存储资源特征数据
- Portrait Data 画像服务:
接收 Recommender 请求,对采集到的服务画像数据进行持久化.
提高服务维度画像数据查询能力, 对外提供 http 查询接口.
- Admission Controller: (非必须组件)
拦截 pod 提交请求, 自动获取 Recommender 中的资源推荐值, 并替换 pod spec 中的资源 requests/limit 值
-
该组件用于提供以下能力
- 解决确保弹性伸缩时扩容出的 Pod 为新资源配比值
- 提供 Updater kill pod 时可以自动生成带有新资源配比值的 Pod 能力
- 综合 b. 中两点考虑,目前我们暂无复杂用法, 先降低系统复杂度. 不做该组件, 转为和 k8s api 打交道
数据库表结构设计
数据库选用 Hbase, 像画像中的资源数据类似指标打点, 会有 TP50/90/95/99 需要较灵活的分位分割, 对表的扩展性需要高.是 MySQL 不易于处理的场景,而列存储天然适合.
同时画像数据通常有较强的实时性要求, 即可能一段时间周期(比如6个月)前的画像数据的参考性不高. 同时为防止表数据膨胀, 也需要定期对画像表进行清理. 基于此可以利用 Hbase TTL 特性进行定期清理.
资源画像表
资源画像表记录服务粒度的资源的静态信息,通过 resource_type 区分资源类型,目前有 cpu, memory, disk io。
类似时序数据库用法,在表中存储类型,特征值, 采集时间字段
表名:resources_portrait
列名 | 类型 | 备注信息 |
---|---|---|
data | varchar(255) | 采集时间 |
id | bigint | 主键 |
resource_suffix | varchar(255) | 特征后缀, 如 cpu_busy_TP90, cpu_busy_TP99 |
resource_type | int(11) | 资源类型:cpu、memory等 |
value | varchar(255) | 资源值 |
设计细节
以下设计均建立在不考虑 Admission Controller 的场景下.
一、一次资源画像更新流程是怎样的?
Recommender 每周一早上 10 点调用服务平台 api 获取全量服务列表, 发起资源画像更新流程
向 Prometheus 查询 avg(rate(container_cpu_usage_seconds_total{container="${container}", pod=~"$service.+"}
Recommender-Updater 将采集到的特征数据更新到 db 中
Recommender-Updater 根据自身策略更新 k8s deployment spec
二、调度流程有啥修改?
调用流程与原来一致
Plume 调度系统接收到调度请求, 根据服务元信息中的资源规格配置生成 k8s pod spec 中相应资源字段
调用 k8s api 进行 apply workload
三、推荐算法是什么?
根据 PaaS 平台的服务类型 给予以下梯度 CPU 超售比
在线无状态服务: 1.5 (核心服务不超售)
离线服务 : 3.0
对于 CPU 按区间(单位为核数)拆分为 (0, 0.5], (0.5, 1], (1, 2], (2,4], (4,6], (6, 8], (8, 10], (10, 12], (12, 14], (14, 16], (16, *]. 按以下公式计算 CPU request 值
for interval in (0, 0.5], (0.5, 1], (1, 2], (2,4], (4,6], (6, 8], (8, 10], (10, 12], (12, 14], (14, 16]:
if 应用_cpu_usage in interval:
应用_cpu_request = ceil(interval) * 1/超售比 # 当前区间向上取整.
break
if 应用_cpu_usage > 16:
应用_cpu_request = 应用_cpu_usage * 1/超售比
对于 Memory 按区间(单位为 GB)拆分为 (0, 0.5], (0.5, 1], (1, 2], (2,4], (4,6], (6, 8], (8, 10], (10, 12], (12, 14], (14, 16], (16, *]. 按以下公式计算 Memory request 值
for interval in (0, 0.5], (0.5, 1], (1, 2], (2,4], (4,6], (6, 8], (8, 10], (10, 12], (12, 14], (14, 16]:
if 应用_mem_usage in interval:
应用_mem_request = ceil(interval) * 1/超售比 # 当前区间向上取整.
break
if 应用_mem_usage > 16:
应用_mem_request = 应用_mem_usage * 1/超售比 # 内存当前不超售, 未来可能超售
四、会自动修改资源的 limit 吗?
本次模型中 Recommender Auto 模式下只会修改 Request, Request 对于用户无感知.
对于 limit 在 console 上会与服务规格概念相关, 故只会给出推荐值, 不会帮用户修改.
五、Recommender-Updater 自动修改 k8s 模板后触发重调度业务可接受吗?
默认使用项目白名单机制,只有能接受这部分场景的应用开启,其余默认关闭 Updater.
对于二期链路上再尝试优化不进行重调度的 k8s 资源垂直扩缩.
六、Admission Controller 的作用是啥,可以去掉吗?
期望提供通用 patch pod spec 能力, 同时对于后续 Recommender-Updater 更新 pod spec 时可以提供通过逐步 kill pods 机制实现无感知上线
在目前的链路上可以去掉,也不计划在这一期支持
七、对于过度超卖的情况下如何矫正?
必然会存在部分项目出现计算出的 cpu requests/mem requests 不合理导致过度超卖的情况, 对于这类服务使用两种方式解决
在 Recommender 中白名单固定超卖比.
在 Recommender 中使用白名单关闭接入.
八、是否会对不同服务给予特定的超售比?
会计划, 但需要根据具体采集到的画像数据进行统一分析才能得到有依据的结论或超售比计算公式.
常见问题
Q: 对于 CPU, 获取 TP50/90/95/99 的 cpu 利用率作为业务容器平均 cpu 利用率 (即从小到大排序有第 50/90/95/99 位的容器 cpu 使用率)
是按哪个利用率作为阀值来决策呢?
A: cpu 默认以 P95 为决策,对于个别有明显波峰波谷的服务需要根据画像数据做具体调整. mem 默认以时间窗口内的峰值为决策.
Q: 内存超卖会影响业务稳定性,建议不进行。如果内存真的不合理,应推动业务主动更改配置更新服务
在资源调度层面不建议做内存和CPU超卖,会导致调度系统过于复杂,k8s pod层面request和limit可以不一样适当超卖
A: 内存不会进行超卖. 本次会对 cpu/mem 给出规格推荐值(界面展示). 会以邮件或其他通知方式推动配置更新. 用户主动在 PaaS 平台界面上按推荐值修改的资源规格实际影响的是 k8s limit. 对于方案中 VPA 调整实际是 k8s request, 对于用户无感. 同时对于 k8s resources limit 不会在调度链路中动态修改
Q: Recommender 虽然做了项目白名单可以选择性开启该功能,但自动调整风险较高。如果是一次性的调整,完全可以由平台给出参考建议,督促业务检查确认是否合理,之后由业务做出调整
如果是针对波峰波谷流量下资源的调整,可以给出利用率等曲线,业务需要综合服务延时等 确定按时段扩缩容规则,平台按业务规则进行操作。需要留一定的利用率buffer,要保证能够快速拉起服务
平台需要全局考虑所有业务在某个时间点扩容后需要的资源是否大于集群物理资源
因为在线任务原则上都是不能kill的,是高优,在线任务需要扩容时需要能保证资源,都是在线就无法完全保证了,可以结合离线任务来填补波峰波谷的资源; 在离线混部需要考虑离线任务对在线任务的干扰,需要一定的资源隔离机制保证。初期可以混部少量离线任务逐步打磨
Q: 在线资源利用率优化需要在考虑业务稳定性和服务延时的情况下去做,建议分步骤逐步实施,主要分为纵向扩缩容和横向扩缩容
1、纵向扩缩容,在满足业务延时情况下,尽力提升qps的情况下,资源仍然有富裕的,这部分应该通过缩减实例配置来优化
2、横向扩容容,在实例配置合理的情况下,因为业务流量下降(固定的)或者波峰波谷,可以调整实例数量来调整单实例的资源利用率
初期可以考虑在业务峰值压力下 如何优化单实例配置 和 实例数量,波峰波谷下的实例调整可以在一期积累更多经验后再进行。
A:合理的. 非常同意,目前计划是在 recommender 组件的机制上先留出这样一个策略的口子. 但不计划对生产业务线接入. 后续有能力建设对业务无感的资源调整机制后再通过这个口子开放业务接入.