转载于:
- https://cloud.tencent.com/developer/article/2051802
- https://cloud.tencent.com/developer/article/2051805
- https://cloud.tencent.com/developer/article/2051806
- https://cloud.tencent.com/developer/article/2051793
(一) 我的运维经历
偶然地,在回看这些年写的文章的时候,发现涉及到软件工程方方面面的内容,但是关于 Ops 的内容却非常少。我觉得这是不太合适的,因为在实际工作中,Ops 显而易见地占据了一大块比重。于是我调整了分类目录,增加了这个单独的分类,并且这一次,我想零零散散地讲一讲我关于 Ops 的一些经历,以及关于 Ops 的一些观点。
所谓 Ops,指的就是 Operations,在中文翻译上看,我觉得 “运维” 这个词可能是最恰当的。作为一个软件工程师,Ops 有时会特指 DevOps,关于它的定义,在维基百科上有这样一张图片,我觉得基本正确地描述了 DevOps 涵盖的内容(见右侧图)。
可以看到三方面的内容,可是,由于我们会把 Development 单独拿出来讲,会把 QA 单独拿出来讲,因此当我们讲起 Ops 的时候,我们更多的指的就是上图中下面那个紫色的环。
我们都希望代码可以一蹴而就,测试可以面面俱到,软件和产品都可以顺利而愉快地跑起来,可这些都是一厢情愿。我们需要把新版本软件安全快速地部署上去,在出了问题之后需要采取措施减小影响,分析问题,并修复问题……这些问题都需要 Ops 这样不可替代的环节。由于我个人认识的局限性,在以往,Ops 显然是被我轻视的环节。当然,这可能也和我工作以前接受的教育也有关。读书的时候,我们学开发,我们学测试,可是我们几乎从来都不学运维。可以说,上面这三个环的于我而言的重视程度,显然 Development > QA > Operations,这是不可否认的。我猜测着对于许多人来说也是如此。但是,随着工作经验的增加,我越来越意识到 Ops 本身的重要性。于是这些内容,打算分为几篇来讲,来自一个并不喜欢运维的软件工程师之手。
我在华为的经历
我工作的第一家公司是华为,这是一家对于 Ops 有着深刻理解和丰富经验的通讯软件公司。有意思的是,这也是在我熟悉的公司中,在 Ops 上花专人投入比例最高的一家。有许多项目的发布和运行,都是基于不同物理地区和物理站点的,这也是电信软件非常典型的一种部署模式。具体来说,就是华为的工程师,需要出差到电信运营商那里去,去安装部署。如果是一个全新的项目,这个过程叫做开局;如果是从别的服务提供商那里把项目接过来(比如一个项目,本来用的是中兴的软件,现在用户体验基本不变的基础上,挪到华为的平台上),这个过程叫做割接。其他我在十年前第一次出差,在中国联通 3G 机房,位置大概在北京上地,就是去开局。和开局花费的时长相比,割接通常更为迅速,但是压力更大,因为割接的场景通常意味着目标设备只有非常短的服务停止的时间,甚至要求无服务中断;另外,有时候竞争对手的关系,没有办法得到之前系统完整的信息,有许多决策需要根据经验来判断,甚至猜测——比如原数据库要从前一家竞争对手的老库迁移到华为的新库,数据转移的时候,某一列原数据库表里的字段在原始文档里没有,需要根据其内容和结构来推理其实际含义,再转移到新库恰当的位置上。
这种方式明显更为传统一些,从客户的角度来说,好处在于,我们跑到客户的地盘上,在他们的设备厂房里做文章,很多事情他们都更方便控制。对于一些复杂项目,需要有多家服务提供商协同合作的情况,甲方客户可以和各家乙方面对面工作,沟通和组织都会更加顺畅。至于这种方式的弊端之一——成本,对于国内的三大电信运营商来说,通常都不是一个问题。
上述成本包括人力成本,毕竟这样的方式需要有专职的运维人员看守。出于安全、审查等等角度的考量,设备是隔离的,网络是隔离的,运维人员需要在现场驻守,以处理预期内或预期外的各种问题。我们把那里叫做前方。而研发基地的工程师团队需要和运维团队沟通协作,解决各种各样的问题。这就是后方。通常后方的研发工程师没有办法直接动手,而是需要和运维工程师沟通以便采集数据和分析问题,在特殊情况下,为了增进效率,后方的研发工程师会出差前往前方现场,直接处理问题。
我还记得在那次开局的过程中,要和所有周边系统协同调试工作,这个过程叫做联调。由于整个 3G 系统庞大,我所负责的视频运营平台,以省为单位,要和不同的服务接口,比如计费服务等等,而甲方客户期望分散风险,每个省份的服务又都是独立的,而且实现厂商都是根据招标结果而来,每个省份都是独立的。因此和我们联调的服务来自各家厂商,这样的协同合作变得无比困难。这里面多数都非软件的问题,而是管理和沟通的问题。
出于成本的考量,如今互联网公司的很多机器设备多为普通性能的廉价设备(例如普通 PC),而硬件损坏的容错是作为整个分布式系统设计的常规一部分进行的。可那个时候,电信运营商的机器设备的选择和如今互联网公司是完全不一样的。我还记得我们的软件是部署在整个机架中间的数块单板上的,存储系统是部署在磁盘阵列上的,而数据库是部署在 IBM 小型机内的……于是我们许多的 Ops 操作,都是基于单台机器进行的。加上联调的许多工作,存在大量重复性的劳动。为此我写了很多 shell 脚本,来帮助提高工作效率,当时觉得很自豪。可是后来才慢慢感受到,真正优秀的 Ops 流程,是不需要自己现场去手写这些脚本的,工具应该帮我们干了几乎所有的事情。手动脚本是介于手动命令和工具之间的手段,但总体来说依然是一个容易犯错而且缺乏延续性的做法。一个成熟的运维流程,应该把这些犯错的可能减到最小。
这种 Ops 的模式可以让研发团队的工程师更专注于本职的研发工作,但是也会带来和实际场景脱节的问题。比如说,我在那个联通项目之后,转去做某一个新产品的基线版本了,基线版本多数情况并不直接上线,而是需要由定制团队进行本地化和具体项目化的定制,之后才能发布上线。这就足以见得我们离实际产品部署有多么遥远了。由此产生的其中一个典型问题就是,当时我们做的那个产品中,网站的那部分,由于搜索引擎优化等等问题,居然被 Google 爬虫给爬死了。这样严重的事情等一层一层分析、讨论和传播上来,等我们获知这样变体了多次的消息的时候,已经过去了很长一段时间,有一些具体的有价值的信息也丢失了,这让我们觉得离实际的产品仿佛很遥远。
我在 Amazon 的经历
Amazon 的库房遍布世界各地,而且更为零散和复杂,因而这种需要奔赴现场才能进行 Ops 的模式,多数情况下都是不适用的。Amazon 的不同团队会负责不同的服务,多数服务只用中心数据库或者数量有限的几个区域数据库,少数服务才在当地库房里面设置数据库。在 Amazon 内部,有一套专门负责版本管理和部署的工具,软件版本、依赖项目、包管理、分析、编译、测试、部署,等等等等,全部都由这套工具来完成。无论是 1 台机器,还是 100 台机器,软件工程师要做的,就是在适当的时候,盯着这套工具提供的界面,把软件按照指定的某种方案,部署到实际的机器上面去。任意两台机器具体部署的代码版本,通过工具就可以对比,如果要打补丁,要升级系统,要改权限,这套工具就可以完成。换言之,多数情况下都不需要 ssh 登陆到具体的机器上去。
我有一些互联网大公司的朋友,包括国内国外,从侧面了解过,再加上如今我所在的公司,综合比较起来,Amazon 内部提供给工程师用于 Ops 的工具应该说多数都非常先进,有些能够领先业界好几年,而这套部署工具尤其可以说极少公司能出其右(我以前的一位老板打趣说过它是 Amazon 内部 “四大金刚” 工具之一)。其实,极少有公司愿意在没有必要的情况下把一个内部工具功能做得无比强大,更何况是以 frugality 作为领导力准则之一的 Amazon。其原因之一就是,工程师的成本。招聘运维团队的工程师,其实并不容易,而如果能够用尽量少的研发工程师团队,“顺便” 去把运维的事情做了,这无疑是很节约成本的事情。从这个角度说,资源的限制才能促进创新和发展。
有了一系列 Ops 工具,Amazon 不需要招特别多的专职 Ops 团队,而多数 Ops 工作自然由不同的工程师完成。其中一个最典型的事情就是 oncall。所谓 oncall,就是值班,一般研发团队里的工程师轮值,一旦出现严重的线上问题,警报就会想起,这个过程叫做 page,这种情况一旦出现,不管是上班时间还是下班时间,都要立刻投身问题应急处理的行动中去。事实上,Google 也好、Facebook 也好,Netflix 也好,专职 Ops 团队的人数相对研发整体来说都比较小,但是我依然认为 Amazon 是其中最不容易的一个,因为 Amazon 的许多产品和服务尤其需要繁重的 Ops 工作,在如今的公司做了将近一年的云设施的工作才慢慢了解,和其它一些互联网服务比起来,提供基础设施的 AWS 需要的运维工作量非常巨大。
有人说,让研发工程师去做运维,能做好吗?不是应该让专业的人去做专门的事情吗?这个观点是两说的,如果不具备合适的工具,那一定是个灾难,但如果具备,情况就不同了。运维技能的缺乏可以通过优秀的运维工具来缓解;而另一方面,每多一种 “专业的人”,就意味着整个工作系统中,多了一个角色,就多了多个需要沟通的环节,这些都是内耗。我在这篇文章里面曾经比较过使用专业运维人员和使用研发人员来代理运维工作的情形。这种方式下,出现的问题能够最快速度和最大程度地引起开发人员的注意,有反向强化软件质量的作用。我相信多数软件开发工程师都不喜欢 Ops,这也容易理解,但是不参与 Ops 是很难想象能够做好产品的。
说一个具体事例。我记得在 Amazon 的销量预测团队工作的时候,有一次我 oncall 被 page 醒,因为新发布的软件本身暴露出来了一个问题,于是着手回滚到上一个版本。可是经过 rollback 之后,发现问题并未解决,调查获知原因是客户端缓存了前一个版本的某些有问题的信息,于是连夜赶补丁,刷新客户端内的信息,从而修复问题。事后,我们团队排查了类似的问题,相当于吃一堑长了一智。这样严重的问题不经过 oncall 这样典型的 Ops 经历,是很快速难反向强化回代码过程的。
在我目前的公司中,Ops 方面所采用的方式和 Amzon 是类似的,Ops 在每个研发团队中的占比不同,我见过 10% 的,我也见过 80% 的。在我目前的项目团队,由于种种原因,Ops 的比重大概占到 40% 左右,这比我今年在前一个项目组中的 Ops 高了近一倍,也比我在 Amazon 期间最后一个团队的 Ops 工作量 30% 高,以我的理解来说,这明显偏高。其中的原因比较复杂,我们希望我们能够努力把它降到 25% 左右。当然,这并不是一件容易的事情,我对此也有一些思考,有关的内容等合适的时候再说吧。
(二) 流程和人
第二部分,我想谈一谈流程,依然来源于我的理解。Ops 的实践上面,有两部分内容紧密结合,不但共同显示了 Ops 的生产力,也在相当程度上体现了 Ops 的技术水平。这第一部分就是流程,也是今天要说的内容,另一部分是工具(也包括和使用工具相关的技能),下一次再说。
我认为 Ops 可以分为几个层次,最次的的一层,其特点是重度依赖于的人的直接 “操作”。风险管理、因果行为,都通过流程来统一把控,并且遗憾的是只有流程——除了它基本没有可靠有效的工具,或是其他办法。
其实,流程本是个好东西。有时候某些工程师被散漫和自由主义惯坏了,听到流程就反感。事实上,流程在很多情况下都有着举足轻重的作用。它们很容易控制,也很容易实施,基本是立竿见影,在不想要深入,不想要挖掘原因和研究对策的时候,流程上做一点改进,很快就能看到效果。
举个例子来说,多数的公司和团队,在线上代码发生变更的时候,都需要进行风险管理,这里面几乎一定会有流程。比如或最有名的叫做 “变更管理”(change management)的请求,一般由开发人员撰写这样的请求,然后由项目经理和 Ops 的责任人审批。这样的请求中,需要包括诸如变更内容、必要性、风险、部署步骤、验证方法、回滚方式等等内容。这样的目的,在于尽量把变化的因素变成预期内的、可控的因素,尽早发现可能存在的问题,降低风险。
但是,流程这样的方式,也有着负面效应。最大的效应就是,它单纯地固化行为,而拒绝人主动的思考。就像几年前国内热炒的 “敏捷” 一样,流程不应该是本质,工具也不应该是本质,只有人才是本质。
在我曾经的一个团队中,在项目发布以前的最后阶段,有限的时间里面(一般都是一个晚上),需要把最重要和最核心功能过一遍,这个功能清单叫做 checklist。为什么不把所有的测试案例都覆盖了?因为时间有限。这就是一个很简单也很容易执行的流程。但是,随着时间推演,问题变得很多。比如产品发布了以后,发现有一个比较大的问题,于是研发团队就要回溯问题,发现问题以后,为了杜绝问题的再次发生,就打算采取某些措施。(到目前为止做法上面都没有什么问题,可接下去就有争议了。)于是一条用于检测这个问题的识别项被加到这个 checklist 当中。这里面有很多检查项事实上在问题修复以后是不会再出现的了,也有一些检查项明显是用于覆盖位于边角的 corner case,而不是主要的 case,但是既然出过问题,为了保险起见,还是都加进去了。就这样,随着时间和版本的演进,这个 checklist 变得越来越长,某些验证项的执行难度颇为复杂,在几年以后,已经到了几个小时都无法过完这个 checklist 的地步,于是这个流程就变成了一个越来越难以执行的累赘。
造成这一问题的原因是什么,就是流程太简便了,太有效了,以至于这些聪明人不再思考应该采用什么样的方式来从根本上彻底地解决问题。
现在我不想进一步分析上面的问题,而是来看看这样一个争议。这个 “古老” 的争议和流程关系密切,到底应该保留单独的测试团队,还是应该让开发来做测试?
无论你对这个争议的观点如何,无论二者取舍的利弊如何,无论这两种结论的场景适用性如何,这个争议本质上反映了一件事——我们是应该用更多流程加上多个单一职责的团队来解决问题,还是用更少流程加上单个承担多种职责的团队来解决问题?
在回到上面 checklist 的那个问题上,这个问题恰恰出在开发和测试团队单独运作的体系之中。有人问,这些新增加的问题中,既然有一些检查项是不会再出现的,既然有一些检查项覆盖的是边角非主要用例,为什么大家还要同意加上去?这就和上面说的测试和开发团队分离的情况有密切关系。因为测试团队多数都做不到白盒,不知道实现,只想用输入输出覆盖黑盒用例的方法来保证正确性,因而所谓的问题 “不再出现” 就无从谈起;而团队和人一样,一朝被蛇咬,十年怕井绳,出过问题,不知道实现,也自然没有人愿意承担这个再出问题的风险,哪怕它曾经说一个边角的 case。
事实上,这个争议的观点可以扩展到更多角色。不要以为只有测试团队遇到这样的困惑。Ops 也是如此——到底应该保留单独的运维团队,还是应该让开发来做运维?
于是,我听过 Ops 团队的朋友说过这样的话,听起来很有意思:
如果线上问题少,boss 说,要你们何用?
如果线上问题多,boss 说,要你们何用?
当然,这些争议,最终都需要达成某种平衡,没有一种方法是放在所有场景下的万全之策。比方说,一些 AWS 服务中,Ops 的比重居然占到了 85% 以上。且不说其最终的合理性,市场和人才等方面的策略永远制约着团队去寻找最优雅的解决方案,而是选择最 “合理” 的解决方案,即便如此,和其庞大的基础设施业务相比,其 Ops 团队依然是小而优秀的。这些单独的 Ops 可能在整个服务的漫长生命周期中始终无可替代,没有他们,开发团队也无法专注于核心功能,而要被大量的 Ops 事务困扰。这也是为什么许多互联网大公司在推行小团队和综合型团队,强调工程师职责需要覆盖 Development、QA 和 Ops 三部分的同时,依然保留少量的独立 QA 团队和独立 Ops 团队。
再从公司和团队发展壮大的角度观察流程在 Ops 中的变化。
在一家公司还小的时候,团队更为原始,但是 Ops 却更容易聚焦在核心问题上面。用户有困难?解决困难。产品有问题?解决问题。没有繁荣缛节,也没有太多可以复制的模式和需要遵循的流程。慢慢地流程多了起来,人做的事情也更加专业,这里可能会达到一个最佳的平衡点。因为再往后,因为那些流程的过度复杂性和开销,使得效率和质量的平衡被打破,一切向着低效和臃肿慢慢滑落。
从这个角度说,互联网公司在这方面要比传统企业更懂得简化和合理化流程的重要性。即便在公司壮大以后,依然有一些自下而上的反馈行为帮助这件事情发生。
总的来说,Ops 和 Dev 一样,兼具影响力、效率,以及风险。和 Dev 比起来,Ops 往往更为枯燥,不可控性更多,有时候不得不响应一些紧急的事情。对于从这三者的角度看来,流程更多地,是用来在效率损失可以接受的情况下,控制风险,从而导向正向的影响力。对于一些服务更 critical 的团队来说,风险控制相对地,更为重要,因而流程的比重可以适当增加;反之,流程需要简化,保证效率在一个高标准之上。
(三) 事务、团队和时间分配
作为普通的开发人员,我们会遇到对于时间分配的思考,没有金标准,只有某些看起来也未必靠谱的 “最佳实践”。不同人眼中对于整体的时间分配也有自己的看法,这篇文章旨在探讨其中的一两种情况。
Ops 的事务类型
Ops 的事务很多很杂,首先要明确一点的就是,Ops 远不止 oncall,远不止线上产品维护。整个软件工程流程中的配置、部署、环境搭建、升级、打补丁,甚至问题定位、故障排查等等,都或多或少可以算作 Ops。
记得在读书的时候,老师给我们把日常事务划为四个象限:紧急重要、紧急不重要、不重要但紧急,以及不重要不紧急。Ops 和一般软件开发活动在这四个象限的分布上来比较,更多地,会偏重于 “紧急”(左侧一半),并且不重要不紧急的比例尤其低。换言之,就传统的 Ops 观念来看,不重要不紧急的事情根本就不会有人主动去搭理——“If it works don’t touch anything.”,甚至,在 Ops 中重要不紧急的事情都会一拖再拖。可见,平心而论,Ops 在传统软件开发人员的心目中,并不具备特别高的地位。
另一个典型例子也可以反映 Ops 地位,作为绩效评估,特别是 promotion(晋升)相关的绩效评估,我们通常都能看到一条,或者 “国际惯例” 一条 “产品/特性的设计开发”,也就是说,如果没有设计开发一个复杂度足够高的产品/特性,这样的 promotion 提议不具备说服力。但是,相比较 Ops 方面在这里的分量却并不常见。换言之,如果一个工程师设计了一个复杂、高效的系统,这可能会成为 promotion 上举足轻重的砝码,但是如果一个工程师在 Ops 工作上兢兢业业,积极响应,极少出错,却不能够成为决定性的 promotion 数据支持。
Ops 个人与 Ops 团队
几乎每一家公司都有 Ops 分工的讨论。我的观点是,一个健康的研发体系,绝大多数 Ops 的工作,就应该交给普通的软件工程师来完成。
有人会有不同的看法,说我们还有单独的 Ops 团队协助呢。
没错,是这样。可是仔细想想,即便有 Ops 团队,假使有充分的工具与设施,他们到底还能够帮到多少忙,我们到底还需要多少单独的 Ops 团队?
Ops 团队,专门做运维的团队,有的公司叫做维优团队(一线团队)。他们往往精通于运维技能,但对开发测试技能要求没有那么高。他们往往被要求熟谙流程,快速响应,长期待命。
前面已经说过,Ops 远不只是维护线上产品,这里面的大多数行为只有开发团队才能做,且无法被替代。即便是 oncall,Ops 团队也无法完成很多问题的追踪修改,我见过许多 Ops 团队只能充当人肉靶子,处理一些回应策略已经清晰但重复率高的问题,以及过滤掉一些基础和客户响应类问题。
而这一类真正可以委托给 Ops 团队做的事情,恰恰有一个共同的特点——都是简单劳动,而且可以自动化。换言之,缺少的是充分和有效的工具。
于是就有人说了,开发人员没有那么多时间去创建这些工具,所以我们需要 Ops 团队。一定程度上说,这话没错,但从成本等角度综合考量,许多 Ops 团队的引进只是类似我在前一篇关于 Ops 的文章中介绍的流程,是一种较为简单粗暴的解决问题的方式。长远来说,多数情况下,投入到工具开发的成本,和让开发人员来做 Ops 的成本,最终会小于聘用 Ops 团队的成本。
我记得有这样一个 “段子”。说一个工程师团队本来独立运作,但是突然有一天来了一个糟糕的程序员,开始写糟糕的代码,质量一塌糊涂。老板看不下去了,给配了一个测试团队来保证质量。可这哥们不只是代码质量不行,时间管理也不行啊,于是老板给多配了一级 manager 来管理。又发现他态度不行,于是又搞了流程管理的一套来约束。代码上线了,不得不配了运维团队来第一时间响应客户抱怨,报告给别的工程师来修复他代码里的问题。于是一个接着一个,队伍就这样壮大起来了。
这似乎有夸张的成分,但是这个朴素的道理却很清楚。Ops 需要反哺,一般情况下,就像只有开发人员才能做好测试一样,只有开发人员才能做好运维。除了开发人员自己做 Ops,没有任何一种组织结构能够提供这样没有回馈损耗的反哺机制,没有任何一种方式能让开发人员 “吃自己的狗食”。另外,我想起了了对于某些团队而言,和客户直接沟通也是这种反哺机制最好的实践之一。Steam 上有不少独立游戏,都是小团队完成的,他们的开发人员可以直接在论坛中和客户讨论。讨论的内容不只是需求,更有问题。
Ops 的时间比例
无论是否 “正确” 或 “合理”,基于现有的这般事实,我们在评估和衡量 Ops 时间比重的时候,要积极考虑。对于绝大多数团队来说,Ops 不应当成为团队最大的时间投入。除了特殊的专职 Ops 的团队,我认为普通开发团队中 Ops 的比重应当保持在 25% 以下,即便是一些相对来说业务发展已经成熟,因而天然的运维压力较大的团队,这个比例也不要超过 35%。为什么有了工具还有那么高的 Ops 成本?因为不是所有的问题都值得做一个工具去解决,这里一定存在一个主要基于投入产出比例的 trade-off。
如果这个比例过高,有许多负面因素会加速情况的恶化:
忙于救火,忙于解决历史问题。这些问题都是具体而局限的,为了解决这些问题很难保证方案的合理性。实际操作的时候往往都是着眼于问题本身的,只要解决了问题,合不合理很难得到足够的约束。
开发人员容易疲劳,这远不只身体的疲劳。据我所知大多数工程师还是更喜欢有节奏的、合理的特性开发,而非不可预见的各种 bug fix。前者更为系统,后者虽然带来职业不同角度的成长,但也容易掉入局限和缺乏规划的境地。
重复劳动,特别是限定时间压力下的重复劳动。可能有朋友想起了 oncall,其实无论是不是 oncall,天然地,从 Ops 的角度来看,重复劳动都是难以避免的。我们能通过工具、脚本、自动化等种种途径简化这样的劳动,但是既然说 Ops 比例过高,这里几乎就意味着这样的途径是明显不足的。
缺少愿景和规划来吸引人才。这也是显而易见的,一个忙于做 Ops 的团队,其吸引力往往敌不过那些做着有趣和有影响力产品开发的团队。这不止表现在能否吸引外部的人才,也表现在能否留住内部的人才。我见过一些 AWS 的团队,从外面看高大上,但是内部的员工流失率却出奇的高。
……
同时需要看到的是,Ops 的比例过高固然不好,要把它降到一个很低的值却也往往很困难,这其中需要付出的代价往往得不偿失。我确实也经历过或者见到过一些 Ops 比例极低的团队,他们有一条或多条这样的特点:
这是一个做项目,而非做产品的团队。我在这篇文章中曾经探讨过。这是第一条,也是最常见的原因。就是天生有一些团队,把项目做起来,验收交付出去,故事就结束了。他们就可以转投另一个项目去了。从长远来看这样的团队并不适合工程师平衡发展。
业务紧要程度低。有时候这就纯粹是一个花瓶团队了,也许产品已经接近废弃了,也许没有那么多挑剔的用户了。
当然还有一个常见原因——这是一个新团队,在做一个新产品。乐观地说,这不是业务紧要程度低,也不是 Ops 工作量不大,而是时候未到。
有人说,还有一个可能,某些团队有专门的 Ops 团队配合,因而 Ops 工作比较少。我觉得不是这样的,对于一个健康的体系来说,即便有 Ops 团队,大多数和 Ops 相关的事情,还是需要原始的工程师团队来完成。这一点,前面已经讲到过。
那么如果发现这个比例已经过高了,需要做什么来缓解这样的问题呢?我听过太多不同的说法了,毕竟这是一个太过普遍的问题。但我认为最重要的一点,是把责任明确到和交回给开发团队。不要期望非开发团队去解决代码层次的问题,工程师们请把 “自己的狗食” 夺回来。
对于现有 Ops 压力过载的问题要花大量时间去分析和规划,而不是定义数量上的目标来关闭问题。问题的分析时间往往要大过问题的解决时间。不能期望这些 Ops 的问题可以在一夜之间消失掉,这些是所谓的技术债务,长远看解决他们往往是比各种规避方式要更节约成本,但短期内的回报却未必,这里的平衡需要一个稳定和自洽的团队来把控。一个走马灯一样换的管理团队不可能具备远见。
最后也是最重要的一点,是要尊重软件规律,堆人和追进度的结果,就是留下一堆债务,就是被迫陷入从 dev 赶工到 ops 噩梦的最常见原因。如果这样做了,就要承担数倍于原有时间等工程开销的后果——
和其它软件工程的问题一样,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 包,可以是任何文件夹和文件。一个好的依赖管理的工具,有这么几点核心特性需要具备:
- 支持基于包和包组的依赖配置。目标软件可以依赖于配置的 Jar 包,而若干个 Jar 包也可以配置成一个组来简化依赖配置。
- 支持基于版本的递归依赖。比如 A 依赖于 B,B 依赖于 C,那么只需要在 A 的依赖文件中配置 B,C 就会被自动引入。
- 支持版本冲突的选择。比如 A 依赖于 B 和 C,B 依赖于 D 1.0,C 依赖于 D 2.0,那么通过配置可以选择在最终引入依赖的时候引入 D 1.0 还是 2.0。
- 支持不同环境的不同依赖配置,比如编译期的依赖,测试期的依赖和运行期的依赖都可能不一样。
当然,还有许许多多别的特性,比如支持冲突包的删除等等,只是没有那么核心。
自动化测试
代码检查、编译和单元测试(Unit Test)。这一步还属于代码层面的行为活动,代码库特定分支上的变动,触发这一行为,只有在这样的执行成功以后,后面的步骤才能得到机会运行。单元测试通常都是是 dev 写的,即便是在有独立的测试团队的环境中。因为单元测试重要的一个因素就是要保证它能够做到白盒覆盖。单元测试要求易于执行,由于需要反复执行和根据结果修改代码,快速的反馈是非常重要的,几十秒内必须得到结果。我见过有一些团队的单元测试跑一遍要十分钟以上,那么这种情况就要保证能够跑增量的测试,换言之,改动了什么内容,能够重跑改动的那一部分,而不是所有的测试集合。
集成测试(Integration Test)。这一步最主要的事情,就是自动部署代码到一个拟真的环境,之行端到端的测试。比如说,发布的产品是远程的 API,UT 关注的是功能单元,测试的对象是具体的类和方法;而在 IT 中,更关心暴露的远程接口,既包括功能,也包括性能。集成测试的成熟程度,往往是一个项目质量的一个非常好的体现。在某些团队中,集成测试通过几个不同的环境来完成,比如α环境、β环境、γ环境等等,依次递进,越来越接近生产环境。比如α环境是部署在开发机上的,而γ环境则是线上环境的拷贝,连数据库的数据都是从线上定期同步而来的。
冒烟测试(Smoke Testing)。冒烟测试最关心的不是功能的覆盖,而是对重要功能,或者核心功能的保障。到了这一步,通常在线上部署完成后,为了进一步确保它是一次成功的部署,需要有快速而易于执行的测试来覆盖核心测试用例。这就像每年的常规体检,不可能事无巨细地做各种各样侵入性强的检查,而是通过快速的几项,比如血常规、心跳、血压等等来执行核心的几项检查。在某些公司,冒烟测试还被称作 “Sanity Test”,从字面意思也可以得知,测试的目的仅仅是保证系统 “没有发疯”。除了功能上的快速冒烟覆盖,在某些系统中,性能是一个尤其重要的关注点,那么还会划分出 Soak Testing 这样的针对性能的测试来,当然,它对系统的影响可能较大,有时候不会部署在生产环境,而是在前面提到的镜像环境中。
部署工具
曾经使用过各种用于部署的工具,有开源的,也有内部开发的。这方面以前写过 Ant 脚本,在华为有内部工具;在 Amazon 也有一个内部工具,它几乎是我见过的这些个中,最强大,而且自动化程度最高的。部署工具我认为必须具备的功能包括:
- 自动下载并同步指定版本的文件系统到环境中。这是最最基本的功能,没有这个谈不上部署工具。
- 开发、测试、线上环境同质化。这指的是通过一定程度的抽象,无论软件部署到哪里,对程序员来说都是一样的,可以部署在开发机(本地)用于开发调试,可以部署到测试环境,也可以部署到线上环境。
- 快速的本地覆盖和还原。这个功能非常有用。对于一个软件环境来说,可能 1000 个文件都不需要修改,但是又 3 个文件是当前我正在开发的文件,这些文件的修改需要及时同步到环境中去,以便得到快速验证。这个同步可能是本地的,也可能是远程的。比如我曾经把开发环境部署在云上,因为云机器的性能好,但是由于是远程,使用 GUI 起来并不友好,于是我采用的办法是在本地写代码,但是代码通过工具自动同步到云机器上。我也尝试过一些其他的同步场景,比如把代码自动同步到本地虚拟机上。
- 环境差异 diff。这个功能也非常有用。开发人员很喜欢说的一句话是,“在本地没问题啊?”,因此如果这个工具可以快速比较两个环境中文件的不同,可以帮助找到环境差异,从而定位问题。
当然,还有其它很有用的功能,我这里只谈了一些印象深刻的。
监控工具
我工作过的三家公司,华为、Amazon,还是 Oracle,它们的监控工具各有特点,但做得都非常出色。且看如下的功能:
- 多维度、分级别、可视化的数据统计和监控。核心性能的统计信息既包括应用的统计信息,包括存储,比如数据库的统计信息,还包括容器(比如 docker)或者是 host 机器本身的统计信息。监控信息的分级在数据量巨大的时候显得至关重要,信息量大而缺乏组织就是没有信息。通常,有一个主 dashboard,可以快速获知核心组件的健康信息,这个要求在一屏以内,以便可以一眼就得到。其它信息可以在不同的子 dashboard 中展开。
- 基于监控信息的自动化操作。最常见的例子就是告警。CPU 过高了要告警、IO 过高了要告警、失败次数超过阈值要告警。使用监控工具根据这些信息可以很容易地配置合理的告警规则,要做一个完备的告警系统,规则可以非常复杂。告警和上面说的监控一样,也要分级。小问题自动创建低优先级的问题单,大问题创建高优先级的问题单,紧急问题电话、短信 page oncall。
- 告警模块的系统化定义和重用。在上面说到这些复杂的需求的时候,如果一切都从头开始做无疑是非常耗时费力的。因而和软件代码需要组织和重构一样,告警的配置和规则也是。
对于其它的工具,比如日志工具,安全工具,审计工具,我这里不多叙述了。这并非是说它们不必须。
糟糕的实践
上面是我的理解,但是结合这些工具,我相信每个有追求的程序员,都对 Ops 的最佳实践有着自己的理解。于是,有一些实践在我看来,是非常糟糕的,它们包括:
- SSH+命令/脚本。这大概是最糟糕的了,尤其是线上的运维,在实际操作中,一定是最好更相信工具,而不是人。如果没有工具,只能手工操作,只能使用命令+脚本来解决问题,于是各种吓人的误操作就成了催命符。你可以看看 《手滑的故事》,我相信很多人都经历过。最好的避免这样事情发生的方式是什么?限制权限?层层审批?都不是,最好的方式是自动化。人工命令和脚本的依赖程度和 Ops 的成熟度成逆相关。
- 流程至上。这里我不是否认流程的作用,我的观点在这篇文章中已经说过了。其中一个最典型的操作就是堆人,发现问题了,就靠加人,增加一环审批来企图避免问题。
- 英雄主义。这是很多公司的通病,一个写优质代码的工程师不会起眼,只有埋 bug 造灾难,再挺身而出力挽狂澜,从而拯救线上产品的 “英雄” 才受人景仰。正所谓,没有困难制造困难也要上。
- 背锅侠。这和上面的英雄主义正好相反,却又相辅相成。找运维不规范操作背锅(可事实呢,考虑到复杂性、枯燥性等原因,几乎没法 “规范” 操作,人都是有偷懒和走捷径的本性的),找开发埋地雷,测试漏覆盖背锅。当场批评,事后追责。
- 用户投诉驱动开发,线上事故驱动开发。这一系列通过糟糕的结果来反向推动的运维反馈开发的方式(其它各种奇葩的驱动开发方式,看这里)。
- 把研发的时间精力投入 ops。这是恶性循环最本质的一条,没时间做好需求分析,没时间做好设计,没时间做好测试,没时间写好代码,什么都没时间,因为全都去 Ops 解线上问题去了。结果呢,糟糕的上游造就了更糟糕的下游,问题频出,于是更多的人花更多的人去 ops。如此恶性循环……