cover_image

Kubernetes 的资源管理艺术

流月 信也科技拍黑米
2024年05月13日 02:09

1. 前言

在当前云原生技术的发展浪潮中,Kubernetes已经无可争议地成为领导者,其卓越的容器编排能力极大地方便了开发人员和运维团队。

然而,支撑Kubernetes的技术远比看上去的更加复杂。Cgroups作为 Linux内核的一个重要功能,被Kubernetes广泛应用于各级资源的管理。通过Cgroups,Kubernetes能够对CPU、内存等资源进行精细的控制和管理,从而显著提升了系统的可用性、稳定性和性能。

本文将深入讨论Kubernetes如何利用Cgroups管理资源,并提供一些推荐的配置和最佳实践。

2. Cgroups简介

Cgroups,全称为Control Groups,即控制组,允许对指定进程进行各种计算资源的限制,包括但不限于CPU使用量、内存使用量、IO设备的流量等。

Cgroups的优势在哪里?在Cgroups出现之前,任何进程都能创建数百甚至数千个线程,这可能会轻易耗尽一台计算机的所有CPU和内存资源。

然而,引入Cgroups技术后,我们便能对单个进程或一组进程的资源消耗进行限制。

Cgroups通过不同的子系统来限制不同的资源,其中每个子系统负责限制一种特定资源。这些子系统以类似的方式工作:将相关进程分配到一个控制组中,并通过树状结构进行管理,每个控制组都有其自定义的资源控制参数。

子系统功能
cpu管理 cgroups 中进程的 CPU 使用率
cpuacct统计 cgroups 中进程的 CPU 使用情况
cpuset为 cgroups 中的任务分配独立的 CPU 和内存节点
memory管理 cgroups 中进程的内存使用量
blkio管理 cgroups 中进程的块设备 IO
devices控制 cgroups 中进程对某些设备的访问权限
...
...

表1 常用 cgroups 子系统

在操作系统中,我们可以使用以下命令查看Cgroups挂载目录

$ mount | grep cgroup
tmpfs on /sys/fs/cgroup type tmpfs (ro,nosuid,nodev,noexec,mode=755)cgroup on /sys/fs/cgroup/systemd type cgroup (rw,nosuid,nodev,noexec,relatime,xattr,release_agent=/usr/lib/systemd/systemd-cgroups-agent,name=systemd)cgroup on /sys/fs/cgroup/pids type cgroup (rw,nosuid,nodev,noexec,relatime,pids)cgroup on /sys/fs/cgroup/memory type cgroup (rw,nosuid,nodev,noexec,relatime,memory)cgroup on /sys/fs/cgroup/net_cls,net_prio type cgroup (rw,nosuid,nodev,noexec,relatime,net_prio,net_cls)cgroup on /sys/fs/cgroup/blkio type cgroup (rw,nosuid,nodev,noexec,relatime,blkio)cgroup on /sys/fs/cgroup/perf_event type cgroup (rw,nosuid,nodev,noexec,relatime,perf_event)cgroup on /sys/fs/cgroup/cpu,cpuacct type cgroup (rw,nosuid,nodev,noexec,relatime,cpuacct,cpu)cgroup on /sys/fs/cgroup/freezer type cgroup (rw,nosuid,nodev,noexec,relatime,freezer)cgroup on /sys/fs/cgroup/cpuset type cgroup (rw,nosuid,nodev,noexec,relatime,cpuset)cgroup on /sys/fs/cgroup/hugetlb type cgroup (rw,nosuid,nodev,noexec,relatime,hugetlb)cgroup on /sys/fs/cgroup/devices type cgroup (rw,nosuid,nodev,noexec,relatime,devices)

在 Cgroups根目录下,我们可以看到各子系统的文件

$ ll /sys/fs/cgroup
total 0drwxr-xr-x 6 root root 0 Apr 16 05:13 blkiolrwxrwxrwx 1 root root 11 Apr 16 05:13 cpu -> cpu,cpuacctlrwxrwxrwx 1 root root 11 Apr 16 05:13 cpuacct -> cpu,cpuacctdrwxr-xr-x 6 root root 0 Apr 16 05:13 cpu,cpuacctdrwxr-xr-x 5 root root 0 Apr 16 05:13 cpusetdrwxr-xr-x 5 root root 0 Apr 16 05:13 devicesdrwxr-xr-x 4 root root 0 Apr 16 05:13 freezerdrwxr-xr-x 5 root root 0 Apr 16 05:13 hugetlbdrwxr-xr-x 6 root root 0 Apr 16 05:13 memorylrwxrwxrwx 1 root root 16 Apr 16 05:13 net_cls -> net_cls,net_priodrwxr-xr-x 4 root root 0 Apr 16 05:13 net_cls,net_priolrwxrwxrwx 1 root root 16 Apr 16 05:13 net_prio -> net_cls,net_priodrwxr-xr-x 4 root root 0 Apr 16 05:13 perf_eventdrwxr-xr-x 6 root root 0 Apr 16 05:13 pidsdrwxr-xr-x 6 root root 0 Apr 16 05:13 systemd

