一篇长文了解Kubernetes Limits和Requests机制

34 阅读9分钟

在Kubernetes中使用容器Containers时,对于了解所涉及的资源及其所需非常重要,有些进程需要比其他进程更多的CPU或内存,因为有些进程的优先级更高,不应该因为资源的争抢而出现饥饿状态。

了解了这一点,我们应该适当配置我们的containers和Pods的属性,以便充分发挥它们的作用。

在这篇文章中,我们将看到:

  • Kubernetes Limit和 Request的介绍
  • 操作示例
  • Kubernetes Request详细说明
  • Kubernetes Limit详细说明
  • CPU 特性
  • Memory特性
  • namespace的资源管控
  • namespace LimitRange
  • 结论

Kubernetes Limits 和 Requests的介绍

Limits 和 Requests 是在使用Kubernetes时非常重要的配置,本文将重点介绍两个最重要的设置:CPU和内存。

Kubernetes将Limits定义为containers可使用的资源的最大值。这意味着containers永远不能超过所指示的内存或CPU

与之相反,Requests,表示为container保留的最小资源用量。

操作示例

让我们看一下这个部署,我们在CPU和内存上为两个不同的container设置Limits 和 Requests

kind: Deployment
apiVersion: extensions/v1beta1

template:
  spec:
    containers:
      - name: redis
        image: redis:5.0.3-alpine
        resources:
          limits:
            memory: 600Mi
            cpu: 1
          requests:
            memory: 300Mi
            cpu: 500m
      - name: busybox
        image: busybox:1.28
        resources:
          limits:
            memory: 200Mi
            cpu: 300m
          requests:
            memory: 100Mi
            cpu: 100m

假设我们正在运行一个4核和16GB节点的集群。我们可以得到如下节点信息:

Pod 有效请求为 400 MiB 内存和 600 毫核 CPU。您需要一个具有足够可分配空间的节点来调度 Pod。 Redis 容器的 CPU 分配将为 512,繁忙容器的分配将为 102。

Kubernetes 总是为每个核心分配 1024 份额,因此 redis:1024 * 0.5 核心 ≅ 512,busybox:1024 * 0.1 核心 ≅ 102。 如果 Redis 容器尝试分配超过 600MB 的 RAM,它将被 OOM 杀死,很可能导致 Pod 失败。 如果 Redis 尝试在每 100ms 使用超过 100ms 的 CPU(由于我们有 4 个核心,可用时间为每 100ms 的 400ms),将导致性能下降。 如果 Busybox 容器尝试分配超过 200MB 的 RAM,它将被 OOM 杀死,导致 Pod 失败。 如果 Busybox 尝试在每 100ms 使用超过 30ms 的 CPU,将导致性能下降。

  1. Pod有效请求是400 MiB内存和600m CPU,需要一个具有足够可分配空间的节点来调度pod
  2. redis容器的CPU份额为512,busybox容器的CPU份额为102。Kubernetes总是为每个核心分配1024个共享,因此redis: 1024 * 0.5 cores = 512和busybox: 1024 * 0.1cores = 102
  3. 如果Redis container 试图分配超过600MB的RAM,它将被OOM杀死,很可能导致pod失败。
  4. 如果Redis试图在每100毫秒内使用超过100毫秒的CPU,它将遭受CPU限流(因为我们有4个核心,每100毫秒可用时间将是400毫秒),导致性能下降。
  5. 如果Busybox容器试图分配超过200MB的RAM,它将被OOM杀死,从而导致pod失败。
  6. 如果Busybox试图每100毫秒使用超过30毫秒的CPU,那么它将被CPU限流,从而导致性能下降。

Kubernetes Requests介绍

Kubernetes 将Requsts 定义为容器使用的资源的保证最小量,基本上,它被用来设置容器所使用的最小资源用量。

当一个Pod被调度时,kube-scheduler会检查Kubernetes的requests,以便将其分配给一个特定的节点,该节点至少可以满足Pod中所有容器的request。如果请求的量高于可用资源,Pod将无法被调度并保持在Pending状态。

比如,在容器定义中,我们设置了对100m核心CPU和4Mi内存的请求:

resources:
   requests:
        cpu: 0.1
        memory: 4Mi

Request的使用:

将Pod分配给某一个节点时,为了满足Pod中容器指定的Request资源。 在运行时,将保证为该Pod中的容器提供所需的最小Request

Kubernetes Limits介绍

Kubernetes将Limits定义为容器可以使用的资源的最大数量,这意味着容器永远不会占用超过指定的内存量或CPU量

    resources:
      limits:
        cpu: 0.5
        memory: 100Mi

Limits 使用说明:

  • 将pod分配给节点时。如果没有设置Request,默认情况下,Kubernetes将分配requests = limits
  • 在运行时,Kubernetes将检查Pod中的容器消耗的资源是否超过了Limits的值

CPU 特性

CPU是一种可压缩资源,这意味着它可以被延伸以满足所有的需求,如果进程请求过多的CPU,那么另外一些进程将被限制。

  • CPU表示计算处理时间,以核为单位
  • CPU使用毫级(m)来表示比一个核心更小的数量(例如,500m将是半个核心)。
  • 最低的核数为1m
  • 一个节点可能有多个内核可用,因此请求CPU > 1 是可能的

Memory 特性

