背景
Cloud Native
上海经证实业集团成立于 2009 年,公司一直致力于为新能源、车辆租赁以及智能机器人行业的客户群体提供高质量的“产投融合”增值服务,通过标准化金融产品和供应链信息技术为纽带,更好的服务实体经济。上海经证科技有限公司是上海经证实业下属的科技公司。
问题及痛点
Cloud Native
公司软件研发部门自 2021 年开始从零组建,之前数年的软件项目全部由于外部软件公司开发管理。研发团队在接收软件项目的管理与开发中,面对设备管理,运维,研发过程管理等诸多事项,挑战巨大。没人:团队从零开始,主要核心人员以研发为主,对于研发过程中所涉及的DevOps基础设施搭建与维护专业经验较弱,无法保证整个基础设施的稳定性。没钱:基础设施从零开始,自建项目管理(Jira)+ 代码仓库(Gitlab)+ 构建工具(Jenkins)+ 版本仓库(Nexus+ Verdaccio)+ 部署(ArgoCD)+ K8s 等前期搭建与维护的成本高,业务的收益无法平衡前期的过高投入。事多:基础设施需要满足合规性与安全性的要求,协同账号管理。促进各部门合作的同时,能够清晰匹配各个部门职能,避免冲突。要求高:所有的一切都是为业务服务,整个产品开发生命周期(PDLC)要足够的快与准确,基础设施能够支持开发的快速迭代上线。前期选型
Cloud Native
我们研究了很多国内的各大云厂商的 DevOps 解决方案以及各个专业产品,最终选定阿里云云效作为我们最终的 DevOps 解决方案。有几点可以分享给大家考虑:
1. 完整度:云效给了众多情景解决方案,涉及项目管理、代码、流水线、应用交付、制品仓库,覆盖 DevOps 的整个生命周期。最关键的是流水线与 K8s 部署与 ECS 服务器部署直接傻瓜式打通,非常方便。
2. 集成度:市场上不乏细化单一板块,提供了体系化解决方案的精品软件,如项目管理(Jira),对与成熟的大体量团队,可以考虑采用先进方法论整合。但是对于中小团队而言,整合难度过高。
3. 成熟度:DevOps 的基础设施需要经过校验,能够稳定的支持开发日常工作。在提供必要性功能的同时,能支持企业的部分自主定义。
4. 性价比:简单高效,单价最低,快速解决问题。减少汇报与团队成本。
解决方案
Cloud Native
注:从 2021 年使用云效以来,我们经历过云效的多次升级改版,项目管理从 project 到 projex。服务部署也经历了流水线的 VM 部署、 K8s 部署和最后应用交付的 K8s 等多种改版优化。案例与方案以当前最新为主。相信大家对于 DevOps 实施方案都有很多理解,这也是我们在设计时考虑的重点,如:
版本控制持续集成和持续交付(CI/CD)敏捷开发基础结构即代码配置管理持续监控与运维此外,在实践中我们将开发设计中“约定大于配置”这一范式补充进来。比如代码仓库、流水线的命名规则、产出物的存放规则、标签应用等等约定规范,减少沟通与选择成本。
DevOps 与项目管理
基于云效【项目管理】,我们可以实现从前期原始诉求的发现到任务进展的跟踪,以及后期缺陷测试的管控。云效官方文档有非常详细的文档与方法论,相信大家对项目管理与各自团队的实际情况有所实践,在此不做赘述。
在官方介绍的情景之外,我们也关注两点可以给大家借鉴,即团队内持续性的任务管理与跨团队间的合作。
持续的任务管理 – 团队有多个项目持续性更新我们公司内部各团队关注不同的业务板块。其中供应链团队负责供应链相关的产品开发。在某个项目如供应链 1 软件完成后,会根据用户反馈长期升级维护。与此同时,后期也会开发同业务线下的其他产品如供应链 2、供应链 3 等。
基于云效项目管理,我们可以建立不同的项目,配置各种类型的工作项(如需求、任务),制定规则。但是每次新立项,即使有模版,我们还是需要重新配置部分设置,如人员,视图,自动化规范等。团队还是这个团队,管理方式还是原来的管理方式。
对此,为了减少冗余,我们将项目衍生为团队的概念,并通过自定义字段“产品线”,“技术栈”等以适应一个团队多个产品的长期持续管理。当然,项目集也是种解决方案,我们使用项目集更多是跨团队的高层次管理。
(团队项目管理案例图)
根据日常的实际情况,建立主题,拆分需求,细化任务。在团队内部充分利用分组与视图,实现对不同产品的管理。
(团队内项目管理案例图)
跨团队任务合作 - 发现问题给了 A,实际是 B 的问题。比如业务发现用户打开供应链业务软件存在部分字段显示与权限不符,向供应链团队提交缺陷。后经供应链团队核实,实际问题是基础平台团队在权限框架上存在缺陷。更甚至每次业务都把问题报给前端,但实际其中部分是后端的问题。
这时候,我们一般由第一处理团队及时响应,检查缺陷原因。确认其他团队原因后,评论相关线索与证据。将任务变更至对应的团队,并分享本团队可见。有必要的话,会建立关联的任务跟踪,待问题解决后跟进验证。这样的话既记录了原始团队的工作情况,又全流程的记录过程数据。
CI/CD 代码管理与流水线的集成- 一份代码,多地部署
我们遵循云原生包括其 12 因素中的比较主流的理念来实践代码管理与持续化集成,即一份代码,一次编译,多地部署。代码编译后的产出物经过测试、预生产等环境的验证,最终晋级到生产环境,保证交付物的可靠性。
(Github Flow 分支管理模式)
在这种目标下,我们最终决定采用 Github Flow 的分支管理模式来管理代码。
以 main 为基准代码,强调所有代码需要和主分支对齐,主分支保证持续稳定的发布。以任务类型加任务编号为分支名(如 feature/XXXX-001),并规范提交信息,实现自动的项目关联。以标签(Tag)为产出镜像的版本,CD 流水线可以根据仓库名,镜像名称等规范约定等自动生成镜像地址。同时,我们在【代码库】将 main 分支设置为保护分支,并强制要求所有代码必须通过合并请求的方式才能提交进入。同时,合并请求需要完成人工审查与自动化测试扫描等流水线任务。
(流水线方案设计)
如图所示,在整个合并请求流程中涉及的规范与流水线分别为:
合并请求时前置检查(命名规范:{application-name} – service/web- precheck)开发建立合并请求时,接受 webhook 事件自动运行。主要以代码扫描,漏洞检查,单元测试等,有能力的可以编译部署实现 SIT 测试。从某种程度上来说,这也是防呆设计,在合并前将所有编译性或者自动化测试的问题发现,防止未检查的直接修改提交。持续集成流水线(命名规范:{application-name} – service/web- ci-k8s)代码合并到主分支后,接受 webhook 事件自动运行。主要以单元测试,构建镜像为主,并发布到测试环境中。经测试验证无误后,确认发布版本标签。持续交付流水线(命名规范:{application-name} – service/web- cd-k8s)手动选择标签或者分支运行。选择分支,则部署当前分支最新镜像版本。选择标签,则部署标签指定版本。通过自定义的 CLI 流程实现,强依赖版本约定。如图,以上是一个服务的流水线案例。一份代码一般通过三条流水线实现 CI、CD 流程。
多环境镜像晋级/复用实现关于多环境镜像晋级[1],很高兴看到云效官方的方案,大家可以直接使用,非常方便。在此再次介绍一下实现路径:
1. 官方-通过「Flow 流水线源」获取上一条流水线镜像产物
2. 官方-通过「ACR」制品源获取镜像
3. 自定义方案-基于自定义 CLI 实现
我们在两三年前就遇到这个问题,如何避免重复编译,如何减少 CD 的时间?尤其在项目建立初期,我们的项目依赖的制品库在外部,而其管理不规范,更新代码的同时不升级版本号,使我们的每次编译带有不确定性。另外在 Emergency Patch 时,编译的 3~10 分钟让我们备受煎熬。而且正如墨菲定律,越是紧急的时候,往往越容易出问题。
为此,经过与云效同事的多次讨论,我们当时采用自定义步骤 CLI[2]方案实现了镜像晋级,可供大家借鉴:
我们通过强约定将 git 的标签规定为版本号,所有应用的代码库都必须遵守。在每次 CI 测试环境验证后,服务会发布相应版本与提交信息到标签上。在 CD 时,选择提前指定标签版本或最新版本。通过自定义步骤可以服务名与标签的组合,关联生成镜像信息,供后续部署流程使用。
以上是流水线自定义组件的核心代码与配置,基于内部共识,对标签用途进行了实现。
与官方的方案相比 CLI 通用性较差,且实现有一定的复杂度。但是优势就是版本发布灵活,都是经过测试环境验证,且删除标签即可删除版本,不影响实际镜像库中版本。足不出户,所有管控操作都在云效内部,出错可以回滚。
多环境产品发布周期(PDLC)的思考我们实践一份代码,多处部署(codebase 理念)的基础是我们采用敏捷开发方式,整个需求到开发,到测试,到最后验证上线的周期是一到两周。
如果未来涉及到团队过多,开发周期较久的情况,叠加线上问题,会存在 产品发布周期冲突的问题,所以也做了以下扩展设计与余量。
版本冲突为了方便理解,我们以一个月为一个发布周期,举例如下:
如果当前月份是 202406,我们在开发环境升级了基础组件版本,这个版本导致众多代码实现发生了变化。但是此时生产环境 202404 版本出现了问题。我们该如果处理?
如果按照正常进度,202406 版本修改完成,发布上线已经是 202408 月份了,持续两个月的问题必然是灾难性的。如果临时基于历史版本修改,测试问题与代码项目间版本依赖又存在挑战。
云效这边在 2023 年 12 月份提供了持续交付最佳实践[3]基本可以解决我们的问题,只不过我们的设计在这个的基础上略有增加,可以供大家思考。
以下是引用云效多环境持续交付的实践,我们新增了约定和流水线流程:
1. 每个最终上线发布的版本必须建立独立的分支,产生版本关联的有序版本号,在部署生命周期结束后必须丢弃。如 2024 年 6 月的分支为 release/202406,版本为 202406-20240630.539481。
2. 每个 release 代码单独实现 codebase 理念,即除测试环境外,每个环境建立与之对应的测试环境,每个 release 分支形成独立的 codebase 生命周期。如预生产环境新增预生产测试环境。
3. 与上两条对应的,建立上线测试期,如每月最后一周。要求非修复性代码禁止进入,最后一周提前切出代码到 release 分支,进入到 pre 阶段的测试环境中,并在验证后灰度发出到 preview 环境。
依赖管理
在我们日常的开发,都会涉及到第三方模块依赖。如 Java 的 Maven 仓库,前端的 NPM 仓库。
为了规范依赖引用、共享公共与基础代码组件、减少因外部依赖导致的网络问题,以及后期对第三方模块的安全性审查,我们采用【制品库】作为前后端微服务的 Maven 与 NPM 库,并结合【流水线】发布公共基础 Jar 与 JS Module。
基础设施与服务配置代码化
基础结构即代码对于基础设施,如 redis,consul 服务等,我们通过 k8s yaml、Helm 文件的形式管理。我们将这些资源存储在【代码库】中,像代码一样管理版本控制以及像评审和还原。
考虑到基础设施改动较小,且很少出问题,我们就维持了一个 main 版本,按发布周期跟进部署到各个环境。
AppStack 管理部署相比较原有的【流水线】,【应用交付】能够方便的管理多集群,多环境部署。
通过【应用交付】的应用模版,批量管理 90% 通用应用。对于 Gateway 等有特殊配置的应用再单独管理,大大节省了成本。
微服配置即代码 – GITOPS与 DevOps 相同,GitOps 是一种运维思想而非具体的工具。GitOps 是指以 Git 为核心来开展配置的部署流程,减少开发人员和运维人员的沟通成本。
在开发流程中,开发人员将各个服务在的配置提交到到【代码库】,在代码库中通过合并请求流程管理。同步工具(Gonsul)会监听仓库的变化情况,每天定时或根据发布周期(CronJob)将仓库中的配置文件的更新同步到微服务配置中心(Consul),也可以通过流水线临时启动同步任务(Job),精简开发过程中的配置的部署流程。
同时,如果是测试或者是线上紧急配置变更,授权用户也可以直接在配置中心更改,快速应用。由于没有持久化,配置会自动在下一个部署周期自动失效,避免未审核的配置长时间游离管理。
容器管理与监控(阿里云+云效)
Kubernates 本身的搭建、管理、网络管理、运维是复杂的,能够完全的进入云原生时代,并且所有的服务进行容器化,对于中小团队来说是有一定挑战的。借助于阿里云的【容器服务 ACK】服务与云效的【资源池】,我们可以快速的将 ECS 服务器组建为集群,纳入整个 DevOps 生命周期灵活管理。
无论我们如何谨慎,应用程序在生产环境中几乎总是以意想不到的方式运行。当用户报告应用程序出现问题时,能够在出现问题时查看应用的情况是有用的。在【云效 AppStack】与【云效流水线 Flow】中,我们能够快速查看服务部署运行状态。通过【容器服务 ACK】的 Prometheus 监控,我们可以查看服务的实施运行情况。通过【SLS 日志服务】收集系统各个部分的事件数据和日志,方便分析和查询,甚至建立报警机制。
写在后面
Cloud Native
DevOps 与云原生的概念已经出现很久,无论是云效或者 CNCF 都给了我们很多、很好的最佳实践案例。从 2021 年以来,我们结合公司的实际业务情况从 0 开始,到现在近百个微服务、数百条【流水线】与【应用交付】,对于我们一个基本没有运维的研发团队来说,一切是难能可贵。这背景与阿里云云效的技术支持,功不可没。
作者介绍:周明轩,上海经证科技有限公司技术专家,负责系统架构与 DevOps 落地。曾就职于凯捷中国,SAP 中国研究院。
合作者:蔡守波,李通,代文文
相关链接:
[1] 《多环境镜像晋级/复用最佳实践》
[2] 自定义步骤 CLI
https://help.aliyun.com/document_detail/153810.html
[3] 《多人协同开发场景,如何做到高效发布》