一文搞懂 Kubernetes Limits 和 Requests

本文涉及的产品
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
云数据库 Tair(兼容Redis),内存型 2GB
全局流量管理 GTM,标准版 1个月
简介: 管理 Kubernetes 集群就像坐镇在大型战场前,指挥千军万马进行火拼。在我们面前有几乎压倒性的旋律和氛围来将斗士气势营造至完美境地,甚至知道从哪里开始似乎都具有挑战性,作为经验丰富的架构师,往往可能很清楚这种感觉。

     管理 Kubernetes 集群就像坐镇在大型战场前,指挥千军万马进行火拼。在我们面前有几乎压倒性的旋律和氛围来将斗士气势营造至完美境地,甚至知道从哪里开始似乎都具有挑战性,作为经验丰富的架构师,往往可能很清楚这种感觉。

    然而,一个没有资源限制的 Kubernetes 集群可能会导致诸多的不可见问题。因此,设置资源限制便是一个合乎逻辑的起点。

    话虽如此,然而,真正的挑战才刚刚开始。要正确设置 Kubernetes 资源限制,我们必须有条不紊并注意找到正确的值。将它们设置得太高,可能会对集群的节点产生负面影响。将该值设置得太低,会对应用程序性能产生负面影响。

    因此,有效地设置 Kubernetes 请求和限制对应用程序的性能、稳定性和成本有重大影响。

    Resource Requests && Limits 即“资源请求和限制”是在容器级别指定的可选参数。Kubernetes 将 Pod 的请求和限制计算为其所有容器的请求和限制的总和。然后 Kubernetes 使用这些参数进行调度和资源分配决策。

    那么,什么是资源限制,它们为什么对我们来说如此重要?

    首先,在 Kubernetes 生态体系中,资源限制与资源请求往往以成对形式展现在大众面前:

    1、资源请求:分配给容器的 CPU 或内存量。Pod 资源请求等于其容器资源请求的总和。在调度 Pod 时,Kubernetes 将保证此数量的资源可供所支撑的 Pod 运行。

    2、资源限制:Kubernetes 将开始对超出限制的容器采取行动的级别。Kubernetes 会杀死一个消耗过多内存的容器或限制一个使用过多 CPU 的容器。

    如果设置了资源限制但没有资源请求,Kubernetes 会隐式设置内存和 CPU 请求等于限制。这种行为非常适合作为控制 Kubernetes 集群的第一步。这通常被称为保守方法,其中分配给容器的资源最多。



Resource Limits && Requests 解析

Requests-请求

    Pod 将获得它们请求的内存量。如果他们超出了他们的内存请求,如果另一个 Pod 碰巧需要这个内存,他们可能会被杀死。只有当关键系统或高优先级工作负载需要内存时,Pod 才会在使用的内存少于请求的内存时被杀死。

    同样,Pod 中的每个容器都分配了它请求的 CPU 量(如果可用)。如果其他正在运行的 Pod/Jobs 不需要可用资源,它可能会被分配额外的 CPU 周期。因此,定义了容器需要的最小资源量。

    注意:如果 Pod 的总请求在单个节点上不可用,则 Pod 将保持在 Pending 状态(即未运行),直到这些资源可用。

Limits-限制

    资源限制有助于 Kubernetes 调度程序更好地处理资源争用。当 Pod 使用的内存超过其限制时,其进程将被内核杀死以保护集群中的其他应用程序。当 Pod 超过其 CPU 限制时,它们将受到 CPU 限制。如果未设置限制,则 Pod 可以在可用时使用多余的内存和 CPU。限制决定了容器可以使用的最大资源量,防止资源短缺或由于资源消耗过多而导致机器崩溃。如果设置为 0,则表示容器没有资源限制。特别是如果你设置了 limits 而不指定 requests,Kubernetes 默认认为 requests 的值和 limits的值是一样的。

    Kubernetes 请求和限制适用于两种类型的资源 - 可压缩(例如 CPU)和不可压缩(例如内存)。对于不可压缩资源,适当的限制非常重要。

    为了充分利用 Kubernetes 集群中的资源,提高调度效率,Kubernetes 使用请求和限制来控制容器的资源分配。每个容器都可以有自己的请求和限制。这两个参数由 resources.requests 和 resources.limits 指定。一般来说,Requests-请求在调度中更重要,而 Limits-限制在运行中更重要。


