k8s的资源管理及应用优先级(包括QoS、pod优先级)

资源管理

资源是什么

在 kubernetes 中,有两个基础但是非常重要的概念:

node和pod :

node 翻译成节点,是对集群资源的抽象;pod 是对容器的封装,是应用运行的实体。node 提供资源,而 pod 使用资源,这里的资源分为计算(cpu、memory、gpu)、存储(disk、ssd)、网络(network bandwidth、ip、ports)。这些资源提供了应用运行的基础,正确理解这些资源以及集群调度如何使用这些资源,对于大规模的 kubernetes 集群来说至关重要,不仅能保证应用的稳定性,也可以提高资源的利用率。

把资源分成可压缩和不可压缩,是因为在资源不足的时候,它们的表现很不一样。对于不可压缩资源,如果资源不足,也就无法继续申请资源(内存用完就是用完了),并且会导致 pod 的运行产生无法预测的错误(应用申请内存失败会导致一系列问题);而对于可压缩资源,比如 CPU 时间片,即使 pod 使用的 CPU 资源很多,CPU 使用也可以按照权重分配给所有 pod 使用,虽然每个人使用的时间片减少,但不会影响程序的逻辑。

在 kubernetes 集群管理中,有一个非常核心的功能:就是为 pod 选择一个主机运行。调度必须满足一定的条件,其中最基本的是主机上要有足够的资源给 pod 使用。

在这里插入图片描述

CPU的含义

CPU 资源的约束和请求以 cpu 为单位。

Kubernetes 中的一个 cpu 等于云平台上的 1 个 vCPU/核和裸机 Intel 处理器上的 **1 个超线程 **。

你也可以表达带小数 CPU 的请求。spec.containers[].resources.requests.cpu 为 0.5 的 Container 肯定能够获得请求 1 CPU 的容器的一半 CPU 资源。表达式 0.1 等价于表达式 100m, 可以看作 “100 millicpu”。有些人说成是“一百毫 cpu”,其实说的是同样的事情。 具有小数点(如 0.1)的请求由 API 转换为 100m;最大精度是 1m。 因此,或许你应该优先考虑使用 100m 的形式。

CPU 总是按绝对数量来请求的,不可以使用相对数量; 0.1 的 CPU 在单核、双核、48 核的机器上的意义是一样的。

内存的含义

内存的约束和请求以字节为单位。你可以使用以下后缀之一以一般整数或定点数字形式来表示内存: E、P、T、G、M、K。你也可以使用对应的 2 的幂数:Ei、Pi、Ti、Gi、Mi、Ki。 例如,以下表达式所代表的是大致相同的值:

128974848、129e6、129M、123Mi、

下面是个例子:

以下 Pod 有两个 Container。每个 Container 的请求为 0.25 cpu 和 64MiB(226 字节)内存, 每个容器的资源约束为 0.5 cpu 和 128MiB 内存。 你可以认为该 Pod 的资源请求为 0.5 cpu 和 128 MiB 内存,资源限制为 1 cpu 和 256MiB 内存。

apiVersion: v1
kind: Pod
metadata:
  name: frontend
spec:
  containers:
  - name: app
    image: images.my-company.example/app:v4
    env:
    - name: MYSQL_ROOT_PASSWORD
      value: "password"
    resources:
      requests:
        memory: "64Mi"
        cpu: "250m"
      limits:
        memory: "128Mi"
        cpu: "500m"
  - name: log-aggregator
    image: images.my-company.example/log-aggregator:v6
    resources:
      requests:
        memory: "64Mi"
        cpu: "250m"
      limits:
        memory: "128Mi"
        cpu: "500m"

请求和约束

如果 Pod 运行所在的节点具有足够的可用资源,容器可能(且可以)使用超出对应资源 request 属性所设置的资源量。不过,容器不可以使用超出其资源 limit 属性所设置的资源量。

例如,如果你将容器的 memory 的请求量设置为 256 MiB,而该容器所处的 Pod 被调度到一个具有 8 GiB 内存的节点上,并且该节点上没有其他 Pods 运行,那么该容器就可以尝试使用更多的内存。

