从二十个严重的配置故障中我们能学到什么?

阅读数:2 2019 年 12 月 24 日 09:40

从二十个严重的配置故障中我们能学到什么?

配置故障是运维人员在工作中经常会遇到的问题,如何才能避免配置故障的发生呢?本文作者列出了自己职业生涯遇到的 20 个不同类型的配置故障,并分析了故障发生的原因,提出了相关的解决方法。

配置变更的痛点

时效性,很多配置变更的场景,其生效时间是有明确要求的,尤其是止损相关的操作。如流量调度的配置生效时间不超过 5min,这是由 SLA 所决定的。因为 5min 的生效要求,所以这类配置变更基本上不会进行灰度,直接就全量生效了。

动态化,在上下游关联关系的场景中,服务的 IP 地址列表和数量均会发生变化的,站在整个系统角度去看,可能每时每刻都会有服务因扩容、缩容、故障转移而产生 IP 列表的变化,加之这类配置是最终一致性的特性,因此,就很难对语义内容的正确性做及时准确的判断。

差异化,同一服务的不同实例或者不同集群间,配置会有一定的区别。以监控系统为例,因为每台机器上部署的服务组合不相同,那么每台机器上需要采集的内容就不相同,那相应的监控配置也会不同。许多的机器生效的配置不同,版本不同,那么要想检查哪些机器未生效最新配置,未完全生效配置,就有困难了。

碎片化,每个服务都需要配置管理,但现实却是大多数服务都是自己写的这套逻辑,且实现非常之简单。如果现实是建立在大多数服务自己实现配置管理的基础上,那么想要做到相对统一的标准规范,你就需要做各种对接和适配,开发各种外挂,持续处在一个较低的层次上。配置中心成熟的开源解决方案有很多,因此内部开发人员也少有动力基于现状写一套适配的方案,大家都会说推动改造是最好的,那改造之前呢怎么办呢

二十个严重的配置故障

笔者曾长期负责配置服务的运维工作,也经常参加公司的故障复盘会,感慨于配置故障影响的严重程度,因此将过去多年和配置相关的各种服务故障脱敏后进行罗列,希望对大家有所帮助。限于篇幅的关系,无法对每个 case 进行准确详尽描述,仅描述故障原因部分,因此阅读下面的 case,需要读者具备一定的相关背景才能更好的理解(本文中罗列的 case 较多,有内容不合适的地方,可直接联系笔者处理)

case1:在 WAF 中部署了配置错误的规则,其中一个规则包含有一个正则表达式,导致节点 100%CPU 占用。和 CloudFlare WAF 的故障是较为相似的

case2:某服务访问数据库错误,获取合法用户列表的结果为空值,该服务认为没有合法用户,将所有的用户请求全部封禁,导致系统完全不可用。和阿里云 20180627 的故障较为相似的

case3:每台物理机上均有一个单机部署器,单机部署器定期从服务端同步配置,用以在单机上部署需要的客户端列表。因为网络的问题,配置被截断,部署器只获取了上半部分配置,部署器基于此进行更新,所有未在配置中的服务被全部卸载,影响极为严重

case4:计算模块对加载的处理规则数量进行了限制,单个租户最大可处理 1000 条规则,经过几个月的使用后,某个租户的规则数超过 1000 条,计算模块全部 coredump

case5:服务启动前需要从远程加载相关配置,某次变更添加配置条目较多,服务请求配置超时后,不断重新启动,直至将配置中心打垮,进而影响多个业务,导致系统彻底崩溃

case6:Zookeeper 的 initlimit 设置为 5(意思是实例启动后可以有 10s 的时间来同步数据),该数值对于大部分集群来说,都没有问题,但是如果 ZK 集群的快照体积超过 1GB,那么节点重启后就可能无法在 10s 内同步完数据进而导致节点异常

case7:Agent 上传给服务端的数据包出现了异常,在头部信息中,100KB 的体积变成了无限大,然后后端模块就按照该值进行内存分配,因为超过 Ulimit 的限制,所以 coredump 了,一部分服务器异常后,Agent 继续将请求发送给剩余的服务器,直到集群全部被打死为止

case8:通过配置服务获取下游服务列表,运维人员误将服务下所有实例从配置中删除,而该服务也未使用系统提供的熔断阈值,导致从配置服务中获取该服务列表为空,其他依赖服务加载了该配置后,导致服务整体故障

case9:新上架的交换机未添加完整的 ACL 规则,导致线下服务请求到线上集群中

case10:用户提交了语法结构不正确的配置,然后该配置下发到了后端,所有加载该配置的后端模块全部 coredump 了,后端模块使用了通用的语法校验库,但和前端使用的校验库不同

case11:后端服务异常后,触发了自动降级(通过修改 Nginx 的配置文件来实现),修改配置文件出现了逻辑错误,该配置通过了语法校验并立即进行了全量自动更新,导致集群整体崩溃

case12:将某台服务器上的 stats.conf 文件同步到全部 nginx 集群上,忽略了 allow 字段的 ip 地址不同,致使 lvs 认为监听的后端 nginx 全部故障,系统直接出默认页面了

case13:批量删除线上服务器的日志,因路径中包含特殊字符,运维系统未能很好的处理该字符,只保留了特殊字符前的路径,导致线上服务器根目录被清空

case14:配置项结尾多了特殊字符,导致策略匹配全部失败。原因是 windows 下的文本文件换行符是\r\n,linux 下的换行符是\n,在 linux 下 vim 打开 windows 的文本文件,在行尾会显示 ^M 字符

case15:通过配置服务获取下游服务列表,因下游服务列表包含了四万多个 IP 地址,导致获取超时,服务继续使用过期的下游列表,而该过期的下游列表中无效实例逐渐增加,最后导致服务连续重试到无效实例,流量被丢弃

