除了主要内容——工具和实践,这篇文章也对“谈谈 Ops”系列做一个汇总,提供一个访问入口。之前几篇,从一个纯粹 dev 狭窄的视角,谈了谈自己对 Ops 的一些认识:
在往下继续以前,如果没有看过前面的文字,不妨移步阅读,因为上面的内容对下面的内容做了一定程度的铺垫。
现在在写的这一篇文字,我准备是最后一篇,主要谈论这样几个事情:一个是工具,另一个是实践。我依然还是从 dev 的视角,而不是从一个专业运维的视角来记叙。
工欲善其事,必先利其器。我在主要且通用的工具中挑了几个,和最佳实践放在一起介绍,并且按照功能和阶段来划分,而不执着于列出具体的工具名称。 其中最主要的阶段,包括开发阶段,集成测试阶段,以及线上部署维护阶段。 顺便也再强调一次,Ops 远不只有线上系统的维护。
Pipeline
把 Pipeline 放在最先讲,是因为它是集成下面各路神仙(工具)的核心,通过 pipeline,可以把一系列自动化的工具结合起来,而这一系列工具,往往从编译过程就开始,到部署后的验证执行结束。
Pipeline 最大的作用是对劳动力的解放,程序来控制代码从版本库到线上的行进流程,而非人。因此,对于那些一个 pipeline 上面设置了数个需要人工审批的暂停点,使得这样的事情失去了意义。这样的罪过往往来自于那些缺少技术背景的负责人,其下反映的是对流程的崇拜(前面关于流程和人的文章已经提到),而更进一步的原因是不懂技术,就无法相信代码,进而无法信任程序员,他们觉得,只有把生杀大权掌握在自己手里,才能得到对质量最好的控制。这样的问题从二十多年前的软件流程中就出现了,到现在愈演愈烈。我只能说,这无疑是一种悲哀。
我见过懂技术的老板,也见过不怎么懂技术的老板,还见过完全不懂技术的老板。但是最可怕的,是那种不太懂或完全不懂,却又非常想要插一手,对程序员在软件流程方面指手画脚的老板。为什么是流程?因为技术方面他们不懂,没法插手,却又觉得失去了掌控,只好搞流程了。
我曾经遇到过这样一件事情:程序有一个 bug,因为在一个判断中,状态集合中少放了一个枚举值,导致了一个严重的线上问题。后来,程序员修正了问题,老板和程序员有了这样的对话:
老板:“你怎么保证未来的发布不会有这样的问题?”
程序员:“我修正了啊。”
老板:“我怎么知道你修正了?”
程序员:“我发布了代码改动,我使用单元测试覆盖了改动。”
老板:“好,我相信你开发机的代码改动做了。可我怎么知道你发布到线上的版本没有问题?”
程序员:“……发布的代码就是我提交的啊。”
老板:“你怎么能保证代码从你提交到线上发布的过程中没有改动?”
程序员:“……(心中一千头草泥马奔腾而过)我可以到线上发布的 Python 包里面查看一下该行是不是已经得到修改。”
老板:“好。我们能不能在 pipeline 里面,添加这样一个步骤——执行部署的人到发布包里面去检查该行代码是不是正确的。”
程序员:“……(现在变成一万头了)不要把,这样一个额外的检查会浪费时间啊。”
老板:“检查一下需要多少时间?”
程序员:“5 分钟吧”
老板:“花费 5 分钟,避免一个严重的问题,难道不值得吗?”
程序员:“……(现在数不清多少头了)如果这一行要校对的话,为什么其它几十万行代码不用肉眼校对?”
老板:“就这一行需要。因为这一行代码曾经引发过严重问题,所以需要。”
程序员:“……”
如果你也见过这样的情形,不妨告诉我你的应对办法是什么。
依赖管理
以 Java 为例,有个搞笑的说法是“没有痛不欲生地处理过 Jar 包冲突的 Java 程序员不是真正的 Java 程序员”,一定程度上说明了依赖管理有多重要。尤其是茁壮发展的 Java 社区,副作用就是版本多如牛毛,质量良莠不齐,包和类的命名冲突简直是家常便饭。我用过几个依赖管理的工具,比如 Python 的 pip,比如 Java 的 Maven,但是最好的还是 Amazon 内部的那一个,很可惜没有开源。注意这里说的依赖,即便对于 Java 来说,也不一定是 Jar 包,可以是任何文件夹和文件。一个好的依赖管理的工具,有这么几点核心特性需要具备:
当然,还有许许多多别的特性,比如支持冲突包的删除等等,只是没有那么核心。
自动化测试
代码检查、编译和单元测试(Unit Test)。这一步还属于代码层面的行为活动,代码库特定分支上的变动,触发这一行为,只有在这样的执行成功以后,后面的步骤才能得到机会运行。单元测试通常都是是 dev 写的,即便是在有独立的测试团队的环境中。因为单元测试重要的一个因素就是要保证它能够做到白盒覆盖。单元测试要求易于执行,由于需要反复执行和根据结果修改代码,快速的反馈是非常重要的,几十秒内必须得到结果。我见过有一些团队的单元测试跑一遍要十分钟以上,那么这种情况就要保证能够跑增量的测试,换言之,改动了什么内容,能够重跑改动的那一部分,而不是所有的测试集合。
集成测试(Integration Test)。这一步最主要的事情,就是自动部署代码到一个拟真的环境,之行端到端的测试。比如说,发布的产品是远程的 API,UT 关注的是功能单元,测试的对象是具体的类和方法;而在 IT 中,更关心暴露的远程接口,既包括功能,也包括性能。集成测试的成熟程度,往往是一个项目质量的一个非常好的体现。在某些团队中,集成测试通过几个不同的环境来完成,比如α环境、β环境、γ环境等等,依次递进,越来越接近生产环境。比如α环境是部署在开发机上的,而γ环境则是线上环境的拷贝,连数据库的数据都是从线上定期同步而来的。
冒烟测试(Smoke Testing)。冒烟测试最关心的不是功能的覆盖,而是对重要功能,或者核心功能的保障。到了这一步,通常在线上部署完成后,为了进一步确保它是一次成功的部署,需要有快速而易于执行的测试来覆盖核心测试用例。这就像每年的常规体检,不可能事无巨细地做各种各样侵入性强的检查,而是通过快速的几项,比如血常规、心跳、血压等等来执行核心的几项检查。在某些公司,冒烟测试还被称作“Sanity Test”,从字面意思也可以得知,测试的目的仅仅是保证系统“没有发疯”。除了功能上的快速冒烟覆盖,在某些系统中,性能是一个尤其重要的关注点,那么还会划分出 Soak Testing 这样的针对性能的测试来,当然,它对系统的影响可能较大,有时候不会部署在生产环境,而是在前面提到的镜像环境中。
部署工具
曾经使用过各种用于部署的工具,有开源的,也有内部开发的。这方面以前写过 Ant 脚本,在华为有内部工具;在 Amazon 也有一个内部工具,它几乎是我见过的这些个中,最强大,而且自动化程度最高的。部署工具我认为必须具备的功能包括:
当然,还有其它很有用的功能,我这里只谈了一些印象深刻的。
监控工具
我工作过的三家公司,华为、Amazon,还是 Oracle,它们的监控工具各有特点,但做得都非常出色。且看如下的功能:
对于其它的工具,比如日志工具,安全工具,审计工具,我这里不多叙述了。这并非是说它们不必须。
糟糕的实践
上面是我的理解,但是结合这些工具,我相信每个有追求的程序员,都对 Ops 的最佳实践有着自己的理解。于是,有一些实践在我看来,是非常糟糕的,它们包括: