在开始使用 Kubernetes 时,社区教给我们的第一件事就是始终为我们 pod 中的每个容器设置 CPU 和内存的请求和限制。
当您指定 Pod 时,您可以选择指定容器需要多少资源。 您指定的最常见资源是 CPU 和内存 (RAM);
apiVersion: v1
kind: Pod
metadata:
name: frontend
spec:
containers:
- name: app
image: images.my-company.example/app:v4
resources:
requests:
memory: "64Mi"
cpu: "250m"
limits:
memory: "128Mi"
cpu: "500m"
如果容器指定了自己的资源限制但没有指定资源请求,那么 Kubernetes 会自动分配一个与指定限制相匹配的资源请求。
然而,经过多年的许多用例经验和调查许多与资源相关的问题,我发现 Kubernetes 资源管理比看起来要复杂得多。
让我们从头开始
Kubernetes 是一种容器编排器,可在资源池(节点)上部署工作负载(Pod)。 当然,这是一个巨大的简化,因为 Kubernetes 复杂得多,并且使用许多不同的参数来调度 pod,但我想在本文中深入探讨的是 Kubernetes 如何管理容器资源。
那么 Kubernetes 可以管理哪些资源呢? 容器消耗多种资源。 最明显的是 CPU 和内存等资源,但它们也会消耗其他资源,例如磁盘空间、磁盘时间 (I/O)、网络带宽、进程 ID、主机端口、IP 地址、GPU、电源等等!
首先,让我们深入了解一下容器
那么,容器到底是什么?
简而言之,容器是一组 Linux 命名空间。
那么,什么是 Linux 名称空间?
Linux 命名空间是一种 Linux 内核功能,它对内核资源进行分区,以便同一 Linux 命名空间中的一个或一组进程可以看到一组内核资源,并与其他命名空间中的进程隔离。 这些命名空间的一些示例是 PID、UID、Cgroups 和 IPC(请参阅 wiki 中的完整列表)。
关于命名空间的另一件事是它们是嵌套的,这意味着命名空间可以在其他命名空间内。 子命名空间与其父命名空间隔离,但父命名空间可以看到子命名空间内的所有内容。
从技术上讲,当运行 Linux 机器时,您已经在容器中(因为您在第一组名称空间中)。 在同一系统中创建另一组名称空间时,我们利用容器的隔离优势。
因此,当启动一个容器时,它会创建一组这样的名称空间并在其中运行您的应用程序。 这也是为什么在容器内部,您会看到应用程序的 PID 通常设置为 1(或较低的数字,具体取决于您正在运行的是什么),而在容器外部(在主 PID 命名空间中),PID 你的应用程序将是一个更大的数字。 这是相同的过程,但容器中的 PID 映射到主命名空间中的更高 PID,并与它和任何其他命名空间集(其他容器)隔离。
命名空间使我们能够将进程彼此隔离,但是资源消耗呢? 如果我们所有的容器都认为它们是孤立运行的,那么它们不会消耗太多资源并影响其他容器吗? 这种现象被称为资源互相影响。
那么我们该如何应对资源互相影响呢? 一种方法是限制每个进程可以消耗的资源,并且(令人惊讶的是)Linux 内核还有另一个可以做到这一点的功能,称为控制组(Cgroups)。 这些是为每个进程配置的,以限制、说明和隔离它们各自消耗的资源。 使用此功能,Kubernetes 可以限制容器的资源使用。
目前,Kubernetes 使用的是 Cgroups v1,但另一个玩家已经进入竞技场(过去五年),Cgroups v2! 它们目前的用途是内存服务质量 (QoS),自从 1.22 处于 alpha 版本以来,它已经打开了一个全新的可能性世界。 你可以在这里读到所有和它有关的。
Kubernetes 目前管理着哪些资源?
Kubernetes 本身目前只管理现有资源的一小部分。 首先,它列出了其节点上每个资源的容量。
# You can see it using kubectl.
kubectl get node -ojson | jq '.items[].status.capacity'
{
"cpu": "2",
"ephemeral-storage": "52416492Ki",
"hugepages-1Gi": "0",
"hugepages-2Mi": "0",
"memory": "8003232Ki",
"pods": "110"
}
然后,它计算用于 Pod 调度的可分配数量。 节点的可分配资源是通过从节点的总资源中减去 Linux 系统、kubelet 的保留资源缓冲区和驱逐阈值来计算的。 从 1.21 开始,kubelet 只计算 CPU、内存、大页面和临时存储的可分配资源。
kubectl get node -ojson | jq '.items[].status.allocatable'
{
"cpu": "1930m",
"ephemeral-storage": "47233297124",
"hugepages-1Gi": "0",
"hugepages-2Mi": "0",
"memory": "7313056Ki",
"pods": "110"
}
每个可分配资源都是一个向量,Kubernetes 调度程序使用它来进行调度决策。
Requests
在调度 pod 时,调度器只考虑 pod 的容器请求对可分配资源的请求(这自然会降低可分配资源的数量,因此下一个 pod 请求将有更少的可分配资源,它可以请求调度)。 它不考虑节点上的实际资源使用情况(即使用资源超过或低于其请求的容器)。
如果我的 pod 中的容器没有分配请求,Kubernetes 可以将它们调度到任何节点(当然,如果没有其他调度限制)。
默认情况下,Kubernetes 每个节点最多可以调度 110 个 pod。
从 Kubernetes 1.21 开始,您可以从 Kubernetes 请求的主要资源是 CPU、内存、临时存储和 HugePages。 此外,您可以通过使用扩展资源请求自定义资源来完成调度(也可以与控制器一起应用,例如 Nvidia 控制器用于 GPU)。
请注意,您还可以在节点级别限制每个 pod 的 PID 消耗。
所以我们了解到资源请求对于调度很重要(不仅仅是调度,更多信息请参见下一部分),其中所有请求的资源都必须在节点中可用,包括扩展资源。
在这系列博文的第二部分,我们将深入探讨 CPU 请求的其他影响。
Limits
资源被考虑用于调度和运行时。 为了限制我们的容器过载和消耗过多资源,Kubernetes 使用了 Cgroups。 Kubernetes 使用容器限制来定义 Cgroup 并限制它们的资源消耗。
可压缩与不可压缩资源
我想退一步谈谈两种不同类型的资源,可压缩和不可压缩。
可压缩资源意味着如果该资源的使用达到其最大值,则需要该资源的进程将不得不等待直到该资源空闲。 换句话说,限制进程。
把它想象成一个水坝; 当大坝的出水管满了,到达大坝的流水超过这些管道的容量时,大坝内的水就会被填满。 通常,我们用时间来衡量可压缩资源。
CPU 是一种可压缩资源,这意味着如果 CPU 使用率为 100%,则需要 CPU 的进程将需要等待,直到它们获得 CPU 时间。
可压缩资源不会被逐出!
另一方面,不可压缩的资源意味着进程不能等待它; 它们要么无法运行,要么必须停止并为新进程释放资源。
把它想象成把盒子放在架子上,一旦架子上装满了盒子,你就不能再把另一个盒子放在架子上了。 您要么必须通过从架子上取下箱子或根本不将箱子放在架子上来腾出空间。 内存是一种不可压缩的资源,这意味着如果您的内存不足并且想要为新的或现有的进程分配内存,您必须终止占用内存空间的进程,否则该进程将崩溃。
对于 Kubernetes,它管理的唯一可压缩资源是 CPU。 Kubernetes 管理的其他资源(内存、HugePages、临时存储和 PID)都是不可压缩的。
当您为 CPU 等可压缩资源指定限制时,Kubernetes 会确保在它们尝试消耗超过其允许水平时限制它们。 另一方面,Kubernetes 必须使用驱逐来处理不可压缩资源的限制。 我们将在接下来的博文中对此进行深入探讨。
Requests vs. Limits
所以我们知道我们使用资源请求作为 Kubernetes 调度程序的“手动”指南,以根据确保我们的工作量所需的最小数量做出调度决策。
我们还可以使用资源限制作为 Kubernetes 的指令,它应该为我们的容器及其阈值配置哪些 Cgroup。
当使用扩展资源时,Kubernetes 将使用请求进行调度,但不会使用限制来设置任何 Cgroup 并限制那些特殊资源的使用。
服务质量——不是真正的底线
我们可以为 Pod 中的容器指定资源请求和限制; 基于这些参数,Kubernetes 还为我们的 pod 分配了一个 QoS 类(服务质量)。
# Try this command to view your current QoS.
kubectl get pods -A -o=jsonpath='{range .items[*]}{.metadata.namespace}{" : "}{.metadata.name}{" --QoS--> "}{.status.qosClass}{"\n"}{end}'
听起来不错,服务质量并不是 pod 优先级的最终决定。 作为 Kubernetes 用户,我们可以看到此参数,以估计在高资源压力和驱逐事件的情况下我们 pod 的可能优先级。 还有很多其他原因,例如所谓的较低 QoS pod 可能会在驱逐事件中幸存下来,而较高 QoS 类 pod 可能会被终止。
到本博客系列结束时,您将了解您需要了解的有关 QoS 含义的所有信息。
首先,QoS 分为三类:
- 保证
- 可爆
- 最大努力
要使 Pod 的 QoS 等级为 Guaranteed,Pod 中的每个容器都必须同时具有内存和 CPU,并且限制和请求相等。
如果 Pod 至少有一个带有内存或 CPU 请求的容器,则该 Pod 的 QoS 等级为 Burstable。
对于具有 BestEffort QoS 类的 Pod,Pod 中的容器不得有任何内存或 CPU 限制或请求。
请注意,这仅使用 CPU 和内存来计算 pod 的 QoS 等级。
关于QoS的使用,你应该知道:
- 它用于设置 OOM_Score_adj 参数——更多内容在第 4 部分。
- 它用于设置 QoS Cgroups——到目前为止没有效果,是未来的 QoS 功能。
总结一下;
所以要了解的信息很多,而这第一部分只是探究resources的基础知识。