近期,Cloudflare在更新 WAF 配置规则时,因其中一个规则包含了正则表达式,导致 Cloudflare 全球机器上的 CPU 峰值使用率达到 100%,在最糟糕的时候,流量下降了 82%,对整个互联网都产生了明显的影响。
因此,变更的定义,不仅仅是狭义的上线新版本代码,也应该包含配置变更,数据变更,操作系统变更,网络变更,基础设施变更等方面。变更是运维人员的主要工作内容,同时也是导致服务故障的主要原因。据 Google SRE 统计,线上 70%的故障都是由某种变更而触发的。
服务变更的关键点
部署清单
部署清单主要是管理部署期间的整个生命流程,通过将各个阶段的各个步骤进行罗列和长期维护,从而逐步形成针对特定变更场景的说明手册。
如果只是升级一台服务器的二进制代码,需要部署清单吗?答案是肯定的。不能把二进制代码变更等同于二进制文件替换,在替换动作之外,有很多的工作内容,仅仅是更新完毕以后,就需要考虑如下问题:
程序是否正常启动
日志是否存在异常信息
服务功能是否正常
服务性能是否符合预期
服务关键指标是否异常
对于多模块,多系统,多团队配合的变更操作,如果没有一份事前经过充分验证的部署清单,谁在什么时候应该做什么事情,准入条件是什么,交付标准是什么,有哪些操作禁忌和注意事项,那这种复杂变更的结果就只能靠运气了。
随着运维自动化水平的提升,部署清单并不会消失,而是在载体上有所不同,从早期的纸质上线单,到现在内置于部署系统中,实现了更好的经验传承,校验完善,流程管控,信息分享等。
灰度发布
绝大部分服务,都不应该由单个实例组成。那么,在变更的时候,就应该避免一次性升级所有实例,而应该分批次的逐步升级,并在每个批次间预留一定的时间间隔对上一批次进行观察和评估,从而决定是否继续进行升级,以此来保障变更的质量。
以 Google 为例,其灰度发布的比例,从 0.1%开始,每 24 小时增长 10 倍推进,从 0.1%-> 1% -> 10% -> 100% (详见 Google SRE 中文版 162 页),并且灰度的初始比例一定不可以超过服务整体的冗余度。同时,在对服务进行变更操作的期间,需要将流量摘除,避免对线上产生影响,变更操作完毕后,方可引入灰度流量进行验证。
在灰度阶段,有针对性的选择灰度流量,尽可能完整的覆盖各类业务场景和用户类型,并通过流量调度形成局部热点,对服务的性能进行验证,避免全量上线可能出现的性能下降。
快速回滚
变更操作一定要有回滚预案,并能够快速回滚!日常的变更操作,只要有备份,大多数情况都可以进行回滚。那些无法进行回滚的,一般都是重大变更,这时候,等着你的基本上就是直接在线上调试并修 bug 以及超长的停机时间和大批的脏数据了。
不同公司对待回滚的态度不同,和其背后的专业能力有很大关系,因此不能盲从。如果对所有的回滚事件不加甄别的进行追责,那么导致的后果就是对于非核心故障,研发坚决不进行回滚操作导致带伤上阵,或者说将回滚美其名曰快速迭代。
功能开关
比回滚更高效的方案是功能开关,在发现新功能上线有问题后,可以通过功能开关立即关闭该功能,从而起到更快速的止损效果。可以想象一个场景,一次上线后,发现 10 个功能里面有 1 个功能异常,且引发了部分脏数据,因为还需要确保其余 9 个功能正常,因此不能全并发回滚,只能按照预置的并发度进行回滚,那么回滚耗时就会较长,这时候,如果有功能开关,那情况就大不一样了。
线下测试
既然线上有了变更保障能力,那为啥还要在线下费劲搞集成测试呢,直接在线上测不就行了吗?我们假设这个观点是正确的,那么所有未经测试的代码全部推送到线上开始灰度,在灰度阶段去发现各种问题,然后回滚,修复后继续上线。但灰度的流量,也是真实的用户,怎么能够拿用户的真实流量做这样的事情呢。因此,线下测试还是非常重要的环节,通过线下测试,将 80%以上的基本问题拦截在线下环节,在灰度环节,更多的去解决线下环境无法覆盖的场景。同时,参照 Chaos Monkey,以随机的方式对线下环境以及灰度环境进行破坏性验证,让服务具备应对各种典型故障场景的容错能力。
效果检查
服务变更后,需要有一系列的基于部署清单管理的效果检查的内容,例如前面提到的程序是否正常启动,功能是否正常,性能是否正常,以及本次调整的内容是否符合预期,通过对变更的效果进行验证,才能最终确认本次变更是否正确。同时,针对服务相关的全局核心指标的监控,在变更期间,既不应该出现异常,更不能被随意屏蔽掉。
时间窗口
早期,Facebook 的交付工程团队,会在每个工作日进行一次非关键性更新,而重大更新则每周进行一次,通常在周二下午进行。这里就体现了时间窗口的概念,时间窗口主要是用来降低变更导致的影响,常见的时间窗口有如下建议:
尽量避免节前做变更,即使是 BAT 和运营商,对于全年重要的节假日,往往会提前数周停止业务的非必要性变更,或者是将自动流程转为审批流程;
尽量避免在业务每天的高峰期做变更,例如很多网络切割都是选择凌晨进行操作,就是避免对业务产生影响;
尽量避免在下班前尤其是周五下班前做变更,提前通告并全员值守的除外;
尽量避免在非工作时间(包括周末和节假日)进行自动部署。
隔离
如果服务是分组部署(多 AZ 部署、多 Region 部署),且分组间能够做到尽量避免服务间的交互和基础设施共享,那么在变更中,就需要利用该特性,对分组进行逐一升级和观察,避免问题发生扩散,在出现问题的时候,通过流量调度即可快速摘掉流量止损。
通告
任何的变更,都需要事前进行通告,告知相关的上下游团队,变更时间,变更内容,可能的影响,应急联系人等,并在变更期间的各个阶段,进行通告。同时,也应该将变更信息录入到统一的系统中,便于相关上下游订阅。这里需要注意的是,在通告系统中,各个业务方的联系方式应该基于邮件组等群组方式,避免直接写入具体的人员列表,从而因人员调整导致通告失效。
避免手工操作
对于低频非标准化的场景,在部署清单中可能会这样描述:”请将存放在 XX 地方的操作系统镜像文件集,拷贝到线上某一批服务器的 XX 路径下“。那为什么这种常规的文件替换不通过部署系统进行呢,是因为操作系统镜像文件体积太大,无法存放在版本库当中,且主流操作系统镜像每年变更至多两次,因此,大家都觉得手工操作一下就好了。但这里面存在一些高风险的地方:
不对网络传输进行限速,可能会在短时间内导致服务器 IO 和网卡使用率较高,进而影响线上的业务;
如果是替换操作,且直接对线上文件进行替换,因为文件体积较大,会导致在替换期间,用户获取到不完整的文件;
文件传输完毕后,所有服务器上的文件是否完整,是否正确,没有校验方案。
从上述的简单 case 可知,即使简单如文件传输这样的操作,如果没有运维系统的支持而手工进行,也可能导致较为严重的线上故障。同时,也不建议为这类场景编写一次性的脚本,因为这类脚本本质上和手工操作并无区别,上述的风险并不会因为你临时写一个脚本而消失,还是应该回归正途,通过部署系统、传输系统或者其他的运维自动化平台来解决。
服务变更的最佳实践
蓝绿部署
本文以蓝绿部署为基础,介绍服务变更的最佳实践。
截图简要说明:将系统按照 AZ 的维度,独立部署了 4 组,分别是 AZ1、AZ2、Z3 和 AZ4,这四组完全一致,基于隔离的思路,四个分组间,尽量避免了服务间的交互和基础设施共享。
考虑到线上环境的复杂度,以及天然存在一定的冗余度,因此每次仅升级一个 AZ 分组。在第一个分组 AZ1 的时候,会进行较为详细的验证,除去常规的自动化检查外,还会有测试人员的手工效果检查,以此确保变更的质量。其余 AZ 因为变更内容一致,因此不会有测试人员的接入,仅保留自动化检查。
如果变更存在问题,因选择的 AZ1 是明确小于冗余度的,因此仅需要摘除流量后,再进行回滚,部分时候,如果研发要求短暂保留现场,也可以满足其要求。
部署系统
部署系统应该将变更的关键点内嵌到部署系统中,不断完善,让其成为变更流程无法逾越的环节,从而更好的保证变更质量。一个部署系统在做好单机部署工作的同时,也应该满足如下业务侧的需求:
提供部署清单功能,并具备自动化的检查能力,阶段性进展通告的能力;
提供版本管理功能,常规变更(二进制代码和配置)必须全部基于版本库进行;
提供快速回滚功能,能够帮助业务快速回滚到上一个稳定版本,并能够按照业务上下游编排顺序进行回滚;
提供时间窗口功能,默认不能够在业务定义的黑名单时间点上线;
提供备份功能,每次变更都需要将可能影响到的内容进行单机备份,便于快速回滚,默认是需要将上次的发布包进行全量备份尽量排除掉日志;
提供灰度发布功能,能够定义分组间和分组内的并发度,分组变更的暂停时长等;
提供效果检查功能,自动化的对业务进行预定义的各类检查并和部署动作联动,如暂停变更,继续变更以及调整灰度的比例;
提供编排功能,满足多模块的联合上线。
评论 6 条评论