Kubernetes+Docker系统的级联停机故障教训 - Dan Woods


Tar​​get的数据中心中运行了许多异构基础架构,为不同的工作负载提供多个不同的后端托管基础架构。其中大部分投入生产的基础架构是用于不同用例和应用程序开发和部署模式的遗留工件。比如目标应用平台(TAP)提供运行和管理的工作量,通过它我们可以管理分布在不同的托管基础架构工作负载,实现透明地使用通用接口。我们将此作为一种工具,以更好地管理系统容量,充分利用我们可用的基础架构,并将应用程序从一个托管提供商转移到另一个托管提供商,而无需应用程序团队重新搭建平台。
我们有许多核心服务,可以作为整个企业的集中系统。我们有意推动这种模式,以便我们可以提供可用的高可用性系统和服务,而不是要求每个团队除了开发和管理他们的应用程序之外还要成为运行基础架构的专家。这些企业服务的案例有数据库,弹性搜索和消息传递。
在Tar​​get,我们大力利用Kafka作为系统之间的消息代理。应用程序使用Kafka通知其他断开连接的系统有关数据更改的信息,以及其他各种原因。在基础架构中,我们使用Kafka将日志和指标从应用程序发送到适当的后端系统。这样,我们就可以将洞察数据的生成与该数据的消耗,存储和可用性分离。通过TAP部署的每个应用程序都使用日志记录和公制侧面部署,这使得应用程序团队可以轻松地从集中式系统中使用此数据,而无需任何额外的设置时间或配置。

Kafka在数据中心OpenStack基础架构中运行,上周一个晚上,对OpenStack的底层网络子系统进行了升级。此升级仅对网络连接造成短暂中断。由于升级过程中遇到的问题,网络连接中断了几个小时,导致与Kafka的间歇性连接。

我们在TAP下运行了许多Kubernetes集群,但是有一个集群构建得比其他集群大得多,并且托管了大约2,000个开发环境工作负载。在正常操作下,应用程序的日志记录边车消耗的数量微不足道。 CPU时间。当Kafka间歇性地可用时,这导致日志记录边车全部“同时唤醒”,虽然他们还没有使用大量的CPU,但是累积起来足以在Docker守护程序中为当前节点增加了高负载。

docker守护程序上的较高负载导致节点向Kubernetes报告它们不健康,此时Kubernetes调度程序尝试将工作负载从受影响的节点移动到仍然健康的节点。由于Kubernetes调度程序不能平衡群集中所有节点的工作负载,因此某些节点能够承受较高的CPU使用率,而其他节点则不能。在将工作负载从不健康的节点重新安排到健康节点时,先前健康的节点变得不健康,并且循环持续存在。

除了日志记录和度量标准边车之外,每个工作负载都部署有Consul代理边车,这允许跨不同基础架构后端的应用程序参与更广泛的计算环境。一旦工作负载启动,Consul代理就会将其注册为Consul中的服务,并开始将节点的成员资格闲置到Consul gossiping 网格中。

在重新安排活动期间,Kubernetes生产了大约41,000个新节点,这些节点很快被启动然后终止。尽管pod中的应用程序在重新安排之前从未启动过,但是pod已经足够长时间在线,可以向Consul注册,并向gossiping 网络发送消息。将新节点注册到Consul会导致Consul的CPU使用率增加,从而增加了对Consul请求的无法容忍的延迟。此外,gossiping 网格中节点数量的增加也导致所有已部署工作负载的总体CPU使用率更高(使之前的累积效应更加复杂)。

Consul代理仅在任何给定循环期间处理来自其他节点的许多消息。这意味着发送到八卦网格的消息指示节点已经消失,这些消息不由对等节点处理。总的来说,这在gossiping 网格中产生了“波浪”效应,其中幻像节点将过期并随后重新添加到网格中。

在gossiping 中毒事件期间,其他几个系统受到影响。开发环境Vault集群为应用程序提供机密密封,因为它无法在合理的时间内与Consul通信。TAP的部署引擎也会受到影响,该部署引擎与Consul通信以发现其基础架构后端,为应用程序切换令牌以及动态配置短暂负载平衡。由于无法在可容忍的时间内与Consul通信,因此部署开始失败。

经过几天的研究,调试和尝试一件事,我们能够通过在Consul上启用gossiping 加密来恢复Consul和Vault。这意味着来自中毒gossiping 网格的任何消息都会立即被拒绝。之后,我们能够稳定部署引擎,并开始完全重新部署开发环境,以推动对加密的更改。在重新部署环境时,未配置加密的实例已终止。

之后,我们对Consul做了一些研究,发现在版本1.2.3中有一个PR合并用于处理大型集群。我们对Consul进行了就地升级,我们能够通过对单个工作负载进行可控扩展以及随后的非优雅缩小来衡量成功。从gossiping 网和服务目录中快速删除节点。

幸运的是,这个问题只影响了TAP开发环境,而且它可能比最终的情况要糟糕得多。但是,从TAP团队的角度来看,我们非常关注确保TAP在不中断的情况下提供世界级的开发人员体验。我们将开发环境视为我们的“prod0”,因此这种中断以最高级别的严重性来解决。

这个问题并没有以同样的方式影响TAP生产环境,因为它只是一个小得多的环境。在生产中感受到相同的净效应(“prod1”),但是因为Kubernetes簇更稀疏地堆积,所以没有发生级联故障。我们后来证实,生产中的gossiping 网也确实中毒,但由于节点数量较少,负载明显较小,升级Consul很快就对此进行了修复。

我从这次停机中断中吸取的教训:

  1. 我的口头禅“smaller clusters, more of them”得到了肯定。我们在较小的开发dev 环境Kubernetes集群中的工作负载没有发生大的集群一样的坏影响。同样适用于prod。如果dev和prod在同一个集群上,我们就会受到冲击。
  2. 共享Docker守护程序是一个脆弱的失败点。需要更加努力地减少个别守护进程中断的表面积。这与“smaller clusters, more of them”相关。
  3. 我过去一直担心过边车,但是在这次事件之后我确信让每个工作量都有他们自己的记录和监测边车是正确的。如果这些是集群的共享资源,那么问题几乎肯定会加剧并且难以修复。