View a markdown version of this page

负载均衡 - Amazon EKS

本文属于机器翻译版本。若本译文内容与英语原文存在差异,则一律以英文原文为准。

负载均衡

负载均衡器接收传入流量,并将其分配到 EKS 集群中托管的预期应用程序的目标之间。这提高了应用程序的弹性。在 EKS 集群中部署时,AWS 负载均衡器控制器将为该集群创建和管理 AWS 弹性负载均衡器。创建类型LoadBalancer 为 Kubernetes 服务时,AWS 负载均衡器控制器会创建一个网络负载均衡器 (NLB),用于在 OSI 模型的第 4 层对接收到的流量进行负载平衡。创建 Kubernetes Ingress 对象时,AWS 负载均衡器控制器会创建一个应用程序负载均衡器 (ALB),用于在 OSI 模型的第 7 层对流量进行负载平衡。

选择负载均衡器类型

AWS 弹性负载均衡器 (ELB) 产品组合支持以下负载均衡器:应用程序负载均衡器 (ALB)、网络负载均衡器 (NLB)、网关负载均衡器 (GWLB) 和经典负载均衡器 (CLB)。本最佳实践部分将重点介绍ALB和NLB,这两个与EKS集群最相关。

选择负载均衡器类型的主要考虑因素是工作负载要求。

如需更多详细信息以及所有 AWS 负载均衡器的参考,请参阅产品比较

如果您的工作负载为,请选择应用程序负载均衡器 (ALB) HTTP/HTTPS

如果工作负载需要在 OSI 模型的第 7 层进行负载平衡,则可以使用 AWS 负载均衡器控制器来配置 ALB;我们将在下一节中介绍配置。ALB 由前面提到的 Ingress 资源控制和配置,将 HTTP 或 HTTPS 流量路由到集群中的不同 Pod。ALB 为客户提供了更改应用程序流量路由算法的灵活性;默认路由算法是循环算法,最少未处理的请求路由算法也是另一种选择。

如果您的工作负载为 TCP,或者您的工作负载需要保留客户端的源 IP,请选择网络负载均衡器 (NLB)

网络负载均衡器在开放系统互连 (OSI) 模型的第四层(传输)上运行。它适用于基于 TCP 和 UDP 的工作负载。默认情况下,网络负载均衡器在向 pod 呈现流量时还会保留客户端地址的源 IP。

如果您的工作负载无法使用 DNS,请选择网络负载均衡器 (NLB)

使用 NLB 的另一个关键原因是您的客户无法使用 DNS。在这种情况下,NLB 可能更适合您的工作负载,因为网络负载均衡器上的 IP 是静态的。虽然建议客户端在连接负载均衡器时使用 DNS 将域名解析为 IP 地址,但如果客户端的应用程序不支持 DNS 解析并且只接受硬编码 IP,那么 NLB 更合适,因为 IP 是静态的,并且在 NLB 的生命周期内保持不变。

配置负载均衡器

在确定最适合您的工作负载的负载均衡器后,客户有多种配置负载均衡器的选项。

通过部署 AWS 负载均衡器控制器来配置负载均衡器

在 EKS 集群中配置负载均衡器的关键方法有两种。

  • 利用 AWS 云提供商(旧版)中的服务控制器

  • 利用 AWS 负载均衡器控制器(推荐)

默认情况下,Kubernetes 服务控制器(也称为树内控制器)会协调类型为 Kubernetes Service 的资源。 LoadBalancer该控制器内置于 AWS 云提供商组件中,该组件充当 Kubernetes 云控制器管理器。

预置的弹性负载均衡器的配置由注释控制,这些注解必须添加到 Kubernetes 服务清单中。服务控制器 AWS 负载均衡器控制器使用的注释是不同的。

服务控制器是旧版,目前仅修复了关键错误。当您创建类型为 Kubernetes 服务时 LoadBalancer,服务控制器默认会创建 AWS CLB,但如果您使用正确的注解,也可以创建 AWS NLB。值得注意的是,服务控制器不支持 Kubernetes 入口资源,也不支持 IPv6。

