Kubernetes 内存管理及调度

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Kubernetes 内存管理及调度相关的知识,希望对你有一定的参考价值。

参考技术A 为了使Kubernetes(K8s)能够可靠地为您的应用程序分配运行时所需的资源,并充分利用计算机资源,您应该明确指定容器运行所需要的资源要求。当前,您可以为两种类型的容器资源(内存和CPU)设置两种限制,即requests和limits。我们会在后面详细说明这两种限制需求的含义以及Docker容器运行时内存的含义。

定义Pod时,可以为内存和CPU指定两类资源限制要求:requests和limits:

Requests 是K8s的概念,主要会影响K8s中容器的调度:会根据 Requests 的值决定将Pod放置在那个node上,因此会影响容器的调度 。 Limits 表示容器可用的资源的上限,因此会影响容器的实际运行。若容器的资源使用超出了limits的限制会导致节流,或者在最坏的情况下会导致容器被终止。

您可能会问为什么要将limits的值设置的高于requests。如果您的应用程序的内存占用空间非常固定,则没有必要这样做,而且一般情况下也不建议将内存的limits值设置的比requests的值大。如果使用CPU,则有可能在其他容器的CPU不繁忙的情况下,使用limits和requests之间的资源部分,从而可以更加有效的利用CPU资源。 Burstable QoS类(下面会详细说明)允许潜在地更有效地利用计算机资源,但具有更大的不可预测性-例如,与CPU绑定的应用程序的响应延迟可能会受到位于同一个node上的其他容器的影响。如果您还不熟悉K8s,一开始最好通过将requests和limits的值设置为相同从而使用Guaranteed QoS类开始,关于Qos的分类,参考 这里 。

内存在这里到底意味着什么?简而言之,它代表了容器的总​​驻留集大小(RSS)和page cache。在纯Docker中,此数字通常还包含了swap,但是在K8s中,我们会禁用swap。

RSS是进程使用的RAM数量。对于Java进程,这可以包括多个区域,包括native heap/非heap区域,线程stack和native的内存分配。 RSS受JVM线程配置、线程数和应用程序的影响。

page cache是用于缓存磁盘中的数据块的RAM区域。出于性能原因,所有I / O通常都通过此cache执行。每当您的程序从文件中读取或写入文件时,都可以期望将相关的数据块缓存在此处。您读取或写入的文件越多,要求就越高。请注意,内核会将可用的备用内存用于page cache,但是如果其他地方需要,它将回收page cache-这意味着如果没有足够的内存,程序的性能可能会受到影响(取决于其对page cache的性能依赖)。这里有一个潜在的问题-Docker的overlayfs存储驱动启用了page cache共享,这意味着在同一个node上的多个containers访问同一文件时,会共享该文件对应的page cache(考虑使用index或其他共享的内容)。 Docker  文档 指出:

计算page cache所占据的内存大小非常复杂。如果不同control groups中的两个进程都读取同一文件(最终依赖于磁盘上的相同块),则会将内存开销在多个control groups之间进行分配。但这也意味着当一个cgroup终止时,它可能会增加另一个cgroup的内存使用量,因为它们不再为这些内存中的pages而分配内存。

…因此请注意, 每个容器的page cache使用情况可能会有所不同 ,具体取决于与同一节点上运行的其他容器共享哪些文件。

内存的limits值在K8s中被认为是不可压缩的,这意味着它不能被限流。如果您的容器存在内存压力,内核将积极删除page cache条目以满足需求,并且最终可能会因为Linux内存不足(OOM)而被杀死。由于K8s禁用了swap(通过将memory-swappiness = 0传递给Docker),因此一旦内存错误配置,容器将会无法启动。

参考: https://medium.com/expedia-group-tech/kubernetes-container-resource-requirements-part-1-memory-a9fbe02c8a5f

Kubernetes-- 资源管理

在 Kubernetes 里,Pod 是最小的原子调度单位。这也就意味着,所有跟调度和资源管理相关的属性都应该是属于 Pod 对象的字段。而这其中最重要的部分,就是 Pod 的 CPU 和内存配置,如下所示:


apiVersion: v1
kind: Pod
metadata:
  name: frontend
spec:
  containers:
  - name: db
    image: mysql
    env:
    - name: MYSQL_ROOT_PASSWORD
      value: "password"
    resources:
      requests:
        memory: "64Mi"
        cpu: "250m"
      limits:
        memory: "128Mi"
        cpu: "500m"
  - name: wp
    image: wordpress
    resources:
      requests:
        memory: "64Mi"
        cpu: "250m"
      limits:
        memory: "128Mi"
        cpu: "500m"

在 Kubernetes 中,像 CPU 这样的资源被称作“可压缩资源”(compressible resources)。它的典型特点是,当可压缩资源不足时,Pod 只会“饥饿”,但不会退出。

而像内存这样的资源,则被称作“不可压缩资源(incompressible resources)。当不可压缩资源不足时,Pod 就会因为 OOM(Out-Of-Memory)被内核杀掉。

其中,Kubernetes 里为 CPU 设置的单位是“CPU 的个数”。比如,cpu=1 指的就是,这个 Pod 的 CPU 限额是 1 个 CPU。当然,具体“1 个 CPU”在宿主机上如何解释,是 1 个 CPU 核心,还是 1 个 vCPU,还是 1 个 CPU 的超线程(Hyperthread),完全取决于宿主机的 CPU 实现方式。Kubernetes 只负责保证 Pod 能够使用到“1 个 CPU”的计算能力。