[administrator@JavaLangOutOfMemory ~ ]% less demo-service-ap.yaml
apiVersion: extensions/v1beta1
metadata:
 name: redis
 labels:
   name: redis-deployment
   app: demo-service-ap
spec:
 replicas: 3
 selector:
   matchLabels:
    name: redis
    role: cachedb
    app: demo-service-ap
 template:
   spec:
     containers:
       - name: redis
         image: redis:6.2.6-alpine
         resources:
           limits:
             memory: 600Mi
             cpu: 1
           requests:
             memory: 300Mi
             cpu: 500m
       - name: busybox
         image: busybox:1.31.1
         resources:
           limits:
             memory: 200Mi
             cpu: 300m
           requests:
             memory: 100Mi
             cpu: 100m


    基于上述 Yaml 文件,在 Kubernetes 中,CPU 不是以百分比分配的,而是以千计(也称为 millicores 或 millicpu)。一个 CPU 等于 1000 毫核。如果希望分配三分之一的 CPU,我们应该为容器分配 333 Mi(毫核)。

    相对于 CPU ,内存往往更简单一些,其主要以字节为单位。

    Kubernetes 接受 SI 表示法 (K,M,G,T,P,E) 和二进制表示法 (Ki,Mi,Gi,Ti,Pi,Ei) 来定义内存。例如,要将内存限制在 256 MB,我们可以分配 268.4 M(SI 表示法)或 256 Mi(二进制表示法)。

    基于上述示例,我们可以看到针对 Resources 的定义涉及四个部分。其实,在实际的业务场景中,这些配置项中的每一个都是可选的。然而,在生产环境中,还是建议大家进行合理设置,以满足应用运行性能要求。

    requests.cpu 是命名空间中所有容器的最大组合 CPU 请求(以毫秒为单位)。在上面的例子中,你可以有 50 个 10m 请求的容器,5 个 100m 请求的容器,甚至一个 500m 请求的容器。只要在 Namespace 中请求的 CPU 总量小于 500m!

    requests.memory 是命名空间中所有容器的最大组合内存请求。在上面的示例参数中,可以拥有 50 个具有 2MiB 请求的容器、5 个具有 20MiB CPU 请求的容器,甚至是一个具有 100MiB 请求的容器。只要命名空间中请求的总内存小于 100MiB!

    limits.cpu 是命名空间中所有容器的最大组合 CPU 限制。它就像 requests.cpu 一样,但有限制。

    limits.memory 是命名空间中所有容器的最大组合内存限制。它就像 requests.memory 但有限制。

    请求和限制在实际的业务场景至关重要,因为它们在 Kubernetes 如何决定在需要释放资源时杀死哪些 Pod 中发挥着重要作用:

    1、没有限制或请求集的 Pod

    2、没有设置限制的 Pod

    3、超过内存请求但低于限制的 Pod

    4、Pod 使用的内存少于请求的内存