我们建议在您的 EKS 集群中使用 AWS 负载均衡器控制器来协调 Kubernetes 服务和入口资源。您必须在 Kubernetes 服务或 Ingress 清单中使用正确的注释,这样 AWS 负载均衡器控制器才能拥有对账流程。(而不是服务控制器)

如果您使用 EKS 自动模式,则会自动为您提供 AWS 负载均衡器控制器;无需安装。

选择负载均衡器 Target-Type

使用 IP 将 Pod 注册为目标 Target-Type

AWS 弹性负载均衡器:网络和应用程序,将收到的流量发送到目标组中的注册目标。对于 EKS 集群,您可以在目标组中注册两种类型的目标:实例和 IP,使用哪种目标类型会影响注册的内容以及如何将流量从负载均衡器路由到 Pod。默认情况下,AWS 负载均衡器控制器将使用 “实例” 类型注册目标,该目标将是工作节点的 IPNodePort,其含义包括:

  • 来自负载均衡器的流量将转发到上的工作节点 NodePort,这由 iptables 规则(由在节点上运行的 kube-proxy 配置)进行处理,然后转发到其 clusterIP(仍在节点上)上的服务,最后服务随机选择向其注册的 pod 并将流量转发给该服务。此流程涉及多个跳跃,可能会产生额外的延迟,尤其是因为服务有时会选择在另一个工作节点上运行的 Pod,而该工作节点也可能位于另一个可用区。

  • 由于负载均衡器将工作节点注册为目标,这意味着发送给目标的运行状况检查不会直接由 pod 接收,而是由工作节点在其 NodePort 上接收,运行状况检查流量将遵循上述相同路径。

  • 监控和故障排除更为复杂,因为负载均衡器转发的流量不会直接发送到 pod,而且您必须仔细地将工作节点上收到的数据包关联到 Service ClusterIP,最后再关联到 Pod,以便端到端地全面了解数据包的路径,以便进行正确的故障排除。

该图说明了负载均衡器的实例目标类型

相比之下,如果您按照我们的建议将目标类型配置为 “IP”,则含义如下:

  • 来自负载均衡器的流量将直接转发到容器,这简化了网络路径,因为它绕过了工作节点和服务集群 IP 的先前额外跃点,减少了服务将流量转发到另一个可用区的容器时可能产生的延迟,最后它消除了工作节点上的 iptables 规则处理开销。

  • 负载均衡器的运行状况检查由 pod 直接接收和响应,这意味着目标状态 “健康” 或 “不健康” 直接表示 Pod 的运行状况。

  • 监控和故障排除更加容易,任何用于捕获数据包 IP 地址的工具都会在其源和目标字段中直接显示负载均衡器与 pod 之间的双向流量。

该图说明了负载均衡器的 IP 地址目标类型

要创建使用 IP 目标的 AWS 弹性负载均衡,请添加:

  • alb.ingress.kubernetes.io/target-type: ip配置 Kubernetes Ingress(应用程序负载均衡器)时对 Ingress 清单进行注释

  • service.beta.kubernetes.io/aws-load-balancer-nlb-target-type: ip配置类型为 LoadBalancer (网络负载均衡器)的 Kubernetes 服务时,对服务清单进行注释。

配置负载均衡器运行状况检查

虽然 Kubernetes 提供了自己的运行状况检查机制(详见下一节),但我们建议将 ELB 运行状况检查作为在 Kubernetes 控制平面之外运行的补充保障措施来实现。即使在以下期间,这个独立层也会继续监视您的应用程序:

  • Kubernetes 控制面板中断

  • 探测执行延迟

  • kubelet 和 pod 之间的网络分区

对于在上述场景中需要最大可用性加速恢复的关键工作负载,ELB 运行状况检查提供了一个基本的安全网,可以与 Kubernetes 的本机机制并驾齐驱,而不是取而代之。

