Fintopia发布系统优化实践

持续集成/持续交付(CI/CD)是 DevOps 领域中非常重要的技术实践。它的目的是通过自动化构建、测试和部署流程,加速软件交付的速度,同时提高软件的质量和可靠性。在软件发布流程中,持续集成(CI)一般指构建和单元测试阶段,主要原理是通过每次代码提交自动执行构建、单元测试以及静态代码扫描等操作,将开发人员从日常手动发布代码的任务中解放出来并且通过频繁构建帮助开发人员及时发现新提代码中存在的Bug、漏洞、重复代码等问题提前完成问题归因,避免问题代码发布到线上环境导致线上服务故障影响用户体验。CD一般代表持续交付或持续部署,两者的区别是持续交付服务发布到生产前需要人工确认。持续交付(CD)是对持续集成的拓展,其主要聚焦软件发布流程自动化,通过每次代码提交自动执行持续集成相关阶段后自动部署服务到测试环境并自动执行各类测试流程,经开发人员功能验证后手动部署到生产环境。通过落地持续集成/持续交付,通过流程自动化、更频繁/更全面的测试可以很大程度提高开发人员工作效率、帮助开发人员发现缺陷问题、提升产品需求交付效率。

我司所有微服务都使用Jenkins手动发布服务到各环境,发布流程各阶段逻辑通过Jenkinsfile(Groovy语法)组织。发布环境包括测试(test)、预生产(feat)及生产(prod)。技术栈方面,后端项目使用的语言有Java、Python、Go,前端项目主要使用Nodejs。由于前期缺少代码管理、分支管理等方面的规范,当前我司很多项目代码仓都托管有多个相互依赖较为密切的微服务代码,同一项目中不同微服务发布逻辑会有细微差异。项目代码仓组织结构及服务发布流程示例如下:

如上图所示,当前服务发布流程主要包括以下阶段:

1、开发本地开发验证完成后提交代码到Gitlab代码仓库,在Jenkins手动触发流水线使用master分支发布服务到测试环境。

2、QA在测试环境验证通过后,开发人员checkout releases分支,并在Jenkins手动触发流水线使用releases分支发布服务到预生产环境。

3、QA在预生产环境验证完后,开发人员在预生产环境验证通过的releases分支重新构建生产环境,构建完成后手动逐个发布版本到生产环境。

分析当前发布流程,存在以下几方面的问题:

1、发布流程方面,测试环境CI流程单测覆盖率低且很多项目不执行单元测试、Sonar扫描,出现预生产环境CI因单元测试不过而影响发布生产。CI流程缺少静态代码检查,经常出现因代码问题导致测试环境构建失败或包含Bug的代码发布到测试环境导致测试环境服务异常影响测试环境稳定性和测试进度。构建产物不稳定,同一分支部署不同环境环境需要CI及构建镜像需要执行多次。

2、发布效率方面,发布流程主要依赖人工触发,不具备自动化CICD能力。对于代码提交频繁的项目,经常会因为发布分支新增commit过多导致构建或单元测失败后问题归因时间过长。另外,自动化程度不足对开发工作效率影响很大。部分阶段阶段缺乏并行能力,单个项目下服务发布时长会随着应用数增长而拉长整体发布耗时。

3、发布稳定性方面,不具备业务灰度、蓝绿发布、秒级回滚能力。因历史原因,部分项目因代码量多大货项目结构复杂经常会导致服务启动缓慢,当线上业务出现问题时对服务SLA影响很大,严重影响用户体验导致客诉增加。

4、发布结果度量,当前发布系统缺少发布成功率、回滚次数等度量指标,不便于后续数据运营。

针对以上问题,期望通过系统优化发布系统,深度落地“持续集成、持续部署”相关理念以提升各项目发布效率。

发布效率是度量发布系统的核心指标,随着产品需求迭代提速,核心服务每天需要发布上百次,此时服务发布效率将直接影响到产品需求交付效率。对于发布系统来说,如何以更短时间将高质量的代码交付到线上环境是发布系统优化的核心目标。对此,我们针对当前发布系统存在的效率问题进行了优化。主要包括:优化发布系统节点资源池、重构CICD流程支持多模块并行发布、提升发布流程自动化程度、发布结果增加实时反馈等。

流程设计

CI阶段提效

分类 优化思路
预留资源池 构建任务执行节点由固定Worker节点修改为动态Agent
流程自动化 CI流程触发方式由手动触发修改为代码提交自动触发CI流程
构建产物 优化构建交付产物,构建产物由交付Jar包修改为交付镜像
并行执行 针对代码仓多模块情况,CI阶段支持多模块并行构建镜像
实时反馈 发布流程执行失败后,及时通知相关人员

CD阶段提效

分类 优化思路
预留资源池 构建任务执行节点由固定Worker节点修改为动态Agent
流程自动化 CD流程由手动触发修改为CI流程结束自动触发CD
并行执行 针对代码仓多模块情况,CD阶段支持多模块并行发布
实时反馈 发布流程执行失败后,及时通知相关人员

频繁的服务部署对线上服务稳定性也带来了潜在的隐患,因线上业务本身存在一定复杂性,即使在测试环境验证通过的功能发布到线上也不一定能正常work,这种情况下如何从发布系统层面保障发布变更过程对用户侧影响最小化呢?另外,由于线上不同等级服务对稳定性要求存在差异,因此也要求发布系统具备丰富的发布方式。对服务对此我们通过对发布系统二次开发扩充了金丝雀发布、蓝绿发布能力,补足发布能力方面存在的短板,支持在发布过程中按用户自定义百分比或请求Header在新版本服务上进行小流量灰度验证,验证通过后再进行全量切换。

滚动发布

随着K8S/Docker容器云在我司的落地,前期所有前后端服务已完成容器化,依托于K8S ReplicaSet的rollingUpdate(滚动更新)服务发布能力我司发布系统已具备滚动发布能力,那什么是滚动发布呢?滚动发布是指发布过程中新版本服务实例逐步启动替换老版本服务,我们可以通过配置rollingUpdate.maxSurage来控制发布过程中新版本服务每批次启动的实例数或百分比、配置rollingUpdate.maxUnavailable来控制发布过程中老版本服务可停止的停止的实例数,对于QPS较大的服务为保证发布过程中可承载的QPS稳定一般配置rollingUpdate.maxUnavailable为0代表新版本实例启动之后再停止老版本服务实例。对于服务实例比较多的应用一般会分多批次进行发布,不同批次之间可以选择暂停以进行观察,以选择是继续发布还是回滚。滚动发布流程如下图所示:

对于滚动发布来说,服务发布过程总耗时跟发布批次正相关,发布批次越多发布耗时也越长单批次启动服务实例就越少,对于服务等级较高且实例比较少的应用来说,即使发布批次跟实例数一致也意味着新发版本如果有Bug对服务SLA影响很大,比如:example-api v1版本有2个pod,分三批发布,发布新版本接收请求的权重1/(2+1)=33%,这也意味着,新版本服务异常将导致服务SLA为60%左右,对线上业务来说,这是不可接受的。对于服务实例较多的应用来说,发布批次越多也意味着发布过程耗时越长,对于需要频繁发布上线的应用和高效的发布系统来说,这是互斥的。所以,如何在兼顾发布耗时的同时提升发布效率缩短发布整改过程耗时呢(减少发布批次及问题版本上线对线上服务的影响)。

灰度发布

如滚动发布所述,对于发布过程来说如何减小问题版本发布上线后对线上稳定性的影响呢,初期我们对发布系统扩充了金丝雀发布能力。金丝雀发布是指对需要灰度的应用启动一个单独的服务进行线上灰度,发布过程中通过观察Canary服务日志、监控等数据表现经人工确认没问题后再进行后续发布流程,通过金丝雀发布以最大程度减小问题版本发布上线之后对线上业务的影响。对于实例数较多的应用通过单独启动单实例的Canary服务来解决减少发布批次对线上服务的影响。跟滚动发布相比有一定的稳定性优势且实现成本相对较低。

蓝绿发布

对于滚动发布与金丝雀发布整体流程,可以在发布过程中发现一些较为明显的问题,如果新版本服务已发布完成后发现异常想要回滚只能重新部署老版本服务,对于一些大型Java服务启动时间可能达到分钟级别,出现问题时将导致线上服务分钟级不可用。线上服务不可用不仅会对公司造成直接经济损失、客户投诉而且影响公司产品在用户和合作伙伴心目中的形象。对此,为解决因发布原因导致线上服务不可用,进一步完善发布系统的发布能力,我司基于istio服务网格组件提供的流量治理能力,我司运维、架构部基于k8s crd+controller模式二次开发为发布系统扩充了蓝绿发布能力。

蓝绿发布过程中线上服务会同时存在两个版本,新版本服务启动后发布系统侧支持实时调整新、老版本流量权重比,并支持在切流页实时查看业务SLA、ERROR日志数等相关指标,提供了发布过程可视化。

