天天看点

Kubernetes 的资源管理艺术

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 0
drwxr-xr-x 6 root root  0 Apr 16 05:13 blkio
lrwxrwxrwx 1 root root 11 Apr 16 05:13 cpu -> cpu,cpuacct
lrwxrwxrwx 1 root root 11 Apr 16 05:13 cpuacct -> cpu,cpuacct
drwxr-xr-x 6 root root  0 Apr 16 05:13 cpu,cpuacct
drwxr-xr-x 5 root root  0 Apr 16 05:13 cpuset
drwxr-xr-x 5 root root  0 Apr 16 05:13 devices
drwxr-xr-x 4 root root  0 Apr 16 05:13 freezer
drwxr-xr-x 5 root root  0 Apr 16 05:13 hugetlb
drwxr-xr-x 6 root root  0 Apr 16 05:13 memory
lrwxrwxrwx 1 root root 16 Apr 16 05:13 net_cls -> net_cls,net_prio
drwxr-xr-x 4 root root  0 Apr 16 05:13 net_cls,net_prio
lrwxrwxrwx 1 root root 16 Apr 16 05:13 net_prio -> net_cls,net_prio
drwxr-xr-x 4 root root  0 Apr 16 05:13 perf_event
drwxr-xr-x 6 root root  0 Apr 16 05:13 pids
drwxr-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           

如下图:

Kubernetes 的资源管理艺术

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

4.1.2 Node 级别的 Cgroups 层级

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

Kubernetes 的资源管理艺术

图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 子系统为例)

Kubernetes 的资源管理艺术

图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 子系统为例)

Kubernetes 的资源管理艺术

图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.shares、cpu.cfs_period_us、cpu.cfs_quota_us、memory.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_us和 cpu.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 子系统为例)

Kubernetes 的资源管理艺术

图4.5 Cgroups 层级目录

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

Kubernetes 的资源管理艺术

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

参考资料

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

作者:流月

来源-微信公众号:信也科技拍黑米

出处:https://mp.weixin.qq.com/s/BiMFUkZ47wo5znBhgAnuPA

继续阅读