case16:配置中心对外提供配置下载服务,其 LB 的负载均衡策略使用了随机策略,因下载集群规模超过百台机器,配置更新需要一定时间,在此期间,请求配置中心获取的配置会不停的在新旧版本之间变化,导致相关的服务被不停的卸载,升级,无法提供服务

case17:配置中心的负载均衡策略使用了 Ip_hash 策略,部分下载集群数据未更新,导致被分配到这些集群的服务,无法获取最新配置,且多次重试依然无效,导致流量统计始终缺少 20% 的流量,对收入造成严重的损失

case18:高防回源携带的头部字段中新增的内容与源站 iis 配置不兼容,导致回源请求被拒绝

case19:named 服务在重载配置时,产生了新的进程但老进程没有正常退出,服务请求数据时,将请求打到了老 named 的进程,该进程的配置没有更新,导致用户拿到的结果不符合预期。类似的问题,nginx 也有,在很多开源软件上也都有存在

case20:自研 nginx 的扩展来实现 upstream 的定位,hash 桶的大小设置为 1024,因 ip 重复个数较多,超过了 hash 桶的大小,导致 hash 分桶策略不断的进行 rehash 任务,请求全部被卡死

故障改进

对于上述的故障,我们从加强测试,加强数据校验,集群隔离,灰度发布,缓存和熔断六个维度来看各个 case 的适用性,结果如下图所示。

从二十个严重的配置故障中我们能学到什么?

假设笔者的给出的改进方案是正确的,对统计结果做如下分析:

  • 加强测试,从有效性来说,该方案可以搞定 85% 的故障,如边界值的问题,异常输入的问题,特殊字符串的问题等。但是对于部分场景,加强测试可以搞定,但成本非常高,例如 WAF 的规则问题,不同的用户,配置的规则不同,如果要在测试阶段解决,那就需要对所有用户的所有规则进行测试,且要有足够丰富的请求组合,这时候,可行但成本很高。还有,加强测试虽然可以解决很多问题,但没有从本质上彻底消除问题,且依赖于测试团队的经验和能力,结果并不可控。

  • 加强校验,从有效性来说,该方案可以搞定 65% 的故障,以 case2 获取空值的故障来讲,对请求结果进行校验,完全可以避免该问题,且这是程序机制天然具备的,并不依赖于人的经验和能力,因此效果更好。另外,加强校验,并非局限于对空值的校验,还包括配置文件完整性的校验,数据包合法性的校验,配置文件的版本、时间戳、更新时间的校验,对特殊字符串容错的能力,还提供一些故障场景下的兜底访问策略,同时还可以将配置变更的信息以 http 接口进行暴露,便于监控。美中不足之处在于,如果对每个模块分别实现上述的功能,那成本太高了。

  • 集群隔离,笔者之前最为推荐的能力建设内容之一,本次居然只能解决 35% 的故障,究其原因,主要在于很多业务虽然隔离部署,但依赖了相同的配置文件,且大部分是高频热加载的,因此集群隔离在此处,略显苍白。

  • 灰度发布,这也是笔者最为推荐的能力建设内容之一,本次也只能解决 35% 的故障,主要是因为大部分的配置变更是没有灰度发布的,直接靠程序的定时轮询热加载,因此灰度在此,也略显苍白。

综合来看,三个建议:

  • 将校验,缓存,熔断等相关的能力合并在一起,在通用的配置服务中实现,会是比较好的方案,退而求其次,如果没有通用的配置服务,也应该具备这些能力。

  • 将配置变更尽可能的视为一种上线,涵盖从版本提交,代码检查,测试,灰度发布,效果监控等各个环节,也能够更好的保障稳定性,毕竟很多配置是最终一致性,而非强一致性的,因此,用时间来换取稳定性,也是值得的

  • 将强一致性转为最终一致性,问题就不会那么棘手了。以账号密码变更为例来说明,账号密码变更是强一致性要求的,但是如果通过新增一组账号密码逐步进行替换,全部替换完毕后删除旧的账号密码,则就将强一致性转为最终一致性了。

还有兜底建议:

  • 通过资源限制 +SSH+Puppet 多种方式的互备,确保任何场景下,不论是 sshd 异常,还是 cpu 打满,都能够通过批量操作服务器快速止损,决不要出现无法登录服务器的惨剧

场景化建议

热加载配置的场景,尽量使用配置中心这种解决方案,类似于上文的加强校验,缓存,熔断等等,都是标配能力了,而且一旦发现了潜在的隐患,通过升级 lib 库,大家都能避免发生同类问题,效果极好。当然,你得用好才行,胡乱使用 zookeeper,最后导致各种故障,然后怪罪于 zookeeper 有问题,人家很冤枉啊。

需要通过重载配置文件才能生效的场景,则尽量使用集群隔离 + 灰度发布 + 加强校验的解决方案,以 DNS 为例说明,不同的 zone 由不同的集群来处理请求,每类 zone 再按需实现同城多活 / 异地多活,并新增小流量集群,尽量杜绝跨集群的变更操作,对每次变更都要进行严格的自动化校验,并分批次灰度生效,通过提升实时监控能力来降低批次间的时间间隔,进而减少故障的发生。

在整理上面资料的同时,配置故障让我联想到和无性繁殖有些类似,文末的参考资料中附带了香蕉和咖啡树的故事,有兴趣的读者可以看看。

扩展阅读:

香蕉可能要灭绝?还不是第一次?香蕉:救救孩子吧…

为什么英国人对茶情有独钟而不是喜欢咖啡?背后原因你一定想不到

评论

发布