简介
本文来自极客时间《持续交付36讲》教程,收获很大,除了一些具体的技术,更重要的是一套成体系的理念:即为什么要这么做?每一个小的做法、调整、优化在整体研发效率优化中的位置、作用及意义。
持续交付的“三观”
组织、团队、工具
- 持续交付必须以平台化的思想去看待,单点突破是无力的
- 持续交付的实施,也要顺应技术的变迁,善于利用技术红利
- 持续交付与系统架构、运维体系息息相关,已经不分彼此
个人再强,放在一个低效的环境下,也无力可施
持续交付是软件研发人员,如何将一个好点子,以最快的速度交付给用户的方法。
标准、规范、流程的落地,都需要载体,而最好的载体就是平台工具
持续交付体系也如同中间件一样,能够从日常的业务研发工作中抽象出来,其不同只在于中间件解决架构问题,而持续交付解决工程问题。这样研发团队能够全力应付业务的需求,而不用总是重复奔波于一些烦人且耗时的工程问题,比如安装测试机、准备编译服务器等等。
学习一下持续交付的内容,它能让你看到更多与编码有关的其他东西,比如不同的编码方式等;也能让你站在更高的角度去看待自己的工作:研发效率的提高往往不是个人能力的提高,而是集体协同效率的提高。
虽然持续交付着重打造的是发布流水线的部分,但它所要达到的目标是在“最终用户”和“研发团队”之间建立紧密的反馈环:通过持续交付新的软件版本,以验证新想法和软件改动的正确性,并衡量这些改动对软件价值的影响。 这里说的“软件价值”,说白了就是收入、日活、GMV 等 KPI 指标了。在保证交付质量的前提下,加快交付速度,从而更快地得到市场反馈,引领产品的方向,最终达到扩大收益的目的。
和devops 的关系
- DevOps 的本质其实是一种鼓励协作的研发文化
- 持续交付与 DevOps 所追求的最终目标是一致的,即快速向用户交付高质量的软件产品;
- DevOps 的概念比持续交付更宽泛,是持续交付的继续延伸;
- 持续交付更专注于技术与实践,是 DevOps 的工具及技术实现
代码分支策略,一切的源头
不同的代码分支策略,意味着实施不同的代码集成与上线流程,这会影响整个研发团队每日的协作方式
-
主干开发
- 特征切换在本质上是条件语句中使用的变量,切换为关的代码块类似于被注释的代码,主要用途是避免在发布前的最后一刻因软件合并而产生的冲突。
- 特性切换需要健壮的工程过程、可靠的技术设计和成熟的特性切换生命周期管理,如果不具备这些能力反而误事。
-
特性分支开发
- 包括git flow、github flow、gitlab flow
- 特点:极端情况下,对代码的任何修改,包括 Bug 修复、热修复、新功能开发等都在单独的分支中进行。具体的分支策略做出一定的简化。
作者有一个周一到周五的最佳协作案例,可以学习一下。
依赖管理
操作系统级,centos的yum、macos的homebrew等 编程语言的依赖管理工具,比如 Java 的 Maven,Golang 的 go get,Python 的 pip
这些平台的解决思路都是将依赖放到共同的仓库,然后管理工具通过依赖描述文件去中央仓库获取相应的包。
Maven 最佳实践
- 生产环境尽量不使用 SNAPSHOT 或者是带有范围的依赖版本
- 将 POM 分成多个层次的继承关系,比如定义公司级的pom.xml(super-pom)、部门级的pom.xml、项目级别的pom.xml,每个项目必须直接或间接的继承super-pom
测试环境治理
分类及什么是好的环境
使用者 | ||
---|---|---|
开发环境 | 开发 | |
功能测试环境 | 开发、测试 | |
验收测试环境 | 产品、测试 | |
预发布环境 | 测试 | |
生产环境 | 用户 |
当一个环境可以满足其真正核心用户的需求时, 就是一个好用的测试环境。
- 开发关注效率
- 测试关注可靠性
- 产品关注真实的用户体验和产品完整性
- 预发布环境的需求其实来自于运维同学,他们需要保证生产环境的稳性,减少生产环境的变更,所以需要将预发布环境与线下环境完全隔离(PS:用真实的环境帮助发现错误)。
环境配置
什么是环境配置
环境配置,此配置非彼匹配,配置是环境管理中最核心的内容,包括:
-
以环境中每台服务器为对象的运行时配置,以一个 Java Web 应用为例,需要哪些运行时配置呢?
- 安装 war 包运行依赖的基础环境,比如 JDK,Tomcat等
- 修改 Tomcat 的配置文件
- 配置 Jvm 参数
- 考虑操作系统参数,比较常见的一个配置是 Linux 的文件句柄数
-
以一个环境为整体目标的独立环境配置。
- 这个环境所依赖的数据库、缓存该如何配置
- 如果是微服务架构,就需要考虑微服务治理框架、配置中心等一系列中间件的配置问题。
- 环境对应的基础服务,比如监控,短信,搜索等。
环境配置理念
- 环境一定要标准化。解决复杂问题的办法:分拆、简化、标准化
- 约定大于配置
-
让环境自己能开口说话,通过环境的自描述文件,让环境能讲清楚自己的作用、依赖,以及状态,而不是由外部配置来解释这些内容。PS:比如部署文件是写在项目里(告诉调度胸系统,要如何部署这个项目),还是由用户额外配置?
- 服务器生成时,写入它自己的描述文件。我们通常把这个文件命名为“Server Spec”。在这个文件里,记录了这台服务器的所有身份信息,包括:IDC,型号,归属环境,作用,所属应用,服务类型,访问路径等。PS,这个是一个新鲜的搞法,每个容器可以搞一个Container.Spec,你在新建一个容器时,可以向容器写入一个Container.Spec,告知所属的环境,作为各个中间件的约定信息源
- 中间件根据 Server Spec 的描述,寻找到它所在环境对应的配置中心。
- 完成服务自发现,根据服务类型,访问路径等,还可以自动生成对应的路由配置,负载均衡配置等。我们是在尝试把环境配置的方向调个个儿:由原来外部通过配置告知环境应该干什么,转变成环境根据自身的能力和属性,决定自己应该去干什么。PS:以前负载均衡配置都是外部软件监控调度系统写入负载均衡组件,外部做和自描述,得好好体味一下,感觉可以做个调度系统框架嵌在项目中来完成这些事儿。另外,google jib 也有点这个意思,根据代码做镜像,而不是将代码无差别的打成war做镜像。
环境配置方法
- 构建时配置,比如maven的profile。问题,改配置就要重新构建一次。
- 打包时配置,以java 为例,作者应该指的是生成class 之后不要立即打成war,而是延迟到发布时。为什么要从构建独立分离出打包这个步骤呢?为了一次构建,多次部署。打包时配置的基本思想是:构建时完全不清楚程序所要部署的环境,因此可根据环境信息,进行相关配置的替换。具体可参见作者举的内部工具的例子。
-
运行时配置
- 修改后实时生效
- 支持灰度发布
- 能分环境、分集群管理配置;
- 有完善的权限、审核机制。
特有问题:配置不会随着代码回滚。
环境创建、拆分与合并
-
环境创建,就是不断提高虚拟机准备和应用部署两个流水线的速度和稳定性
- 环境构建流水线
- 应用部署流水线
- 环境拆分,当有新项目时,开发人员会挑选部分应用,组成一个独立的子环境。这里的重点是,要保证子环境和完整环境的调用是互相隔离的。
- 环境合并,当存在多个子环境时,可能在某个时间点需要做多个项目的集成,这时开发人员需要合并多个环境。需要注意合并后的环境冲突,一般将这些冲突罗列出来,交由用户选择决策。
环境的创建和拆分,最主要的问题就是如何复制和重新配置环境中的各个零件。此外要处理:
- 用户访问应用的入口管理
- 应用之间调用链的管理
-
对数据库的访问
- 数据库连接串的维护问题,与 SOA 调用链(即服务之间的调用关系)的维护类似
- 二是,数据库的快速创建策略。提供基准库和数据库脚本变更接口,创建环境时根据基准库执行变更接口
容器对持续交付的影响
不可变基础设施(Immutable Infrastructure)是 Chad Fowler 在 2013 年提出的一个很有前瞻性的构想: 在这种模式中,任何基础设施的实例(包括服务器、容器等各种软硬件)一旦创建之后便成为一种只读状态,不可对其进行任何更改。如果需要修改或升级某些实例,唯一的方式就是创建一批新的实例来替换它。其好处包括:
- 很多与 runtime 相关的配置工作都可以被简化,这让持续集成与持续部署过程变得更流畅。
- 它也更易于应对部署环境间的差异及版本,进行更有效、全面的管理
- 对回滚来说,更是得到了充分的保证,只要原先版本的镜像存在,它就一定能被修复。
没有容器之前,交付标准包括软件环境(也就是所谓的机器)和软件代码两部分。交付系统更关注的是软件代码,环境一旦产生后,我们就不再关心或者很难再干预用户后期是如何对其做变更的了。容器技术则统一了软件环境和软件代码,重新定义交付标准。
容器比较轻量
- 具备分发和存储的优势
- 启动容器也不需要启动整个操作系统
- 体积小+启动快+镜像版本控制,最适合做不可变基础设施
- 镜像带来的环境一致性,交付结果一致
- 应用的启动统一成了运行镜像,交付自动化与个性化
容器技术已经解决了很多问题。比如,服务器操作系统级别的依赖的标准化更容易了;当出现硬件故障时,迁移和恢复服务也更加方便了。但容器/云计算不是银弹,比如容器技术并没有解决故障定位的问题、不能很好的满足用户的个性化需求等。
构建
Maven Enforcer 插件
- 明确操作系统、maven、jdk版本
- 明确依赖jar的版本
- 显式报错依赖jar版本冲突
- 依赖jar中是否包含同样命名的class
- 其它内置检查规则,PS:非常值得学习
- 自定义的 Enforcer 检查规则
- 对于其它语言则实现通用的依赖检查服务
一个组织的集成和构建往往是周期性的,高峰和低谷都比较明显,而且随着组织扩大,幅度也有所扩大。所以,如果按照高峰的要求来配备 Slave 实例数,那么在低谷时,就很浪费资源了。反之,又会影响速度,造成排队。
镜像构建环境(根据dockerfile创建容器)容器化
- Docker Out Of Docker(DooD)
- Docker In Docker(DinD)
镜像发布不能很好的满足用户的个性化需求:容器内的环境也是由发布系统定义的,用户即使登录到容器上去做变更,下一次发布之后还是会被回滚回来。但是,对 Dockerfile 的编写和控制需要一定的学习成本,因此我们又不可能将镜像的内容与构建流程完全交给用户来自定义。
对于完全自定义镜像发布我们使用 Docker 多阶段构建(multi-stage build),也就是说用户可以将构建代码和构建镜像合并成一个步骤,在同一个 Dockerfile 中完成。(这个要研究下)
发布
蓝绿发布、滚动发布、金丝雀发布总是分不清楚,其脉络是一致的
- 集群状态下,不能一波流一起发,只能一个一个发
- 蓝绿发布:新启动一批对等的实例,在新实例上更新,然后再用新实例替换老实例。此时老实例仍旧存在,以便回滚。 而金丝雀发布是在现有实例上更新,回滚便麻烦了一点。
待解决,从这个角度看,我们用k8s service 作为pod 的入口,作用不仅是负载均衡,同时也解决滚动发布时,流量损失的问题。
平台化
互联网厂商平台化的玩法,往往是指自己搭台子,让其他人唱戏。也就是说,由互联网厂商自己提供一些基础保障能力,建立必要的标准,从而形成底层支撑平台;而由其他供应商或用户利用这个底层平台提供的服务,自己完成具体业务、功能流程设计,从而达到千人千面的个性化服务能力。
-
确定模块及其范围
- 学会做减法,并不是只有完整的端到端自动化才叫“持续交付”,代码管理,集成编译,环境管理、发布部署这四大核心模块,其实就是一个交付的闭环。挑选最为重要或最为急迫的模块,优先加以实施。另外一种做减法的方式是减少纵向的深度,比如只支持容器部署服务化java应用,再逐步向scala等提供支持。
- 制定标准,对持续交付平台的设计来说,最重要的标准是定义各个模块交付产物的标准
- 选择合适的驱动器,小团队用jenkins,较大规模团队自研
- 抽象公共能力,账户与权限、用户行为日志、消息通知、安全与故障处理等
- 选择用户入口,统一站点、命令行格式、ide插件、jenkins界面,比较建议为持续交付独立形成一个 Portal,这样不会受到其他系统的限制,你可以根据自己的设计更好地完成用户引导和使用体验
- 聚力而成
从中体会到的几点(也是其他地方看到的)
- 迭代一步到位
其它
Jenkins主要用作异步任务驱动,携程只有编译系统使用Jenkins。vivo 自研Jenkins资源调度系统设计与实践 未读。
个人微信订阅号