共计 5329 个字符,预计需要花费 14 分钟才能阅读完成。
本文作者:成龙
腾讯前端开发工程师,负责腾讯文档前端开发与研发效力晋升,AlloyTeam 成员。
导语
本篇文章将着重探讨 DevOps 在继续集成阶段须要提供的能力,将对工作流的设计及流水线的优化思路做一个简要解说。
DevOps 一词源于 Development 和 Operations 的组合,行将软件交付过程中开发与测试运维的环节通过工具链买通,并通过自动化的测试与监控,缩小团队的工夫损耗,更加高效稳固地交付制品。
随着腾讯文档的我的项目规模越来越大,性能个性与保护人员越来越多,个性交付频率与软件品质之间的矛盾日渐尖利,如何均衡两者成为了目前团队亟需关注的一个重点,于是,落地一个欠缺的 DevOps 工具链便被提上日程。
当咱们在议论 CI 时,咱们在议论什么
CI(Continuous Integration),即继续集成,指频繁地(一天屡次)将代码集成到骨干的行为。
留神,这里既蕴含继续将代码集成到骨干的含意,也蕴含继续将源码生成可供理论应用的制品的过程。因而,咱们须要通过 CI,自动化地保障代码的品质,并对其构建产物转换生成可用制品供下一阶段调用。
因而,在 CI 阶段,咱们至多有如下阶段须要实现:
1. 动态代码查看
这其中包含,ESLINT/TSLINT 动态语法查看,验证 git commit message 是否符合规范,提交文件是否有对应 owner 能够 review 等等。这些动态查看不须要编译过程,间接扫描源代码就能够实现。
2. 单元测试 / 集成测试 /E2E 测试
自动化测试这一环节是保障制品品质的要害。测试用例的覆盖率及用例品质间接决定了构建产物的品质,因而,全面且欠缺的测试用例也是实现继续交付的必备因素。
3. 编译并整顿产物
在中小型我的项目中,这一步通常会被间接省略,间接将构建产物交由部署环节实现。但对于大型项目来说,屡次频繁的提交构建会产生数量宏大的构建产物,须要失去妥善的治理。产物到制品的建设咱们接下来会有具体解说。
利于集成的工作流设计
在正式接入 CI 前,咱们须要布局好一种新的工作流,以适应我的项目切换为高频集成后可能带来的问题与难点。这里波及到的革新层面十分多,除了催促开发人员习惯的转变以及进行新流程的培训外,咱们次要关怀的是源码仓库的更新触发继续集成步骤的形式。
1. 流水线的组织模式
咱们须要一个适合的组织模式来治理一条 CI 流水线该在什么阶段执行什么工作。
市面上有十分多的 CI 工具能够进行抉择,仔细观察就会发现,无论是 Drone 这样的新兴轻量的工具,亦或是老牌的 Jenkins 等,都原生或通过插件形式反对了这样一个个性:Configuration as Code,即应用配置文件治理流水线。
这样做的益处是相当大的。首先,它不再须要一个 web 页面专门用于流水线治理,这对于平台方来说无疑缩小了保护老本。其次对于应用方来说,将流水线配置集成在源码仓库中,享受与源码同步降级的形式,使得 CI 流程也能应用 git 的版本治理进行标准与审计溯源。
确立了流水线的组织模式后,咱们还须要思考版本的公布模式以及源码仓库的分支策略,这间接决定了咱们该以什么样的形式布局流水线进行代码集成。
2. 版本公布模式的取舍
在《继续交付 2.0》一书中提到,版本公布模式有三要素:交付工夫、个性数量以及交付品质。
这三者是互相制衡的。在开发人力与资源绝对固定的状况下,咱们只能对其中的两个因素进行保障。
传统的我的项目制公布模式是就义了交付工夫,期待所有个性全副开发实现并经验残缺人工测试后才公布一次新版本。但这样会使得交付周期变长,并且因为个性数量较多,在开发过程中的不可控危险变高,可能会导致版本无奈按时交付。不合乎一个成熟的大型项目对于继续交付的要求。
对于继续集成的思维来说,当咱们的集成频率足够高,自动化测试足够成熟且稳固时,齐全能够不必一股脑地将个性全堆在一次公布中。每开发实现一个个性就主动进行测试,实现后合入期待公布。接下来只须要在特定的工夫周期节点主动将曾经稳固的期待中的个性公布进来即可。这对于公布频率越来越高,公布周期越来越短的古代大型项目中无疑是一个最优解。
3. 分支策略
与大部分团队一样,咱们原有的开发模式也是分支开发,骨干公布的思维,分支策略采纳业界最成熟也是最欠缺的 Git-Flow 模式。
能够看出,该模式在个性开发,bug 修复,版本公布,甚至是 hotfix 方面都曾经思考到位了,是一个能利用在生产环境中的工作流。但整体的构造也因而变得极为简单,不便治理。例如进行一次 hotfix 的操作流程是:从最新公布前应用的骨干分支拉出 hotfix 分支,修复后合入到 develop 分支中,期待下一次版本公布时拉出到 release 分支中,公布实现后能力合回骨干。
此外,对于 Git-Flow 的每一个个性分支来说,并没有一个严格的合入工夫,因而对于较大需要来说可能合入工夫距离会很长,这样在合入主干时可能会有大量的抵触须要解决,导致我的项目工期无端缩短。对此,做大型革新与重构的同学应该深有体会。
针对这一点,咱们决定大胆 采纳骨干开发,骨干公布的分支策略。
咱们要求,开发团队的成员尽量每天都将本人分支的代码提交到骨干。在达到公布条件时,从骨干间接拉出公布分支用于公布。若发现缺点,间接在骨干上修复,并依据须要 cherry pick 到对应版本的公布分支。
这样一来,对于开发人员来说须要关注的分支就只有骨干和本人 working 的分支两条,只须要 push 与 merge 两条 git 命令就能实现所有分支操作。同时,因为合入频率的进步,均匀每人须要解决的抵触量大大减少,这无疑解决了很多开发人员的痛点。
须要阐明的是,分支策略与版本公布模式没有银弹。咱们采纳的策略可能并不适宜所有团队的我的项目。进步合入频率尽快能让产品疾速迭代,但无疑会让新开发的个性很难失去充沛的手工测试及验证。
为了解决这一矛盾点,这背地须要有弱小的基础设施及长期的习惯造就做反对。这里将难点分为如下几个类型,大家能够针对这些难点做一些考量,来确定是否有必要采纳骨干开发的形式。
1)欠缺且疾速的自动化测试。只有在单元测试、集成测试、E2E 测试覆盖率极高,且通过变异测试得出的测试用例品质较高的状况下,能力对我的项目品质有一个整体的保障。但这须要团队内所有开发人员习惯 TDD(测试驱动开发)的开发方式,这是一个相当漫长的工程文化造就过程。
2)Owner 责任制的 Code Review 机制。让开发人员具备 Owner 意识,对本人负责的模块进行逐行审查,能够在代码批改时躲避许多设计架构上的破坏性批改与坑点。实质上难点其实还是开发人员的习惯造就。
3)大量的基础设施投入。高频的自动化测试其实是一个相当耗费资源的操作,尤其是 E2E 测试,每一个测试用例都须要启动一个无头浏览器来撑持。另外,为了晋升测试的效率,须要多核的机器来并行执行。这里的每一项都是较大的资源投入。
4)疾速稳固的回滚能力和精准的线上及灰度监控等等。只有在高度自动化的全链路监控下,能力保障该机制下公布的新版本可能稳固运行。这里的建设我会在之后的文章里具体介绍。
大型项目中产物 -> 制品的建设
对于大多数我的项目来说,在代码编译实现生成产物后,部署我的项目的形式就是登录公布服务器,将每一次生成的产物粘贴进公布服务器中。生成的动态文件因为 hash 不同能够同时寄存,html 采纳间接笼罩的形式进行更新。
间接应用复制粘贴的形式来操作文件的更新与笼罩,这样既不不便对更新历史的审计与追溯,同时这样的更改也很难保障正确性。
除此之外,当咱们须要回滚版本时,因为服务器上并没有寄存历史版本的 html,因而回滚的形式其实是从新编译打包生成历史版本的产物进行笼罩。这样的回滚速度显然不是令人满意的。
一个解决办法是,不要对文件进行任何的笼罩更新,所有的产物都应该被上传长久化存储。咱们能够在申请上游增设一个流量散发服务,来判断每一条申请应该返回哪一个版本的 html 文件。
对于大型项目来说,返回的 html 文件也不肯定不是变化无穷的。它可能会被注入渠道、用户自定义等标识,以及 SSR 所须要的首屏数据,从而扭转其代码模式。因而,咱们认为 html 文件的制品提供方应该是一个独自的动静服务,通过一些逻辑实现对模板 html 的替换并最终输入。
总结一下,在每次编译实现后,产物将会进行如下的整顿以生成最终的前端制品:
- 针对动态文件,如 CSS、JS 等资源将会公布到云对象存储中,并以此为源站同步给 CDN 做访问速度优化。
- 针对 HTML 制品,须要一个直出服务做撑持,并打包成 docker 镜像,与后端的微服务镜像等同级别,供上游的流量散发服务(网关)依据用户申请抉择调起哪些服务负载进行生产。
速度即效率,流水线优化思路
对于一个好的工具来说,外部设计能够很简单,但对于使用者来说必须足够简略且好用。
在骨干开发这样高频的继续集成下,集成速度即效率,流水线的执行工夫毫无疑问是开发人员最关怀的,也是流水线是否好用的决定性指标。咱们能够从几个方面着手,进步流水线执行效率,缩小开发人员的等待时间。
1. 流水线工作编排
对流水线各个阶段须要执行的工作咱们须要遵循肯定的编排准则:无前置的工作优先,执行工夫短的工作优先,无关联的工作并行。
依据这一准则,咱们能够通过剖析流水线中执行的各个工作,对每一个工作做一次最短门路依赖剖析,最终得出该工作的最早执行机会。
2. 巧用 Docker Cache
Docker 提供了这样一个个性:在 Docker 镜像的构建过程中,Dockerfile 的每一条可执行语句都会构建出一个新的镜像层,并缓存起来。在第二次构建时,Docker 会以镜像层为单位逐条查看本身的缓存,若命中雷同镜像层,则间接复用该条缓存,使得多次重复构建的工夫大大缩短。
咱们能够利用 Docker 的这一个性,在流水线中缩小通常会反复执行的步骤,从而进步 CI 的执行效率。
例如前端我的项目中通常最耗时的依赖装置 npm install,变更依赖项对于高频集成来说其实是一个较小概率的事件,因而咱们能够在第一次构建时,将 node_modules 这个文件夹打包成为镜像供下次编译时调用。Dockerfile 示例编写如下:
FROM node:12 AS dependenciesWORKDIR /ciCOPY . .RUN npm installENV NODE_PATH=/ci/node_modules
咱们给流水线减少一条查看缓存命中的策略:在下次编译之前,先查找是否有该镜像缓存存在。并且,为了保障本次构建的依赖没有更新,咱们还必须比对本次构建与镜像缓存中的 package-lock.json 文件的 md5 码是否统一。若不统一,则重新安装依赖并打包新镜像进行缓存。若比对后果统一,则从该镜像中间接取到 node_modules 文件夹,从而省去大量依赖装置的工夫。
流水线拉取镜像文件夹的办法示例如下,其中 –from 后跟的是之前缓存构建镜像的别名:
COPY --from=dependencies node_modules/ .# 其余步骤执行
同理,咱们也能够将这一个性扩大到 CI 过程中所有更新频率不高,生成工夫较长的工作中。例如 Linux 中环境依赖的装置、单元测试每条用例运行前的缓存、甚至是动态文件数量极多的文件夹的复制等等,都能利用 Docker cache 的个性达到简直跳过步骤,缩小集成工夫的成果。因为原理大致相同,在此就不赘述了。
3. 分级构建
家喻户晓,流水线的执行工夫肯定会随着工作数量的增多而变慢。大型项目中,随着各项指标计算的接入,各项测试用例的数量逐步增多,运行工夫迟早会达到咱们难以忍受的境地。
然而,测试用例的数量肯定水平上决定着咱们我的项目的品质,质量检查决不能少。那么有没有一种办法既能够让我的项目品质失去继续保障的同时,缩小开发者期待集成的工夫呢?答案就是分级构建。
所谓分级构建,就是将 CI 流水线拆分为主构建和次级构建两类,其中主构建须要在每次提交代码时都要执行,并且若查看不通过无奈进行下一步操作。而次级构建不会阻塞工作流,通过旁路的形式在代码合入后继续执行。然而,一旦次级构建验证失败,流水线将会立刻发出通知告警,并阻塞其余所有代码的合入,直到该问题被修复为止。
对于某工作是否应放入次级构建过程,有如下几点准则:
1)次级构建将蕴含执行工夫长(如超过 15 分钟)、消耗资源多的工作,如自动化测试中的 E2E 测试。
2)次级构建该当蕴含用例优先级低或者出错可能性低的工作,尽量不要蕴含重要链路。如果自动化测试中的一些测试用例通过实际发现失败次数较高,该当思考减少相干性能单元测试,并移入主构建过程。
3)若次级构建依然过长,能够思考用适合的办法宰割测试用例,并行测试。
总结
咱们认为,从代码集成、功能测试,到部署公布、基础设施架构治理,每一个环节都应该有全面且欠缺的自动化监控伎俩,并尽量避免人工染指。只有这样,软件能力同时兼顾品质与效率,在进步公布频率的状况下保障可靠性。这是每一个胜利的大型项目最终肯定要实现的指标。
参考资料
- 《继续交付 2.0》—— 乔梁 著
- https://www.redhat.com/zh/top…
- https://www.36kr.com/p/121837…
立刻开启高效云端研发工作流