作业帮上万个CronJob和在线业务混部,如何解决弱隔离问题并进一步提升资源利用率?

2021-12-09 11:10:30 浏览数 (1)

导语 | 日前,腾讯云中间件团队联合StreamNative社区正式发布了RoP 0.2.0版本,该版本在架构上全新升级,用户在使用中可以完全避免消息丢失、消息重复消费、只能消费一部分Partition的数据等问题。

作者简介

吕亚霖,作业帮基础架构-架构研发团队负责人。负责技术中台和基础架构工作。在作业帮期间主导了云原生架构演进、推动实施容器化改造、服务治理、GO微服务框架、DevOps的落地实践。

别路,作业帮基础架构-高级研发工程师。在作业帮期间,负责多云K8s集群建设、K8s组件研发、Linux内核优化调优相关工作。

背景

作业帮在云原生容器化改造的过程中,随着集群规模越来越大、业务混合部署的场景越来越复杂,面临的集群问题也越来越多,走到了Kubernetes及容器化的深水区,尤其是在上万个CronJob容器化,和在线业务混合部署在同一个生产集群后,问题就更加明显。

作业帮在线的生产业务使用TKE部署在黑石2.0物理机上,单个机器规格比较大,部署的pod也就比较多,而cronjob的特性是频繁、定时启动和销毁,同时也需要给这部分业务预留一定的固定资源,所以这块主要有2个问题:一是在大规模pod频繁创建销毁场景下,cgroup弱隔离性导致的节点稳定性问题,从而影响同一节点其他业务,二是资源预留导致的资源利用率低的问题。这两个问题其实已经超出了原生Kubernetes的能力覆盖范围,我们需要新的思路来解决。

下面将详细介绍这两个问题产生的原因及解决办法

问题一:集群内节点稳定性

由于业务上存在很多分钟级执行的定时任务,导致pod的创建和销毁非常频繁,单个节点平均每分钟有上百个容器创建和销毁,机器的稳定性问题频繁出现。

一个典型的问题是频繁创建pod导致节点上cgroup过多,特别是memory cgroup不能及时回收,读取/sys/fs/cgroup/memory/memory.stat变慢,由于kubelet会定期读取该文件来统计各个cgroup namespace的内存消耗,CPU内核态逐渐上升,上升到一定程度时,部分CPU核心会长时间陷入内核态,导致明显的网络收发包延迟。

在节点perf record cat/sys/fs/cgroup/memory/memory.stat和perf report会发现,CPU主要消耗在memcg_stat_show上:

而cgroup-v1的memcg_stat_show函数会对每个CPU核心遍历多次memcg tree,而在一个memcg tress的节点数量达到几十万级别时,其带来的耗时是灾难性的。

为什么memory cgroup没有随着容器的销毁而立即释放呢?主要是因为memory cgroup释放时会遍历所有缓存页,这可能很慢,内核会在这些内存需要用到时才回收,当所有内存页被清理后,相应的memory cgroup才会释放。

整体来看,这个策略是通过延迟回收来分摊直接整体回收的耗时,一般情况下,一台机器上创建容器不会太多,通常几百到几千基本都没什么问题,但是在大规模定时任务场景下,一台机器每分钟都有上百个容器被创建和销毁,而节点并不存在内存压力,memory cgroup没有被回收,一段时间后机器上的memory cgroup数量达到了几十万,读取一次memory.stat耗时达到了十几秒,CPU内核态大幅上升,导致了明显的网络延迟。

除此之外,dockerd负载过高、响应变慢、kubelet PLEG超时导致节点unready等问题。

问题二:集群的节点资源利用率

由于我们使用的是TKE vpc-cni的网络模式,这种网络模式依赖节点绑定的弹性网卡数量,所以单个节点上的pod ip数量存在上限,节点有几乎一半的podip是为定时任务的pod保留的,造成ip浪费,另外定时任务的pod运行时间普遍很短,这就导致了集群为定时任务预留的资源产生了较多闲置,不利于整体的机器资源使用率提升。

