K8s生产最佳实践-限制NameSpace资源用量

2022-12-01 16:19:54 浏览数 (1)

前言

想象一下这个场景:多个系统运行在同一套 K8s 集群上, 有重要系统, 也有不太重要的系统。但是某一天, 某个不重要的系统突然占用了该 K8s 集群的所有资源, 导致该集群上的其他系统的正常运行受到影响. 本文介绍了 Kubernetes 平台如何管理容量,以及作者对管理员的注意事项和建议。

Kubernetes 资源限制概述

我们寿险了解 Kubernetes 平台如何在容器和节点级别应用资源约束。 为了讨论合理规模,我们将专门关注CPU和内存,尽管还有其他因素需要考虑。

可以为每个容器和 Pod 指定 resource requests 和 limits。 Requests 是为 pod 预留的有保证的资源,而 limits 则是旨在保护集群整体架构的安全措施。 在Kubernetes中,pod 的requests和limits之间的关系被配置为服务质量(QoS)。 在节点上,kubelet(可以监控资源的代理)将此信息传递给容器运行时,容器运行时使用内核cgroups来应用资源约束。

要调度新的pod,Kubernetes调度程序将确定可用节点上的有效位置,并考虑现有pod资源限制。 Kubernetes 会预配置一些系统预留,以留出资源供操作系统和Kubernetes系统组件使用(具体如下图)。 剩余量被定义为可分配的,调度程序将其视为节点的容量。 调度器可以基于所有单元的合计资源请求来调度单元到节点的容量。 请注意,所有单元的聚合资源限制可以大于节点容量,这种做法称为超额使用(超配 or 超卖)。

K8s Node 资源分配

在管理节点容量时,我们要尽量避免两种情况。 在第一种情况下,实际内存利用率达到容量,并且kubelet基于驱逐信号触发节点压力驱逐。 如果节点在kubelet可以回收内存之前用完内存,则节点oom-killer将做出响应,根据从每个pod的QoS计算的oom_score_adj值选择要删除的pod。 因此,构成这些 pod 的应用程序会受到影响。

在CPU上过量使用的底层机制与内存的行为不同,因为它将CPU时间分配给各个容器。 高CPU利用率会导致CPU节流(CPU throttling),但不会触发节点压力驱逐,也不会自动导致Kubernetes终止Pod。 但是也请注意,CPU耗尽仍可能导致应用程序 pod 降级、活动探测失败并重新启动。

我们还希望避免另一种情况。 在节点级别,requests 是有保证的资源,并且必须小于容量,因为Kubernetes调度程序不会超额预订。 如果requests明显且始终大于实际使用的资源,则多余的容量基本上未被使用。 虽然可能需要为高峰处理时间保留资源,但管理员应在这一点与运行可能不需要的过剩容量的重复成本之间进行平衡。 根据实际使用情况配置请求是一种平衡行为,应考虑应用程序的风险管理(平衡可用性和成本).

Kubernetes 管理员能做啥

Kubernetes 管理员的一个主要关注点是管理和合理调整集群容量,我们可以在Web 上利用 Prometheus Grafana 仪表盘和命令行捕获集群利用率指标,供管理员使用。

但是 Kubernetes 管理员还面临一个棘手的大问题:正在运行的应用程序(的资源管理)。 解决特定问题的应用程序可以由不同的开发人员以不同的方式编写,从而导致不同的性能(比如 java 编写的可能消耗内存较多, golang 消耗内存相对较少)。 每个应用程序都是独特的,没有一种适合所有应用程序的方法。 管理员对开发人员的应用程序的控制能力较弱,在大型企业中,单个管理团队可能很难接触到众多的开发团队。 因此,管理员的重点应该是设置护栏,以允许开发人员(在护栏内)调整自己的应用程序。

配置 LimitRange

绕了这么久, 终于进入正题了.

为此,管理员可以为每个 NameSpace 配置不同的 LimitRanges,为开发人员提供针对各个容器和pod的建议大小限制。 以下是LimitRange的示例。 由于每个集群和应用程序都有不同的业务和风险要求,因此各位读者实际应用时数字会有所不同。