如果你将某容器的 memory 约束设置为 4 GiB,kubelet (和 容器运行时) 就会确保该约束生效。 容器运行时会禁止容器使用超出所设置资源约束的资源。 例如:当容器中进程尝试使用超出所允许内存量的资源时,系统内核会将尝试申请内存的进程终止, 并引发内存不足(OOM)错误。

约束值可以以被动方式来实现(系统会在发现违例时进行干预),或者通过强制生效的方式实现 (系统会避免容器用量超出约束值)。不同的容器运行时采用不同方式来实现相同的限制。

带资源请求的 Pod 如何调度和运行

调度:
当你创建一个 Pod 时,Kubernetes 调度程序将为 Pod 选择一个节点。 每个节点对每种资源类型都有一个容量上限:可为 Pod 提供的 CPU 和内存量。 调度程序确保对于每种资源类型,所调度的容器的资源请求的总和小于节点的容量。 请注意,尽管节点上的实际内存或 CPU 资源使用量非常低,如果容量检查失败, 调度程序仍会拒绝在该节点上放置 Pod。 当稍后节点上资源用量增加,例如到达请求率的每日峰值区间时,节点上也不会出现资源不足的问题。

运行:
当 kubelet 启动 Pod 中的 Container 时,它会将 CPU 和内存约束信息传递给容器运行时。

当使用 Docker 时:

spec.containers[].resources.requests.cpu 先被转换为可能是小数的基础值,再乘以 1024。 这个数值和 2 的较大者用作 docker run 命令中的 --cpu-shares 标志的值。

spec.containers[].resources.limits.cpu 先被转换为 millicore 值,再乘以 100。 其结果就是每 100 毫秒内容器可以使用的 CPU 时间总量。在此期间(100ms),容器所使用的 CPU 时间不会超过它被分配的时间。

说明: 默认的配额(quota)周期为 100 毫秒。 CPU配额的最小精度为 1 毫秒。
spec.containers[].resources.limits.memory 被转换为整数值,作为 docker run 命令中的 --memory 参数值。

如果 Container 超过其内存限制,则可能会被终止。如果容器可重新启动,则与所有其他类型的 运行时失效一样,kubelet 将重新启动容器。

如果一个 Container 内存用量超过其内存请求值,那么当节点内存不足时,容器所处的 Pod 可能被逐出。

每个 Container 可能被允许也可能不被允许使用超过其 CPU 约束的处理时间。 但是,容器不会由于 CPU 使用率过高而被杀死。

kubernetes资源对象

请求(requests)

前面说过用户在创建 pod 的时候,可以指定每个容器的 Requests 和 Limits 两个字段,下面是一个实例:

resources:
  requests:
    memory: "64Mi"
    cpu: "250m"
  limits:
    memory: "128Mi"
    cpu: "500m"

Requests 是容器请求要使用的资源,kubernetes 会保证 pod 能使用到这么多的资源。请求的资源是调度的依据,只有当节点上的可用资源大于 pod 请求的各种资源时,调度器才会把 pod 调度到该节点上(如果 CPU 资源足够,内存资源不足,调度器也不会选择该节点)。

需要注意的是,调度器只关心节点上可分配的资源,以及节点上所有 pods 请求的资源,而不关心节点资源的实际使用情况,换句话说,如果节点上的 pods 申请的资源已经把节点上的资源用满,即使它们的使用率非常低,比如说 CPU 和内存使用率都低于 10%,调度器也不会继续调度 pod 上去。

上限(limits)

Limits 是 pod 能使用的资源上限,是实际配置到内核 cgroups 里面的配置数据。对于内存来说,会直接转换成 docker run 命令行的 --memory 大小,最终会配置到 cgroups 对应任务的 /sys/fs/cgroup/memory/……/memory.limit_in_bytes 文件中。

如果 limit 没有配置,则表明没有资源的上限,只要节点上有对应的资源,pod 就可以使用。

使用 requests 和 limits 概念,我们能分配更多的 pod,提升整体的资源使用率。但是这个体系有个非常重要的问题需要考虑,那就是怎么去准确地评估 pod 的资源 requests?如果评估地过低,会导致应用不稳定;如果过高,则会导致使用率降低。这个问题需要开发者和系统管理员共同讨论和定义。

默认资源配置(limit range)

