热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

使用Kubernetes和Istio构建大规模集群带来的挑战和解决方案

原文地址:https:blog.houzz.comchallenges-and-solutions-in-building-a-large-scale-cluster-with-k


原文地址:https://blog.houzz.com/challenges-and-solutions-in-building-a-large-scale-cluster-with-kubernetes-and-istio/


译者:陈龙全


为更好的管理不断增长的服务和流量,Houzz 基础架构团队最近将 Web Server 服务从 Amazon Elastic Compute Cloud(Amazon EC2)迁移到 Kubernetes 集群。 这次迁移使资源使用降低了 33%,首页延迟有了 30% 的改善。


Kubernetes 集群的整体架构包含多个应用程序,包括用 NodeJS 编写的前端(FE)应用程序和用 HHVM 编写的后端(BE)服务。FE 应用程序通过 HTTP 上的 Apache Thrift 协议与 BE 服务进行通信。每个应用程序都启用了水平 Pod 自动缩放(HPA)。集群内以及与外部服务的通信由 Istio 管理,都会经过 Envoy Sidecar。


迁移过程中有诸多挑战,本文主要是想和大家分享一下我们迁移过程中的最佳实践。


Pod 延迟启动


在开始 Kubernetes 迁移时,我们注意到 Pod 延迟启动有时会在新配置的节点上发生。Envoy 容器准备就绪大约花了六分钟,阻止了其他容器的启动。从 Envoy 日志中,我们观察到 pilot-agent 不断报告 Envoy 尚未准备就绪,并提出了检查 Istiod 是否仍在运行的建议。


我们实现了一个 DaemonSet,其唯一的工作就是解析 Istiod 服务的 FQDN。从其指标来看,我们观察到在新节点引导后 DNS 名称解析几分钟后会超时,推测 Envoy 遇到了相同的超时问题。


我们确定了导致该问题的根因是 dnsRefreshRate,其在 Istio 1.5.2 中的默认值为 5 分钟,并且与观察到的延迟大致匹配。由于在启动某些 Pod 之后新节点上的 DNS 客户端已准备就绪,因此较长的重试间隔导致 Envoy 无法及时检测到 DNS 客户端的就绪状态。通过强制 Envoy 进行更频繁的重试,将附加的 Pod 启动延迟从 360 秒减少到 60 秒。


请注意,在 Istio 1.6 中,默认的 dnsRefreshRate 已更改为 5 秒。


HHVM Pod 预热和级联伸缩


我们的 BE 服务内置于 HHVM 中,该服务在其代码缓存预热之前具有很高的 CPU 使用率和高延迟。预热阶段通常需要几分钟,因此在默认的 15 秒 HPA 同步周期或 HPA 评估 CPU 使用率指标并调整所需 Pod 数量的时间间隔内,它不能很好地工作。当由于负载增加而创建新的 Pod 时,HPA 从新 Pod 中检测到更高的 CPU 使用率,并扩展了更多 Pod。这种积极的反馈循环一直持续到新的 Pod 被完全预热或达到 Pod 的最大数量为止。新的 Pod 完全预热后,HPA 检测到 CPU 使用率显著下降,并缩减了大量 Pod。级联伸缩导致了不稳定和延迟峰值。


我们进行了两项更改以解决级联伸缩问题。我们根据官方建议改进了 HHVM 预热过程。预热期间的 CPU 使用率从正常使用的 11 倍减少到 1.5 倍。Pod 开始提供流量后的 CPU 使用率从正常使用的 4 倍降低到 1.5 倍。


此外,我们将 HPA 同步时间从 15 秒增加到 10 分钟。尽管 HPA 对负载增加的响应速度较慢,但它避免了级联扩展,因为大多数 Pod 可以在 10 分钟内完成预热并开始正常使用 CPU。我们发现这是一个值得权衡的选择。





更改之前的 HPA 活动





更改之后的活动


负载均衡