为了配置和微调 ELB 的运行状况检查,您必须在 Kubernetes 服务或 Ingress 清单中使用由服务控制器或 AWS 负载均衡器控制器协调的注释。

可用性和 Pod 生命周期

在应用程序升级期间,您必须确保您的应用程序始终可以处理请求,这样用户就不会遇到任何停机时间。在这种情况下,一个常见的挑战是在 Kubernetes 层和基础架构(例如外部负载均衡器)之间同步工作负载的可用性状态。接下来的几节重点介绍了解决此类情况的最佳实践。

注意

以下说明基于,EndpointSlices因为它是Kubernetes 终端节点的推荐替代品。在下文介绍的情景中,两者之间的差异可以忽略不计。AWS 负载均衡器控制器默认使用终端节点,您可以 EndpointSlices 通过在控制器上启用启用终端节点切片标志来启用。

使用运行状况检查

默认情况下,Kubernetes 运行进程运行状况检查,节点上的 kubelet 进程会验证容器的主进程是否正在运行。如果不是,则默认情况下它会重新启动该容器。但是,您也可以配置 Kubernetes 探测器,以识别容器进程何时运行但处于死锁状态,或者应用程序是否成功启动。探测器可以基于 exec、grpc、HttpGet 和 TcpSocket 机制。https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle/#probe-check-methods根据探测的类型和结果,可以重新启动容器。

请参阅下文附录部分中的 Pod 创建,重新审视 Pod 创建过程中的事件顺序。

使用就绪探测器

默认情况下,当 Pod 中的所有容器都在运行时Pod 条件被视为 “就绪”。但是,该应用程序可能仍然无法处理客户端请求。例如,应用程序可能需要从外部资源提取一些数据或配置才能处理请求。在这样的状态下,你既不想杀死应用程序,也不想向它转发任何请求。Readiness 探测器使你能够确保 Pod 不被视为 “就绪”,这意味着在探测结果出来之前,它不会被添加到 EndpointSlice对象中success。另一方面,如果探测进一步失败,则 Pod 将从 EndpointSlice 对象中移除。您可以在 Pod 清单中为每个容器配置就绪情况探测。kubelet每个节点上的进程对该节点上的容器运行就绪情况探测。

利用 Pod 就绪门

就绪探测的一个方面是其中没有外部feedback/influence 机制,节点上的 kubelet 进程执行探测并定义探测的状态。这不会对 Kubernetes 层中微服务本身之间的请求(东西向流量)产生任何影响,因为 EndpointSlice 控制器会使端点(Pod)列表始终保持最新状态。那你为什么以及何时需要一个外部机制呢?

当你使用 Kubernetes 服务类型的负载均衡器或 Kubernetes Ingress(用于南北流量)公开应用程序时,相应 Kubernetes 服务的 Pod IP 列表必须传播到外部基础设施负载均衡器,这样负载均衡器也有最新的目标列表。AWS 负载均衡器控制器弥合了这里的差距。当您使用 AWS 负载均衡器控制器和杠杆时target group: IP,就像 kube-proxy AWS 负载均衡器控制器也会收到更新(通过watch),然后它与 ELB API 通信,配置并开始将 Pod IP 注册为 ELB 上的目标。

当你对部署进行滚动更新时,会创建新的 Pod,一旦新 Pod 的状态为 “就绪”, old/existing Pod 就会终止。在此过程中,Kubernetes EndpointSlice 对象的更新速度比 ELB 将新 Pod 注册为目标所需的时间更快,请参阅目标注册。https://docs.aws.amazon.com/elasticloadbalancing/latest/application/target-group-register-targets.html在短时间内,您可能会出现 Kubernetes 层和基础设施层之间的状态不匹配,从而可能会丢弃客户端请求。在此期间,在 Kubernetes 层中,新的 Pod 将做好处理请求的准备,但从 ELB 的角度来看,它们还没有。

