一、背景
在安卓手机DevOps的CI流程中,我们基于原生jenkins自研了分布式jenkins平台承载编译任务,而在分布式jenkins平台中主要有三大构建业务:
个人级验证
:相比个人调试有更正式的编译环境用于工程师跑构建任务,基本是工程师上班时间才会触发门禁构建
:一些门禁场景触发的构建任务,基本是上班时间触发项目构建
:正式版本的构建任务,配置的任务基本是晚上触发
在初始阶段,三大构建业务的编译资源都是各自管理的物理机,这种方式虽然有很好的编译性能,但是每个业务都差不多占有上千台物理机,业务空闲时段对应资源完全空闲,造成很大的浪费;而且物理机模式下,从业务申报新资源到采购部署一般都是好几个月的时间,过长的时间周期难以和灵活多变的业务需求匹配。
jenkinsjenkins_10">二、jenkins静态节点与jenkins动态节点
在收到某些业务构建资源到达瓶颈告警后,我们找到了云部门,云部门为我们提供了k8s+kubevirt的kvm虚拟机调度平台。kvm相比于物理机,性能上的确有一些损耗,但经过测试,这些损耗是在可接受范围内的。
有了云平台的能力支持,基于jenkins节点生命周期我们把节点分为两种:
jenkins静态节点
:挂载到分布式jenkins平台长期运行的节点,这些节点构建完会清理工作目录(也可配置成构建前清理工作目录)jenkins动态节点
:当有构建任务时,先调k8s+kubevirt接口创建kvm,之后把该kvm挂载到分布式jenkins平台跑构建任务,构建任务跑完后会删除该kvm
注意,这里的jenkins节点可以是物理机,也可以是kvm
分布式jenkins平台也支持了多种节点选择策略,包括现在大部分业务配置的优先jenkins静态节点,jenkins静态节点用完时使用jenkins动态节点
策略,这样就解决了某些时段内业务构建资源不足导致排队严重的问题。
三、静态资源使用率与优化
到这里,我们的构建任务基本跑成了云下物理机+云上jenkins动态节点的工作模式,但是毕竟云下物理机规模占比太大,以及各个业务资源独立,必然出现资源使用率低的空闲时段。
为了更客观的看到jenkins静态节点资源使用率,我们对jenkins静态节点的执行器使用情况做了资源监控。如下图,以某一天门禁资源为例,可以看出资源使用率很低,特别是晚上时段,空闲率达到了80%以上:
为了提升静态构建资源使用率,我们对静态资源做了新的规划。首先,我们把静态资源划分为三部分:
常规资源
:全天时段业务均会用到的一个资源量波峰资源
:业务构建峰值时在常规资源外额外使用的资源空闲资源
:业务构建峰值时也无法用到的资源
对于这三部分资源,我们又做了如下规划:
常规资源
:注意到云下物理机弹性管理差,后续可能还会做相关调整,所以准备在云上用等量的kvm替换云下物理机;同时考虑到动态节点需要调用云平台的k8s+kubevirt接口,为了防止云平台接口短时间内的异常(例如4h以内的维护)对构建业务的影响,常规资源的kvm仍然保留“静态”特点长期运行波峰资源
:波峰资源打算放到动态资源池,这样这部分资源在业务空闲时可以给其它业务使用,从而提高资源利用率空闲资源
:做回收处理
四、滚动搬迁上云
为了后续更灵活的管理构建资源,我们打算全部用kvm替换物理机,因此需要把云下物理机全部搬到云上。但是这些物理机机和云平台不在一个机房,我们需要搬迁到云平台机房纳入k8s集群管理,而如果把一个业务的所有物理机节点一次性搬迁上云,耗时可能超过一周,这会造成业务无节点可用,并且搬迁过程本身就具有一定的风险,因此一次性搬迁评估下来风险太大,不能执行。
考虑到k8s集群有几百个临时的空闲节点,于是有了滚动搬迁方案:先在集群临时空闲节点中创建好对应规格的kvm,之后在分布式jenkins平台把这些kvm打上云下物理机对应的标签,接着把对应数量的物理机节点从分布式jenkins下线并搬迁到云上,最终实现不影响业务的滚动搬迁。
五、动态资源池
各个业务的“波峰资源”纳入动态资源池后,由于是错峰使用资源(项目构建晚上、门禁构建白天),因此各业务放到共享资源池的资源应该足够并且有剩余,接下来我们从技术实现上来探讨下共享资源池的逻辑。
为了更清楚的看到各个业务在共享资源下的资源使用情况,结合k8s+kubevirt特性,我们首先做了如下资源监控指标:
- request CPU分配率:业务动态专享池所有pod request CPU之和,除以业务动态专享池所有node allocatable CPU之和的百分比
- request memory分配率:业务动态专享池所有pod request memory之和,除以业务动态专享池所有node allocatable memory之和的百分比
- limit CPU分配率:业务动态专享池所有pod limit CPU之和,除以业务动态专享池所有node allocatable CPU之和的百分比,由于kubevirt支持CPU超分,该比值可能超过100%
- limit memory分配率:业务动态专享池所有pod limit memory之和,除以业务动态专享池所有node allocatable memory之和的百分比,内存不超分,而且内存是不可压缩资源,所以一般会预留一小部分内存,因此limit memory分配率理论上是达不到100%
- hugePages-2Mi(2Mi大页内存)分配率:业务动态专享池所有pod limit/request hugePages-2Mi之和,除以业务动态专享池所有node allocatable hugePages-2Mi之和的百分比,由于大野内存不支持超分,且limit必须等于request,因此大页内存只有一个指标
- lvm分配率:采购的服务器一般有4~5T的本地盘,为了充分利用这些资源,云平台支持在VMI/Pod中声明和使用本地盘,因此lvm分配律为业务动态专享池所有pod lvm之和,除以业务动态专享池所有node allocatable lvm之和的百分比,该百分比不会超过100%
由于多个业务都会用到共享资源,如果不做额外划分,就会形成各个业务优先级一样争抢资源的局面。表面上看着没问题,但是可以想象如下场景:共享资源用完了,有几个重要性不一样的业务都在排队等着空闲资源,当你此时往共享池加入机器后,有可能很快被不重要的业务抢占了,重要业务还是要接着排队。
为了体现业务的优先级概念,同时又尽可能地简单管理,我们可以通过给各个业务划分不同标签节点,并且还有个标签(例如project=share)用来存放空闲节点。而节点标签概念与k8s node lable、kubevirt VMI(VirtualMachineInstance)和k8s pod节点标签选择概念一致,于是有下图:
当A业务需要动态资源时:
- 统计该业务下所有待调度的构建VMI;
- 把project=a的节点按资源使用指标排序,最空闲的靠前;
- 由于此时还不知道k8s scheduler会把排队中的VMI调度到哪个节点,我们可以先用“乐观保守调度法”,即假设VMI会从上述节点列表前面开始找直到找到第一个可以调度上去的节点作为目标节点,并且在内存中把该节点资源减去该VMI资源,以便计算下一个VMI的调度结果;
- 如果计算后发现project=a下的节点资源就已满足所有VMI的调度,则本轮计算结束,间隔一定时间后又从第一步开始计算;如果计算后project=a的资源已无法满足所有带调度的VMI,则继续往下走;
- 把project=share的节点按资源使用指标排序,最空闲的靠前;
- 把剩余仍不可调度的VMI按“乐观保守调度法”在project=share节点执行,从project=share节点中选择出目标节点。从这里可以看出,之所以从最空闲的节点开始计算,是因为这样计算可以尽可能的少拿用节点,不至于一次拿过量节点再归还,所以取名“乐观保守调度法”;
- 把上述目标节点标签从project=share修改为project=a;
- 重复上述步骤。
随着各笔构建逐渐跑完,project=a节点的资源使用率会越来越低,当低于一定阈值时,需要把project=a中最空闲的节点慢慢归还,也就是把节点标签修重新修改为project=share。在实现时,可以给每个业务设置一个节点数量下限和上限,以防止某个业务无限扩张。我们这边一般把下限设置为0,表示业务空闲时会把所有节点都归还,供其他业务拿过去使用;而上限则是根据业务资源监控做动态调整。
注意,在归还节点时,不能保证归还的节点是完全空闲的,而是允许少部分业务的构建kvm在节点归还后继续跑,直至构建结束。
当出现资源耗尽需要新增资源并且优先给重要业务(假设该业务对应标签是project=a)时,可以直接设置新增节点标签为project=a,这样业务a无需做资源争抢。
对于project=share这个标签下的节点,我们也做了资源监控,但监控指标主要是节点数。而这个节点数是一个反向指标,也就是project=share标签下的节点数越多,表示越多的节点空闲;当project=share节点数长期大于50以上,则说明还可以腾挪空闲节点出来;当project=share节点数时不时接近或者等于0时,说明可能动态资源池趋于饱和或者需要增资源了。
综上所述,结合jenkins静态节点资源,最终对于各个业务来说,jenkins构建资源情况就有如下图:
六、腾挪出来的资源
我们通过物理机搬迁上云kvm化、“削峰”、共享等手段,腾挪出来几百台物理机,对于这些物理机,我们做了如下处理:
- 近年采购的服务器:过保时间还比较长,配置也更好,退回给IT,IT再重新规划给其它部门使用,这样从部门层面看减少了硬件资源成本,从公司层面看减少了新服务器的采购成本;
- 过保替换节点:除去退还的服务区,剩余服务器一般是快过保和配置更小的机器,这些机器我们可以把一部分作为过保备用节点。对于构建节点过保与故障,采取能修则修,不能修则下线的策略,过保备用节点一般会按即将过保机器数量的5%来预备;
- 剩余节点:再除去过保预备节点,其它节点则可用来填补来年部门新增采购需求,或者预防突发大批量构建排队等场景。
以上是我们对安卓手机jenkins构建资源池化和降本增效做的相关举措,通过这些措施使得部门内连续2年0新增采购资源,也没有出现因资源不足而导致的构建长时间排队问题。我们的方案也可能不是最优解或者还没达到最优状态,仅提供大家参考。