其它问题:调度速度、服务间隔离性

在某些时段,比如每天0点,会同时产生几千个Job需要运行。而原生调度器是K8s调度pod本身对集群资源分配,反应在调度流程上则是预选和打分阶段是顺序进行的,也就是串行。几千个Job调度完成需要几分钟,而大部分业务是要求00:00:00准时运行或者业务接受误差在3s内。

有些服务pod是计算或者IO密集型,这种服务会大量抢占节点CPU或者IO,而cgroup的隔离并不彻底,所以会干扰其他正常在线服务运行。

解决思路及方案

所以,对CronJob型任务我们需要一个更彻底的隔离方式,更细粒度的节点,更快的调度模式。

为解决上诉问题,我们考虑将定时任务pod和普通在线服务的pod隔离开,但是由于很多定时任务需要和集群内服务互通,还不能通过分集群的方式隔离。

腾讯云弹性容器服务EKS提供的虚拟节点,给我们解决上诉问题提供了一个新的思路

EKS的虚拟节点是serverless形态的Kubernetes服务,可以加入到现有TKE集群中,部署在虚拟节点上的pod具备与部署在正常TKE节点上的pod具备一致的网络连通性,但虚拟节点上的pod是在vm层面做了隔离,又具有无需预留资源,按量计费的特性,可以很好的满足我们这个场景的需求,所以我们将CronJob这种类型的业务都调度到了虚拟节点。

如图所示:

任务调度器

为解决K8s默认串行调度慢的问题,我们针对job类任务,开发了任务调度器,所有CronJob型workload都使用任务调度器,任务调度器批量并行调度任务pod到虚拟节点,实现大规模pod任务ms级调度,也支持虚拟节点故障时或者资源不足时调度回标准TKE节点。

解决TKE节点和虚拟节点在运维方式上的差异

在使用虚拟节点前,首先要解决虚拟节点pod和运行在标准节点上的pod差异,做到对业务研发无感。

  • 日志采集统一

在日志采集方面,由于EKS这种nodeless的形态,无法运行DaemonSet,而我们的日志采集组件是以DaemonSet形式运行的,这就需要对虚拟节点上的日志做单独的采集方案。EKS虚拟节点本身提供日志采集agent,  可以将容器的标准输采集并吐到一个Kafka topic,然后我们统一在这个topic里消费。

  • 监控报警统一

在监控方面,我们对虚拟节点上的pod做了实时CPU/内存/磁盘/网络流量等监控,做到了和普通节点上的pod一致,暴露pod sanbox的export接口,promethus负责统一采集,迁移到虚拟节点时做到了业务完全无感。

提升启动性能

虚拟节点上的Job需要具备秒级的启动速度才能满足定时任务对启动速度的要求,比如业务要求00:00:00准时运行或者业务接受误差在3s内。

主要耗时在以下两个步骤

  • 业务镜像拉取加速
  • 虚拟节点pod创建和初始化加速

针对第一个问题:EKS提供镜像缓存的功能,第一次拉取的时候稍微慢一些,拉下来后默认会缓存一段时间,同一个业务第二次启动就不需要再拉取镜像,所有镜像下载慢的问题基本就没有了。

针对第二个问题:业务要求的启动时间误差在3s内,所以我们和腾讯云EKS团队沟通后,为这种大规模、高频、短时的计算作业场景进行了针对性优化,提升了频繁启动的效率并降低了运行环境初始化的时间。

最终实现了虚拟节点上的Pod秒级启动。

总结

通过TKE EKS虚拟节点的方式,我们将正常在线任务和定时任务隔离开,有效保障了在线业务的稳定性,结合自研Job任务调度器、EKS镜像缓存、pod 启动加速等能力,实现任务pod秒级调度并启动,同时TKE 虚拟节点都是标准的K8s  API,做到了业务平滑迁移。最重要的是,我们固定的集群不需要再为CronJob类任务预留资源,释放了集群里10%的资源,结合EKS随用随取、按量计费的特性,定时任务的资源成本降低了70%左右

0 人点赞