内存是一种不可压缩的资源,这意味着它不能像CPU那样被扩展。如果一个进程没有足够的内存来工作,那么这个进程就会被kill掉

内存在Kubernetes中以字节为单位进行测量。

  • 您可以使用E、P、T、G、M、k来表示Exabyte、Petabyte、Terabyte、Gigabyte、兆字节和千字节,尽管通常只使用后四个字节。(如500M、4G)
  • 警告:不要使用小写的m表示内存(表示"Millibytes" 是计算数据存储容量或传输速度时使用的单位,它是字节的一千分之一)
  • 您可以使用Mi来定义Mebibytes,以及其他的Ei、Pi、Ti(例如500Mi)

最佳实践

在很少的情况下,您应该使用Limits来控制Kubernetes中的资源使用。这是因为,如果想要避免“饥饿”(确保每个重要进程都得到它的份额),我们应该首先使用Request。

通过设置Limits,我们只能防止进程在特殊情况下获取额外资源,在内存事件中导致OOM终止,在CPU事件中导致Throttling(进程将需要等待,直到CPU可以再次使用)。

如果设置的Request值等于一个Pod的所有容器中的限制,那么该Pod将获得有保证的服务质量。

还要注意,资源使用量高于请求的pod更有可能被驱逐,因此设置非常低的请求弊大于利

Namespace 资源限制

通过名称空间,我们可以将Kubernetes资源隔离到不同的组中,也称为租户。

使用ResourceQuotas,我们可以为整个名称空间设置内存或CPU限制,确保其中的实体不能消耗超过该数量的内存或CPU限制。

apiVersion: v1
kind: ResourceQuota
metadata:
  name: mem-cpu-demo
spec:
  hard:
    requests.cpu: 2
    requests.memory: 1Gi
    limits.cpu: 3
    limits.memory: 2Gi
  • requests.cpu: 该命名空间下所有请求之和的最大cpu量
  • reqquests.memory: 该命名空间中所有请求总和的最大内存量
  • limits.cpu: 该命名空间中所有限制之和的最大cpu数量
  • limits.memory: 该名称空间中所有限制之和的最大内存量

接下来,apply 我们的namespace:

kubectl apply -f resourcequota.yaml --namespace=mynamespace

你可以用下面的命令列出一个命名空间的当前资源配额:

kubectl get resourcequota -n mynamespace

注意,如果我们为名称空间中的给定资源设置了ResourceQuota,那么需要为该名称空间中的每个Pod指定相应的限制或请求。如果没有,Kubernetes将返回" failed quota "错误:

Error from server (Forbidden): error when creating "mypod.yaml": pods "mypod" is forbidden: failed quota: mem-cpu-demo: must specify limits.cpu,limits.memory,requests.cpu,requests.memory

如果你尝试添加一个带有容器限制的新Pod,或者请求超过了当前的ResourceQuota, Kubernetes将返回一个“exceeded quota”错误:

Error from server (Forbidden): error when creating "mypod.yaml": pods "mypod" is forbidden: exceeded quota: mem-cpu-demo, requested: limits.memory=2Gi,requests.memory=2Gi, used: limits.memory=1Gi,requests.memory=1Gi, limited: limits.memory=2Gi,requests.memory=1Gi

Namesapce LimitRange

通常用于 Kubernetes 中,用于限制容器中资源的使用范围,例如 CPU、内存、存储等。它可以帮助管理员控制容器的资源使用,避免出现资源耗尽的情况。

如果我们想要限制一个namespace可分配的资源总量,那么ResourceQuotas非常有用。但是,如果我们想给里面的元素提供默认值,会发生什么呢?

LimitRanges是Kubernetes的一个策略,它限制了命名空间中每个实体的资源设置。

apiVersion: v1
kind: LimitRange
metadata:
  name: cpu-resource-constraint
spec:
  limits:
  - default:
      cpu: 500m
    defaultRequest:
      cpu: 500m
    min:
      cpu: 100m
    max:
      cpu: "1"
    type: Container
  • default:如果没有指定,创建的容器将具有此值
  • min:创建的容器不能有小于这个的限制或请求。
  • max:创建的容器不能有大于此的限制或请求。

之后,如果你创建了一个没有设置请求或限制的新Pod, LimitRange将自动为它的所有容器设置这些值:

    Limits:
      cpu:  500m
    Requests:
      cpu:  100m

现在,假设添加了一个限制为1200m的新Pod。将收到以下错误:

Error from server (Forbidden): error when creating "pods/mypod.yaml": pods "mypod" is forbidden: maximum cpu usage per Container is 1, but limit is 1200m

请注意,默认情况下,Pod中的所有容器将有效地具有100m CPU request,即使没有设置LimitRanges。

结论

  • 为我们的Kubernetes集群选择最佳的Limits值是获得最佳能耗和成本的关键。
  • 为我们的pod投入太多资源可能会导致成本飙升。
  • 精简或使用很少的CPU或内存将导致应用程序不能正确执行,甚至pod被驱逐。

如前所述,Kubernetes Limits一般不建议使用,除非在非常特殊的情况下,因为它们可能会造成弊大于利。在内存不足的情况下,容器有可能被杀死,或者在CPU不足的情况下被限流

对于requests,在需要确保进程获得有保证的资源共享时使用它们。

原文地址:sysdig.com/blog/kubern…