3. Cgroups驱动介绍及选型

3.1 Cgroups 驱动介绍

Cgroups驱动主要有两种类型:

  • systemd

  • cgroupfs


systemd:

  • systemd是一个系统和服务管理器,广泛用作许多现代Linux发行版的 init系统。

  • systemd直接通过其控制接口管理cgroups,并通过工具(如 systemctl、systemd-cgls、systemd-cgtop等)提供cgroups的管理和监控功能。


cgroupfs:

  • cgroupfs通过文件系统接口管理cgroups。

  • cgroupfs将cgroups以文件系统的形式挂载至/sys/fs/cgroup目录,允许用户通过文件和目录操作来管理cgroups,这类似于对普通文件系统的操作。

systemd驱动与cgroupfs驱动的主要区别在于它们的管理接口。systemd 提供了一整套专用工具和接口来管理cgroups,而cgroupfs则通过文件系统接口进行管理。在实际应用中,尽管大多数现代Linux发行版采用 systemd来管理cgroups,cgroupfs仍然作为Linux内核支持的一个选项存在。

3.2 Kubernetes 中 Cgroups 驱动选择

Kubelet默认使用cgroupfs作为Cgroups驱动。然而,如果您通过 kubeadm部署Kubernetes 集群(版本 1.22 及以上),系统会默认采用 systemd作为Cgroups驱动。

我们不能让cgroupfs和systemd都作为我们的翅膀,必须二选一,那么我们应该如何选择呢?

其实也很简单,操作系统使用的是哪个Cgroups驱动,我们就选哪个

因为如果一个系统中同时存在两种Cgroups管理器,假设kubelet和容器运行时(例如containerd)使用cgroupfs,而操作系统则使用systemd,那么在资源压力增大时,节点可能会变得不稳定。(修罗场)

对于大多数现代Linux发行版,如RHEL、CentOS、Fedora、Ubuntu、Debian等,它们都采用systemd来管理cgroups。因此,在这些Linux发行版上,kubelet和容器运行时应选择systemd作为Cgroups驱动。

4. Kubernetes 是如何使用 Cgroups 来管理资源的

在Kubernetes中,Cgroups用于管理不同级别的资源,这些级别从大到小依次为:

  • Node

  • Pod

  • Container


接下来,我们将按照Node、Pod 和Container这三个级别,逐级描述 Kubernetes中的Cgroups管理方式。同时,我们也会讨论Kubernetes如何利用QoS (Quality of Service) 策略来优化资源分配和管理。

4.1 Node 级别

在Kubernetes集群的一个节点中,资源可分为三部分:

  • 系统组件使用的资源:操作系统本身的进程所使用的资源,如 sshd, systemd等

  • Kubernetes组件使用的资源:如 kubelet, 容器运行时(如 Docker, containerd)等

  • Pod使用的资源:集群内Pod所使用的资源

如果我们不对这三个部分的资源使用进行管理,节点资源紧张时,可能会导致集群雪崩。

想象以下场景:

  • 集群中的A节点上的Pod glutton吃光了节点上所有的资源

  • 由于资源竞争,系统组件和Kubernetes组件因为抢不到资源而崩溃

  • 节点A状态变为Not Ready

  • Kubernetes尝试将节点A上的Pod glutton迁移到其他处于Ready状态的节点,例如节点B

  • 节点B上的资源随后也被Pod glutton完全占用

  • ...

本来集群和人都能跑,现在只有人能跑了。

那么,为了集群的健康,更为了人的健康(饭碗),聪明的你,或许已经想到了一个智慧的方法:提前为系统组件和Kubernetes组件预留资源,防止资源被恶意或异常的Pod完全占用。

巧了,Kubernetes的设计者也是这么想的。

4.1.1 Node 级别资源管理

在Node层面,Kubernetes允许我们修改以下配置来预留并限制资源:

  • systemReserved

  • kubeReserved

  • evictionHard

  • enforceNodeAllocatable


systemReserved