常见资源异常

    在实际的业务场景中,是否对所有容器设置了请求和限制?如果没有,Kubernetes 调度程序将随机分配任何没有请求和限制的 Pod。设置限制后,将避免以下大多数问题:

    1、内存不足 (OOM) 问题:节点可能死于内存不足,影响集群稳定性。例如,具有内存泄漏的应用程序可能会导致 OOM 问题。通常,在 Kubernetes 中看到两种主要的 OOMKilled 错误:OOMKilled:限制过度使用和 OOMKilled:已达到容器限制。

    OOMKilled: Limit Overcommit - 限制过度使用

   当 Pod 限制的总和大于节点上的可用内存时,可能会发生 OOMKilled: Limit Overcommit 错误。例如,如果我们有一个具有 8 GB 可用内存节点,可能在前期的糟糕规划设计中会分配 8 个 Pod,每个 Pod 都需要 1 gig 内存。但是,即使其中一个 Pod 配置了 1.5 gigs 的限制,我们也会面临内存不足的风险。只需要一个 Pod 出现流量峰值或未知的内存泄漏,Kubernetes 将被迫开始杀死 Pod。

    此时,我们需要检查主机本身,看看是否有任何在 Kubernetes 之外运行的进程可能会占用内存,从而为 Pod 留下更少的内存。

     OOMKilled: Container Limit Reached - 达到容器限制

    虽然 Limit Overcommit 错误与节点上的内存总量有关,但 Container Limit Reached 通常归结为单个 Pod。当 Kubernetes 检测到一个 Pod 使用的内存超过了设置的限制时,它会杀死该 Pod,并显示错误 OOMKilled—Container Limit Reached。

    发生这种情况时,请检查应用程序日志以尝试了解 Pod 使用的内存超过设置限制的原因。可能有多种原因,例如流量激增或长时间运行的 Kubernetes 作业导致它使用比平时更多的内存。

    如果在调查期间发现应用程序按预期运行并且它只需要更多内存来运行,则可能会考虑增加 request 和 limit 的值。

    2、CPU 饥饿:应用程序会变慢,因为它们必须共享有限数量的 CPU。消耗过多 CPU 的应用程序可能会影响同一节点上的所有应用程序。

    3、Pod 驱逐:当一个节点缺乏资源时,它会启动驱逐过程并终止 Pod,从没有资源请求的 Pod 开始。

    4、资源浪费:假设我们的集群在没有请求和限制的情况下运行状态很好,这意味着很可能过度配置。换句话说,你把钱花在了你从未使用过的资源上。

    因此,防止上述问题在业务运行中的发生,第一步,则是为所有容器设置资源限制操作。

源码解析

    在上述的解析中,我们已经了解了一些配置请求和限制的最佳实践,让我们更深入地研究源代码。

    下面的代码展示了 Requests and Scheduling (请求与调度)中 Pod 的请求和 Pod 中容器的请求之间的关系。具体如下所示:


func computePodResourceRequest(pod *v1.Pod) *preFilterState {
  result := &preFilterState{}
  for _, container := range pod.Spec.Containers {
    result.Add(container.Resources.Requests)
  }
  // take max_resource(sum_pod, any_init_container)
  for _, container := range pod.Spec.InitContainers {
    result.SetMaxResource(container.Resources.Requests)
  }
  // If Overhead is being utilized, add to the total requests for the pod
  if pod.Spec.Overhead != nil && utilfeature.DefaultFeatureGate.Enabled(features.PodOverhead) {
    result.Add(pod.Spec.Overhead)
  }
  return result
}
...
func (f *Fit) PreFilter(ctx context.Context, cycleState *framework.CycleState, pod *v1.Pod) *framework.Status {
  cycleState.Write(preFilterStateKey, computePodResourceRequest(pod))
  return nil
}
...
func getPreFilterState(cycleState *framework.CycleState) (*preFilterState, error) {
  c, err := cycleState.Read(preFilterStateKey)
  if err != nil {
    // preFilterState doesn't exist, likely PreFilter wasn't invoked.
    return nil, fmt.Errorf("error reading %q from cycleState: %v", preFilterStateKey, err)
  }
  s, ok := c.(*preFilterState)
  if !ok {
    return nil, fmt.Errorf("%+v  convert to NodeResourcesFit.preFilterState error", c)
  }
  return s, nil
}
...
func (f *Fit) Filter(ctx context.Context, cycleState *framework.CycleState, pod *v1.Pod, nodeInfo *framework.NodeInfo) *framework.Status {
  s, err := getPreFilterState(cycleState)
  if err != nil {
    return framework.NewStatus(framework.Error, err.Error())
  }
  insufficientResources := fitsRequest(s, nodeInfo, f.ignoredResources, f.ignoredResourceGroups)
  if len(insufficientResources) != 0 {
    // We will keep all failure reasons.
    failureReasons := make([]string, 0, len(insufficientResources))
    for _, r := range insufficientResources {
      failureReasons = append(failureReasons, r.Reason)
    }
    return framework.NewStatus(framework.Unschedulable, failureReasons...)
  }
  return nil
}

    基于上述代码定义,我们可以看到:调度器(调度线程)计算了待调度的 Pod 所需的资源。具体来说,它根据 Pod 规范分别计算 init 容器的总请求数和工作容器的总请求数。在接下来的 Filter 阶段,会检查所有节点是否满足条件。

    备注:对于轻量级虚拟机(例如 kata-container),它们自己的虚拟化资源消耗需要计入缓存中。

    通常而言,调度过程需要不同的阶段,包括前置过滤器、过滤器、后置过滤器和评分。有关详细信息,可参阅过“滤器和评分节点”内容。具体可参考如下地址所示:

