源宝导读:Jenkins作为一个开源的持续集成工具,被大家广泛使用。本文将分享,Jenkins在明源云研发协同平台中的运用,以及在其作业设计方面的演进历程。
一、作业设计1.0
起初,为了尽快推出研发协同平台v1.0,我们运用Jenkins工具,快速实现了站点的部署、配套服务的安装、以及文件打包等功能。
使用过程包括以下简单的几个步骤:
1、定义Jenkins Job模板
参照Jenkins Job的config.xml结构,定义出Job模板,以及相关业务对应的子模板。
2、替换业务参数
依据业务场景,选择使用不同的子模板,替换对应的业务参数与子模板,生成最终的Jenkins Job配置文件。
3、调用Jenkins API
通过直接调用Jenkins Api完成Job的创建与Build。
4、轮询监控job状态
通过不停的轮询监控已出发build的job,获取其当前执行状态,得到最终执行结果。
刚开始为了快速实现业务需求,采用最简单直接的方式引入了Jenkins,并快速的完成了相关业务功能的实现。
二、作业设计1.x
随着相关业务需求的不断递增,起初的代码结构也慢慢显露出其弊端,主要体现在:
子模板难以维护:数量不断增加,子模板的相似度也在不断增加;
参数变量追溯困难:为应对各种不同场景,使用了多种多样的业务参数变量;
Job状态不一致问题:业务数据状态与job实际执行状态出现不一致;
接入新作业代码冗余:每接入一个新的Job作业,都需要定义过多的与Jenkins相关的内容。
为解决上述问题,我们对Jenkins作业的设计进行了不断的改进,改进主要分为2个方向。
2.1、业务抽象
我们将不同的业务进行了划分,站点部署、配套服务、打包等,并且提取统一的Job构造器,避免业务代码与功能代码相混淆。
除此之外还对Job的执行、状态监控、失败重试等功能性业务托管到Jenkins调度服务,并通过消息通知实现了解耦。
2.2、Jenkins SDK
为了更方便的操作Jenkins相关功能,提取了针对Jenkins的SDK,避免直接与Jenkins进行交互,该SDK也是Jenkins调度服务的底层工具。
最终设计结构如下图所示:
三、作业设计2.0
在完成作业设计1.x的落地后,已经能很好的支撑当时的持续集成业务,但是在后续的业务发展中,不足之处也逐渐显现:
Job作业的模板组装不够灵活,当原有业务发生变化时,不太容易灵活的调整。
当有新产品类型的持续构建需求时,需要重新定义新的模板和作业。
Job和业务的耦合度较高。
从设计的角度来看,主要是扩展性和配置的灵活性不足。
一次持续集成,也就是一组Jenkins作业按照一定的顺序组成,而一个作业里面实际上都是在执行一个一个的命令,基于此, 我们设计了作业2.0的基础模型:管道、作业、命令。
本次作业设计的演进充分利用了Jenkins的插件功能,并且取代了之前的Jenkins调度服务
总体设计图如下:
管道:一个持续集成管道由一系列持续集成作业组成; 不同功能的作业组合成不同功能的管道; 持续集成管道中的作业可以是串行,也可以是并行
作业: 管道中的作业由一组命令组成; 不同的命令组合成不同功能的作业
命令:命令是持续集成中的最小功能单元;研发协同平台内置了一批命令集
Jenkins执行器:专门负责执行管道作业,利用Jenkins SDK与Jenkins进行交互;
Job状态回调管道:这里运用了Jenkins的Notification插件,Jenkins Job执行过程中的状态会通过该插件即时回调;
3.1、类设计图
服务
ServiceDomainService:服务调用的场景类,负责统一调度执行服务。
BaseService:服务基类,所有服务都继承与该类进行扩展。
若有新的服务,只需通过继承BaseService进行扩展即可。
/// <summary>
/// 服务
/// </summary>
public abstract class BaseService : ITransientDependency
{
/// <summary>
/// 服务类型
/// </summary>
public virtual ServiceType ServiceType { get; set; } = ServiceType.Other;
/// <summary>
/// 执行操作
/// </summary>
/// <param name="args"></param>
/// <returns></returns>
public abstract Task Execute(BaseServiceArgs args);
/// <summary>
/// 执行器
/// </summary>
internal ICiExecutor CiExecutor { get; }
/// <summary>
/// 构造函数
/// </summary>
/// <param name="ciExecutor"></param>
public BaseService(ICiExecutor ciExecutor)
{
CiExecutor = ciExecutor;
}
}
管道与作业
BaseCiPipeline:管道抽象类,其中定义了管道作业的的构建动作。
Job:单个作业对象,包含Jenkins Job 必须的属性。
若新服务需要构造新的执行管道,需要通过BaseCiPipeline抽象类进行派生。
/// <summary>
/// CI流水线
/// </summary>
public abstract class BaseCiPipeline : ITransientDependency
{
/// <summary>
/// 构建Jenkins Job 集合
/// </summary>
/// <param name="args">CI流水线参数</param>
/// <returns></returns>
internal abstract Task<List<Job>> StructureJobs(CiPipelineArgs args);
}
/// <summary>
/// JenkinsJob定义
/// </summary>
public class Job
{
/// <summary>
/// Job执行顺序编号
/// </summary>
public int JobIndex { get; set; }
/// <summary>
/// 执行步骤
/// </summary>
public List<CiAction> ActionList { get; set; }
/// <summary>
/// Job参数
/// </summary>
public Dictionary<string, string> BuildParams { get; set; } = new Dictionary<string, string>();
/// <summary>
/// 执行节点
/// </summary>
public string AssignedNode { get; set; }
/// <summary>
/// 工作空间
/// </summary>
public string BaseJobName { get; set; }
/// <summary>
/// 当前JobName
/// </summary>
public string JobName { get; set; }
/// <summary>
/// Job目录地址
/// </summary>
public string JobPath { get; set; }
/// <summary>
/// 业务回调参数
/// </summary>
public string Notes { get; set; }
}
命令
CiAction:命令基类,定义了命令的基本属性与动作。
每个新增的命令只需要实现自己的变化点即可。
/// <summary>
/// 操作
/// </summary>
public abstract class CiAction
{
/// <summary>
/// 操作类型
/// </summary>
public virtual ActionTypeEnum ActionType { get; set; } = ActionTypeEnum.BatchFile;
/// <summary>
/// 操作说明
/// </summary>
internal string _actionName { get; set; }
/// <summary>
/// 模板
/// </summary>
/// <returns></returns>
internal abstract string Template();
/// <summary>
/// 参数
/// </summary>
/// <returns></returns>
internal abstract Dictionary<string, string> BuildParams();
/// <summary>
/// 生成Action内容
/// </summary>
/// <returns></returns>
public string Build()
{
var result = this.Template();
var pars = this.BuildParams();
if (pars != null && pars.Count > 0)
{
foreach (var item in pars)
{
result = result.Replace(item.Key, item.Value, StringComparison.OrdinalIgnoreCase);
}
}
return result;
}
}
执行器
ICiExecutor:执行器接口,该接口定义了执行器的动作以及对外扩展的回调事件。
JenkinsExecutor:是基于Jenkins 实现的执行器,通过解析管道作业,获得最终的Job并与Jenkins进行交互。
这里只实现了Jenkins的执行器,若将来扩展了新的持续集成工具可以直接扩展新的执行器。
/// <summary>
/// 执行器
/// </summary>
public interface ICiExecutor
{
/// <summary>
/// 开始构建
/// </summary>
event EventHandler<CiPipelineArgs> Started;
/// <summary>
/// 构建失败
/// </summary>
event EventHandler<CiPipelineArgs> Failed;
/// <summary>
/// 构建成功
/// </summary>
event EventHandler<CiPipelineArgs> Succeed;
/// <summary>
/// 执行流水线
/// </summary>
/// <param name="ciPipeline">流水线</param>
/// <param name="args">参数对象</param>
Task Execute(BaseCiPipeline ciPipeline, CiPipelineArgs args);
/// <summary>
/// 触发构建事件
/// </summary>
/// <param name="args">业务参数对象</param>
/// <returns></returns>
Task InvokeStarted(CiPipelineArgs args);
/// <summary>
/// 触发构建失败事件
/// </summary>
/// <param name="args">业务参数对象</param>
/// <returns></returns>
Task InvokeFailed(CiPipelineArgs args);
/// <summary>
/// 触发构建成功事件
/// </summary>
/// <param name="args">业务参数对象</param>
/// <returns></returns>
Task InvokeSucceed(CiPipelineArgs args);
}
回调管道
BaseServiceCallbackHandler:回调处理基类,所有的回调处理都应集成与该类。
每个回调处理,可以通过定义 ServiceTypes去侦听自己所关心的回调事件,并做相应的业务处理。
/// <summary>
/// 服务回调处理
/// </summary>
public abstract class BaseServiceCallbackHandler
{
/// <summary>
/// 处理的服务类型
/// </summary>
public virtual ServiceType[] ServiceTypes { get; set; }
/// <summary>
/// 执行器
/// </summary>
public ICiExecutor CiExecutor { get; }
public BaseServiceCallbackHandler(ICiExecutor ciExecutor)
{
CiExecutor = ciExecutor;
}
}
作业运行流程
执行流程:客户端通过调用服务场景,指定需要执行的具体服务,服务定义了其对应的管道作业,最终交由执行器进行执行。
回调流程:回调场景主要用于接收Jenkins的回调请求,通过分析请求类型进而触发相应的处理事件。
3.2、本次演进主要带来的好处包括:
1、可扩展性
实现个性化功能,只需自定义命令。
对于不同的业务场景,只需定义不同的持续集成管道。
2、灵活性
通过原子性的命令定义,可以自由组合出各种想要的作业,进一步定义出各种功能的持续集成管道,从而满足业务多样化的需求。
3、解耦
通过抽象出Jenkins执行器,来实现业务和Jenkins管道之间的解耦,从而较小业务对Jenkins管道的影响。
四、展望未来
目前的作业设计已经具备了一定的灵活性和扩展性,在下阶段我们还会对作业设计做更进一步的优化,通过封装标准的作业步骤(Action)、统一的CI流程,进而提供可视化的流水线定制功能。由之前的代码业务驱动改为数据驱动,最终实现作业的全面可定制与可配置。
------ END ------
作者简介
朱同学: 研发工程师,目前负责RDC研发协同平台的设计与开发工作。
也许您还想看