为系统组件预留的资源,如sshd等。

如果配置了此项,则需要同时配置systemReservedCgroup来指定系统组件的Cgroups路径。

同时,需要在kubelet启动前预先检查/创建systemReservedCgroup的路径,否则kubelet会启动失败。

示例:

systemReserved:  cpu: 100m  memory: 2048Mi  ephemeral-storage: 1Gi  pid: "1000"systemReservedCgroup: /system.slice

kubeReserved

为Kubernetes组件预留的资源,如 kubelet,容器运行时等。

如果配置了此项,则需要同时配kubeReservedCgroup来指定Kubernetes 组件的Cgroups路径。

同时,需要在kubelet启动前预先检查/创建kubeReservedCgroup的路径,否则kubelet会启动失败。

示例:

kubeReserved:  cpu: 100m  memory: 2048Mi  ephemeral-storage: 1Gi  pid: "1000"kubeReservedCgroup: /kube.slice

evictionHard

节点压力驱逐的条件,kubelet会监视集群节点上的内存、磁盘空间和文件系统inodes等资源。当这些资源中的一项或多项达到指定条件时,kubelet 会主动终止 pod以回收节点资源。

示例:

evictionHard:  imagefs.available: 10%  memory.available: 500Mi  nodefs.available: 5%  nodefs.inodesFree: 5%
enforceNodeAllocatable

指定需要限制的资源类型,默认为pods,即只会限制节点上pod使用的资源。

另外,也可以限制系统组件的资源(system-reserved)和Kubernetes 组件的资源(kube-reserved)。

为了确保核心组件的稳定运行,通常建议不要限制系统组件和 Kubernetes组件的资源,以避免这些核心组件出现资源不足的情况。

示例:

enforceNodeAllocatable:-pods

经过上述配置后,节点上Pod所能使用的资源总量(Allocatable)为:

节点资源总量(Capacity) - evictionHard - kubeReserved - systemReserved

如下图:

图片

图4.1 节点上 Pod 所能使用的资源总量

4.1.2 Node 级别的 Cgroups 层级
Cgroups层级目录如下(以 CPU 子系统为例)

图片

图4.2 Cgroups 层级目录

在上图中:

  • /system.slice:管理系统组件的资源使用

  • /kube.slice:管理Kubernetes组件(如kubelet、容器运行时等)的资源使用

  • /kubepods.slice:管理该节点上Kubernetes集群中Pod的资源使用

4.2 QoS 级别

QoS,全称Quality of Service,即服务质量。

Kubernetes对所有Pod进行分类,并根据它们的资源请求(Requests)和限制(Limits)的不同,将它们分配到特定的QoS级别。

在节点资源压力情况下,Kubernetes会根据QoS级别来决定哪些Pod应该被驱逐。

4.2.1 QoS 等级

Qo 等级共有 3 个:

  • Guaranteed

  • Burstable

  • BestEffort

Guaranteed

Pod中所有的容器(不包含临时容器)都设置了CPU和内存资源的请求(Requests)和限制(Limits),且请求和限制的值相等。

Burstable

不满足Guaranteed等级的条件,但至少有一个容器设置了CPU和内存资源的请求(Requests)或限制(Limits)。

BestEffort

既不满足Guaranteed等级的条件,也不满足Burstable等级的条件,即 Pod中所有的容器都没有设置CPU和内存资源的请求(Requests)和限制(Limits)。

三种QoS等级的Pod重要性等级如下:

Guaranteed > Burstable > BestEffort

即当节点资源耗尽时,会首先驱逐BestEffort等级的Pod,然后是 Burstable等级的Pod,最后是Guaranteed等级的Pod。当此驱逐是由于资源压力而导致时,只有超出资源请求的Pod才是驱逐的候选者。

4.2.2 QoS 级别的 Cgroups 层级

Cgroups层级目录如下(以 CPU 子系统为例)

图片

图4.3 Cgroups 层级目录

如图所示,Guaranteed等级的Pod的Cgroups目录在 最外层,Burstable 和BestEffort等级的Cgroups目录则是在子目录。

4.3 Pod 级别

4.3.1 Pod 级别的资源管理

一个Pod可以包含一个或多个容器。然而,Pod的总资源消耗并不仅限于其内部所有容器资源消耗的总和。实际上,Pod的资源使用还包括额外的开销,具体包括:

  • Pod基础资源开销:每个Pod都会产生一些基础的资源开销,这主要包括网络配置和Pod内部运行的一些基础服务的资源消耗。

  • Kubernetes系统容器:Kubernetes在每个Pod中运行一个特殊的容器,通常称为pause容器。这个容器作为所有其他容器的父容器,负责管理网络、卷挂载等任务。虽然pause容器消耗的资源相对较少,但在计算Pod的总资源消耗时,这部分资源也需要被考虑。

