作者 | 朱烨
本系列内容是我们在不同项目的维护过程中总结的关于DevOps/SRE方面的优秀实践,我们将致力于在项目上尽最大的努力来推行这些优秀实践。我们希望这些最佳实践能对项目的稳定运营提供帮助,也希望刚接触DevOps/SRE的新人能通过学习这些优秀实践来提升自己在这方面的水平。
因为DevOps/SRE涉及到的方方面面比较多,一次性完成的工作量太大,所以我们决定分阶段来完成,这一次发布的是“应用开发和部署”这个部分的内容,后续我们将逐步发布“云平台与网络”,“操作系统和服务”,“用户与权限”,“监控与可视化”,“数据与备份”,“敏感数据”,“故障与应急响应”这几部分的内容。
所谓“最佳实践”应该是最适合自己的实践,而不一定是最先进的,而且每一种实践本身也存在一定的局限性,所以我们在描述了对应实践的优点的同时,也把可能存在的缺点写了出来,就是希望大家在看到它的好处的时候,也能知道可能存在的风险在那里,理性地去评估到底是不是要采用相应的实践,所以这里总结的最佳实践请适度取用,不要为了“最佳”而实践。
我们深知自己在诸多方面存在一定的局限性,相关的内容可能存在一些不足,而且优秀实践本身会随着技术更新等因素不停地变化,我们将会把蓝皮书内容同步发布在Github上(https://github.com/toc-lib/DevOps-SRE-best-practice) ,希望引发更广范围的传播和讨论。也请使用PR或Issue的方式来提出你的不同的观点和更好的建议,谢谢。
在传统的运维环境中,由于条件的限制无法快速的提供新的基础设施和环境,所以通常在业务的依赖环境如操作系统内核,服务,类库,运行时版本等需要变化时,我们会根据需要在现有的环境上做持续性变更。而且我们还可能会在机器上运行一些临时任务,做调试和排错等,很多的时候,这些操作对应的变化并不具有可追溯性,甚至不可以恢复到之前的状态。这样,刚开始统一配置的无差别的一批机器随着时间的推移慢慢的就会变得各自具有一些独有的特性。另外还有一些类型的服务,比如数据库,存储等,其业务本质就导致了集群中的每一台机器具有独特的属性。当我们在维护这些服务的时候,需要根据每台机器的特性来做不同的管理和配置,而且一旦机器出现故障的时候,也很难去创建出一样的机器来替代。因为这种情形和养宠物类似,比如我们会给宠物起一个名字,它也需要悉心照料,生病的时候要带去看病,所以我们称这种服务模式为宠物模式。
而在具有云原生能力的平台上,我们可以按需定制基础镜像,也能快速的从这个基础镜像中创建出运行环境,我们的变更就可以基于基础镜像来做更新和版本迭代。这样当某一台机器发生了故障,我们可以快速的复制出一台一模一样的机器来替代。如果需要做一些临行性的操作和变化,在任务结束之后,也可以销毁这台已经发生了变化的机器,使用一台新的机器来替代,使整个集群恢复到一个最初的收敛状态。这个场景和我们现实生活中的规模化牲口养殖类似,对应的我们称这种服务模式为牲口模式。
大家所熟知的无状态应用,就是牲口模式的最常用的一种实现方式。在业务的设计和实施过程中,我们建议把逻辑和数据分离,在逻辑运行环境不要兼顾数据存储工作,比如请求的session相关的数据,不要保存在本地,而是把它放在一个共享数据服务中,从而达到无状态的目的,这样就可以对逻辑运行环境进行牲口化的管理方式。
优点:
缺点:
实施要点:
向前兼容指低版本的系统、程序或技术能优雅处理(例如:忽略其不理解的部分)高版本的系统、程序或技术。向前兼容技术的目标是让旧系统能够识别为新系统生成的数据,简单的说就是旧版本的系统可以接受新版本的数据,是旧版本对新版本的兼容。
我们建议在做业务升级时候,设计你的业务具有向前兼容的能力,以应对升级失败时某一功能模块或者依赖无法随之回滚的风险。比如说在有数据库字段变化的升级中,在正式对数据库做变动之前,基于旧的业务流程做代码层面更新,使其可以兼容数据库将要发生的改动并加以部署。在数据库升级完成之后,如果新的业务流程上线后不幸出现重大的问题等情况需要回滚时,回滚之后的代码仍然可以兼容数据库的变化,而不用对数据库也进行回滚,毕竟数据库的回滚成本非常高。
优点:
缺点:
实施要点:
当生成容器镜像时,应当使用唯一性标识来给容器镜像打标签,唯一标识可以更好的标记当次生成的镜像,避免出现多个同名标签但不同的版本镜像被使用的情况。例如多次部署都使用了latest标签的镜像,可能因为拉取和缓存策略导致不同节点使用了不同版本的镜像,从而导致功能上的不一致,在这种情况下,并不能很方便地判断出某个节点部署的是哪一个版本。
唯一标识最好有一定的含义,不仅可以用来区分产物,还可以获取到本次构建的关键信息。比如git提交哈希等关联性比较强的标识。虽然时间戳也是一个唯一性比较强的标识,但是关联性相对较差,如果长度不足,也有一定的几率产生碰撞。可以考虑使用组合型标签,比如使用时间戳,build号,版本号等根据自己的需求来组合生成唯一标识,这样的标签本身就包含了很丰富的信息。
不建议单纯使用pipeline的build序号来作为镜像的标签,如果需要更换CI工具或者重建pipeline时,这个序号将会被重置而可能产生重复,除非在构建脚本中加入偏移量。而且不同的CI工具获取这个序号的方法也有所不同,对于迁移并不友好。虽然它的可追溯性看起来较好,但是单纯的Build序号和代码之间并没有直接的关联。
如果不是需要对外公开发布的镜像,并不建议对同一镜像打上多个不同标签。因为绝大部分的情况下,我们只会选用其中一个标签在所有的地方使用,多个标签的实际意义并不会很大。
如果制品库支持immutable特性,强烈建议开启这个功能,防止因为意外情况导致对已上传的镜像的覆盖。
优点:
缺点:
实施示例:
应该在不同环境中使用相同的构建产物来部署,避免对不同的环境生成不同的构建产物,以确保环境的一致性,同时也保证部署在不同环境中的业务代码是测试和验证通过的。比如某次的构建产物,在测试环境部署后经由测试人员和相关的自动化测试工具完成相关的测试验证,如果没有问题才会继续部署到后续环境中,应继续使用该产物部署后面的环境,不建议重新构建新的产物来做后续环境的部署,也不建议覆盖之前的构建产物标识。因为在现有流行的语言和框架中,普遍存在大量的第三方依赖,即便是同一份源代码,由于其依赖以及构建环境的不同,会有一定几率出现由于外部依赖的更新导致构建产物存在差异,从而产生非预期的情况出现。
优点:
缺点:
下面例子展示了在使用nginx的容器镜像里,通过在CMD指令里面先执行一段脚本来对配置进行修改,来达到在容器运行时根据传入的环境变量WEB_ENV的值来访问对应环境的后端服务的目的。
实施要点:
一般情况下,脚本都会或多或少的使用到一些外部工具。而我们的脚本很有可能会运行在不同的环境中,不同环境中提供的工具也会有版本和用法的差异。如果需要在环境中维护某一工具的多个版本的,工具本身的版本管理,以及多个工具之间的依赖冲突和升级更新也会产生较高的管理和维护成本。
我们建议尽可能的减少所使用的工具对环境的依赖,尤其是系统不会默认安装的工具。另外在编写脚本的时候,也尽量避免使用只有某些版本特有的语法特性。这些情况都会导致脚本有可能出现一些不可预期的结果。我们建议使用容器化工具或者容器化环境管理工具如Batect来替代对应的需求。
优点:
解耦对CI/CD工具的依赖,虽然在实际项目中很少会有更换CI/CD工具的情况,但是如果需要迁移,我们也只需在新的工具环境中构建出容器运行环境即可,大大减少了切换工具工作量,提高迁移的速度。
缺点:
实施示例:
在使用 terraform 时,不同版本之间的 terraform 并不兼容,那么如何保证所有人与 CI 都使用相同的 terraform 版本就是一个非常麻烦的事情。那么如果我们无论在 CI 还是本地都基于 docker 去运行 terraform 就可以解决这个问题。
auto/ACTION是我们在项目实践中总结并希望可以广泛推广的一个经验总结,在和客户合作过程中,尤其是有很多团队的大型项目上,我们从这个模式中受益匪浅。auto/ACTION模式的核心是使用统一语义能表明脚本目的的ACTION来命名管理脚本,如应用的测试(test),验证(validate),打包(build),发布(deploy)等相关任务,统一把这些管理脚本归放在auto目录下来维护。
因为类unix系统在运行的时候并不真正使用文件后缀来识别文件的类型,我们建议脚本名字不要加后缀。这个建议是基于管理脚本有可能会在多个地方被使用,而不同的开发和维护人员对于语言的偏好不同,如果在需要使用另外一种语言重写脚本的时候,使用这个脚本的地方就不需要做更新,消除了因为文件名变化可能导致的自动化任务的错误和中断。虽然没有后缀可能会带来一些不便,比如编辑器的语言类型识别错误等,但是相对于它带来的优点,还是非常值得的。
优点:
缺点:
实施要点:
我们的应用中一般都会有一些脚本来做一些辅助性的工作。这些脚本通常会和业务代码放在同一个代码仓库,使用版本控制来进行管理。这些脚本大致分为两种:管理脚本和业务脚本。
管理脚本是用来做应用打包,部署等管理相关工作工作,这种类型的脚本是无需打包进业务运行所需的产出物中的;业务脚本是辅助业务运行,比如说初始化环境和配置,结束时的清理工作等,这些脚本需要打包到业务运行的产出物中。
我们建议除了一些有特殊要求的脚本外,不要把脚本放在根目录。并且把这两种不同类型的脚本存放在不同的目录中。
优点:
缺点:
实施要点:
基础镜像是业务镜像的地基,其包含了我们业务和应用所必需的基础库、二进制文件和配置文件等。一个良好维护的基础镜像通常会根据需要做更新,这些更新通常包含安全补丁,新功能或对操作系统或框架的改进等,我们建议及时的更新容器的基础镜像来保障业务的安全性。除非有特定原因需要继续使用旧版本镜像,否则应及时跟进使用经过充分评估和测试的最新版本镜像。
在Dockerfile和compose等文件中,可以通过指定镜像中的标识和sha256值组合来指定基础镜像的版本。当镜像有了更新之后,及时沿用了如latest或大版本号这类通用性比较高的标签时,其sha256的值也会发生变化,通过更新这个组合可以更新使用最新版本的基础镜像。
优点:
缺点:
实施示例:
随着 Bug 修复、新功能的开发或者其他更新,我们应用的依赖包可能会过时。此时应用的依赖项越多,就越难跟上这些更新。过时的依赖包可能对安全构成威胁,并对性能产生负面影响。最新的软件包可防止漏洞,这意味着定期的依赖性检查和更新很重要。我们建议定期的对应用的依赖包做更新和安全检查,并升级到一个合适的版本。并且我们建议在应用的 pipeline 中加入这些检查任务,并在常规的开发过程中及时发现和升级。如果应用已经处于维护阶段,我们也建议定期执行这些检查并在需要的时候加以升级。
优点:
缺点:
实施示例:
(1) 手动检查
JS 篇:
首先需要全局安装 npm-check-updates: npm install -g npm-check-updates ;
ncu: 检查需要升级的包信息,这里类似 npm outdated;
ncu --upgrade/ncu -u: 将所有的包升级到最新版本,即便是包含重大更改,也会进行更新。注意:更新完成后不会自动运行 npm install,所以还需要再手动执行来更新 package-lock.json。
ncu --interactive/ncu -i : interactive mode 安装某个包。
小结:npm-outdated 和 npm-check-updates都可以用来做 JS项目的包检查、升级。
Java 篇:
查看报告:项目根目录>build>reports>dependency-check-report.html
(2) CI Pipeline 集成
(3) 工具集成检查
如果项目 code 托管在 Github,我们可以使用 Dependabot 和 Renovate 工具和 Github 集成来做依赖检查。这两个工具都会做定期扫描,创建依赖版本升级的 PR。
配置 Dependabot 进行版本更新:
示例 dependabot.yml:
配置 Renovate:
在应用处于维护阶段,如果业务不再会增加新的功能,抑或因为某些原因无法做定期的应用依赖升级,我们也建议你定期的重新部署这个应用,以应对平台等更底层的变化带来的部署失败的风险。定期部署可以确保你的应用在新的平台环境中也可以正常的部署,如果在周期性的部署过程中发现应用无法在新的环境部署,你也会有一个缓冲期来制订应对策略,而不是在平台完成升级之后的某一天,应用发生了问题才发现已经无法部署。
优点:
缺点:
原文链接:DevOps最佳实践之应用开发和部署 (qq.com)
分享标题:DevOps优秀实践之应用开发和部署
网页地址:http://www.shufengxianlan.com/qtweb/news41/391941.html
网站建设、网络推广公司-创新互联,是专注品牌与效果的网站制作,网络营销seo公司;服务项目有等
声明:本网站发布的内容(图片、视频和文字)以用户投稿、用户转载内容为主,如果涉及侵权请尽快告知,我们将会在第一时间删除。文章观点不代表本网站立场,如需处理请联系客服。电话:028-86922220;邮箱:631063699@qq.com。内容未经允许不得转载,或转载时需注明来源: 创新互联