作为一个有DevOps追求的程序员,你的目标,肯定是要让代码能在生产环境中,持续稳定地为用户提供良好服务。
但是该如何起步DevOps呢?你决定先看看代码再说。
但当你撸起袖子翻开祖传的代码后,心里就凉了半截。因为代码实在是难以理解和维护。
于是,你买来老马的《重构》第2版,苦修代码重构。另外还学习和实践了整洁代码、测试驱动开发、持续集成和持续交付。
功夫不负有心人,祖传代码在你手上,开始变得更可读,更易维护。
你还暖心地增加了覆盖关键业务的自动化测试,并且上了流水线,每次代码提交就能跑上一次。
如果流水线变红,10分钟内你就能修复。
当你正为自己感到自豪的时候,意外发生了。
你所参与开发的系统,在生产环境出现了严重的事故——用户无法登录系统。
运维人员用了大半天时间,把公司所开发的所有系统都重启了一遍,但依然无济于事。
最后,一个运维人员无意中发现,一个生产系统所依赖的外部系统,发生了死机。
这导致了层叠失效,并波及到你所开发的模块,并最终把整个系统拖垮。
为什么会这样?因为你当时在开发时,总是假设所依赖的外部系统,从来不会死机。这样就没有设计超时或断路器等机制,来应对这种未知的失效场景。
这时,你开始意识到,代码重构是面向已知场景的。所以无法发现未知场景的漏洞。
你翻开《重构》,找到了重构的定义。
重构,是对软件内部结构的一种调整,目的是在不改变软件可观察行为的前提下,提高其可理解性,降低其修改成本。
定义里面的“不改变软件可观察行为”,就意味着重构所涉及的软件行为,必然是已知的,否则就无法判断是否改变了该行为。
上图有4个象限。
第1象限,已知的已知行为,就好比用纸叠一架纸飞机一样,一切尽在掌握。
这就像在程序员本机运行单元测试,或修复静态扫描出来的技术债一样。测试用例和技术债,都是已知的。
第2象限,未知的已知行为,就好比一边听音乐一边外出锁门那样,自己都不知道自己已经把门锁得好好的。
当你养成了重构和整洁代码的习惯,也会到达“未知的已知行为”的境界。
第3象限,已知的未知行为,就好比一个最近半年因为疫情在家上网课很久的高三学生,知道今年的高考一定很特别,但就是不知道特别在哪里一样。
当过程序员的都知道,当你写完所负责的模块,第一次在测试环境中进行集成测试,就会预感到,因为所依赖的模块可能没有遵循契约,或测试环境的配置可能有错误,会出现各种状况,但你不知道具体会碰到什么状况。但测试环境还是相对简单的,而且用户一般就你一个人,所以你能估计出一些常见的问题,所以这还算“已知的未知行为”。
第4象限,未知的未知行为。就好比你现在正在北京丰台区,你真的不知道明天疫情会出现怎样的发展。
与疫情的不确定性相比,生产环境的不确定性一点也不少——用户会不会一窝蜂地点击“重试”按钮?生产系统所依赖的外部系统的响应会不会突然出现延迟,或者违反了契约?生产环境所依赖的包,会不会有内存泄露?……林林总总,你真的是两眼一抹黑。
那该如何发现未知的漏洞呢?
能靠人事先评估出来吗?不能。因为如果能,那还算“未知的未知行为”吗?你能事先评估出海鲜市场会爆发疫情吗?
之前未知的漏洞,只能靠系统在实际运行时暴露出来。
让系统在实际运行时暴露出漏洞,也分“被动”和“主动”两种情况。
比如生产事故,以及今年北京新发地农场品批发市场所发现的疫情,都属于被动暴露漏洞。虽然被动,但确实能发现未知的漏洞。
虽然被动的方式能发现未知的漏洞,但是以生产事故或确诊染病为代价的,成本较高。
有没有主动发现未知漏洞的方法?有的,变着花样多考验生产系统就行。比如西游记中四圣试禅心那样,变成母女四人,主动考验师徒四人。又比如北京目前在居民小区所进行的大规模核酸检测筛查,也是主动考验居民健康状态的一种形式。
你弥补了上次生产事故所暴露的漏洞。
接下来,你针对这个有可能会停机的外部系统,设计了能限制爆炸半径的灾难恢复测试——一旦这个外部系统停机,生产系统可以为用户发出提示信息,而不至于一起死机。
为防范灾难恢复测试中的意外,你还设计了能让测试紧急中止的应急机制。
你先在测试环境中运行通过了这个灾难恢复测试,然后又在生产环境上运行通过。这验证了漏洞确实被补上了。
你还将这个灾难恢复测试进行了自动化,使其能持续运行,以验证漏洞不会再漏。
为了引导大家发现更有价值的未知漏洞,你还召开了“考验会”,即让团队成员,根据生产环境可能出现的考验场景发生频次、概率和经受考验的信心,排序选出最应优先进行的考验,并为其设计灾难恢复测试,来验证考验能否通过,以及在考验中发现未知的漏洞。
这样,你就实现了面向未知漏洞的系统的韧性(Resilience)构建。
韧性,来源于“韧性工程”(Resilience Engineering,也称作“弹性工程”),指系统在生产环境的动荡(包括变更、干扰和机会)发生之前、之中和之后,都能相应调整其功能,从而在预期和意外的情况下,仍然可以保持可做出所需操作的能力。
有关构建韧性的更多信息,参见【混沌工程入门】领导让我做混沌工程……该咋做?。
通过上面的故事,你获得了以下启示:
代码重构,能让代码更易读和更易维护,让以后的漏洞修补更加快速。在流水线上运行自动化测试,能发现和修复已知的漏洞。
韧性构建,能通过生产事故,及在测试环境或生产环境所进行的精心设计的考验(比如运行灾难恢复测试),来发现并修复未知的漏洞。如将灾难恢复测试自动化,且持续运行,能验证漏洞不会再漏。
代码重构的下一站,就是应对未知漏洞的韧性构建,是向DevOps迈出的更大一步。
针对已知行为的代码重构,为针对未知行为的韧性构建,创造了易理解和易维护的代码库,以便分析未知漏洞。
而韧性构建,能将所发现的未知漏洞,转化为已知的软件可观察行为,进行重构,以修复漏洞。
代码重构,一般发生在本机或开发环境,主要由Dev来完成。
韧性构建,一般发生在测试环境与生产环境,主要由Dev和Ops一起协作,来定义系统稳态行为的假设,设计一系列灾难恢复测试,来考验系统在未知动荡下的韧性,并在灾难恢复测试进行时,搜集系统稳态相关的数据,以便发现并修复未知的漏洞。由此可见,这需要Dev与Ops的密切协作。
两者的有机结合,能弥补软件系统已知和未知漏洞,从而更好地让代码,能在生产环境持续稳定地为用户提供良好服务。
如果你听了很久DevOps,但不知该从何做起,那么可以尝试从“代码重构”和“韧性构建”,开始你的DevOps之旅。