为每个 pod 都手动配置这些参数是挺麻烦的事情,kubernetes 提供了 LimitRange 资源,可以让我们配置某个 namespace 默认的 request 和 limit 值,比如下面的实例:

apiVersion: "v1"
kind: "LimitRange"
metadata:
  name: you-shall-have-limits
spec:
  limits:
    - type: "Container"
      max:
        cpu: "2"
        memory: "1Gi"
      min:
        cpu: "100m"
        memory: "4Mi"
      default:
        cpu: "500m"
        memory: "200Mi"
      defaultRequest:
        cpu: "200m"
        memory: "100Mi"

如果对应 namespace 创建的 pod 没有写资源的 requests 和 limits 字段,那么它会自动拥有下面的配置信息:

内存请求是 100Mi,上限是 200Mi
CPU 请求是 200m,上限是 500m
当然,如果 pod 自己配置了对应的参数,kubernetes 会使用 pod 中的配置。使用 LimitRange 能够让 namespace 中的 pod 资源规范化,便于统一的资源管理。

资源配额(resource quota)

前面讲到的资源管理和调度可以认为 kubernetes 把这个集群的资源整合起来,组成一个资源池,每个应用(pod)会自动从整个池中分配资源来使用。默认情况下只要集群还有可用的资源,应用就能使用,并没有限制。kubernetes 本身考虑到了多用户和多租户的场景,提出了 namespace 的概念来对集群做一个简单的隔离。

基于 namespace,kubernetes 还能够对资源进行隔离和限制,这就是 resource quota 的概念,翻译成资源配额,它限制了某个 namespace 可以使用的资源总额度。这里的资源包括 cpu、memory 的总量,也包括 kubernetes 自身对象(比如 pod、services 等)的数量。通过 resource quota,kubernetes 可以防止某个 namespace 下的用户不加限制地使用超过期望的资源,比如说不对资源进行评估就大量申请 16核 CPU 32G内存的 pod。

下面是一个资源配额的实例,它限制了 namespace 只能使用 20核 CPU 和 1G 内存,并且能创建 10 个 pod、20个 rc、5个 service,可能适用于某个测试场景。

apiVersion: v1
kind: ResourceQuota
metadata:
  name: quota
spec:
  hard:
    cpu: "20"
    memory: 1Gi
    pods: "10"
    replicationcontrollers: "20"
    resourcequotas: "1"
    services: "5"

resource quota 能够配置的选项还很多,比如 GPU、存储、configmaps、persistentvolumeclaims 等等,更多信息可以参考官方的文档。

Resource quota 要解决的问题和使用都相对独立和简单,但是它也有一个限制:那就是它不能根据集群资源动态伸缩。一旦配置之后,resource quota 就不会改变,即使集群增加了节点,整体资源增多也没有用。kubernetes 现在没有解决这个问题,但是用户可以通过编写一个 controller 的方式来自己实现。

应用优先级

QoS(服务质量)

Requests 和 limits 的配置除了表明资源情况和限制资源使用之外,还有一个隐藏的作用:它决定了 pod 的 QoS 等级。

如果 pod 没有配置 limits ,那么它可以使用节点上任意多的可用资源。这类 pod 能灵活使用资源,但这也导致它不稳定且危险,对于这类 pod 我们一定要在它占用过多资源导致节点资源紧张时处理掉。优先处理这类 pod,而不是资源使用处于自己请求范围内的 pod 是非常合理的想法,而这就是 pod QoS 的含义:根据 pod 的资源请求把 pod 分成不同的重要性等级。

kubernetes 把 pod 分成了三个 QoS 等级:

  • Guaranteed:优先级最高,可以考虑数据库应用或者一些重要的业务应用。除非 pods 使用超过了它们的 limits,或者节点的内存压力很大而且没有 QoS 更低的 pod,否则不会被杀死
  • Burstable:这种类型的 pod 可以多于自己请求的资源(上限有 limit 指定,如果 limit没有配置,则可以使用主机的任意可用资源),但是重要性认为比较低,可以是一般性的应用或者批处理任务
  • Best Effort:优先级最低,集群不知道 pod的资源请求情况,调度不考虑资源,可以运行到任意节点上(从资源角度来说),可以是一些临时性的不重要应用。pod可以使用节点上任何可用资源,但在资源不足时也会被优先杀死

根据QoS进行资源回收

