CD 平台是掌门的持续交付系统,流水线功能自 2020.3 月在 CD 平台上正式开放,目前已稳定运营一年多,超过 900 个应用启用了流水线功能,应用接入率超 60%,下图展示了流水线功能的关键指标。可以说流水线已经成为了 CD 平台的一个核心能力,极大地帮助研发提升了持续交付的效率。
2. 流水线实现方案
2.1 方案选型
2.2 架构介绍
解决了 workflow 引擎的问题,我们就可以将工作聚焦到上层设计,下图展示了流水线功能的分层设计架构,业务逻辑相关的模块和抽象层实现由 CD 平台自己实现,流水线的执行由 Jenkins 负责。抽象层用于将流水线和 workflow 引擎的解耦,让我们复用 Jenkins 能力的同时保留了其他扩展性,方便以后尝试引入更适合容器的云原生流水线方案。
抽象层的核心是渲染模块和同步模块,渲染模块需要将输入转化成后端能识别的脚本,同步模块需要对接后端的 API,所以这两个模块需要为对应的流水线后端进行适配,在我们的场景里就是 Jenkins。模板和变量确定一个流水线的行为,通过渲染模块生成 jenkins pipeline 脚本,同步模块负责将脚本文件同步至 Jenkins 服务生成一个 jenkins project。理论上,如果要引入新的流水线引擎,便需要在渲染和同步模块增加对应后端的适配。抽象层设计上也符合 infrastructure-as-code 的原则,本质上即便我们新搭建一套 Jenkins 集群,基于“流水线脚本”就能迅速自动化还原出所有的流水线任务。
每一个流水线都对应 Jenkins 上的一个 pipeline 类型的任务,为了方便管理,我们按 #{app-id}-pipeline-#{pipeline-id}
做为 jenkins project 的命名规范。这套流程也充分考虑了后期更新的需求,尤其是在后期流水线规模庞大了之后,每次的模板更新出错都可能造成大范围的执行失败,目前我们可以针对一些特定应用打灰度标记,在模板的上线环节引入灰度过程,保证全量发布前有个小范围的灰度验证,对于错误版本能及时回滚。
2.3 扩展性说明
以下展示了一个经典的 Java 应用 git 触发 => 打包 => FAT 发布
流水线对应的 jenkins pipeline 脚本,从代码里可以看出:
pipeline 脚本只做 workflow 的编排,不负责具体步骤的实现
基于 CD 平台接口来推进整个 workflow 的执行
因为核心逻辑不在 pipeline 脚本中实现,理论上只要支持 workflow 编排的工具,便都能作为我们的流水线引擎。另外具体的执行逻辑由 CD 端实现,即复用了 CD 的原子能力,也让流水线脚本自身彻底解耦,虽然随着 CD 平台的演进我们在原子能力上做了很多的调整和优化,但是流水线始终能很顺畅地工作,两组开发人员在迭代各自功能时不需要有所顾虑。
// JENKINS PIPELINE EXAMPLE: Build => FAT Deploy
// predefined variables, value assignment made by CD pipeline renderer
def pipeline_id = 3182
def app_id = 13207
def fat_group_id = 13032
def api_url = "http://{cd-platform}/api/v1"
...
// variables will be used by following pipeline steps
def pkg_id
def build_result
def pipeline_activity_id
def fat_deploy_id
def fat_deploy_status
pipeline {
agent {label "pipeline"}
stages {
stage('build') {
steps {
// call cd to create package
script {
def payload = """
{"create_username":"$creator", "pipeline_id":"$pipeline_id", "application_id":"$app_id" ...}
"""
def jsonResponse = postRestCall("$api_url/packages/", payload)
pkg_id = jsonResponse.data.id
}
}
}
stage('log execution') {
steps {
retry(3) {
// call cd to create a pipeline activity for tracing pipeline execution
script {
def body = """
{"pipeline_id": "$pipeline_id", "package_id": "$pkg_id", ...}
"""
def jsonResponse = postRestCall("$api_url/pipelines/$pipeline_id/activities/", payload)
if (jsonResponse.http_code != 201) {
error "call cd error"
}
pipeline_activity_id = jsonResponse.data.id
}
sleep(3)
}
echo "log pipeline activity successful:)"
}
}
stage('check build status') {
steps {
// polling cd api to check build result
timeout(time: 10, unit: 'MINUTES') {
retry(20) {
script {
sleep(20)
def jsonResponse = getRestCall("$api_url/packages/$pkg_id/", payload)
buildChecker(jsonResponse)
}
}
}
script {
if (build_result == 'FAILURE') {
error('Stop early by building package failure…')
}
}
}
}
stage('deploy') {
steps {
// call cd to deploy package on specific fat group
script {
def version_name = sh(returnStdout: true, script: "date '+CI-%Y%m%d%H%M%S'").trim()
def payload = """
{"app_id": $app_id, package_id": $pkg_id, "group_id": $fat_group_id, "pipeline_activity_id": $pipeline_activity_id ...}
"""
def jsonResponse = postRestCall("$api_url/deployments/", payload)
fat_deploy_id = jsonResponse.data.id
}
}
}
stage('check deploy status') {
steps {
// polling cd api to check deploy status
timeout(8) {
waitUntil {
sleep(15)
script {
def jsonResponse = getRestCall("$api_url/deployments/${fat_deploy_id}/")
fat_deploy_status = jsonResponse.data.tars_status
return (fat_deploy_status == "SUCCESS" || "$fat_deploy_status".endsWith("FAILURE") || fat_deploy_status == "REVOKED")
}
}
}
}
}
...
}
2.4 UI 交互
在流水线详情页,除了可以编辑变量,还可以查看流水线的执行记录,可以查看具体的执行步骤,查看哪个步骤导致执行失败。CD 上除了独立的流水线入口,得益于平台的自研属性,我们可以将流水线功能与 CD 平台的持续交付做进一步整合,比如通过流水线触发的打包和发布记录都可以在 CD 平台的相关功能模块直接进行展示,再比如操作的权限和审计功能都可以沿用原有功能等。
流水线所有的操作入口,包括流水线的创建、配置、执行记录的查看等都在 CD 平台上完成,不对外暴露 Jenkins,对用户而言,使用的服务依然是一站式的。以一个经典的流水线执行流程为例,用户所有的流水线管理都在 CD 平台上发生。当用户通过 CD 平台设置好流水线后,便能把自己从 CD 上的手工操作中解放出来。下图的这个例子中,用户只需要跟 git 交互,然后关注流水线执行结果。通常只有收到流水线执行异常的通知,才会到 CD 上看具体的错误原因,这样便减少了打包、发布等操作对开发过程的干扰。
酒香也怕巷子深,即便我们对于流水线功能充满了信心,但是要推广用户接受并高频使用依然要做出很多额外的努力。
在功能对外开放之前,我们在团队内部就已经试用了一段不短的时间。我们要求研发效能自己的 Java 和 H5 应用在日常开发中坚持使用流水线功能,我们很快就确认了流水线对效能的提升,同时基于团队内部反馈,我们也对底层逻辑进行了不少优化。可以说最初的信心正是来自于早期的吃狗粮经历。下图是我们开放功能后的每周流水线任务执行次数统计趋势图,我把整个推广过程可以分为 4 个阶段:种子用户期,冷启动期,稳定增长期和爆发期。
作为效能团队,我们格外重视对用户的赋能,对功能的自助化是我们一贯的追求和原则。仅仅底层健壮还不够,必须降低用户的使用门槛,所以我们组织了一批早期用户进行试用。这个时期除了解决一些未发现的 Bug,主要是打磨交互逻辑和界面细节。非常幸运 CD 平台一直有一批核心用户乐于给我们提建议,积极地充当小白鼠,也能宽容我们的一些不足,对于这一点我们一直心存感激。
3.2 冷启动期
一个月后,我们认为功能正式毕业,开始筹备公司层面的推广。我们将项目纳入进 OKR,并设定了 KR 目标:提升流水线功能覆盖率到 60%。
在团队之前完成的如全公司统一接入发布系统,经典网络到 VPC 网络迁移等项目时,虽然我们也做了大量减轻用户负担的工具开发,但根本上还是靠自上而下的行政式命令得以完成。这种方式虽保证了项目实施的高效,但也容易让业务线积累抵触的情绪,使用上必须有所克制。
3.3 稳定增长期
经历了“冷启动阶段”,团队积累了跟用户推广的经验,总结出较好的操作文档和用户案例,功能上也得到进一步完善,来到了 “稳定增长期”。
因为 CD 平台本身就是强入口,我们便在平台“广告位”上增加了更多的曝光。这个时期用户已经展现出比较强的主动使用意向,标志之一就是新应用使用流水线的比例非常高,所以我们的工作重心不再是推广,而是做好内功的修炼。流水线功能导致了 Jenkins 集群的任务量上涨了几倍,所以我们投人大量的精力来完善整套 Jenkins 服务,包括扩容、完善监控告警、优化任务编排等等。类似的还有 gitlab 服务。
有段时间似乎到了增长的天花板,应用接入率指标的增长在逐步放缓,而我们距离的 60% 的 KR 目标依然很远。不过也有一些非常积极正面的信号:
每周流水线任务的执行数量非常健康 我们抽样调研的用户都认为功能很好地帮助了他们,他们非常愿意推荐给其他同事使用
所以一定有被我们忽略掉的细节。分部门统计后,我们发现各部门间在指标上有非常大的差异,如下图所示,背后的原因对我们很有启发:基础架构组和业务线的应用的开发模式不一样;不同事业部的内部规范也不一样:比如分支策略不一样,还有些部门测试在操作上扮演了更重要的角色。这些发现有助于我们功能完善和推广策略优化,另外我们发现公司内部有一个数量不小的“僵尸应用”,这类应用已经失去了迭代的活力,但是生产上依然有流量不能下线,所以需要从我们的统计基数里去掉。解决完这些问题,我们迎来了“爆发期”。
4. 总结与展望
没有两个公司的流水线是完全一样的,从一开始,我们的目标就是设计适用于掌门自身研发体系的流水线。基于这个目标,我们对流水线功能做了调整和裁剪,并不去追求功能的最大集,这更有助于我们在设计方案时有的放矢。另外一个体会就是流水线能不能做好,底层原子能力的建设非常重要,可能正是由于我们早期没匆忙上线流水线,而是花了极大的耐心和精力去打磨构建、发布等原子能力,反而让后期实现流水线变得更为顺利。我们的底层架构非常简洁清晰,这也有利于我们设计出更简单的用户界面,而简单的用户界面又让我们在推广时受益。
相比功能本身,我们更看重团队在推广过程中得到的成长。相较于行业内其他的研发效能团队,团队成员相对更年轻,年轻人总是更痴迷于功能开发而容易忘记提升效率的初衷,导致对功能落地缺乏积极性和持续的反思总结。特别是对于一个长周期项目,一定要首先建立起有效的推进机制,比如专门的用户支持群、CD 平台的用户反馈中心,持续的核心数据指标跟踪等。同时作为服务型团队,一定要深刻理解工作的职责是协助用户解决问题。以我们支持的一类流水线为例:git 触发 => 打包 => FAT 发布 => UAT 发布+FAT 回滚
,这里的 FAT 回滚
直觉上是反人类的,但背后的原因却有其合理性。因为应用的 FAT 环境只有一套,我们的发布规范上又要求所有上生产的版本包必须是沿着 FAT => UAT => 生产 的路径发布上去的,所以用户就希望在上线过程中,将 FAT 环境短暂出让给待上线的版本,结束后再重新把 FAT 环境部署为开发中的版本。在我们支持该类型流水线后,发现使用量颇为可观。
目前掌门内部已经有成熟的子环境方案支持同一套微服务体系内的链路隔离,理论上基于此方案是可以彻底解决上述流水线涉及的问题,但是由于掌门有大量的 socket 长连接应用,导致全场景的子环境隔离方案依然有一些改造工作要持续推进。
如今流水线功能已经深度融入 CD 平台,本身具备了持续进化的长效机制。不过,现在的流水线功能本身也依然有很大的优化空间,主要有:
与外部系统对接不够灵活(可以考虑在每个阶段提供 webhook 对接)。 不支持对流程自定义编排。 随着流水线任务越来越多,执行流水线的 Jenkins 集群规模变大,任务的调度效率不佳,高峰期可能出现大量任务等待的问题。 Jenkins 集群未容器化,导致需要按峰值设置容量,造成资源浪费。 流水线按应用粒度设计,不支持跨应用的流程编排。
在合适的时间,我们会启动这些优化项。
我们团队负责掌门研发效能平台的建设,服务于掌门所有研发人员。作为掌门研发体系的核心组成部分,致力于保障高质量的交付和研发效率的提升。当前 CD 平台已经在 CI/CD 和监控告警形成闭环,今后我们会沿着需求、设计、开发、构建、验证、发布这个路径进行更广的探索,打造真正的研发效能平台。目前掌门正在进行生产环境容器化的升级,如果你对 K8s 的生产应用容器化方案的设计和落地有独到的见解,欢迎加入我们。
Web
前端/ Java
/ iOS
/ 安卓 )、测试工程师( 功能/自动化/性能 )、DBA
、大数据工程师、算法工程师( OCR
/用户画像/推荐 )、K8s
架构师、运维工程师、产品经理。欢迎加入掌门教育大家庭,一起畅谈技术,分享交流。