SRE 生产环境上线操作指南

Posted by Mike on 2023-10-13

我们每天要进行大量的线上变更操作。怎么保证这些操作安全,不会导致故障,是我每天都在思考的问题。

这篇文章从工作经历总结一些原则和想法,希望能有帮助。

线上操作有几点基本的要求:

  • 操作需要是可以灰度的 (Canary):即能够在一小部分范围内生效,如果没有问题,可以继续操作更多的部分;
  • 操作必须是可以验证和监控的:要知道自己操作的结果,是否符合预期;
  • 操作必须是可以回滚的:如果发现自己的操作不符合预期,那么有办法能够回到之前的状态;

逻辑很简答:假设我一开始做操作范围很小,可以灰度,做完之后我可以监控是否符合预期,如果不符合预期就回滚,那么,操作就是安全的。

这三步中的每一步看似很简单,但是实际做起来很难。

灰度

发布过程是最简单的一种灰度场景,现在有蓝绿发布模式:

  1. 分成两组,将所有的流量切换到绿组;
  2. 先发布蓝组,此时是没有流量的,发布完成之后,将流量逐渐切换到蓝组;清空绿组;
  3. 然后发布绿组,发布完成之后将流量切换到平均到两组

还有滚动发布,对于每一个实例:让 Load Balancer 不再发给它新的流量,然后升级,然后开始接收流量,如果没有问题,继续以此处理其他的实例。

几乎每个人都可以理解灰度的必要性,但是不是每一种操作都是可以灰度的。

比如说数据库 DDL 的变更,很难灰度,提交到数据库,数据库就开始应用了;还有一些动态配置系统,一些全局配置,如果修改,就对所有的应用同时生效的;一般都是这样一些数据源类型的变更,很容易出现不支持灰度的情况。不可以灰度的情况也是最容易导致问题的。

一个替代方案是,搭建一套一模一样的环境,在这个环境先应用变更,测试一下是否符合预期。但是在今天分布式环境下,很难模拟出来一模一样的环境,可能规模小了,可能测试环境没有一些用户的使用场景,等等。总之,模拟的环境没有问题,不能代表生产的环境就没有问题。

最好的解决办法,还是在软件和架构,从设计上就能支持灰度。

验证与监控

所有的操作,一定要知道自己在做什么,效果是什么。做完之后进行验证。听起来很简单,但是实际上,很多人做事像是闭着眼睛,不知道自己在做什么,做完之后有什么效果也不管。

验证操作的结果

举一个例子,比如目前网关遇到了什么问题,经过查询,发现和 Nginx 的一个参数有关,然后根据网上的内容修改了这个参数,回头去看问题解决了没有。如果没有,继续在网上查资料,看和什么参数有关。

上述操作,一个潜在的问题是,当问题真正修复了之后,我们不知道自己做了啥才修复问题的。也有一些时候,相同的配置变了名字,实际上这个修改这个参数是可以解决问题的,只不过我们用了从网上得到的过时的参数名字,所以不生效。

所以,对于每一个操作,推荐直接去验证目前的操作结果。比如改了一个 log 参数,那么直接去看这个参数是否生效,是否符合预期,然后再去看其他的问题是否得到解决。

做操作要一步一步来,做一步验证一步。

另外,最好去验证操作的副作用,而不是验证操作本身。比如,修改了一个配置,不是去 cat 一下配置文件确认就可以了,而是要去看自己修改的配置是否真的生效了。比如路由器设备,我们执行了一些命令 ip route ... ,验证的方法并不是 show running-config 去看配置是否有这一条,而是要去看 show ip route 确定配置是否生效。

验证核心的业务指标

除了验证操作结果之外,也要关注业务指标是否还正常。

如果业务指标不正常了,而恰好和自己的操作时间吻合,那么就应该立即回滚。

听起来很合理?但是实际上,很多人(我也是)第一反应都会是,我的操作不可能引起这个问题,让我先看看日志,到底发生什么了。

当发生问题的时候,时间很宝贵,正确的做法是第一时间在群组里面宣布自己的操作(事实上,操作之前就宣布了,但是消息太多,没有问题的时候没有人会认真看操作历史),然后开始进行回滚。可惜的是,我发现这么做的人很少,大部分都是想去排查,直到确定是自己的操作导致的,才开始回滚。

回滚

同上,不是所有的操作都可以回滚的。一些可以补偿的方案有,操作上尽量设计成可以回滚的(有些废话)。比如,DDIA 这本书就介绍了数据上如何做向前兼容和向后兼容的方法。

举个例子,比如软件新版本的一个配置要从名字 A 改成 B,不要直接改,而是添加一个配置 B,代码里面可以读 B,如果没有的话,尝试读 A。等升级完成之后,在下一个新版本中,去掉 A 的逻辑。这样,每两个版本之间都是兼容的。

除此之外,还有一些我认为非常重要的东西。

操作计划和操作记录

一些复杂的操作,比如修改 DNS,配置网关,配置其他东西,可能是联动的。而且显示中也不是所有的东西都适合自动化的。这些复杂的操作,推荐在操作之前就写好操作计划,然后对着一步一步操作,贴上必要的验证结果和操作时间。万一出现什么异常,就可以将异常出现的时间和自己的操作记录对照,很有用的。操作计划也可以相互 review,如果是 gitops 的话,就更好了。

效率

这是 Last but not least! 操作的效率至关重要。

我认为运维平台要设计成简洁,没有歧义,流程清晰的,非必要不审批。这可能跟直觉相反,尤其是领导的直觉。

领导(不知为何)觉得审批流程越多越好,出了事故就开始思考在哪一个阶段可以加上一个审批流程,来避免类似的问题发生。但其实,我觉得流程越多,出问题的概率不减反增。

程序员天生就不喜欢繁重的流程,如果流程太重,就会出现其他的问题,比如,人们会想办法绕过不必要的流程;会想办法“搭车发布”(意思就是将多个操作合并成一个,这也是违反原则的,一次应该只做一个操作);对于明显出现异常苗头的时候,因为不想重新走审批而铤而走险。

但是出现这种情况,领导不会觉得流程有问题,领导会觉得你小子不按照流程办事,开除。

最后导致 SRE 的幸福感很低,事情还是要那么多,完成工作不得不铤而走险,还得责任自负。

事实上,真正能保证安全的是架构设计简单,做事的人知道自己在做什么,操作按照如上灰度、验证,出问题回滚,而不是靠流程。SRE 之间 Review 是有价值的,审批是没有价值的,大部分的审批仅仅是请示一下领导而已,领导可能看不懂操作的后果是什么。

所以,流程是有代价的。

本文转载自:「卡瓦邦噶」,原文:https://url.hi-linux.com/0LB3T ,版权归原作者所有。欢迎投稿,投稿邮箱: editor@hi-linux.com