摘要
DevOps最有趣的地方莫过于它是一种思想上的"反模式"。一般我们认为,一个行业发展越成熟,它的工种划分会越细致。因此在软件业,开发者Dev和运维人员Ops自然而然地被分成两个独立的部门。而DevOps则反其道而行之,它鼓励开发者和运维坐在同一间办公室里,并对彼此充分了解。这不仅是为了在服务挂掉时方便找人,更是为了Dev和Ops从一开始就频繁沟通,精诚合作。
为什么会出现这种情况?本文的观点是,DevOps出现的驱动力是人们的需求,即用户对软件有快速开发的需求,企业也对软件有快速迭代的需求。为了应对这种需求,人们将先前的各类实践进行总结,各类问题进行分析,提出了DevOps的思想。而在这种思想的指导下,我们开发时使用的工具,开发流程甚至部门组织都出现了改变。
1. 工种细分的好与坏
当我们去观察一个公司的发展历程时,会认为工种细分是天经地义的事:创始人团队往往不超过十个人,甚至可能只有一个人。那时候人人身兼多职,技术人员不仅要会写代码,也要有必要的PS技巧。CEO不仅需要深谙公司的核心业务,对于营销广告词也要有巧妙构思。随着公司不断壮大,研发组,运营组,产品组,人事部等等部门开始出现。以研发组为例,随着人数的增多,自然就划分出了开发,运维,测试,DBA等等工种。不同工种之间掌握着不同的技能链,工作的内容也不尽相同。就是这样的一种体系,使得大家的责任更清晰,分工更明确,公司因此大而不倒。
再把情况抽象一下,工种细分其实体现了朴素的分而治之的思想。我们把人群分成模块,模块之间承担不同的职责,一边提供功能,一边隐藏信息。因此我们可以组织出更大和更复杂的系统。
听起来很美好,可惜现实是残酷的。工种细分同样带来了一系列问题。
1.1 目标冲突
这是职责划分带来副作用。通常一个负责人的产品经理会不断研究用户群,这会使得需求产生变更。而通常一个负责任的程序员会尽快完成手头的工作。所以当两个负责任的人碰面时,反而产生更猛烈的冲突。同样的情况适用于开发者和运维,开发者想迫不及待地将包含自己心血的代码交给运维,并且在心中默默树下一块里程碑。但运维却像接到一块烫手山芋,小心翼翼地把文件推到线上,神色凝重地检查着CPU和内存占用率。想象一下,如果此时服务挂掉了,那么运维一定会快步跑上楼,拉着刚刚还得意洋洋的开发解决问题。这对两个人的积极性都是一种打击。
究其原因,无非是开发人员追求将需求尽快转化为代码,而运维人员则希望服务器能安安稳稳地运行。这两个目标是有冲突之处的,因为软件不真正部署到线上,谁都不敢保证它能平稳运行,而“上线”这个词正是让运维头大的原因。更不用说一个服务反复上线,反复崩溃时对士气的打击了。
1.2 责任推诿
将开发和运维分开,其实是将代码的开发调试阶段和代码的上线阶段分给两拨人去处理。隐含这一种推卸责任风险:既然已经有人在维护服务器的运行了,开发人员再去关注系统的上线状态是不是有些多余呢?举一个例子:假设有一个服务出于数据一致性的考虑,无论用户离哪个机房比较近,请求都要发到一个特定机房统一处理。
开发人员认为:这是服务上线之后的路由规则,当然是交给运维来负责。而且就算我想负责,运维那一套路由配置我也搞不懂啊。
运维人员认为:保障数据一致性是开发的需求决定的,理应由开发来实现。我一旦接手维护了,不仅破坏我现在路由规则的一致性,而且将来需求变更了可能会影响到我。一个业务规则的变更居然影响到了运维,这太荒谬了。
这种职责的不明确性还在软件的其他阶段体现出来。当产品和开发确认需求时,应不应该叫运维来参加?参加的话,运维丝毫不关心讨论过程中各种需求细节,而不参加的话,运维相关的问题可能直到临上线之前才被发现,从而付出巨大的代价。开发组织设计评审时,应不应该叫运维来参加?我们发现上边的问题依旧是存在的。
1.3 沟通不畅
这是信息隐藏的副作用。因为开发和运维分别负责软件生命周期中的不同阶段,这使他们的工作内容大相径庭。从开发的角度来说,首先他需要对需求有准确无误的理解,相比于产品岗,他需要更加细致和精确的把握软件的功能。在此之上,程序逻辑本身将耗费他大量的精力:上下游服务有哪些,依赖关系是什么,错误码怎么定义,如何分层,如何定接口。更不用说出于权衡可用性和一致性的各种复杂折中了。
工具链上,开发者的行动往往就集中在自己的机器上或者一个沙箱环境中。这里的一切都是为了开发而准备的:一个秒级启动的local web server,直观自然的windows/Mac图形界面,调用链路从头到尾只有一条,没有复杂的负载均衡或者故障切换策略,请求不会飘来飘去。最令人开心的是,环境中的DB和Cache往往都是可以随时重启甚至重装的。
让我们来看看运维这边的情况,幸运的是他们往往不需要理解复杂的需求,但不幸的是他们必须面对残酷的计算机世界。他们要精心照料每一个服务,有的服务要跨机房部署,有的服务要同机房多份部署,有的还要和别的服务混部。路由配置文件中写错一个数字,一个通配符或者一个注释号,后果都是不可预料的。没有图形界面的环境中,一个进程的生死往往是悄无声息的,等他们真正发现时,只能在好几个G的日志中寻找蛛丝马迹。更不用说DB和Cache的健康状态的重要性了,任意一个挂了,一台机器都可能被瞬间冲垮。
所以我们就不难理解开发和运维之间的日常对话了:
“不可能啊,这个在我机子上运行的好好的”
“线上连的是生产数据库,不能让你测试的”
“就修一个小BUG,你们要两天后才能全量上线,这效率也太低了”
“你的服务依赖的XX版本太高了,现网没法装的,回去修改一下吧”
彼此对对方的工具不了解,总是会在意料之外甚至十分诡异的地方出问题,开发和运维一方面对自己的环境十分熟悉,一方面又对对方隐瞒了许多隐喻和默认规定,这使得双方都陷入了知识的诅咒,即因为已经拥有知识的人,不能理解其他人为什么听不懂自己说什么。
2。 唯快不破
现在的软件市场上,任何一个产品火了,它的竞争对手都会加班加点赶出一个产品与之竞争,就算是分不走一块蛋糕,也要抢上一块奶油。只需要回想一下今年五颜六色的共享单车大战就知道市场竞争有多激烈。不仅仅是初创公司,即使是一个大型公司的最成熟、最成功的产品也在马不停蹄地发展。支付宝出了一个新特性,一两个月之内微信支付也会出一个相同功能。美团的界面优化一下,不久之后我们也能在饿了么上看到类似的痕迹。
只要留心一下手机上的APP更新提示,几乎每过去一个月,手机上的软件就会重装个遍,半年之内,连手机的OS也会更新。linux内核每天会受到数千行的patch请求,Tomcat的源码在笔者查看时的5分钟前有更新。连功能十分成熟稳定的Github,也在一版一版地更新着开发者接口。
不单单是快能让企业获得更多利润,更多的时候是只有够快才能活下去。留给淘宝双十一团队的开发时间只有1年整,到期撑不住请求量,公司的财产和声誉都要蒙受损失。留个微信红包团队的时间只有各个节假日之间的间隙,0点的钟声响起,四面八方发来的红包可不会管你的服务器吃不吃得消。更不用说群雄割据的创业战场,产品比别人快一个月就能收获大量用户。曾经移动端支付的产品有多达十几个,个个都有强有力的后台支撑,然而几年之后留在大家脑海里的就只剩两个了。外卖、直播、电商、即时通讯,都经历过这样一个各方争霸最后只剩几家进入决赛的情况。
总的来说,在2B和2C领域,用造教堂的方式来做软件的团队也许早就被时代的洪流冲走,用造集市甚至是摆地摊的方式开发的团队才有机会站在赛场上。快一点,团队需要从市场的反馈中快速调整战略;快一点,用户希望不断地看到有趣的新功能;快一点,以避免被后来的竞争对手赶上;快一点,别被前边的领跑者甩开。
这给了整个团队很大的压力。如果一切顺利当然万事大吉,但是一旦整个流程中出了问题,公司面临的损失都是沉重的。而根据我们在上文关于开发与运维之间矛盾的讨论,可以得出以下结论:开发与运维之间存在各种隔阂,而这种隔阂大大增加了服务上线时出故障的概率。而以上问题在初创团队中往往不会遇到,原因是初创团队的软件规模还不大,每个模块即使手工维护也照顾的过来。另外一个初创团队的规模是小于十个人,沟通起来十分顺畅,靠吼也能足够快的解决问题。但当团队规模不断扩大,沟通成本越来越大,组织开始分层,分部门。但是他们必须交付一个完整的,可运作的产品。这使得人员把过多的时间和精力耗费在沟通,协作,控制变更,修改BUG上。
具体到开发和运维,他们往往是两个比较大的团队,这使得一个产品想要交付必须走过漫长的审批流程。预估,提单,一层一层的评审,一层一层的审批。当出了问题时,又要面临着人员互相推诿,找不到负责任的窘境。就更不用说服务上线时可能遇到的各种诡异BUG了。这一切就像是给公司戴上了镣铐,每人都很忙,但产品就是慢腾腾地做不出来。
3. 借鉴敏捷的思想
让我们暂时把目光从开发与运维的困境转向另外一个地方:产品与开发。在很多地方产品与开发面临相同的局面:产品希望尽可能的满足客户的需求,并不断适应市场变化,而开发则最希望看到一个稳定的、精确的需求。而开发也经常抱怨这个产品经理什么技术都不懂,只会空口说白话,产品也会认为这个程序员对需求的理解总是不深入,活在自己的世界中。知识的诅咒依然生效。
某种程度上,敏捷软件开发让上边的局面有所好转。敏捷将关注点从一份一份长篇大论的文档转移到最终可执行的代码上的,相比于传统的瀑布模型,敏捷最主要的特点就是抛弃繁文缛节,并且以需求驱动开发。敏捷之所以盛行,不是因为它相对于其他开发模型更优越,而是因为它更适应目前的开发环境,尤其适用于互联网这种需求瞬息万变,竞争异常激烈的环境。相比于以文档来推动开发瀑布模型,敏捷更加轻量,更加便捷。以沟通举例,敏捷非常注重面对面交流,既然大家一起开个会就能讲清楚的事,何必还要写事无巨细的文档呢。另外以需求为驱动的好处是开发和产品的目标一致了,开发不在以固定不变的需求来规划自己的节奏,而是从一开始就将变更的可能考虑进去,他们针对变更而设计。
“敏捷”这个名字恰如其分的表达了其方法论的内涵,它强调迅速,强调变化,强调自适应。正如人月神话中所揭示的:“向进度落后的项目中增加人手,只会使进度更加落后”,敏捷偏爱短周期,偏爱小团队。这看似增加了开发过程的复杂性,也减少了人力投入,但实际上却加快了产品的开发速度。
同样的,即使我们现在不知道DevOps方法,也可以试着借鉴敏捷的思想来解决开发和运维之间矛盾。
3.1 更换目标
产品与开发之间的根本矛盾是,产品的目标是提出更能吸引用户,解决痛点的需求,而技术实现只是过程之一。而开发的目标是将各种各样的需求转化为可以运行的代码,技术实现是结果,而需求只是过程。看起来只是流水线上的两个不同步骤而已,但却带来很多问题。因为提出需求相比于将它落地实现更加简单,因此需求的变化总是快于程序员的预料,再加上不断变化的外界环境和竞争产品,使得需求变化地更加频繁。而敏捷开发通过改变开发人员的固有观念来解决这个问题。开发人员不应该期待稳定的需求,而是应该假设需求在下一个迭代就会变化。开发人员不应该期待做出一个大而全的,功能丰富的软件,而是应该做出一个功能简陋的软件,再一点一点添加新特性。开发人员不应该期待能获得一个长时间的开发周期,而是应该小步快跑,让软件每周都能看到变化。
同样的,运维人员和开发人员也处在同一条流水线上,只不过开发人员希望将开发完的软件尽快上线,以检验自己成果。而运维人员则期望能维护一个稳定的、不变的软件,而不是三天两头就增加新特性的软件。这次我们需要改变运维人员的固有观念了。即运维人员不应该期待一个稳定不变的软件,而是从一开始就针对频繁变更而运维,运维人员不应该期待开发人员把多次修改精心合并成一个里程碑,然后一次发布,而是应该接受一个又一个的beta甚至alpha版本被推到线上,运维人员不应该期待自己能有较长的时间仔细检查环境配置,而是要想尽办法让运维变得快速,简单,自动化。
3.2 去除繁文缛节
传统的瀑布式开发中,最核心、最繁重的任务莫过于文档的编写和管理。前景范围文档,用例文档,需求规格说明,概要设计,详细设计,部署文档等等。即使是为了描述一个学校中的玩具项目,也需要写十万字左右的文档,以及几十张图。足够四个人全力编写一学期,五六位助教评审数个小时。这导致队伍中甚至出现全职的文档编写人员。最糟糕的是这些文档是有耦合关系的。一份文档改变,为了维护一致性,往往其下游的文档又要变化,以致于每次变更都要牵一发而动全身。这使得需求变更被各种繁文缛节束缚住,寸步难行。
而敏捷恰恰相反,它强调:个体与互动高于流程和工具, 工作的软件重于详尽的文档。它从来不指望软件能像流水线上的汽车一样,每个人闷头在流水线上干自己的事,最后软件就像变魔术一样出现了。它强调大家从一开始就频繁沟通,虽然分工合作,但有充分合作,不对彼此隐瞒信息。
之所以瀑布式开发如此强调文档,如此恪守规范,我想是出于一种“流程神话”,即它相信足够优秀详尽的流程,一定能导致令人满意的成果。这个观点在制造业或者硬件行业体现得很好,一套完善的流水线或者一套精密的机械,能源源不断地制造出高质量的产品。然而这招在软件业却不怎么奏效:没有任何一种流程能避免需求变更,没有任何一种流程能替代人与人之间直接的交流。流程再怎么严格,文档再怎么详尽,也没有办法避免需求变动和人本身的错误造成的返工。
既然如此,不如直接去除这些繁文缛节。能用纸条说清楚的需求,就不要填十几个字段的表格,能在白板上讲明白的设计,就去画不要去画各种各样的设计图了。
同样的,为什么想要发布一个服务需要一层一层的评审和审批,为什么想要部署一下变更需要提前多少天就提单,然后进行漫长的等待,为什么运维不能整装待发,早早就像部署前的一切工作准备好,等待新的模块开发完成就立刻上线呢?过于严苛的流程看似保障了结果的正确性,但它实际上限制了整个团队的生产力。
4. 自动化偏执
与产品岗不一样的是,开发和运维都是专业的技术人员,所从事的工作都是技术性的。而技术性通常意味着有自动化的空间。开发和运维之间有多少人力操作呢? 提单,审批,编译,部署,检查环境,检查配置,分配机器,部署文件,上线观察。在人手工操作的情况下,每一项都非常消耗精力,更不用说把这些操作全部来一遍了。
正如Perl语言的发明人Larry Wall所说,程序员有三大美德: 懒惰、急躁和傲慢(Laziness, Impatience and hubris)。懒惰使得程序员讨厌重复劳动,不断追求一劳永逸。急躁使得程序员讨厌细碎繁琐而又机械化的工作,希望能快速,省力地得到反馈。傲慢使得程序员对于工作追求极致,追求完美。
显然,运维所面临的困境使得程序员的美德荡然无存,无穷无尽的人力操作限制了开发团队的生产力。因此,我们需要新的技术和新的工具让运维敏捷化。
对于代码,我们需要完善的版本控制工具,来让我们在时间线中进退自如。对于构建,我们需要持续集成服务器来保证代码时刻可以运行。对于测试,我们需要完善的沙箱环境,是之既可以随便破坏,也尽量与实际环境相符。对于上线发布,我们要将复杂的机器操作和配置管理自动化,一键上线,一键回退。对于监控,我们应该把它变成基础设施,只要登录一个网页,就能看到各个机器的各项信息。
如果只是这些,那还没有到“偏执”的程度,真正的偏执是希望一切能自动化。难道每次缩扩容都要去机房搬机器吗?不,硬件应该抽象成可配置的资源,底层的资源分配和隔离全部自动化。难道每次拿到新资源都要把环境配一遍吗?不,基础设施不过是一段代码,运行即可。甚至在有了docker之后,环境就像积木一样,可以整块搬运,相互堆叠。
既不是devops创造了各种工具,也不是各种工具创造出了devops。毕竟在devops提出之前,很多工具已经出现了。我认为应该是devops的方法论和价值观,使得这些工具被组合在一起,并促进了新工具的出现。
5. 总结
就像是高级语言必然出现,面向对象必然出现或者分布式系统必然出现一样,devops方法是必然出现的。它出现的逻辑是:一方面,随着业务复杂化和人员的增加,开发人员和运维人员逐渐演化成两个独立的部门,他们工作地点分离,工具链不同,业务目标也有差异,这使得他们之间出现一条鸿沟。而发布软件就是将一个软件想从鸿沟的这边送去那边,这之中困难重重。另一方面,行业竞争更加激烈,无论是客户还是公司自身,都要求软件能快速发布,频繁修改,而上边所说的这种隔阂,阻碍了开发团队的生产力,成了企业亟待解决的难题。
因此,devops提出了解决问题的办法:它提倡软件持续交付,频繁部署。它力图填平开发和运维之间的鸿沟,他们应该抛弃各种繁文缛节,频繁沟通,密切协作。但只有观念和组织结构上的改变是不够的,一切必须建立在自动化的基础上。devops热忱的追求自动化和工具链,硬件资源应该触手可及,环境与配置应该可以随意复制,修改和回退,开发和运维的流程应该便捷,省力,甚至一键完成。
有人认为devops之所以能提高效率,是因为它提出了一种新的流程,或者引入了新的工具,但我认为流程,工具都不是devops的本质,它的本质是倡导开发团队拧成一股绳,紧密合作,频繁沟通,流程和工具只不过是大家合作得更充分,更广泛。将人的地位放在技术之前,将交流与沟通放在流程之上,才是devops提高生产力的原因。
网友评论