https://kubernetes.io/docs/concepts/scheduling-eviction/kube-scheduler/#kube-scheduler-implementation

    过滤后,如果只有一个适用的节点,则将 Pod 调度到该节点上。如果有多个适用的 Pod,调度器会选择加权分数总和最高的节点。评分基于多种因素,因为调度插件实现了一个或多个扩展点。请注意,requests 的值和 limits 的值直接影响插件 NodeResourcesLeastAllocated 的最终结果。 其源代码实现如下所示:


func leastResourceScorer(resToWeightMap resourceToWeightMap) func(resourceToValueMap, resourceToValueMap, bool, int, int) int64 {
  return func(requested, allocable resourceToValueMap, includeVolumes bool, requestedVolumes int, allocatableVolumes int) int64 {
    var nodeScore, weightSum int64
    for resource, weight := range resToWeightMap {
      resourceScore := leastRequestedScore(requested[resource], allocable[resource])
      nodeScore += resourceScore * weight
      weightSum += weight
    }
    return nodeScore / weightSum
  }
}
...
func leastRequestedScore(requested, capacity int64) int64 {
  if capacity == 0 {
    return 0
  }
  if requested > capacity {
    return 0
  }
  return ((capacity - requested) * int64(framework.MaxNodeScore)) / capacity
}

    对于 NodeResourcesLeastAllocated,如果同一 Pod 拥有更多资源,则节点将获得更高的分数。 换句话说,一个 Pod 将更有可能被调度到资源充足的节点上。


    在创建 Pod 时,Kubernetes 需要分配不同的资源,包括 CPU 和内存。每种资源都有一个权重(源代码中的 resToWeightMap 结构)。作为一个整体,它们告诉 Kubernetes 调度程序什么是实现资源平衡的最佳决策。在 Score 阶段,调度器除了

NodeResourcesLeastAllocated 外,还使用其他插件(InterPodAffinity)进行评分。

     接下来,我们顺带了解一下 QoS and Scheduling (QoS 与调度),QoS 作为Kubernetes 中的一种资源保护机制,主要用于控制内存等不可压缩资源。 它还会影响不同 Pod 和容器的 OOM 分数。 当节点内存不足时,内核(OOM Killer)会杀死低优先级的 Pod(分数越高,优先级越低)。其源代码实现如下所示:


func GetContainerOOMScoreAdjust(pod *v1.Pod, container *v1.Container, memoryCapacity int64) int {
  if types.IsCriticalPod(pod) {
    // Critical pods should be the last to get killed.
    return guaranteedOOMScoreAdj
  }
  switch v1qos.GetPodQOS(pod) {
  case v1.PodQOSGuaranteed:
    // Guaranteed containers should be the last to get killed.
    return guaranteedOOMScoreAdj
  case v1.PodQOSBestEffort:
    return besteffortOOMScoreAdj
  }
  // Burstable containers are a middle tier, between Guaranteed and Best-Effort. Ideally,
  // we want to protect Burstable containers that consume less memory than requested.
  // The formula below is a heuristic. A container requesting for 10% of a system's
  // memory will have an OOM score adjust of 900. If a process in container Y
  // uses over 10% of memory, its OOM score will be 1000. The idea is that containers
  // which use more than their request will have an OOM score of 1000 and will be prime
  // targets for OOM kills.
  // Note that this is a heuristic, it won't work if a container has many small processes.
  memoryRequest := container.Resources.Requests.Memory().Value()
  oomScoreAdjust := 1000 - (1000*memoryRequest)/memoryCapacity
  // A guaranteed pod using 100% of memory can have an OOM score of 10. Ensure
  // that burstable pods have a higher OOM score adjustment.
  if int(oomScoreAdjust) < (1000 + guaranteedOOMScoreAdj) {
    return (1000 + guaranteedOOMScoreAdj)
  }
  // Give burstable pods a higher chance of survival over besteffort pods.
  if int(oomScoreAdjust) == besteffortOOMScoreAdj {
    return int(oomScoreAdjust - 1)
  }
  return int(oomScoreAdjust)
}


