本文属于机器翻译版本。若本译文内容与英语原文存在差异,则一律以英文原文为准。
负载均衡
提示
通过亚马逊 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 服务时 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 目标的 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 运行进程运行状况检查
请参阅下文附录部分中的 Pod 创建,重新审视 Pod 创建过程中的事件顺序。
使用就绪探测器
默认情况下,当 Pod 中的所有容器都在运行时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
优雅地关闭应用程序
您的应用程序应通过正常关闭来响应 SIGTERM 信号,这样客户端就不会遇到任何停机时间。这意味着您的应用程序应该运行清理程序,例如保存数据、关闭文件描述符、关闭数据库连接、优雅地完成正在进行的请求并及时退出以完成 Pod 终止请求。应将宽限期设置得足够长,这样清理工作才能完成。要了解如何响应 SIGTERM 信号,您可以参考用于应用程序的相应编程语言的资源。
如果您的应用程序在收到 SIGTERM 信号后无法正常关闭,或者ignores/does 没有收到信号
事件的总体顺序如下图所示。注意:无论应用程序正常关闭过程的结果如何,或者 PreStop 挂钩的结果如何,应用程序容器最终都会通过 SIGKILL 在宽限期结束时终止。
请参阅下文附录部分中的 Pod 删除,重新审视 Pod 删除过程中的事件顺序。
优雅地处理客户请求
删除 Pod 中的事件顺序与 Pod 创建 Pod 的顺序不同。创建 Pod 时,kubelet更新 Kubernetes API 中的 Pod IP,只有这样, EndpointSlice 对象才会更新。另一方面,当 Pod 被终止时,Kubernetes API 会同时通知 kubelet 和 EndpointSlice 控制器。仔细检查显示事件顺序的下图。
状态从 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 中断预算
参考
-
KubeCon 2019 年欧洲会议- 准备好了吗? 深入探讨服务运行状况的 Pod 就绪门槛
-
图书- Kubernetes 在行动
-
AWS 博客- 如何在 EKS 上使用 ALB 快速扩展应用程序(不损失流量)
附录
创建 Pod
在部署 Pod 然后开始 healthy/ready 接收和处理客户端请求的场景中,必须了解事件的顺序是怎样的。让我们来谈谈事件的顺序。
-
Pod 是在 Kubernetes 控制平面上创建的(即通过 kubectl 命令、部署更新或扩展操作)。
-
kube-scheduler将 Pod 分配给集群中的一个节点。 -
在分配的节点上运行的 kubelet 进程接收更新(通过
watch),并与容器运行时进行通信以启动 Pod 规范中定义的容器。 -
当容器开始运行时,kubelet 会更新 Pod 条件,就像 Kubernetes API
Ready中的 Pod 对象一样。 -
EndpointSlice控制器(通过
watch)接收 Pod 状态更新,并将该 Pod IP/Port 作为新端点添加到相应 Kubernetes 服务的EndpointSlice 对象(Pod IP 列表)中。 -
每个节点上的 kube-proxy
进程接收EndpointSlice 对象的更新(通过 watch),然后使用新的 Pod 更新每个节点上的 iptables规则。 IP/port
Pod 删除
就像创建 Pod 一样,必须了解删除 Pod 期间的事件顺序是什么。让我们来谈谈事件的顺序。
-
Pod 删除请求会发送到 Kubernetes API 服务器(即通过
kubectl命令、部署更新或扩展操作)。 -
Kubernetes API 服务器通过在 Pod 对象中设置 deletionTimestamp
字段来启动宽限期 ,默认为 30 秒。(宽限期可以通过以下方式在 Pod 规范中配置 terminationGracePeriodSeconds) -
在节点上运行的
kubelet进程接收 Pod 对象的更新(通过监视),并向该 Pod 中每个容器内的进程标识符 1(PID 1)发送 SIGTERM信号。然后它会观看 terminationGracePeriodSeconds. -
EndpointSlice控制器
还接收来自步骤 2 的更新(通过 watch),并在相应 Kubernetes 服务的EndpointSlice对象(Pod IP 列表)中将端点条件设置为 “终止”。 -
每个节点上的 kube-proxy
进程(通过 watch)接收EndpointSlice 对象的更新,然后 kube-proxy 会更新每个节点上的 iptables规则,以停止将客户端请求转发到 Pod。 -
当
terminationGracePeriodSeconds过期时,kubelet会向 Pod 中每个容器的父进程发送 SIGKILL信号并强制终止它们。 -
API 服务器删除 Pod 对象。