策略

Kubernetes 通过cgroup给pod设置QoS级别,当资源不足时先kill优先级低的 pod,在实际使用过程中,通过OOM分数值来实现,OOM分数值范围为0-1000。OOM 分数值根据OOM_ADJ参数计算得出。

对于Guaranteed级别的 Pod,OOM_ADJ参数设置成了-998,对于Best-Effort级别的 Pod,OOM_ADJ参数设置成了1000,对于Burstable级别的 Pod,OOM_ADJ参数取值从2到999。

对于 kuberntes 保留资源,比如kubelet,docker,OOM_ADJ参数设置成了-999,表示不会被OOM kill掉。OOM_ADJ参数设置的越大,计算出来的OOM分数越高,表明该pod优先级就越低,当出现资源竞争时会越早被kill掉,对于OOM_ADJ参数是-999的表示kubernetes永远不会因为OOM将其kill掉。

QoS pods被kill掉场景与顺序
  • Best-Effort pods:系统用完了全部内存时,该类型 pods 会最先被kill掉。
  • Burstable pods:系统用完了全部内存,且没有 Best-Effort 类型的容器可以被 kill 时,该类型的 pods 会被kill 掉。
  • Guaranteed pods:系统用完了全部内存,且没有 Burstable 与 Best-Effort 类型的容器可以被 kill时,该类型的 pods 会被 kill 掉。

pod优先级(priority)

除了 QoS,kubernetes 还允许我们自定义 pod 的优先级,比如:

apiVersion: scheduling.k8s.io/v1alpha1
kind: PriorityClass
metadata:
  name: high-priority
value: 1000000
globalDefault: false
description: "This priority class should be used for XYZ service pods only."

优先级的使用也比较简单,只需要在 pod.spec.PriorityClassName 指定要使用的优先级名字,即可以设置当前 pod 的优先级为对应的值。

Pod 的优先级在调度的时候会使用到。首先,待调度的 pod 都在同一个队列中,启用了 pod priority 之后,调度器会根据优先级的大小,把优先级高的 pod 放在前面,提前调度。

另外,如果在调度的时候,发现某个 pod 因为资源不足无法找到合适的节点,调度器会尝试 preempt 的逻辑。

简单来说,调度器会试图找到这样一个节点:找到它上面优先级低于当前要调度 pod 的所有 pod,如果杀死它们,能腾足够的资源,调度器会执行删除操作,把 pod 调度到节点上。

例:
在这里插入图片描述

  1. 具有高、中和低优先级的三个 pod 正在等待安排。调度程序找到了一个可用的工作程序节点,该节点有容纳所有 3 个 pod的空间,并按优先级顺序安排这 3 个 pod,首先安排最高优先级的 pod。
  2. 具有高、中和低优先级的三个 pod正在等待安排。调度程序找到了一个可用的工作程序节点,但该工作程序节点只有足够的资源支持高优先级和中等优先级 pod。因此并未安排低优先级pod,该 pod 仍处于暂挂状态。
  3. 具有高优先级和中等优先级的两个 pod 处于等待安排状态。具有低优先级的第三个 pod存在于一个可用工作程序节点上。但是,该工作程序节点没有足够的资源来安排任何暂挂的 pod。因此,调度程序会抢占或除去低优先级pod,这将使该 pod 恢复为暂挂状态。然后,调度程序会尝试调度高优先级 pod。但是,该工作程序节点没有足够的资源来调度高优先级 pod,因此调度程序改为调度中等优先级 pod。

为什么要设置pod优先级

作为集群管理员,您会希望控制哪些 pod 对集群工作负载更关键。优先级类可以帮助您控制 Kubernetes 调度程序决策,使优先级较高的 pod 优先于优先级较低的 pod 进行处理。调度程序甚至可以抢占(除去)正在运行的较低优先级的 pod,以便可以安排暂挂的更高优先级的 pod。

通过设置 pod 优先级,可以帮助防止较低优先级的工作负载影响集群中的关键工作负载,尤其是集群即将达到其资源容量的情况下。

确保已针对集群设置正确的用户访问权,如果适用,还请确保设置了 pod 安全策略。访问权和 pod 安全策略可以帮助防止不可信用户部署高优先级的 pod 而阻止调度其他 pod。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值