负载均衡 - Amazon EKS

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

负载均衡

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

选择 Load Balancer 类型

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

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

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

如果您的工作负载为 HTTP/HTTPS,请选择 Application Load Balancer (ALB)

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

如果您的工作负载是 TCP,或者您的工作负载需要保留客户端的源 IP,请选择 Network Load Balancer (NLB)

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

如果您的工作负载无法使用 DNS,请选择 Network Load Balancer (NLB)

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

配置负载均衡器

在确定最适合您的工作负载的 Load Balancer 之后,客户可以选择多种配置负载均衡器。

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

在 EKS 集群中配置负载均衡器有两种主要方法。

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

  • 利用 AWS Load Balancer 控制器(推荐)

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

已配置的 Elastic Load Balancer 的配置由必须添加到 Kubernetes 服务清单中的注释控制。服务控制器AWS Load Balancer 控制器使用的注解不同。

服务控制器是旧版,目前仅修复了严重的错误。当您创建类型为 Kubernetes 服务时 LoadBalancer,服务控制器会默认创建 AWS 负载均衡,但如果您使用正确的注释,也可以创建 AWS NLB。值得注意的是,服务控制器不支持 Kubernetes Ingress 资源,也不支持。 IPv6

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

如果您使用的是 EKS 自动模式,则会自动为您提供 AWS Load Balancer 控制器;无需安装。

选择 Load Balancer 目标类型

使用 IP 目标类型将 Pod 注册为目标

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

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

  • 由于 Load Balancer 将工作节点注册为其目标,这意味着发送到目标的运行状况检查不会直接被 Pod 接收,而是由 Pod 上的 Worker 节点接收,运行状况检查流量将遵循上述相同的路径。 NodePort

  • 监控和故障排除更为复杂,因为 Load Balancer 转发的流量不会直接发送到 pod,因此您必须仔细关联工作节点上收到的数据包与服务 ClusterIP,并最终关联到 Pod,才能 end-to-end完全了解数据包的路径,以便进行正确的故障排除。

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

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

  • 来自 Load Balancer 的流量将直接转发到 pod,这简化了网络路径,因为它绕过了之前额外的 Worker 节点和服务集群 IP 跳数,减少了服务将流量转发到另一个可用区中的 pod 时本来会产生的延迟,最后它消除了 iptables 规则在工作节点上的开销处理。

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

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

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

要创建使用 IP 目标的 AWS Elastic Load Balancing,请添加:

  • alb.ingress.kubernetes.io/target-type: ip配置 Kubernetes Ingress(Application Load Balancer)时的 Ingress 清单的注释

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

配置 Load Balancer 运行状况检查

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

  • Kubernetes 控制飞机中断

  • 探测器执行延迟

  • kubelet 和 pod 之间的网络分区

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

要在 ELB 上配置和微调运行状况检查,您必须在 Kubernetes 服务或 Ingress 清单中使用将由服务控制器或 AWS Load Balancer Controller 协调的注释。

可用性和 Pod 生命周期

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

注意

以下解释是基于的,EndpointSlices因为它是 Kubernetes 中端点的推荐替代品。就下文所述情景而言,两者之间的差异可以忽略不计。默认情况下,AWS Load Balancer Controller 使用终端节点,您可以 EndpointSlices 通过在控制器enable-endpoint-sliceflag上启用来启用。

使用运行状况检查

默认情况下,Kubernetes 会运行进程运行状况检查,节点上的 kubelet 进程会验证容器的主进程是否正在运行。如果不是,则默认情况下它会重新启动该容器。但是,您也可以配置 Kubernetes 探测器来识别容器进程何时正在运行但处于死锁状态,或者应用程序是否成功启动。探测器可以基于 exec、grpc、HttpGet 和 tcpSocket 机制。根据探测的类型和结果,可以重新启动容器。

请参阅下方附录中的 Pod 创建部分,重新了解 Pod 创建过程中的事件顺序。

使用就绪探测器

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

利用 Pod 就绪门

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

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

当你对部署进行滚动更新时,会创建新的 Pod,一旦新 Pod 的状态为 “就绪”, old/existing Pod 就会被终止。在此过程中,Kubernetes EndpointSlice 对象的更新速度快于 ELB 将新 Pod 注册为目标所需的时间,请参阅目标注册。在短时间内,Kubernetes 层和可能丢弃客户端请求的基础设施层之间可能会出现状态不匹配。在这段时间内,在 Kubernetes 层中,新的 Pod 已经准备好处理请求了,但从 ELB 的角度来看,它们还没有。

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

优雅地关闭应用程序

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

如果您的应用程序在收到 SIGTERM 信号后无法正常关闭,或者它忽略/未收到信号,则可以改用 PreStophook 来启动应用程序的优雅关闭。Prestop hook 在发送 SIGTERM 信号之前立即执行,它可以执行任意操作,而不必在应用程序代码本身中实现这些操作。

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

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 hook 可以是一个简单的 Exec 处理程序,例如。sleep 10

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

使用 Pod 中断预算

为您的应用程序配置 Pod 中断预算 (PDB)。 PDBlimits 由于自愿中断而同时关闭的复制应用程序的 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 会像 Kubernet es API Ready 中的 Pod 对象一样更新 Pod 条件

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

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

吊舱删除

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

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

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

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

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

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

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

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

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