资源管控

    要仔细分析指标,我们需要一步一步来。 通常,可概括为2个阶段组成,每个阶段都会导致不同的策略。从最激进的开始,挑战结果,并在必要时转向更保守的选择。


    通常,我们必须独立考虑 CPU 和内存,并根据每个阶段的结论应用不同的策略。

     1、观测内存或 CPU

    在第一阶段,查看内存或 CPU 的百分之九十九。这种激进的方法旨在通过强制 Kubernetes 对异常值采取行动来减少问题。如果使用此值设置限制,我们的应用程序将有 1% 的时间受到影响。容器 CPU 将受到限制,并且永远不会再次达到该值。

激进的方法通常有利于 CPU 限制,因为其后果相对可以接受,并且可以帮助我们更好地管理资源。关于记忆,百分之九十九可能有问题;如果达到限制,容器将重新启动。


    此时,我们应该权衡后果并得出结论,如果 99 个百分位对有意义(作为旁注,我们应该更深入地调查为什么应用程序有时会达到设定的限制)。可能 99 个百分位数过于严格,因为我们的应用程序尚未达到最大利用率。在这种情况下,请继续使用第二个策略来设置限制。

    2、实时优化调整

    最后阶段是通过添加或减去一个系数(即,最大值 + 20%)来找到基于最大值的折衷方案。如果达到这一点,我们应该考虑执行负载测试以更好地描述我们的应用程序性能和资源使用情况。无限制地对每个应用程序重复此过程。


    以上为 Kubernetes 中资源限制与资源请求相关简要解析,更多内容欢迎大家深入沟通、探讨!


# 参考资料