负载不均衡是我们迁移到 Kubernetes 期间遇到的最值得注意的挑战,尽管它仅在最大的虚拟服务中发生。症状是某些 Pod 在重负载下无法通过就绪检查,然后更多的请求将路由到这些 Pod,从而导致 Pod 在就绪状态和未就绪状态之间摆动。在这种情况下添加更多的节点或 Pod 将导致更多的飘动 Pod。发生这种情况时,延迟和错误计数会大大增加。缓解此问题的唯一方法是强行缩小部署规模,以杀死不断波动的 Pod,而无需添加新 Pod。但是,这不是可持续的解决方案,因为更多的 Pod 很快就开始波动。由于此问题,我们多次回退了迁移。





负载均衡


为了方便排查,我们添加了额外的日志记录,发现负载不均衡时触发,一个可用区(AZ)的请求明显多于其他两个。我们怀疑这种不均衡是由于我们当时使用的最少请求负载均衡策略中的正反馈回路造成的。我们尝试了其他几种策略(Round Robin,Locality Aware 和 Random),都没有解决问题。


在排除了负载均衡策略之后,我们在其他两个方面寻找了积极的反馈循环:重试失败的请求和异常检测。


尽管 Istio 的官方文档中指出默认情况下不会对失败的请求进行重试,但实际的默认重试次数设置为 2。重试会导致级联失败,因为某些请求失败后会发送更多请求。此外,我们观察到异常检测中的某些行为(也称为被动健康检查)无法解释,因此我们决定禁用这两个功能。之后,不平衡问题消失了,我们能够将 95%的请求迁移到 Kubernetes。我们在旧平台上保留 5%的资源用于性能比较和调整。最初,我们不确定重试或异常检测这两个功能中的哪一个是造成负载不均衡的原因,尽管我们现在认为它与重试有关。


在将 Istio 升级到 1.6 版,进行了一些性能改进并将 100%的请求迁移到 Kubernetes 之后,我们尝试重新启用离散值检测 - 我们愿意承担这种风险,因为更改可以在几秒钟内恢复。在撰写本文时,我们还没有遇到负载不均衡的问题。就是说,我们用以下事实证明了我们的理论,即当前 Istio 版本的集群配置与发生不均衡时的配置不同。


发布后性能下降


我们观察到,每次发布后,Kubernetes 上的延迟都会随时间而增加,因此我们创建了一个仪表板来显示 Envoy 在 Ingress 网关,FE 应用程序和 BE 服务 Pod 中报告的入站 / 出站延迟。仪表盘表明,总体增加是由 Envoy 在 BE Pod 中报告的入站延迟的增加所驱动的,这包括服务延迟和 Envoy 本身的延迟。由于服务延迟没有显着增加,因此代理延迟被认为是延迟增加的驱动力。我们发现,每个版本发布后,Envoy 在 BE Pod 中的内存使用量也随着时间增加,这使我们怀疑延迟增加是由于 BE Pod 中的 Envoy 的内存泄漏引起的。我们 exec 到一个 BE Pod,并列出了 Envoy 和主容器中的连接,发现 Envoy 中有大约 2800 个连接,主容器中有 40 个连接。在 2800 个连接中,绝大多数是与 FE Pod(BE Pod 的客户)连接的。


为了解决 Envoy 内存泄漏问题,我们做了一些更改,包括:


将 FE Pod 到 BE Pod 之间的连接的 idleTimeout 从默认的 1 小时减少到 30 秒。此更改减少了错误数量并提高了请求成功率,但同时也增加了 FE 和 BE 容器之间每秒的连接请求数量。  将 Envoy 的并发或线程数从 FE Pod 中的 16 减少到 2。该更改取消了自第一次更改以来每秒大多数连接请求数量的增加。  在 BE Pod 中将 Envoy 内存限制设置为 300MB。观察到预期的行为,并且 Envoy 的内存使用量超出限制时重新启动。容器继续运行,但是内存使用率较低。重新启动 Envoy 时,有些 Pod 有短暂的准备就绪时间,这是对前两个更改的补充。虽然前两个更改减少了 Envoy 的内存使用量,但第三个更改将在 Envoy 的内存使用量超出限制时重新启动。与重新启动主容器相比,重新启动 Envoy 所导致的停机时间明显更少,因为后者会在 HHVM 中产生几分钟的预热时间。