Kubernetes 里 Pod 的 CPU 和内存资源,实际上还要分为 limits 和 requests 两种情况,如下所示:


spec.containers[].resources.limits.cpu
spec.containers[].resources.limits.memory
spec.containers[].resources.requests.cpu
spec.containers[].resources.requests.memory

在调度的时候,kube-scheduler 只会按照 requests 的值进行计算。而在真正设置 Cgroups 限制的时候,kubelet 则会按照 limits 的值来进行设置。

更确切地说,当你指定了 requests.cpu=250m 之后,相当于将 Cgroups 的 cpu.shares 的值设置为 (250/1000)*1024。而当你没有设置 requests.cpu 的时候,cpu.shares 默认则是 1024。这样,Kubernetes 就通过 cpu.shares 完成了对 CPU 时间的按比例分配。

Kubernetes 这种对 CPU 和内存资源限额的设计,实际上参考了 Borg 论文中对“动态资源边界”的定义,既:容器化作业在提交时所设置的资源边界,并不一定是调度系统所必须严格遵守的,这是因为在实际场景中,大多数作业使用到的资源其实远小于它所请求的资源限额。

基于这种假设,Borg 在作业被提交后,会主动减小它的资源限额配置,以便容纳更多的作业、提升资源利用率。而当作业资源使用量增加到一定阈值时,Borg 会通过“快速恢复”过程,还原作业原始的资源限额,防止出现异常情况。

而 Kubernetes 的 requests+limits 的做法,其实就是上述思路的一个简化版:用户在提交 Pod 时,可以声明一个相对较小的 requests 值供调度器使用,而 Kubernetes 真正设置给容器 Cgroups 的,则是相对较大的 limits 值。不难看到,这跟 Borg 的思路相通的。

在 Kubernetes 中,不同的 requests 和 limits 的设置方式,其实会将这个 Pod 划分到不同的 QoS 级别当中。

当 Pod 里的每一个 Container 都同时设置了 requests 和 limits,并且 requests 和 limits 值相等的时候,这个 Pod 就属于 Guaranteed 类别,如下所示:


apiVersion: v1
kind: Pod
metadata:
  name: qos-demo
  namespace: qos-example
spec:
  containers:
  - name: qos-demo-ctr
    image: nginx
    resources:
      limits:
        memory: "200Mi"
        cpu: "700m"
      requests:
        memory: "200Mi"
        cpu: "700m"

当这个 Pod 创建之后,它的 qosClass 字段就会被 Kubernetes 自动设置为 Guaranteed。需要注意的是,当 Pod 仅设置了 limits 没有设置 requests 的时候,Kubernetes 会自动为它设置与 limits 相同的 requests 值,所以,这也属于 Guaranteed 情况。

而当 Pod 不满足 Guaranteed 的条件,但至少有一个 Container 设置了 requests。那么这个 Pod 就会被划分到 Burstable 类别。比如下面这个例子:


apiVersion: v1
kind: Pod
metadata:
  name: qos-demo-2
  namespace: qos-example
spec:
  containers:
  - name: qos-demo-2-ctr
    image: nginx
    resources:
      limits
        memory: "200Mi"
      requests:
        memory: "100Mi"

而如果一个 Pod 既没有设置 requests,也没有设置 limits,那么它的 QoS 类别就是 BestEffort。比如下面这个例子:


apiVersion: v1
kind: Pod
metadata:
  name: qos-demo-3
  namespace: qos-example
spec:
  containers:
  - name: qos-demo-3-ctr
    image: nginx

那么,Kubernetes 为 Pod 设置这样三种 QoS 类别,具体有什么作用呢?

实际上,QoS 划分的主要应用场景,是当宿主机资源紧张的时候,kubelet 对 Pod 进行 Eviction(即资源回收)时需要用到的。

而当 Eviction 发生的时候,kubelet 具体会挑选哪些 Pod 进行删除操作,就需要参考这些 Pod 的 QoS 类别了。

  1. 首当其冲的,自然是 BestEffort 类别的 Pod。( Pod 既没有设置 requests,也没有设置 limits)
  2. 其次,是属于 Burstable 类别、并且发生“饥饿”的资源使用量已经超出了 requests 的 Pod。( Pod 不满足 Guaranteed 的条件,但至少有一个 Container 设置了 requests。那么这个 Pod 就会被划分到 Burstable 类别)
  3. 最后,才是 Guaranteed 类别。并且,Kubernetes 会保证只有当 Guaranteed 类别的 Pod 的资源使用量超过了其 limits 的限制,或者宿主机本身正处于 Memory Pressure 状态时,Guaranteed 的 Pod 才可能被选中进行 Eviction 操作。

以上是关于Kubernetes 内存管理及调度的主要内容,如果未能解决你的问题,请参考以下文章

kubernetes之计算机资源管理

Kubernetes-- 资源管理

Kubernetes-- 资源管理

Kubernetes-- 资源管理

常见的嵌入式OS内存管理和进程调度方式

Linux 内核Linux 操作系统结构 ( Linux 内核在操作系统中的层级 | Linux 内核子系统及关系 | 进程调度 | 内存管理 | 虚拟文件系统 | 网络管理 | 进程间通信 )