Pod Readiness Gates 允许您定义在将 Pod 条件视为 “就绪” 之前必须满足的额外要求。对于 AWS ELB,AWS 负载均衡器控制器监控 AWS ELB 上目标(Pod)的状态,一旦目标注册完成且其状态变为 “正常”,控制器就会将 Pod 的状态更新为 “就绪”。使用这种方法,您可以根据外部网络的状态来影响 Pod 状况,这是 AWS ELB 上的目标状态。Pod Readiness Gates 在滚动更新场景中至关重要,因为它使您能够防止部署的滚动更新终止旧 Pod,直到 AWS ELB 上新创建的 Pod 目标状态变为 “健康” 为止。

优雅地关闭应用程序

您的应用程序应通过正常关闭来响应 SIGTERM 信号,这样客户端就不会遇到任何停机时间。这意味着您的应用程序应该运行清理程序,例如保存数据、关闭文件描述符、关闭数据库连接、优雅地完成正在进行的请求并及时退出以完成 Pod 终止请求。应将宽限期设置得足够长,这样清理工作才能完成。要了解如何响应 SIGTERM 信号,您可以参考用于应用程序的相应编程语言的资源。

如果您的应用程序在收到 SIGTERM 信号后无法正常关闭,或者ignores/does 没有收到信号,则可以改为利用PreStop挂钩启动应用程序的正常关闭。Prestop 挂接在 SIGTERM 信号发送之前立即执行,它可以执行任意操作,而不必在应用程序代码本身中实现这些操作。

事件的总体顺序如下图所示。注意:无论应用程序正常关闭过程的结果如何,或者 PreStop 挂钩的结果如何,应用程序容器最终都会通过 SIGKILL 在宽限期结束时终止。

Pod 终止的过程顺序图

请参阅下文附录部分中的 Pod 删除,重新审视 Pod 删除过程中的事件顺序。

优雅地处理客户请求

删除 Pod 中的事件顺序与 Pod 创建 Pod 的顺序不同。创建 Pod 时,kubelet更新 Kubernetes API 中的 Pod IP,只有这样, EndpointSlice 对象才会更新。另一方面,当 Pod 被终止时,Kubernetes API 会同时通知 kubelet 和 EndpointSlice 控制器。仔细检查显示事件顺序的下图。

该图说明了更新 kubelet 的过程

状态从 API 服务器一直传播到上述节点上的 iptables 规则的方式产生了一个有趣的竞争条件。因为容器很有可能比每个节点上的 kube-proxy 更早地接收 SIGKILL 信号,更新本地 iptables 规则。在这种情况下,值得一提的两种情况是:

  • 如果您的应用程序在收到SIGTERM后立即直言不讳地删除了正在进行的请求和连接,这意味着客户将看到50倍的错误。

  • 即使您的应用程序确保在收到 SIGTERM 后已完全处理所有正在进行的请求和连接,但在宽限期内,新的客户端请求仍会发送到应用程序容器,因为 iptables 规则可能仍未更新。在清理过程关闭容器上的服务器套接字之前,这些新请求将导致新的连接。当宽限期结束时,那些在 SIGTERM 之后建立的连接,自发送 SIGKILL 以来,这些连接将无条件中断。

在 Pod 规范中设置足够长的宽限期可以解决这一挑战,但根据传播延迟和实际客户端请求的数量,很难预测应用程序正常关闭连接所需的时间。因此,这里不是那么完美但最可行的方法是使用 PreStop 挂钩来延迟SIGTERM信号,直到iptables规则更新为止,以确保不会向应用程序发送新的客户端请求,而是只有现有的连接才能继续。 PreStop 钩子可以是一个简单的执行处理程序,sleep 10例如。