代码语言:javascript复制
apiVersion: v1
kind: LimitRange
metadata:
  name: "resource-limits"
spec:
  limits:
  - max:
      cpu: "2"
      memory: 4Gi
    min:
      cpu: 125m
      memory: 128Mi
    type: Pod
  - default:
      cpu: "0.5"
      memory: 1Gi
    defaultRequest:
      cpu: 250m
      memory: 256Mi
    max:
      cpu: "2"
      memory: 4Gi
    maxLimitRequestRatio:
      cpu: "25"
      memory: "4"
    min:
      cpu: 125m
      memory: 128Mi
    type: Container

在 Kubernetes 中进行开发的良好实践是创建微服务应用程序,而不是大型的巨石应用程序。 为了鼓励微服务的开发,应该应用 limits 来约束pod的最大大小。 节点的物理容量可能会决定此最大大小,因为它应该可以轻松地容纳几个最大的pod。 还是类似这个图:

1个K8s node 应该可以轻松地容纳几个最大的pod

让我们继续上面的LimitRange示例。 最小pod和容器大小可能由正在运行的应用程序的需求确定,管理员不必强制执行。 为了简单起见,我们还鼓励开发人员在每个pod上运行一个容器(一个典型的例外是使用 sidecar 容器,如Istio 的 sidecar)。 因此,上面的示例对pod和container使用相同的资源值。

默认 requests 和 limits 作为开发人员的建议值。 未显式声明容器大小的工作负载资源(即 pod)将继承默认值。 作为一种好的做法,开发人员应明确定义工作负载资源中的资源请求和限制,而不采用默认值。

CPU和内存的 maxLimitRequestRatio 是开发人员的突发准则。 在开发环境中,当原型应用程序经常空闲运行,但在使用时需要合理的按需资源时,高CPU maxLimitRequestRatio会很好地工作。 开发人员可能只在工作时间工作,在自己的IDE中离线编码,偶尔测试单个微服务,或者测试CI/CD管道的不同阶段。 相比之下,如果许多最终用户在一天中同时访问应用程序,您将看到更高的基准利用率。 这可能更接近您的生产环境,并可能降低maxLimitRequestRatio(可能是事件1:1的请求限制)。 由于管道各阶段的不同利用率模式将导致不同的请求和限制,因此在生产之前使用模拟工作负载进行测试以确定适当的单元大小非常重要。

开发人员将使用maxLimitRequestRatio作为适当调整大小的准则。 Kubernetes调度程序基于资源请求做出调度决策,因此开发人员应配置资源 requests 以反映实际使用情况。 然后,基于应用程序的风险状况,开发人员将配置 limits 以遵守maxLimitRequestRatio。 将maxLimitRequestRatio设置为1的管理员强制开发人员将requests 配置为等于限制,这在生产中可能是理想的,以降低风险并优先考虑稳定性。

在本文前面,我们比较了内存和CPU,并描述了这两种资源在负载下的不同行为,高内存可能导致pod逐出或从内存不足的情况重新启动。 因此,最好是谨慎行事,并为不同环境的内存配置较低的maxLimitRequestRatio,以防止应用程序 pod 重新启动。 为OpenJDK pod 配置内存时还应注意其他事项。 (如果没配置相应动态调整的参数), 容器和pod内部的JVM heap 对容器的请求和限制一无所知,但应用于前者的资源约束将影响后者。

配置 ResourceQuota

管理员还可以配置 ResourceQuotas,它为 NameSpace 提供基于容量的限制,以指导开发人员根据预测的估计值来调整应用程序的规模。 下面是一个ResourceQuota示例。

代码语言:javascript复制
apiVersion: v1
kind: ResourceQuota
metadata:
  name: compute-resources
spec:
  hard:
    limits.memory: 20Gi
    requests.cpu: "4"
    requests.memory: 20Gi

在应用程序 NameSpace 的初始创建过程中,开发团队应与管理员一起预测其应用程序大小并应用适当的配额。 管理员应根据服务、副本的数量和pod的估计大小来预测应用程序大小。 为了简化对众多 NameSpace 的管理,管理员可考虑类似 AWS 的方法作为起始准则,其中 small、medium、large、xlarge 应用程序被给予对应的预定配额。