为了有效管理Pod的资源消耗,Kubernetes在Pod层面建立了一个 Cgroups层级,通过这种方式可以更精确地控制和监控Pod及其容器的资源使用情况。

4.3.2 Pod 级别的 Cgroups 层级

Cgroups层级目录如下(以 CPU 子系统为例)

图片

图4.4 Cgroups 层级目录

4.4 Container级别

4.4.1 Container级别的资源管理

容器级别的资源隔离是由底层容器运行时(如 containerd)实现的。在 Kubernetes环境中,为容器配置资源请求(requests)和限制(limits)后,这些配置信息通过 kubelet 传递给容器运行时。这一过程依赖于 Kubernetes的容器运行时接口(Container Runtime Interface, CRI),CRI为kubelet与容器运行时之间的交互提供了标准化的协议和工具集。

容器运行时接收到资源配置信息后,利用Cgroups对每个容器的资源使用进行限制和隔离。通过配置Cgroups,容器运行时可以对CPU、内存等资源进行精确的控制。具体来说,它会配置cpu.sharescpu.cfs_period_uscpu.cfs_quota_usmemory.limit_in_bytes等参数,确保容器的资源消耗不会超出预设的限制。

总体而言,kubelet负责传递资源配置信息,而具体的资源隔离和限制则是由容器运行时通过配置cgroups实现的。这种设计使得Kubernetes能够高效且安全地管理容器资源。

4.4.2 CPU资源配置
  • CPU 请求(request):通过Cgroups的cpu.shares 实现。当容器的 CPU请求设置为x millicores时,其cpu.shares 值计算为x * 1024 / 1000。例如,如果容器的CPU请求为1000 millicores(即相当于 1 个CPU核心),那么cpu.shares的值将为1024。这样做确保了即使在 CPU资源紧张的情况下,该容器也能获得至少相当于1个CPU核心的计算资源。

  • CPU 限制(limit):通过Cgroups的cpu.cfs_period_uscpu.cfs_quota_us实现。在Kubernetes中,cpu.cfs_period_us被设置为100000微秒(即 100 毫秒),而cpu.cfs_quota_us根据CPU限制来计算。这两个参数共同作用,严格限制容器的CPU使用量,确保不会超过设定的限制。如果只指定了request而没有指定limit,那么cpu.cfs_quota_us将被设置为-1(表示没有限制)。如果既没有指定 request也没有指定limit,那么cpu.shares将被设置为2,意味着分配给该容器的CPU资源将是最低限度的。


4.4.3 内存资源配置
  • 内存请求(request):Kubernetes 在进行调度决策时会考虑内存请求,这一参数虽然不会直接反映在容器级别的Cgroups配置中,但它确保了在节点上为容器分配了足够的内存资源。内存请求主要用于 Kubernetes 的调度过程,以保证节点具有为容器分配所需的内存资源。

  • 内存限制(limit):内存限制是通过Cgroups的 memory.limit_in_bytes 参数来实现的,它定义了容器进程可以使用的最大内存量。若容器的内存使用超过了这一限制,将会触发OOM Killer,可能会导致容器被终止并重新启动。如果没有指定内存限制,则 memory.limit_in_bytes会被设置为一个极大的值,这通常意味着容器可以无限制地使用内存。

简而言之,Kubernetes通过Cgroups配置实现容器的资源隔离,确保每个容器根据其请求(request)和限制(limit)获得合理的资源分配。这一机制既提高了资源的利用效率,又防止了任何单一容器的过度资源消耗,从而保障了系统的整体稳定性。

4.4.4 Container 级别的 Cgroups 层级

Cgroups 层级目录如下(以 CPU 子系统为例)

图片

图4.5 Cgroups 层级目录

最后,让我们一起看下CPU子系统的Cgroup目录结构。

图片

图4.6 CPU子系统的Cgroups 层级目录

参考资料

[1] Kubernetes 官方文档(https://kubernetes.io/docs/home/)

作者介绍

图片

招聘信息

Java、大数据、前端、测试等各种技术岗位热招中,欢迎了解~

更多福利请关注官方订阅号信也科技拍黑米拍码场

喜欢请点击↓↓↓

继续滑动看下一个
信也科技拍黑米
向上滑动看下一个