当您使用 Kubernetes 服务类型的负载均衡器或使用 AWS 负载均衡器控制器和杠杆的 Kubernetes 入口(用于南北流量)公开应用程序时,上述行为和建议同样适用。target group: IP因为就像 kube-proxy AWS 负载均衡器控制器一样,它也会收到 EndpointSlice 对象的更新(通过监视),然后它与 ELB API 通信,开始从 ELB 注销 Pod IP。但是,视Kubernetes API或ELB API的负载而定,这也可能需要时间,而且SIGTERM可能早就已经发送到应用程序了。一旦 ELB 开始取消注册目标,它就会停止向该目标发送请求,因此应用程序将不会收到任何新请求,而且 ELB 还会启动取消注册延迟,默认情况下为 300 秒。在注销过程中,目标基本上是 ELB 等待与该目标的飞行中 requests/existing 连接耗尽draining的地方。一旦取消注册延迟到期,则该目标将处于未使用状态,并且向该目标发出的任何正在进行的请求都将被强制丢弃。

使用 Pod 中断预算

为您的应用程序配置 Pod 中断预算 (PDB)。PDB限制复制应用程序中因自愿中断而同时关闭的 Pod 的数量。它确保在或部署中保持最低数量或百分比的 pod 可用。 StatefulSet 例如,基于仲裁的应用程序需要确保运行的副本数量永远不会低于法定人数所需的数量。或者,Web 前端可以确保提供负载的副本数量永远不会低于总量的某个百分比。PDB 将保护应用程序免受诸如节点耗尽或部署新版本部署等操作的影响。请记住,PDB 无法保护应用程序免受非自愿中断(例如节点操作系统故障或网络连接中断)的影响。有关更多信息,请参阅 Kubernetes 文档中为应用程序指定中断预算。

参考

附录

创建 Pod

在部署 Pod 然后开始 healthy/ready 接收和处理客户端请求的场景中,必须了解事件的顺序是怎样的。让我们来谈谈事件的顺序。

  1. Pod 是在 Kubernetes 控制平面上创建的(即通过 kubectl 命令、部署更新或扩展操作)。

  2. kube-scheduler将 Pod 分配给集群中的一个节点。

  3. 在分配的节点上运行的 kubelet 进程接收更新(通过watch),并与容器运行时进行通信以启动 Pod 规范中定义的容器。

  4. 当容器开始运行时,kubelet 会更新 Pod 条件,就像 Kubernetes API Ready 中的 Pod 对象一样。

  5. EndpointSlice控制器(通过watch接收 Pod 状态更新,并将该 Pod IP/Port 作为新端点添加到相应 Kubernetes 服务的EndpointSlice对象(Pod IP 列表)中。

  6. 每个节点上的 kube-proxy 进程接收EndpointSlice 对象的更新(通过watch),然后使用新的 Pod 更新每个节点上的 iptables 规则。 IP/port

Pod 删除

就像创建 Pod 一样,必须了解删除 Pod 期间的事件顺序是什么。让我们来谈谈事件的顺序。

  1. Pod 删除请求会发送到 Kubernetes API 服务器(即通过kubectl命令、部署更新或扩展操作)。

  2. Kubernetes API 服务器通过在 Pod 对象中设置 deletionTimestamp 字段来启动宽限期,默认为 30 秒。(宽限期可以通过以下方式在 Pod 规范中配置terminationGracePeriodSeconds

  3. 在节点上运行的kubelet进程接收 Pod 对象的更新(通过监视),并向该 Pod 中每个容器内的进程标识符 1(PID 1)发送 SIGTERM 信号。然后它会观看terminationGracePeriodSeconds.

  4. EndpointSlice控制器还接收来自步骤 2 的更新(通过watch),并在相应 Kubernetes 服务的EndpointSlice对象(Pod IP 列表)中将端点条件设置为 “终止”。

  5. 每个节点上的 kube-proxy 进程(通过watch)接收EndpointSlice 对象的更新,然后 kube-proxy 会更新每个节点上的 iptables 规则,以停止将客户端请求转发到 Pod。

  6. terminationGracePeriodSeconds过期时,kubelet会向 Pod 中每个容器的父进程发送 SIGKILL 信号并强制终止它们。

  7. TheEndpointSlice控制器从EndpointSlice对象中移除端点。

  8. API 服务器删除 Pod 对象。