应用程序跨CI/CD管道的各个阶段进行运行,每个阶段都位于不同的集群或 NameSpace 中,并具有自己的配置配额。 在不考虑性能和高可用性的开发和测试 NameSpace 中,应用程序应配置最小的 pod,并为每个服务配置1个pod副本,以减少资源使用。 另一方面,在生产集群或NameSpace中,应使用更大的pod和每个服务至少2个单元副本,以处理更高的业务量并提供高可用性。 通过使用CI/CD管道中的模拟工作负载进行压力和性能测试,开发人员可以在生产发布之前确定适当的生产pod大小、副本数量和配额。

管理员应针对未来的扩展制定配额预算,并考虑应用程序的使用模式、峰值容量和已配置的pod或节点的autoscaler(如果有)。 例如,可在快速添加新微服务的开发NameSpace、用于确定适当生产pod大小的性能测试NameSpace、或使用HPA来调整峰值容量的生产 NameSpace 中分配附加配额。 管理员应针对上述各种情况和其他情况提供足够的配额开销,同时平衡基础架构容量的风险并保护架构容量。

管理员和开发人员都应该预期会随着时间的推移调整配额。 开发人员无需管理员的帮助即可收回配额,方法是查看每项服务并减少Pod requests 或 limits 以匹配实际使用量。 如果开发人员已经采取了这些步骤,但仍然需要额外的配额,那么他们应该联系管理员。 管理员应将开发人员的定期配额请求作为一个机会,根据以前预测的估计值分析实际消耗量,并相应地确认或调整配额大小和新的预测估计值。

另外再介绍在调整配额大小时的一些次要注意事项。 在确定CPU和内存的配额比率时,应考虑节点容量,以便有效地利用两者。 例如,m5.2xlarge类型的AWS EC2实例为8个vCPU、32 GiB RAM。 由m5.2xlarge节点组成的集群可以按照每4 GB RAM对应1个vCPU的比例分配应用配额(不考虑节点的系统保留空间),从而高效地使用CPU和内存。 如果应用程序工作负载(即CPU或内存密集型)与节点大小不匹配,则可以考虑使用不同的节点大小。

管理员对何时应用和不应用配额的CPU limits 一直存在争议,这里我们将提供一些考虑事项,而不是正式的指导。 正如我们前面所讨论的,pod的CPU不足会导致节流,但不一定会导致pod终止。 如果管理员倾向于过量使用并利用节点上的所有可用CPU,则不应设置配额的CPU limits相反,应设置配额(resource quota)的CPU limits,以减少过度使用和应用程序性能风险,这可能是一个业务和成本决策,而不是技术决策。 与生产环境相比,开发环境可以容忍更高的风险和不可预测的性能,因此管理员可以考虑将CPU limits 应用于生产而不是开发

最后,在某些特殊情况下,不建议应用配额。 应用配额的目的是让管理员能够对自定义开发的应用程序的容量规划进行一定程度的控制。 配额不应应用于Kubernetes 自身的组件,因为这些项目需要预先配置的资源量。 出于类似的原因,配额也不应适用于第三方供应商提供的企业版应用程序。

总结

在本文中,我们介绍了Kubernetes平台如何通过资源约束保护架构,包括:

•Pod 的 requests 和 limits•Node 的资源分配•NameSpace 级别的针对 Pod 和容器的 LimitRange•NameSpace 级别的 ResourceQuota

并提供了在应用程序 NameSpace 中应用 limits 和 quota 的保护措施时的合理调整注意事项。 每个应用的风险偏好和Kubernetes集群的容量各不相同,需要综合考量再实施。

参考文档

•Kubernetes instance calculator (learnk8s.io)[1]•限制范围 | Kubernetes[2]•资源配额 | Kubernetes[3]

References

[1] Kubernetes instance calculator (learnk8s.io): https://learnk8s.io/kubernetes-instance-calculator [2] 限制范围 | Kubernetes: https://kubernetes.io/zh-cn/docs/concepts/policy/limit-range/ [3] 资源配额 | Kubernetes: https://kubernetes.io/zh-cn/docs/concepts/policy/resource-quotas/

0 人点赞