相关实践学习
通过Ingress进行灰度发布
本场景您将运行一个简单的应用,部署一个新的应用用于新的发布,并通过Ingress能力实现灰度发布。
容器应用与集群管理
欢迎来到《容器应用与集群管理》课程,本课程是“云原生容器Clouder认证“系列中的第二阶段。课程将向您介绍与容器集群相关的概念和技术,这些概念和技术可以帮助您了解阿里云容器服务ACK/ACK Serverless的使用。同时,本课程也会向您介绍可以采取的工具、方法和可操作步骤,以帮助您了解如何基于容器服务ACK Serverless构建和管理企业级应用。 学习完本课程后,您将能够: 掌握容器集群、容器编排的基本概念 掌握Kubernetes的基础概念及核心思想 掌握阿里云容器服务ACK/ACK Serverless概念及使用方法 基于容器服务ACK Serverless搭建和管理企业级网站应用
相关文章
|
Kubernetes 监控 调度
在 Kubernetes 中应该如何设置 CPU 的 requests 和 limits
在 Kubernetes 中应该如何设置 CPU 的 requests 和 limits
341 0
|
Prometheus Kubernetes 监控
Kubernetes Memory Requests超标引发的血案
Kubernetes Memory Requests超标引发的血案
108 0
|
Kubernetes 网络协议 应用服务中间件
Kubernetes必备知识: Kubernetes资源模型limits
Limits 是 Pod 能使用的资源上限,是实际配置到内核 cgroups 里面的配置数据。对于内存来说,会直接转换成 docker run 命令行的--memory大小,最终会配置到 cgroups 对应任务的/sys/fs/cgroup/memory/……/memory.limit_in_bytes 文件中。
1281 0
Kubernetes必备知识: Kubernetes资源模型limits
|
Kubernetes 网络性能优化 调度
Kubernetes必备知识: Kubernetes资源模型requests
Requests是容器请求要使用的资源,Kubernetes 会保证 Pod 能使用到这么多的资源。请求的资源是调度的依据,只有当节点上的可用资源大于 Pod 请求的各种资源时,调度器才会把 Pod 调度到该节点上(如果 CPU 资源足够,内存资源不足,调度器也不会选择该节点)。 需要注意的是,调度器只关心节点上可分配的资源,以及节点上所有 Pods 请求的资源,而不关心节点资源的实际使用情况,换句话说,如果节点上的 Pods 申请的资源已经把节点上的资源用满,即使它们的使用率非常低,比如说 CPU 和内存使用率都低于 10%,调度器也不会继续调度 Pod 上去。
614 0
Kubernetes必备知识: Kubernetes资源模型requests
|
1月前
|
缓存 容灾 网络协议
ACK One多集群网关:实现高效容灾方案
ACK One多集群网关可以帮助您快速构建同城跨AZ多活容灾系统、混合云同城跨AZ多活容灾系统,以及异地容灾系统。
|
2月前
|
Kubernetes Ubuntu 网络安全
ubuntu使用kubeadm搭建k8s集群
通过以上步骤,您可以在 Ubuntu 系统上使用 kubeadm 成功搭建一个 Kubernetes 集群。本文详细介绍了从环境准备、安装 Kubernetes 组件、初始化集群到管理和使用集群的完整过程,希望对您有所帮助。在实际应用中,您可以根据具体需求调整配置,进一步优化集群性能和安全性。
147 12
|
2月前
|
Prometheus Kubernetes 监控
OpenAI故障复盘 - 阿里云容器服务与可观测产品如何保障大规模K8s集群稳定性
聚焦近日OpenAI的大规模K8s集群故障,介绍阿里云容器服务与可观测团队在大规模K8s场景下我们的建设与沉淀。以及分享对类似故障问题的应对方案:包括在K8s和Prometheus的高可用架构设计方面、事前事后的稳定性保障体系方面。
|
2月前
|
Kubernetes 网络协议 应用服务中间件
Kubernetes Ingress:灵活的集群外部网络访问的利器
《Kubernetes Ingress:集群外部访问的利器-打造灵活的集群网络》介绍了如何通过Ingress实现Kubernetes集群的外部访问。前提条件是已拥有Kubernetes集群并安装了kubectl工具。文章详细讲解了Ingress的基本组成(Ingress Controller和资源对象),选择合适的版本,以及具体的安装步骤,如下载配置文件、部署Nginx Ingress Controller等。此外,还提供了常见问题的解决方案,例如镜像下载失败的应对措施。最后,通过部署示例应用展示了Ingress的实际使用方法。
86 2
|
2月前
|
存储 Kubernetes 关系型数据库
阿里云ACK备份中心,K8s集群业务应用数据的一站式灾备方案
本文源自2024云栖大会苏雅诗的演讲,探讨了K8s集群业务为何需要灾备及其重要性。文中强调了集群与业务高可用配置对稳定性的重要性,并指出人为误操作等风险,建议实施周期性和特定情况下的灾备措施。针对容器化业务,提出了灾备的新特性与需求,包括工作负载为核心、云资源信息的备份,以及有状态应用的数据保护。介绍了ACK推出的备份中心解决方案,支持命名空间、标签、资源类型等维度的备份,并具备存储卷数据保护功能,能够满足GitOps流程企业的特定需求。此外,还详细描述了备份中心的使用流程、控制台展示、灾备难点及解决方案等内容,展示了备份中心如何有效应对K8s集群资源和存储卷数据的灾备挑战。
|
3月前
|
Kubernetes 监控 Cloud Native
Kubernetes集群的高可用性与伸缩性实践
Kubernetes集群的高可用性与伸缩性实践
99 1

热门文章

最新文章