变更之前的延迟





变更之前内存变化





变更之后的延迟


解决了发布后的性能下降问题之后,我们将 100%的请求迁移到 Kubernetes 并关闭了旧主机环境。


集群的瓶颈


随着我们将更多请求迁移到 Kubernetes 中最大的虚拟服务,我们遇到了跨集群范围的资源的问题,这些资源在虚拟服务之间共享,包括 APIServer,DNS 服务器和 Istio 控制平面。在事件期间,观察到持续了一两分钟所有虚拟服务的错误峰值,我们发现这是由于未能解析 FE Pod 中 BE 虚拟服务的 DNS 名称所致。错误峰值还与 DNS 解析错误和 DNS 请求下降有关。Ingress 服务调用不应依赖于 DNS。相反,应该将 FE Pod 中的 Envoy 定向为将出站 HTTP 请求定向到 BE 服务中的端点的 IP 地址。但是,我们发现 NodeJS Thrift 客户端库对无用的服务 IP 进行了 DNS 查找。为了消除 DNS 依赖性,我们部署了 Sidecar,将 Virtual Service 中 BE 服务的主机绑定到本地套接字地址。





Sidecar 清单示例


尽管 Istio 从应用程序角度最大程度地提高了透明度,但除了在应用程序代码中用本地 IP 地址和端口号替换 DNS 名称之外,我们还必须显式添加 Host 标头。值得一提的是,sidecar 的一个附带好处是可以优化内存使用。默认情况下,无论是否需要,Istio 都会将跨 Kubernetes 集群的每个服务的上游集群添加到 Envoy。维护那些不必要的配置的一项重大成本是 Envoy 容器的内存使用。使用 sidecar 解决方案,我们将 DNS 服务器故障与关键路径中的服务调用隔离开来,将 DNS 服务器上的 QPS 从 30,000 减少到 6,000,并将 Envoy 的平均内存使用量从 100MB 减少到 70MB。





coreDNS QPS 的变化





变更前内存使用情况





变更后内存使用情况


我们遇到的另一个错误高峰与不一致的集群成员身份(节点终止时出现这种情况)有关。尽管 Kubernetes 应该能够优雅地处理节点终止,但是节点终止时有一种特殊情况会导致错误尖峰:在终止的节点上运行 Istiod pod。节点终止后,一些 FE Pod 花费了大约 17 分钟的时间从新的 Istiod Pod 接收更新。在他们收到更新之前,他们对 BE 集群成员身份看法不一致。鉴于此,这些有问题的 FE Pod 中的集群成员很可能已过时,导致它们向终止或未就绪的 BE Pod 发送请求。





集群成员身份不一致且是旧数据


我们发现 tcpKeepalive 选项在检测终止的 Istiod Pod 中起作用。在我们的 Istio 设置中,将 keepaliveTime,keepaliveProbes 和 keepaliveInterval 分别设置为默认值 300 秒,9 秒和 75 秒。理论上讲,Envoy 可


能需要至少 300 秒加 9,再乘以 75 秒(16.25 分钟),才能检测到终止的 Istiod Pod。我们通过将 tcpKeepalive 选项自定义为更低的值来解决了这个问题。


建立大规模的 Kubernetes 集群具有挑战性,并且对大家来说非常有意义。希望你从我们的经验中找到有用的信息。


加入云原生社区 Istio SIG


扫描下面的二维码加入云原生社区 Istio SIG, 与 Istio 专家及爱好者们共同交流。




首届 IstioCon 演讲嘉宾招募中 ,议题提交截止时间为北京时间 2021 年 1 月 19 日 15:59。


点击下方“阅读原文”查看更多


↓↓↓




推荐阅读
author-avatar
手机用户2502872401
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有