随着基于面向服务架构(SOA)的容器化服务的兴起,对Kubernetes等编排软件的需求正在迅速增加。Kubernetes非常适合于大型系统,但其复杂性和缺乏透明度会导致云成本增加、部署延迟和利益相关者的沮丧。大型企业使用它来纵向和横向扩展其应用程序和底层基础设施,以满足不同的负载,而使Kubernetes具有如此适应性的细粒度控制也使得有效地调整和优化变得困难。
Kubernetes架构在集群内做出自主的工作负载分配决策。然而,Kubernetes本身并不能确保高可用性。它可以在只有一个主节点的生产环境中轻松操作。类似地,Kubernetes不帮助优化成本。例如,如果集群中的服务器利用率仅为20%,它不会发出警报或警告,这可能表明我们在过度配置的基础设施上浪费了资金。
优化Kubernetes集群以平衡性能和可靠性以及运行这些集群的成本至关重要。在本文中,我们将学习如何借助机器学习(ML)技术优化Kubernetes。
Kubernetes的复杂性使手动优化变得徒劳
默认情况下,Kubernetes会分配大量的计算和内存资源,以防止运行时出现性能低下和内存不足的错误。然而,使用默认值构建节点集群会导致云成本浪费和集群利用率低下,而无法确保足够的性能。此外,随着容器数量的增加,需要考虑的变量(CPU、RAM、请求、限制和副本)数量也会增加。
在K8s集群中,我们必须配置数个参数。但是,正如下文所示,由于Kubernetes的复杂性,手动优化这些参数既耗时又无效。
CPU和内存
CPU定义计算处理资源,而内存定义pod可用的内存单元。我们可以为pod可以使用的CPU和内存配置一个请求值。如果运行pod的节点有可用的资源,pod可以将其消耗到设置的CPU和内存限制。
设置CPU和内存限制非常重要,但要找到正确的设置以确保效率并不容易。为了优化这些限制,我们需要预测未来的CPU和内存需求——这是一个很难计算的问题。然后,为了优化这些资源,我们必须对值进行微调,这既繁琐又耗时。
应用程序特定参数
除了Kubernetes技术组件(如CPU或内存)之外,我们还应该查看特定于应用程序的参数。其中包括堆大小、工作线程、数据库连接池和垃圾收集等,因为这些也会对资源的有效利用率产生重大影响。
堆大小
以Java应用程序为例。配置JVM(Java虚拟机),包括确定可用内存和堆大小,在调整大小方面起着至关重要的作用。性能基准(例如Java应用程序的性能基准)显示,内存分配为256Mi(Mebibyte)或512Mi时,堆大小仍为127Mi左右。在这个设置中没有立即分配512Mb的理由,因为堆大小保持不变,占50%。然而,一旦超过512Mi,堆大小也会呈指数增长。
垃圾收集
除了堆大小之外,垃圾收集也是必须配置的性能指标。所以知道如何优化这一点也是关键。通常,如果内存大小设置处于关闭状态,垃圾收集器也会低效运行。换言之,JVM堆大小调整得越好,垃圾收集器应该运行得越优化。
访问系统资源
容器化应用程序通常可以访问所有系统级资源,但这并不意味着单个pod运行时可以最佳地使用它们。运行同一应用程序的多个线程,而不是将更大的CPU或内存值分配给单个容器,这可能是有益的。
数据库连接
除了应用程序容器本身之外,资源性能的影响可能来自其他因素,例如数据库。如果从单个应用程序容器到数据库的性能可能很好,那么当多个pod同时连接到数据库时,可能会变得很困难。数据库池在这里可能会有所帮助。
探针
在Kubernetes环境中监视容器化应用程序的健康状态是使用Kubernete探针完成的。我们可以在K8s配置中设置活动性、就绪性和启动探测。
活动性
活动性探针检查应用程序的运行状况。这对于验证应用程序是否仍在运行(死锁)特别有用。活动性探针不仅检查容器的运行状态,还试图确保容器内的应用程序已启动并正在运行。pod可能已经就绪,但这并不意味着应用程序已经就绪。最简单的活动性探测类型是GET HTTP请求,这将导致HTTP 200-399 RESPONSE消息。
就绪性
就绪性探针检查应用程序是否准备好接受流量。如果就绪探测处于失败状态,则不会向pod发送IP地址,并且pod将从相应的服务中删除。就绪性探针保证容器内运行的应用程序100%就绪,可以使用。就绪性探针总是期望HTTP 200 OK RESPONSE作为反馈,以确认应用程序是否正常。
启动
启动探针检查容器应用程序是否已启动。该探针是第一个启动的探针,其他两个探针将被禁用,直到启动探针处于成功状态。
配置Kubernetes探针
Kubernetes探针提供了几个可以配置的不同参数。这里的关键是微调探针配置,对活动性和就绪性探针都有效:
——timeoutSeconds反映探测超时的秒数。默认值为1秒。如果此参数设置得过低或过高,可能会导致容器或应用程序失败。这可能会导致用户在尝试连接到工作负载时收到错误消息。
——periodSeconds反映执行探测检查的频率(以秒为单位)。与timeoutSeconds参数类似,找到准确的设置非常重要。如果检查太频繁,可能会使应用程序工作负载饱和。如果检查不够频繁,可能会导致应用程序工作负载失败。
——failureThreshold反映失败的请求/响应数。这里的默认值是三个。这意味着,默认情况下,如果timeoutSeconds和periodSeconds配置为默认值,容器可能会在三秒钟后被标记为失败。
——initialDelaySeconds反映了容器成功启动后探测器开始发信号的等待状态。默认值为零,这意味着运行中的容器在成功启动后立即发送探测。
水平pod自动缩放(HPA)
Horizontal Pod Autoscaler通过部署更多的pod来满足不断增长的需求,从而扩展了工作量。当负载降低时,它会终止一些pod以满足降低的需求。
默认情况下,HPA根据目标CPU利用率向外扩展(添加pod)或向内扩展(删除pod)。或者,我们可以根据内存利用率或自定义使用度量来配置它。
虽然添加更多的pod(扩展)可能会带来更好的应用程序性能,但情况并非总是如此。正如我们在前面讨论JVM堆大小和垃圾收集器调优时所看到的,有时添加更多的pods并不能提高服务的性能。与已经讨论过的其他大小调整复杂性一样,微调容器工作负载的水平缩放可能很难手动执行。
垂直pod自动缩放(VPA)
与水平缩放相反的是垂直缩放,这涉及调整性能不佳的pod的大小,增加CPU和内存限制,或者减少未充分利用的pod的CPU和内存。
与正确调整HPA的复杂性相似,正确调整VPA也存在同样的挑战。工作负载通常是动态的。在执行调整和调整时,活动用户的变化、季节性负载峰值、某些群集组件的计划外停机等都是需要考虑的因素。因此,我们可以定义VPA配置来调整pod的CPU和内存限制,但很难确定新值。
应该注意的是,默认情况下,VPA不能与HPA组合,因为两者都根据相同的度量(CPU目标利用率)进行缩放。
副本
副本表示工作负载所需的相同运行pod的数量。我们可以在K8s配置中定义副本的值。HPA还可以控制pod的副本数量。
很难确定应该为pod配置的副本的确切数量,因为如果pod的工作负载发生变化,一些副本可能会利用不足。此外,手动更新pod配置非常繁琐。
随着集群复杂性的增加,手动配置和微调这些参数变得越来越困难。下图说明了我们可以调整以优化资源使用的不同Kubernetes参数。
借助机器学习优化Kubernetes
由于对容器的实际操作行为了解甚少,DevOps团队很难确定资源的最佳值。我们可以在不同级别的容器资源优化中使用ML。
借助最先进的ML算法,我们可以理解使用模式。通过从Prometheus这样的云监控框架收集粒度容器数据,学习活动模式并应用复杂的算法生成最佳结果,ML可以生成精确和自动化的建议。这种方法将静态资源规范替换为从ML支持的使用模式分析中导出的动态规范。
基于ML的优化方法
优化Kubernetes应用程序是一个多目标优化问题。这里,资源配置设置充当输入变量,而性能、可靠性和运行应用程序的成本充当输出。基于ML的优化可以通过两种方式进行:试验和观察。
基于试验的优化
我们在非生产环境中执行基于试验的优化,使用各种测试用例来模拟潜在的生产场景。我们可以运行任何测试用例、评估结果、更改变量并重新运行测试。基于试验的优化的好处包括检查任何场景的灵活性以及对结果进行深入分析的能力。然而,这些试验仅限于模拟环境,可能不包含真实情况。
基于试验的优化通常包括以下五个步骤。
定义输入变量
输入变量包括但不限于:计算、存储、内存、请求、限制、副本数量和特定于应用程序的参数,如堆大小、垃圾收集和错误处理——实际上,任何可能影响输出或目标的配置设置。
定义优化目标
我们在此步骤中指定要最小化或最大化的度量。我们还可以优先考虑试图优化的变量,强调一些目标而不是其他目标。例如,我们可以考虑在不太关注成本的情况下提高计算密集型任务的性能。
尽管基于ML的优化是有帮助的,但仍需要运维和业务团队来确定可能的或需要的(预期的)优化目标。例如,优化目标可以是使用历史可观测性信息来帮助优化性能。同样,可以使用服务级别目标和其他关键性能指标来优化规模和可靠性。
从业务角度来看,你可能希望优化成本,这可能涉及使用每月固定成本,或者在季节性时间段内预测异常或高峰负荷的预算。
设置优化场景
一旦确定了优化目标并达成一致,我们需要在运行试验之前确定要优化的不同可能场景。与其优化系统可能遇到的所有场景,不如将重点放在那些对性能和业务影响最大的场景上。
假设目标是优化性能,并允许作为预期峰值负载的一部分进行精确的自动调整大小。在这种情况下,我们将使用过去不同的数据集来运行预测。例如,在电子商务平台的情况下,这些高峰负载可能发生在超级碗之后,并导致感恩节销售、节礼日或假日购物高峰。如果我们的目标是更好地了解成本优化,那么这些参数和预期的运行场景将有所不同。
一旦定义了优化目标并达成一致,我们就可以设置实际场景并为这些场景构建负载测试。负载测试将帮助我们在实验阶段模拟生产负载。对于负载测试,我们可以使用几个为Kubernetes环境设计的开源或商业工具。
执行试验
我们使用自动化在集群中使用基线参数自动部署应用程序。然后,该自动化运行基准测试以向系统施加负载。基准测试完成后,收集度量并发送到ML服务进行分析。然后ML创建一组新的参数值以在负载下进行测试,实验过程继续进行。
在每次迭代中,该算法都会对应用程序的参数空间有一个完整的理解,并接近Kubernetes集群的最佳配置目标。
分析结果
试验结束后,我们可以做更多的分析。通过制作图表来说明输入和期望结果之间的关系,我们可以发现哪些参数对结果有重大影响,哪些参数影响较小。
基于观察的优化
通过观察实际系统行为,可以在生产中或生产外执行基于观察的优化。对于动态条件,例如高度波动的用户流量,这可能是最佳的。它通常包括以下三个阶段:
配置阶段
根据我们的优化方法,可以考虑各种参数,例如:
——提供命名空间以限制算法的范围。
——确定要调整的K8s参数的值,例如CPU、内存和HPA目标利用率。
——最后,指定配置参数,如推荐频率和部署策略(手动与自动)。
学习阶段
ML引擎分析来自实时可观察性工具(如Prometheus和Datadog)的数据,以确定资源利用率和应用程序性能模式。之后,系统建议按照指定的间隔进行配置更新。
推荐阶段
最后一个阶段是实现ML分析生成的建议。我们可以确定在配置过程中是自动部署还是手动部署这些建议。
然后,根据特定工作负载的可变性,以有意义的频率重复这三个步骤。
总之,基于试验的优化允许更详细的分析,而基于观察的优化在真实场景中以更少的工作量更快地提供价值。这两种方法都可以弥补生产环境和开发环境之间的差距。
结论
使用Kubernetes和其他相关工具进行持续部署、监控和维护的应用程序容器化是软件开发和部署的新范式。ML算法能够以自动化方式控制多个可配置参数,使预测模型与现实相关联,并优化给定场景以满足特定业务需求。
借助ML的强大功能,自动化可以减轻配置多个Kubernetes参数的复杂性,优化性能和成本之间的权衡。