DigitalOcean Kubernetes (DOKS) 是一项托管的 Kubernetes 服务。它使用完全托管的控制平面,具有高可用性、自动扩展特性,以及与 DigitalOcean 负载均衡器和块存储的原生集成的 Kubernetes 集群。DOKS 集群与标准 Kubernetes 工具链以及 DigitalOcean API 和 CLI 兼容。
在上周举行的 DigitalOcean Deploy 大会上,DigitalOcean Kubernetes团队高级软件工程师 Oliver Love分享了中小型企业的 Kubernetes 可靠性最佳实践。
可靠性对于中小型企业(SMBs)与大型企业来说,含义是不同的。中小型企业往往资源相对有限,部署规模较小,这在确保其 Kubernetes 系统的可靠性方面构成了独特的挑战。
而Oliver Love这次分享的重点是中小型企业在集群上面临的可靠性挑战。所以这次分享的实践经验适用于集群节点在500个以下的企业。
他分享的内容主要包括以下几方面:
- 可靠性挑战
- 正确地估算资源需求
- 探针(健康检查)
- 部署策略
- Pod 调度
- Pod 拓扑分布约束(Pod topology spread constraints)
- 滚动升级
- 唯一容器镜像标签
可靠性挑战
在功能层面,经常出现的就是应用程序不工作。导致这种功能性问题出现的原因有很多。例如:
- Pod crash-looping;
- 资源限制或请求不正确,这会导致你的Pod被终止,并可能造成部分用户服务不可用;
- 配置错误的依赖关系;
- 负载均衡或入口配置不正确,这会导致你的流量无法正确接入后端应用程序;
- 非唯一容器镜像标签;
此外,有的客户由于 Pod 受到资源限制,无法完成所需任务,客户可能会遇到更高的延迟。为了修复这些问题,我们需要在环境中实施有效的可靠性改进。这需要结构化的方法和 Kubernetes 的最佳实践。其中之一就是正确设置你的 Pods。
围绕 Pod 的优化
![](https://www.aidroplet.cn/wp-content/uploads/2024/07/2-1-2.png)
在集群中部署资源时,明确指定所需分配给已部署资源的 CPU 和内存(RAM)数量至关重要。通过定义这些资源限制,可以辅助Kubernetes调度器(Kube-scheduler)在将 Pods 分配至工作节点时做出更好的调度决策。完成 Pods 的分配后,Kubernetes 组件将确保执行 Pods 及其容器的资源请求和限制。
重要的是,必须确保 Pods 的工作节点池中包含能够满足这些 CPU 和 RAM 请求的工作节点。否则,可能会发生 Pods 无法被调度到工作节点的情况,进而导致应用程序出现停机。此外,开发者需要避免过度请求资源,以免支付不必要的额外费用或占用可能被其他工作负载使用的资源。
在 Kubernetes 环境中,我们建议对CPU和内存(RAM)进行资源限制,目的是防止部署的资源超出其分配的额度。理解这些限制的行为差异至关重要:
- CPU限制:当容器达到CPU限制时,其运行将被限流。这种限流可能会影响应用程序的性能,尤其是在容器长时间运行在CPU限制值下时。尽管如此,容器不会被终止或驱逐,因为CPU是一种可压缩资源。尽管应用仍可访问CPU资源,但其性能可能会受到影响。如果应用程序持续接近或达到CPU限制,建议重新评估这些限制,以避免性能下降。
- 内存限制:与CPU不同,内存是一种不可压缩资源。当容器达到内存限制时,它可能会被终止。因此,正确设置内存限制对于确保应用程序的稳定性和避免意外终止至关重要。
开发者在配置资源限制时,应考虑这些行为差异,合理设置CPU和内存限制,以优化应用程序的性能和稳定性。如果应用程序频繁达到CPU限制,应重新评估资源需求,可能需要增加CPU分配或优化应用程序的资源使用。同时,应确保内存限制设置得当,避免因内存不足而导致容器被终止。我们建议你的应用程序应该进行 CPU 的资源限制,除非有特殊情况。
在Kubernetes中,内存资源的管理至关重要。当Pod的内存消耗超过其分配的限制时,容器可能会被标记为终止候选。这种情况下,Pod的状态将显示为“内存不足”(Out of Memory,OOM)。这一机制对于资源管理至关重要,因为出现 Pod OOM,可能会出现服务的短暂中断。
探针(健康检查)
在提升 Kubernetes 应用的可靠性方面,探针的使用至关重要。这里我们详细讨论两种关键的Kubernetes探针:Readiness探针和Liveness探针。
Readiness探针
Readiness 探针的主要作用是指示容器是否已经准备好接收并服务流量。在应用程序的初始化或运行过程中,可能需要执行一些任务,例如从外部源拉取密钥、加载或缓存数据、与其他服务进行交互等。在这些任务完成之前,应避免向容器发送流量。
如果 Kubernetes 服务指向 Pod,并且其 Readiness 探针报告容器尚未就绪,如示下图中编号 1 的红色 Pod 所示,这些 Pod 将不会接收流量。相反,只有那些报告为就绪状态的Pod,如下图中编号 2 和编号 3 的 Pod,才会接收流量。这是一种理想的状态,确保流量仅被发送到那些已经准备好处理请求的 Pod。
![](https://www.aidroplet.cn/wp-content/uploads/2024/07/3-1-2.png)
重要的是,如果 Pod 容器定义中未包含 Readiness 探针,容器将默认被视为就绪,并立即被考虑接收流量。这可能导致系统在容器完成初始化任务之前接收流量,从而引发错误。因此,如果应用程序在启动时需要执行初始化任务,添加 Readiness 探针是最佳实践。
Liveness探针
Liveness 探针用于确保应用程序持续运行并处于健康状态。如果检测到不可恢复的问题,如内存损坏、内存泄漏或死锁,Liveness 探针可以帮助通过重启 Pod 来恢复服务。此探针会定期检查Pod应用是否正常运行,如果检查失败,它将终止 Pod。这种机制对于处理那些可以通过重启解决的暂时性问题非常有用。
Liveness 探针的一个关键特性是其不应依赖于外部逻辑或组件。引入外部依赖可能会引发级联事件,导致多个应用的探针失败,从而引发大量 Pod 重启。为了避免这种情况,建议 Liveness 探针的逻辑保持简单,不与外部组件、API或第三方服务进行交互。这样可以减少因外部组件故障导致的多米诺效应。
此外,如果 Liveness 探针的逻辑与外部组件交互,并且故障未被及时修复,可能会使 Pod 陷入频繁重启的循环,延长恢复时间。这不仅会影响用户体验,还会增加系统的不可靠性。
总之,合理配置 Readiness 和 Liveness 探针对于确保 Kubernetes 应用的高可用性和稳定性至关重要。开发者应仔细考虑这些探针的设置,以优化应用程序的运行和故障恢复机制。
部署策略优化
在这里,我们将探讨两种在Kubernetes环境中常用的部署策略:金丝雀部署和蓝绿部署。这些策略旨在提高应用部署的可靠性和减少部署过程中可能出现的风险。
金丝雀部署
![](https://www.aidroplet.cn/wp-content/uploads/2024/07/4-1-1.png)
金丝雀部署是一种渐进式的部署方法,它首先推出少量带有新版本的副本,并监控其在生产流量下的表现。这种方法的关键在于逐步验证新版本的行为,确保其稳定性和性能。
- 初始阶段:部署少量的新版本副本,使其接收一部分生产流量。例如,如果总共有 10 个 Pods,可能只有 1 个 Pod 运行新版本,从而接收10%的流量。
- 监控与评估:在新版本副本运行期间,密切监控其表现。如果新版本表现良好,可以逐步增加新版本副本的数量,从而增加其接收的流量比例。
- 问题处理:如果在监控过程中发现问题,可以迅速回滚到旧版本,同时从日志和指标中收集有关故障域的有用信息,以便进一步分析和修复。
通过这种方式,金丝雀部署可以将问题的影响限制在一小部分生产流量中,从而减少整体部署的风险。
蓝绿部署
![](https://www.aidroplet.cn/wp-content/uploads/2024/07/5-1.png)
另一个可以轻松通过 DigitalOcean Kubernetes实现的部署就是蓝绿部署。蓝绿部署是一种并行的部署策略,它通过分离的两个部署来引入新版本。这种方法允许在不中断现有服务的情况下,逐步切换到新版本。
- 设置:在Kubernetes中,可以设置两个部署,一个包含新版本(绿色部署),另一个保留现有版本(蓝色部署)。这两个部署共享相同的服务标签,但通过不同的版本标签进行区分。
- 流量切换:通过更改Kubernetes服务选择器字段,可以将全部流量重定向至新版本的绿色部署。这一操作取决于收集到的指标和日志,确保新版本在全面部署前表现良好。
- 决策与回滚:如果新版本测试成功,可以中断旧版本的蓝色部署,并继续使用新版本的绿色部署。如果测试不成功,可以通过将服务选择器标签切换回旧版本V1,轻松回滚到蓝色部署,并撤除绿色部署。
实施建议
- 金丝雀部署:适用于需要逐步验证新版本表现的场景,通过逐步增加新版本副本的数量,降低风险。
- 蓝绿部署:适用于需要快速切换至新版本的场景,通过并行运行两个部署,确保服务的连续性和可回滚性。
开发者应根据应用需求和风险偏好,选择合适的部署策略,并确保在部署过程中密切监控应用表现,以便及时做出调整。通过这些策略,可以有效地减少部署过程中可能出现的问题,提高应用的稳定性和可靠性。
Pod 调度优化
在 DigitalOcean Kubernetes 中,你的集群可能包含一个或多个运行应用程序的工作节点。这意味着,如果Pod未被正确调度至这些工作节点,它们可能会因工作负载的故障而受到影响。此外,由于 DigitalOcean Kubernetes 平台只提供 Kubernetes 最近的三个小修订版(minor version),这意味着你的集群需要定期升级,以支持最新的Kubernetes 版本。这种升级将触发工作节点的更新,如果应用程序只部署在一个节点上,可能会引发可靠性问题。同样,手动回收工作节点或使用节点自动扩展时,也可能遇到调度问题。为了解决这些问题,Kubernetes 提供了一个强大的特性——Pod 拓扑分布约束(Pod Topology Spread Constraints)。这一特性允许开发者控制 Pod 在集群节点间的调度方式,从而优化应用的可靠性和稳定性。
在这里再简单解释一下 Pod 拓扑分布约束。Pod拓扑分布约束是Kubernetes中的一种功能,它允许用户控制Pod在集群中各个节点上的分布方式。这个特性尤其在大型集群中非常有用,它可以帮助提高系统的容错能力、数据局部性和资源利用率。
拓扑分布约束基于节点的元数据(例如区域、机架、标签等)来决定Pod的分布,从而确保Pod不会过于集中在一个特定的拓扑域内。这可以防止在某个特定的拓扑层级(如一个机架或一个可用区)发生故障时,导致大量Pod不可用的情况。
Pod拓扑分布约束有两个主要组成部分:1、最大不平衡度(maxUnavailability):定义了在满足分布约束的同时,可以有多少个Pod暂时不可用。这在滚动更新或删除节点时特别重要,因为这将允许Kubernetes在不违反分布约束的情况下,暂时让一些Pod不可用。2、最大分布数(maxSpread):定义了在每个拓扑域内,Pod的最大分布数量。例如,你可以指定在一个机架中最多只能有x个副本。
![](https://www.aidroplet.cn/wp-content/uploads/2024/07/6-1.png)
如果我们以一个包含三个工作节点的 DigitalOcean Kubernetes 集群为例,该集群运行着一个具有四个副本的部署。我们希望避免上图右上方所示的情形,即节点 1 成为单一 Pod的单一故障点。如果发生回收事件、集群升级导致节点 1 被清空,或节点 1 上发生的任何其他问题,Kubernetes 重新调度 Pod 到另一工作节点所需的时间可能会导致应用停机和可靠性问题。下图右方通过在部署描述中使用拓扑分布约束解决了这些问题。这样我们就可以将Pod分散到多个工作节点上,从而降低了来自节点1的单一故障点的风险。
设置唯一的容器镜像标签
最后,关于创建新节点时的可靠性最佳实践,容器规范中的默认镜像代码策略确保镜像仅在节点上没有已存在的情况下才会被拉取,这里的节点指的是Pod被调度的节点。如果你对应用的不同构建重复使用相同的标签,例如“latest”,这可能会导致不一致性。设想这样一个场景:开发者构建了一个新版本的代码,并将其打包为镜像,使用“latest”标签推送到容器注册表(如OCR)。随后,在 DOKS 集群中使用该标签创建部署。在首次部署时,所有节点都会从注册表中拉取镜像,因为他们本地没有缓存。但是,如果开发者更新了代码,构建了一个新的镜像,并再次使用“latest”标签推送到 OCR 注册表,然后触发一个新的滚动更新,问题就会出现。
在这种情况下,那些已经有先前“latest”标签镜像缓存的节点上运行的 Pod 将继续使用旧代码,而新节点上的Pod则会拉取最新的镜像。这将导致应用版本在集群中的不一致性,影响应用的稳定性和可靠性。
为了避免这种不一致性,并确保 Pod 始终运行预期的应用程序版本,强烈建议使用语义版本控制标签(例如 v1.1.2)或提交哈希(例如a1b2c3)来唯一标识每个构建。语义版本控制标签或提交哈希可以帮助开发者区分不同的二进制文件和配置,防止版本冲突,并确保应用的正确版本在集群的所有节点上一致部署。
以上就是中小型企业和初创企业在使用 DigitalOcean Kubernetes 过程中,为了提升集群可靠性,总结下来的一些最佳实践。如果大家在使用 DigitalOcean Kubernetes 时遇到问题,或者有任何功能服务建议,可以联系我们。