前不久,我因为运动时的姿势不对,导致右腿骨折,喜提三个月的居家修养。按照作家刘震云的说法,这叫做着正确的事情,却迈着不正确的步伐。于是乎,我的活动空间骤减,每日除了短暂地楼下放风,便是卧坐于方寸之间。周遭静下来,许多回忆便涌了上来。
从 2008 年开始,我陆陆续续参与了多个 DevOps 系统的建设,如今,审视这些系统的建设初衷和它们的设计思路或遇到的问题,依然有不少借鉴意义。我会按照时间顺序,把每个 DevOps 系统的特点,诞生的背景,以及在当时所主要解决的问题做一个概要的介绍,同时,我们也会以今天的视角再次审视这些问题,来看下同样的问题,经过十几年的发展,解决方案上有哪些不同。
CruiseControl 和 Hudson,我对 CI 的最初印象
2008 年中,我加入一家外资电信设备商,先是从事测试自动化工具的开发工作,后来又开始涉及持续集成(CI)相关的内容,当时研发部门正好处于敏捷研发转型有摸索到逐渐成型的时期。
入职不久,我收到一个需求,需求的大致内容是调研下在 CruiseControl(CC)上集成测试自动化用例执行的方案。虽然,那时持续集成已经不是一个太新的概念了,2000 年 Martin Fowler 就发表了关于 Continuous Integration 的博客文章,2007 年名为 Continuous Integration 的书籍也出版了。
但我当时是第一次知道持续集成,也是通过 CruiseControl 这个工具,第一次看到持续集成是怎么运作的。借由这个需求,我第一次了解了流水线任务(Job)、任务的触发(代码源触发、定时触发)、任务的运行环境、任务的运行日志和产物(artifacts)、任务的运行结果和通知机制(邮件),以及持续集成的纪律和某些实践要求如每日构建(daily build)。
CruiseControl 这个工具,当时的我只觉得配置繁琐,不好用,于是,后来 Hudson 出现的时候,我们发现配置简化了很多,使用体验也好了不少,便立刻切换了过去。
CruiseControl 和 Hundson(以及后来的 Jenkins)这两个工具有不少相似之处,比如:
他们都是由 Java 开发,用户通过下载一个 war 包就可以在机器上跑起来
他们都有插件机制,能通过插件增加对各类工具的支持
他们都有 Job(Project)、Build(Run)这样的基本概念,也都支持如定时触发这样在当时非常普遍的使用方式
他们都有自己的账户体系,也可以接入外部账户体系
他们都可以通过配置或插件来定义 dashboard,直观展示 Job 的持续集成情况
在那个时候,我们主要用 CruiseControl 或者 Hudson 来做什么呢?
最主要的是两类工作:构建出包和执行自动化测试。
1.1. 构建出包,大型嵌入式软件在前容器时代的解决方案
先说构建出包,其带有明显的行业和时代特征。在电信设备商,我们构建和出包的对象也就是电信设备软件。电信设备软件大多属于嵌入式软件的范畴,模块众多,代码量庞大(动辄上千万行),当时被托管在一个大的 svn 仓库中,仓库位于国外,构建工具链众多,且构建极为耗时。
在没有 Docker,虚拟机初始化太慢的时代,构建都是在真实的服务器上执行的,与 svn 代码库位于同一地域,服务器均运行特定的 Linux 系统,所有的工具链版本存放在公共存储,并挂载到构建上。在构建的时候,构建脚本执行 chroot 将环境切换到所需的工具链版本。构建完成后,再借助 CI 工具的 artifact 机制,将构建产物保存到 CI 服务器上。
另外,为了加速构建,多台服务器会组成构建集群,通过 distcc 等分布式构建手段,提升构建效率。
此时,每个 Job 构建执行的频率并不高,大约为每天 1~2 次,主要用于出包,出包的时间点一般是下午三四点,出包之后会进行基本的冒烟测试,如果通过,则晚上就会进行自动化测试的验证。
放到现在,大型嵌入式软件的分布式构建仍然是一个不那么好解决的问题,只不过可能构建框架从 distcc 换成了 bazel,构建环境从物理机换成了容器,但是构建缓存、构建机的调度等等仍然都是需要单独设计和处理的。
1.2. 执行自动化测试,测试用例即代码的最佳实践
如果按执行时长来计算,自动化测试占据持续集成系统使用量的大头。那个时候,电信设备软件的测试成本高、时间长,一个包出了之后,往往需要在多个测试设备上并行执行一晚上甚至更长时间才能完成。
我们选择了 RobotFramework[1]作为测试自动化的基础工具框架,并将测试用例和测试工具都维护到 SVN 上,每次执行自动化测试,只需要从 SVN 获取最新的测试用例库,即可在构建机上通过执行脚本的方式来触发。在当时,这是非常适应持续集成实践的设计,也正因为这一点,很快,在公司内部,许多图形化的测试自动化工具就逐步被取代了。
这种实践到现在仍然有很多团队在使用,只不过代码从 svn 搬到了 git,执行环境从物理机集群改成了容器集群。
1.3. 小结
这一阶段可以说是持续集成的开始阶段,我们需要一套持续集成工具,用来完成基本的构建和测试执行,这个工具使用的人比较少(一两个团队),使用的频率也比较低(按天),对可靠性也没有非常高的要求。但随着持续集成理念的逐步深入,执行频率变得越来越高,Job 越来越多,使用的人也越来越多,此时,原先的人工维护和配置的方式就开始力不从心了。
Jenkins+自定义插件,早期解决
CI 定制化场景,降低维护成本的探索
因为开源的原因,我们用 Jenkins 替代了 Hudson。
随着团队在持续集成领域探索的深入,我们在 Jenkins 上配置的 Job 越来越多,添加的插件也越来越多,但是仍然有很多需求满足不了,比如为了提升测试设备的利用率,我们采用抢占式的使用方式,但此时就必须对测试设备加锁和池化,这种定制化的场景,开源的插件满足不了;再比如我们需要持续改进工程规范,对各个模块由一开始的只做构建,逐步增加了单元测试、测试覆盖率检查、数据收集与分析等内容,那如何快速地更新数千个 Job 的配置,也是一个急需解决的问题。
除此以外,随着插件数量的增加,插件的更新和维护也是一个大的问题,我们经常遇到因为插件的 bug 带来的问题。
2.1. 收敛插件和工具入口
大约在 2009 年的时候,鉴于这些问题,我们做了如下调整:
1. 以 Jenkins 为持续集成的基础工具平台,围绕其进行后续建设
2. 收敛 Jenkins 的插件,仅保留通知、认证等少数几个必备插件
3. 开发场景化的 Jenkins 插件,用于测试自动化等复杂配置和交互场景
4. 开发统一的模块持续集成执行工具,用于模块的持续集成和持续检视
其大致结构如下:
这个设计最大的改进就是把 Jenkins Job 的维护工作大幅度地降低了,除日常地 Jenkins Slave 节点的维护外,剩下的工作主要是自建插件的更新以及统一执行工具的更新。而只有自建插件的更新需要停掉持续集成服务,重启 Jenkins Master,这样大部分时候持续集成平台都处于可用状态。
经过这一改进,整个持续集成和测试自动化相关的工具维护工作收敛到 1 个人以内,支撑数百人的研发团队和数千模块的日常开发迭代。
2.2. 新的问题很快出现
这一套架构运行了一年多,随着团队工程实践水平的进一步提升,新的问题逐渐显露出来。
新的问题主要源于 Jenkins 自身架构及其插件能力的局限性。Jenkins 的架构非常简单清晰,一个 Master 承载了所有 Job 的配置,并可通过连接多个 Slave 节点执行具体的 Job 任务。但由于 Master 是单点且不支持热更新,导致如果 Master 出现问题或需要更新,就必须停机处理,其可用性无法保证。
随着敏捷研发的推进,我们交付系统版本的频率大为增加,由之前的月度交付版本,向每天一个版本迈进。在这种情况下,Jenkins 上无时无刻都有任务在运行,而且很多回归用的测试 Job 一次执行就需要十几个小时,如果此时为了修复 bug 等原因更新插件,就需要停掉这些 Job,导致大量的时间和资源浪费。
另一方面,Jenkins 成为团队工程活动的越来越重要的组成部分,越来越多的研发规范希望承载到 Jenkins 上(通过自建插件的方式),基于 Jenkins 插件实现的难度越来越大,尤其是用户界面相关的部分,由于团队研发关注的维度不是一个个 Job,而是团队、模块、产品、版本等等,与 Jenkins 自身的模型差异很大。
同时,背后承载具体能力的服务越来越多,比如角色权限管理、测试设备管理、测试策略管理、环境管理、日志分析等等,仅靠简单的插件视图其交互体验也不好。
第三点,默认的 Job 日志都是 archive 到 master 节点,导致随着 Job 数量的增加和使用的深入,master 节点的磁盘和 IO 经常成为瓶颈。
2.3. 小结
时至今日,仍然有许多团队基于类似 Jenkins 这样的工具,配合开源或自研的插件搭建自己的持续集成和交付平台,这种方案落地非常快,外部资料也很多,在团队规模较小且使用量较小的时候,不失为一个好的选择。
但是,随着规模的增大,它的局限性也非常明显,相比于工程维护行的问题,其自身的模型限制才是影响后续演进的最大挑战,也是这套方案仅存续了不到 2 年时间就被替换的根本原因。
如果在研发规范和工程实践上有很多想法和诉求,我建议将 Jenkins(或其它类似的工具) 置于“工具”而非“平台”的位置。
自定义平台+Jenkins+
HDFS,平台工程的早期探索
大约在 2011 年初的时候,我们开始设计和开发新的工具平台,我们期望这个工具平台能支撑未来几年的研发诉求,且具备较高的可用性和一定的扩展能力,并着重解决以下问题:
解决 Jenkins Master 的高可用问题
统一各个部门、团队的研发流程规范
降低回归测试的维护和分析时长,并进一步提升测试设备的利用率
提供数据挖掘所需要的基础数据和分析能力
为此,我们改变了之前基于工具的视角,改为以研发团队和研发流程为主视角,面向工程师来设计整个工具平台,Jenkins 仅作为一个任务的执行者,被完全地藏在了后面。
3.1. 面向研发团队的平台视角
工具平台的使用者由之前的少数持续集成和测试回归的接口人,转变为整个研发团队,工具的使用界面也基于研发团队的工作诉求来设计和实现。
针对研发团队的各类诉求,平台通过提供各种独立的服务来满足,如测试设备管理、日志存储等。Jenkins 由之前的单一 Master 变成了多 Master 架构,每个 Master 上均安装一个自研的调度插件,用于从平台获取执行任务并触发执行。Jenkins 退化为任务的执行者,Job 也退化为任务的临时载体,因此 Job 变成了按需动态创建和销毁,数量大为减少,数据因为都存储到日志存储服务中,Jenkins 侧也无需持久化了。这样 Jenkins Master 的磁盘 IO 和内存占用都大为改善。
而多 Jenkins Master 的架构使得升级维护的时候不会导致任务停机,日志信息不存储在 Jenkins 上,使得即便 Jenkins 服务器损坏,也不影响研发团队的日常使用。
由于在用户界面上与 Jenkins 完全解耦,工具平台看起来便是为研发团队量身定制的。
但高可用问题并未消失,只是从 Jenkins 的高可用,转变为平台服务及其背后的数据库的高可用,这就是常见的 Web 开发所面临的问题,属于通用问题。对团队来说,面对通用问题,要远好过面对特殊问题。
3.2. 成于定制、败于耦合
新的平台推广得很快,获得了还不错的用户反馈,但需求也源源不断地进来了,工具平台以肉眼可见的速度变得臃肿起来,功能点越来越多,但不少功能点只有少数几个人会去使用,对其他人来说更多的是干扰。
加之,作为一个内部的研发工具,我们缺乏统一的产品理念和架构设计,质量也开始出现问题。
我们当时将该工具定位为一个针对团队所负责的产品研发的持续集成和验证平台,与团队所研发的产品有非常紧密的联系,比如测试设备管理,从界面到实现,都与特定的电信设备深度耦合;另外,平台的范围,也被框在了当时所处研发团队的职责范围内,即以拆分好的需求为起点,以交付到下游进行系统测试为终点。这就导致了该工具的生命周期受制于团队和产品的生命周期,无法灵活应对团队的职能和研发模式的变化。
从 2011 年这个项目启动,到 2015 年我从团队离开,四年多的时间,我们一直在改进和维护这套工具平台。中间有个插曲,大约在 2013 年左右的时候,我们意识到功能堆得太多了,于是针对各个功能点,进行了一次分析和梳理,通过排查用户访问日志等手段,将一段时间内没有被使用到的功能点标记出来,再逐一进行下线处理,下线后如果观察期没有问题,再将该部分功能代码移除。
几个月的时间,我们共移除了十多万行的功能代码,之前的特性冗余可见一斑。
3.3. 小结
套用现在的话术,这可以说是一次不成功的研发团队内部的平台工程尝试,虽然作为一个内部工具平台,它支撑了一个 500 人规模的研发团队 5 年以上的内部研发诉求,对提升团队工程规范、协作效率等起到了应有的作用。
但如同我后来看到的各种内部工具平台的尝试一样,缺乏清晰的领域模型,缺乏明确的架构分层,导致其在团队发生大的变化的时候无法快速适应,最终随着老的产品和研发模式一起被淘汰了。
从自研的代码检视 Portal 到集成 ELK
和 Grafana,在效能监控和度量上的探索
几乎是从做持续集成的第一天开始,关于效能监控和度量的讨论就没有停止。在这一部分,我将聊下 2008 ~ 2017 年这段时间,我们当时的研发团队在效能度量上的一些探索。
4.1. 当前进展的监控
对于当前进展的监控,可以算是早期应用范围最广的度量场景了,一开始利用 Jenkins 的 dashboard 插件,后来发现很难按阶段定义和展示,便有了类似下面这样的简单页面。其本质也是一个对多个 Jenkins Job 按某种内在逻辑的状态聚合。
按照每日构建的标准,作为团队的负责人,在下班前只要看一眼这个 dashboard,立马就能发现存在风险的模块,并及时加以干预。因此,该页面虽然比较简陋,却非常管用。
4.2. 提交频率、成功率和修复时长,早期的度量数据
除当前进展的监控外,每隔一段时间,团队需要看下这段时间内,各个模块的持续集成情况,最早关注的统计性数据,主要是三个:提交频率、执行成功率、失败修复时长。对比如今流行的 DORA 四指标,这三个数据差不多可以和部署频率、变更失败率和恢复平均时间对应,唯一缺的是变更前置时长。
这 3 个指标中,团队最为关注的是失败修复时长,即期望达到快速修复,减少阻滞的效果。
4.3. 建立代码检视的基线
相信很多人都对 Sonar 不陌生,我认为其最大的亮点,即用一套设计良好的开放机制将各类代码检视工具和数据集成到一起,提供统一的规则设置、度量和分析的界面。在 2013 年前后,我们也针对当时的团队情况,定制了一个简陋版的代码检视工具,其 dashboard 类似下图。
在这个 dashboard 中,我们按编译问题、复杂度、重复度、静态分析等维度,将各个应用模块的代码检视数据进行了聚合,同时可以为每一个模块的每一个指标设置检测阈值,超过阈值,则该模块的持续集成会判定失败,强制开发者修复该问题。
后来,熟悉了 sonar 之后,我发现这个设计与 sonar 很像,也是分为了 Agent 和 Server 两部分,Agent 用于连接各个代码检视工具,并与 Server 通信和传递分析数据,Server 用于策略的配置和问题的分析查看。在之后,再遇到类似的诉求,我都会优先考虑基于 sonar 的解决方案。
4.4. 引入 ELK 和人工智能
大约在 2016、2017 年的样子,我的同事对效能度量和改进的整体工程框架做了全新的设计,引入了 ELK 和人工智能技术,使得无论是数据时效性、处理的数据量级、适用的范围,还是分析的深度,均有了极大的提升,关于这一部分的实践,可以参考何军在 Jenkins Conf 上的演讲:基于 Jenkins Log 秒级数据的研发效能改进 - 何军[2]
4.5. 小结
直到最近两年,我还经常收到研发效能度量相关的咨询和问题,但是,大部分的关注者,所关心的都是指标体系和评价机制。其实,在此之前,拥有完整、可信、彼此连接的高质量的数据才是更关键的。感兴趣的读者,可以去翻一本名为《程序员度量》的有 20 多年历史的老书。
Gitlab、Jenkins,everything is code,
10 人初创团队的 CICD 探索
2017 年初,我加入了一个仅有 10 个人的初创团队,做基础设施类的网络产品。从一家有着数万研发的大厂,转到一个 10 人的初创公司,我们所有的参与者都有机会重新思考下如何快速从零搭建一套适合初创团队的研发工具。
相比大厂完善的基础设施和分工体系,初创公司往往是追求团队的全能与灵活,所有人都是开发,都是测试,也都负责运维。基于以往的经验,我们首先定的目标是持续构建出可用的包并能持续执行测试且获得反馈。
5.1. CICD 的起点:代码托管 + 流水线 + 制品库
第一步,我们需要找到一个代码托管的地方,毕竟代码是技术创业团队最重要的资产和最主要的协作方式。我们当时选择的是 Github 的私仓(多说一句,按今天国内的 DevOps 工具链格局,云效的 Codeup 是一个不错的选择),但由于 Github 的访问效率问题,我们在本地搭建了 Gitlab 作为 mirror,作为 CICD 工具链这种只读访问的源头。
第二步,我们需要一个支持按 git 事件触发,能够满足日常构建和部署诉求的持续集成工具。最开始的时候,我们打算采用 Gitlab 内置的 gitlab-ci,但是当时的社区版 gitlab 不支持 matrix 等能力,而多架构(x86、android、iOS、arm、mips 等)编译对于我们来说则是刚需。
于是我们又引入了 Jenkins,作为持续集成的工具。在实际落地中,发现当时的 Jenkins 对于 tag 的触发场景存在问题,便仍然启用了 gitlab-ci,作为当 tag 更新时触发 Jenkins Job 的源头。
第三步,我们需要有地方能托管构建产物,包括各种类型的制品如 rpm 包、apk 包、pypi 包等,一方面用于后续测试环境的部署验证,另一方面用于线上的部署和发布。
其它,还有像用于代码静态检查的 cppcheck、golint、sonarqube,用于自动化测试的 Robotframework、用于通知的微信机器人(现在应该不行了)等工具,可以说,到目前为止,整个 CICD 工具链还是围绕研发来打造的。
整个工具链类似下图:
5.2. 类主干开发的分支模式
考虑到团队的扩张,以及研发规范的延续,我们在 CICD 实践上做了如下约束:
CICD 流程的触发应是自动的,事件驱动的,任何手工触发都应当被禁止
无论是构建配置、流水线配置、单元测试配置还是代码扫描策略,都应以代码的形式存在于代码库中
代码库按子系统划分,各模块间通过源码依赖,通用的 library 统一维护于独立代码库中
并根据团队的实际情况,制定了类主干开发的分支模式。
这个分支模式的基本约定是这样的:
任何开发均直接提交到 master 分支,提交到 master 分支上的 commit 需保证单元测试和静态扫描通过,如果单测或静态扫描失败,需在当前下班前修复
极少数情况,新特性会对当前产品产生破坏性影响,可以拉出特性分支,但分支的生命周期不得超过 2 周
为了保证发布稳定,可拉取 release 分支,但 release 分支的生命周期不得超过 1 周
在 master 分支或 release 分支上打了 Tag,即认为符合待发布标准
可以看到,作为初创团队,我们非常强调频繁且高效地集成,期望产品始终向前演进。
5.3. 小结
初创团队更像特种作战小队,在实践上更注重灵活和快速,为此,我们只保留了最核心的工具链,即代码库、流水线和制品库。对于需求管理、项目管理,由于大家都在一个会议室里办公,我们直接使用了物理看板解决。
但是,从一个大公司的纯研发团队出来,到一个初创公司,我们一开始并没有意识到的是,我们这 10 个人已经是这个企业的全部了,我们不再只是个研发团队(团队在业务人才上的缺失,是导致后来难以为继的重要原因,这是后话)。
这个问题在团队成立不到 1 个月的时候就显现了出来。
从研发走到运维,第一次以运维负责人
的身份考虑 DevOps 平台的建设
大约在 2017 年 5 月初的时候,我们开始考虑产品第一个版本的上线,此时有两个问题摆在我们面前,一是线上部署怎么做,二是线上运维怎么搞。像电信设备这种行业,部署都是由专门的交付实施团队负责的,属于私有化交付的形式,运维也是,产研做好工具和产品特性就可以了。
但这一次,我们对客售卖的是服务本身(类 SaaS 的模式),因此线上部署和运维就必须由自己保证了。
我们只能去请教在互联网行业的朋友,再结合我们的实际情况,进行摸索和调整。这个期间,谷歌出的一本书给了我很大的启发,即:
于是,我职业生涯第一次以运维负责人的身份,来考虑工具平台该怎么建设,怎么把 CICD 和后面的部署运维高效地连接起来。
6.1. 线上线下分离、客户端服务端分离
我们要解决的第一个问题是如何安全准确地把应用部署到线上。
为此,我们首先做的事情是把线上和线下的环境进行区分。即:
线下环境:即开发测试环境,可连接测试机器、代码、流水线和制品
线上环境:即生产和灰度环境,可连接线上机器、生产可用的制品和部署运维工具
另一方面,客户端的生产发布与服务端差异很大,其制品形态也不相同,为此,我们针对客户端和服务端需要分别设计部署运维工具。
于是,架构就变成了下面这样:
6.2. 收敛配置变更
我们不希望应用自身感知环境的差异,也不希望为应用准备冗长的配置文件,为此,我们将应用的配置收敛为 2 类:启动配置、运行时配置。
所谓启动配置,仅指应用程序的启动参数。为此,我们做了一些约定,例如,应用如果需要访问 mysql 数据库,则地址都约定为 mysql-xxx.my.internal,具体 ip,则交给对应环境的 DNS 去处理。
而运行时配置,就是咱们通常所知的动态配置,包括 log level,黑白名单等等都属于运行时配置,运行时配置的调整在运维平台上处理。
于是,在前面架构的基础上,我们引入了 DNS 和配置中心服务。
很少出现启动配置需要修改的场景,加之大量的配置都通过约定的方式统一了,配置变更的次数减少了很多。
因此,我们并没有去将配置变更整合进发布流水线,而是尝试将配置减少,尽量降低配置变更的频率。
6.3. 集成监控,连接部署、测试和观测
在上一阶段完成的时候,我们做到了应用从代码提交到生产部署的两段式流程打通,即持续自动地产生待发布的制品,加上线上自动化的部署运维。但随着研发的推进,有两个问题浮出了水面:一是线上的问题如何快速地观测并处理,另一个是如何在全量发布前进行灰度验证或 AB 测试,两者都与监控有关,也都与发布流程存在联系。
于是我们工具链又扩充了,我们引入了监控服务和线上的测试服务,而此时,距离我们项目启动也只过去了不到 3 个月。
可以看到,工具越来越多,有自研的,有基于开源工具二开的,工具一多,不同工具间数据的连接和元数据的管理就成为了一个问题。另一方面,如果团队大一些,分工细一些,不同角色间的协同也将成为问题。
随着 DevOps 链路越来越长,按完成单点能力建设的工具链,其不同工具间的数据连接和元数据管理将逐渐成为团队研发活动的阻碍;同时,如何让链路上不同的角色高效的协同,也成为 DevOps 建设中绕不开的问题。
6.4. 小结
可以说,直到这一章,我们才真正在做 DevOps,把运维相关的需求融入到研发工具链的建设中。相比之前代码库、流水线、制品仓库的 CICD 三件套,我们还需要关注资源(比如主机)、应用(比如线上应用的配置)、环境(线上的灰度、生产环境)等。如果这些运行侧关注的对象与研发侧关注的代码、流水线、制品等无法连接,整个 DevOps 流程也就无法做到自动化。
大约两年之后,我已经离开了这个项目,回过头来看前面的设计,我意识到我们缺少一个研发运维能一起协作的工作空间,后来我们认为这个工作空间是应用;我们同时缺少一个元数据中心,来保证 DevOps 各阶段的数据对齐,后来我们认为需要以应用为中心来建设元数据体系。
从 SaaS 产品到私有化交付平台,
我们造了一个版本制交付的 DevOps 平台
2018 年秋,我参与到一个新的项目中,为外部的企业提供私有化部署的安全产品,乍看起来,与之前的电信设备软件的场景差不多,但实际有两点差异很大:
1. 软件规模和团队规模差异,电信设备软件往往需要数千甚至数万人协同研发,而这个安全产品整个研发团队也就是几十人。
2. 关注的范围不同,相比之前的纯研发团队,这里前面需要对接售前,参与 POC,后面需要承接售后,解决客户现场问题。
简单说来,这是一个业务驱动、产品导向的团队,业务这个角色第一次具体起来(说来惭愧,我之前工作十多年,都是没有明确的业务方的)。
7.1. 业务和研发的关注点和协同方式
这里,团队的组成符合一个互联网公司的典型特征,业务、产品、前后端、算法、测试、实施,100 多人,也可谓是麻雀虽小,五脏俱全了,加之 toB 的业务形态,业务和研发的协同比以往任何时候都更紧密。
当时,业务团队使用一款 SaaS 版的项目协作工具来收集需求并管理客户交付,而研发则多在代码库上工作,每两周有产品经理组织会议来确定需求重点和排期。研发完成之后,再按需给各个客户出包,实施同学也不清楚此次出包的需求范围,需要线下另外沟通。
套用之后比较常见的话术,这是典型的业技协同问题。
我们首先把双方的关注点和工作空间进行明确的划分。业务团队关注的是客户的原始需求、碰到的缺陷和接下来预期交付的版本及其范围,其关注的核心对象是需求。研发团队关注的是接下来的客户重点需求、产品建设方向和交付计划,其关注的核心对象是任务(用于支撑需求)。
考虑到当时团队的情况,我们做了如下的约定。
业务团队的所有工作都在公网上开展,按售卖的产品创建项目,在项目中管理需求(主要为客户需求),并在项目中管理版本(对客的交付版本)。研发团队的工作几乎都在内网和线下,代码库托管在本地 GitLab,线下管理迭代和发布,通过任务与需求建立关系。上图中虚线的部分都是在线下完成的,因此,业技协同仍然主要依靠线下进行,显然,这样的效率是不高的。
7.2. 私有化交付的特点以及开源工具链的局限
除了业技协同的问题外,这个互联网背景的团队在面临私有化交付场景时存在水土不服,主要问题在于其按照互联网应用微服务架构和只维护一个线上环境的思路来应对私有化客户交付。私有化交付有两个特点:按系统交付、多版本并存。所以,私有化交付的模型对象是这样的:
无论是研发之前的业务,还是研发之后的实施,其关注的都是系统和版本,而不是应用,更不是代码分支。想通了这一点,工具链的选型和设计就比较清楚了。
我们对主要的开源工具进行了评估,发现其大都关注在某个基础工具层面,比如代码托管、流水线、制品托管这种,而上层的工具需要我们自己建设。
于是,便有了下面这个针对私有化交付场景的工具,下图是某个发布的详情页截图。
7.3. 从领域模型的角度看平台的通用性
时隔几年之后,又造了一个内部用的轮子,我希望它的通用性能好一些。不同于以往,这一次我们很早就确定了它的领域模型,我们觉得,至少对于版本制交付的场景,它是有通用性的,同时持续交付如果作为版本制的一个特例的话,那它对于非版本制交付的场景也是适用的。
上面这个模型中,“特性”这个对象是我最不确定的,其主要问题在于应用是否可以独立交付,在后来思考云效的版本制交付方案的时候,也确实将其进行了调整。
7.4. 小结
关于这个平台的更多建设细节,感兴趣的读者可以参考我在 2019 年 pycon 大会上的分享:PyCon2019-从零开始快速构建 DevOps 系统.pdf[3]。
第一次以供应商的视角审视 DevOps 平台
2022 年我加入阿里云云效团队,第一次以供应商的视角审视 DevOps 平台。相对于以往,最大的变化在于交付场景的不确定。作为供应商,我们的软件应当是实践中立的,即我们不假设用户的技术栈、开发习惯、交付方式,具体怎么用我们只有建议权。
在这个背景下,为了避免支持的场景受限,我们按理想的划分方式,定义了两个极端场景:
1)版本制发布
一个极端是完全的版本制发布场景,其特点是从需求分析开始,到集成、验收、发布,各个阶段都是按批量依次进行的,且前序阶段未通过,后序阶段无法开始。在当前的研发活动中,这样的场景已经很少会看到了。
2)持续发布
另一个极端则是完全的持续发布场景,其特点是无论需求还是应用,彼此之间都是解耦的,且研发极为高效,需求可以独立发布,应用也可以独立部署,简单来说就是随时随地,想发就发。这种极致的敏捷,是很多团队都期望达到的状态。
我们将这两种场景作为基础场景的两个极端,其他各种场景可以描述为这两种场景的中间过渡态,即:
DevOps 产品需要能支持这中间的各种实践场景。显然,像以前做企业内部平台那样,直接提供定制好的平台是不行的,且如今随着技术演进、业务和团队的快速变化,企业内部的平台的灵活性要求也越来越高,这也是平台工程越来越多地受到大家追捧的原因。
因此,像云效这样的 DevOps 平台的供应商,它所要解决的就是 2 个问题:
1. 提供基础的 DevOps 工具链
2. 提供企业建设 DevOps 平台(或者说平台工程)的基础设施
在这个视角下,云效就有两种不同的用户:
第一种,我称之为云效的直接用户,他们所面对的是云效产品本身,通过使用云效提供的产品能力完成日常工作,比如大量的开发者、项目管理者、运维人员等。
第二种,我称之为云效的间接用户,他们所面对的是云效的扩展能力和定制能力,通过定制和扩展云效,再结合其他工具,形成一套研发平台,被其他人使用,他们是平台的建设者,云效是他们的建设平台的基础设施。
8.1. DevOps 平台的专业性体现在哪里
两种用户的诉求是有差别的,直接用户更关注使用体验与易用性,间接用户更关注产品的专业能力。短视频 APP 可以说是易用性的极致典型,无论是谁都能很快上手使用,而像 Emacs/Neovim 这样的软件,虽然能力强大,却不是普通用户能快速上手的,要不然也不会产生 Spacemacs、Lazy.nvim 这样的开源项目。
对于云效来说,易用性固然重要,但对广大的企业开发者来说,专业性却不可或缺。
那作为 DevOps 平台的基础设施,其专业性体现在哪里呢?我们认为 OpenAPI、webhook、三方扩展等等都是外在的表象,其内核是这个平台的基础模型是否清晰且具备足够的扩展性。不同于为特定企业建设 DevOps 平台,云效这样的 DevOps 供应商必须能适应各类企业的不同 DevOps 场景。
最简单的是我们只提供基础的工具能力,即前面所说的需求管理、代码托管、制品托管、流水线等等,但这就要求用户自行建设平台。我们决定再多做一点,即贴近 DevOps 的本质去定义基础模型,围绕模型在基础工具之上建设平台的基础能力,提供建设 DevOps 平台所需的工具、连接和数据能力,让平台建设者在其之上进行配置、扩展和定制。
在 2022 年,云效联合产学研各界一起发布了必致(BizDevOps)白皮书 2022[4],在该白皮书中,我们给出了一个基础模型(见下图)。
这两年,我们一直在产品中验证和完善这个模型,但其主体框架没有发生大的变化。
8.2. 基础工具还是基础平台
很多 DevOps 平台的建设者都会面临这样一个问题:我们打算建设自己团队的 DevOps 平台,这时候我们是把云效作为基础工具藏在后面,还是在云效之上通过配置和扩展来构建平台?
这个问题反映了选型者的一个担忧,将云效作为 DevOps 平台的基础设施,真能满足团队的 DevOps 诉求吗?会不会被产品所绑定了?
我们认为软件研发是复杂的,DevOps 的场景是多样的。无论是把云效仅作为基础工具使用还是作为 DevOps 平台的基础设施,这两种场景都是存在的,我们的目标是尽量减少平台建设者的建设成本,尽量复用产品的已有能力和数据。DevOps 平台既分领域,又分层次,横向纵向都是可以按需选择的,类似下图的结构。
一般来说,按纵向选择,例如只使用云效的研发域和交付域,而自行建设业务协作和产品规划工具,那可以把云效作为基础工具使用,在更上层建设统一平台,收口和连接各域的数据(类似第七章那样);按横向选择,无论是否使用平台层,我们都建议把云效作为基础设施使用,由云效统一收口数据,通过云效的扩展能力建设符合自己诉求的 DevOps 平台。
8.3. 小结
DevOps 平台的用户大都是开发者,开发者可以说是最挑剔的用户,但也是最忠诚的用户。很多有长久生命力的开发者软件,都有一个共同的特点,即其核心模型很小,且非常稳定,这样一方面让开发者的理解成本降低,另一方面让软件拥有巨大的扩展空间。我们前面给出的 DevOps 领域模型仍然臃肿了些,需要靠这个领域的参与者一起来优化和精简。
未来:AI 来了,但工具和数据仍然无法替代
AI 来了,平台工程会有哪些变化,未来的研发会变成什么样,这些仍然在不断地探索之中。
9.1. 我们曾有过哪些期望
十几年前,我被编写和调试测试自动化用例搞得焦头烂额,当时,我就在想,有没有可能只要把需求描述清楚,测试自动化用例就能自动产生并调试完成。
再后来,面对着大量的研发数据,为了分析研发效能,我编写了不少代码用于处理和展示数据,当时,我在想,如果有一个智能机器人,把数据给它,把期望告诉它,它就能按各种维度给出效能分析,并指出存在的问题,甚至改进方案,那该多好。
在几个人的初创团队,我们缺乏专业的安全人员,那时,为了一个安全问题,各种查资料,如果有个智能体能作为安全专家,帮我们补上短板,那该有多好。
如今,在 AI 的帮助下,很多方面都有了明显的发展,当年的那些期望可能在不久就能成为现实。
9.2. 我们有哪些资产会继续存在
那哪些又是我们的重要资产,值得被保存和反复使用的呢?
我认为一是解决特定问题的高价值工具,另一个是领域研发多年沉淀下来的的高价值数据,也包括 DevOps 领域的数据。工具可以弥补大模型处理特定问题能力的不足,数据可以帮助 AI 培养出该领域的研发专家。
随着 AI 的持续演进,可能以后我们会看到由 AI 独立设计研发的产品,其在领域认知上像 AlphaGo 超越人类棋手一样超越我们。
9.3. 小结
AI 到来之前 DevOps 领域主要的发展还是在研发流程本身的延伸上,具体到某个能力,大的发展并不多。但是,像编码、测试等强耗时的场景,都有了不少新的发展,在需求分析、设计等方面也有许多探索正在进行,未来可期。
结 语
我想以诺基亚公司的一句著名的广告语作为结尾:“科技以人为本”。我也把它作为 DevOps 以及平台工程的使命。无论新技术如何发展,我们解决的问题仍然是那两个:
团队获得更快的价值交付
开发者获得更低的心智负担
无论是引入流水线、统一构建产物为容器镜像,还是当前正在发生的 AI 深入研发各个环节,我们都是在为那两个本质目标而努力。
相关链接:
[1] RobotFramework
https://robotframework.org/
[2] 基于 Jenkins Log 秒级数据的研发效能改进 - 何军
https://www.zybuluo.com/gaoxiaoyunwei2017/note/1079681
[3] PyCon2019-从零开始快速构建 DevOps 系统.pdf
https://tpsservice-files-inner.cn-hangzhou.oss-cdn.aliyun-inc.com/files/resources/WB524047/66df0a3e3ab093f0d243c314b4fad061.pdf
[4] 必致(BizDevOps)白皮书 2022
https://developer.aliyun.com/ebook/7847