随着业务需求迭代提速以及用户对产品功能的多样化需求,业务系统需要更频繁发布上线将以提供更丰富的功能。服务频繁发布上线对线上服务稳定也提出了更高的要求,对于线上服务来说如何最大程度减少服务部署过程用户侧无感知呢?对于发布稳定性,我们分别从服务部署前、中、后三个阶段对发布系统稳定性进行全面改造优化。

对于Pre Deploy阶段,我们期望CI阶段交付的产物是一个稳定的可运行的版本,对此我们通过在CI阶段引入SonarQube进行静态代码检查并结合定时扫描+通知机制、对Bug设置卡点来确保线上环境CI构建的代码版本没有功能Bug;另外,我们通过在CD流程中引入发布功能,CD之前通过强制(可选)人工Review确认是否有异常需求被带上线。

对于Deploy阶段,我们期望发布过程尽可能对用户无感不影响C端用户访问,为此我们基于istio服务网格提供的流量治理能力对服务扩展了预热功能,通过一段时间内小流量预热减小对用户侧的影响。另外通过在发布系统中整合服务稳定性监控指标,提升发布过程可视化能力。

对于Post Deploy阶段,我们期望新发版本存在异常时具备快速回滚能力,以减少问题版本对真实用户的影响,为此我们基于蓝绿部署提供了快速回滚能力。

静态代码扫描

静态代码扫描是CI/CD流程中的重要一环,通过将静态代码分析嵌入CI流程,依托自动化CICD流程实现提交代码时自动触发静态代码分析便于及时发现代码中存在的缺陷、错误及重复代码等问题并及时通知相关开发同学修复,以达到提升代码质量的目的。对于影响服务启动或产品功能的Bug问题,我们通过在配置质量阈设置卡点以避免问题代码发布到线上。通过将静态代码扫描融入到CI/CD流程中,可以帮助团队规范代码编写习惯,提高软件效率和代码质量,以交付更高质量的产品。

上线单

基于新切分支发布上线经常会出现新上线分支漏掉线上分支代码提交的情况,针对这种情况我们通过在CICD流程中记录线上服务对应版本并整合Gitlab等平台数据在CD流程中增加上线单检查功能,开发同学在服务部署之前能Review新发版本差异与线上版本差异,避免漏掉线上代码或者应该Revert的代码被带上线,导致线上服务出现问题。

发布预热

随着生产业务量的逐步增长,部分访问量比较大的Java服务“新版本发布”或“因CPU负载较高达到HPA自动扩容阈值”时将触发自动创建新Pod。待readinessProbe检查通过之后新创建的Pod将会被添加到k8s service endpoints中并开始接收处理用户请求。初期因尚未配置任何流量治理规则,用户请求将按照LVS默认RR算法平均分配到每个endpoint。当用户请求转发到Pod时,因为JIT(Just In Time)即时编译器机制JVM会根据访问频次将部分访问频次较高的代码编译为本地代码执行以提高执行效率,由于JIT过程比较耗费CPU资源,所以在JIT结束之前Pod处理响应时间相比平时要慢很多。最终导致接口调用耗时波动、healthCheck接口响应超时导致服务重启等稳定性问题。

发布过程可观测

对于线上发布系统来说服务部署过程可视化是提升系统易用性单核心点,通过查看发布过程中各种业务指标的变化以评估新版本服务对用户侧真实访问的影响,以缩短问题发现时间达到及时止损。

快速回滚

在日常的软件发布过程中,及时新发版本已经过静态代码分析、单元测试也不能保证服务发布到线上之后完全不出现问题。针对因服务部署而导致的线上问题,我们期望发布系统能具备快速回滚能力尤其对于Java等启动较慢的服务更为明显。

对于发布系统来说除了提供最基本CICD服务构建部署能力,随着公司业务体量逐步增长、业务线拆分逐步细化接入发布系统的项目越来越多,在此背景之前我们期望基于发布系统提供各类发布数据报表,以作为衡量各项目代码质量、服务稳定性的其中一项指标。简单点说就是发布数据运营能力。通过定期分析相关指标及暴露出的问题制定后续改进优化措施。那如何来衡量发布系统稳定性以及各业务线代码质量呢?

对此,我们基于应用中心数据构建了各维度报表,包括业务线、项目、应用维度各类指标,其中指标包括发布次数、发布成功率、发布耗时、阶段耗时、回滚次数、回滚成功率等。通过定期对发布成功率低、阶段耗时较长以及回滚次数较多的流水线分析原因并进行针对性治理以逐步提高发布系统效率及用户体验。