集群计算资源管理

1. 集群计算资源规划

1.1. 概述

Kubernetes 集群中的 Node 都属于提供计算资源的 Node,kubelet 会管理计算资源 Node 上的资源预留和可分配资源(Node Allocatable Resources )。kublete 通过以下几个重要参数来管理资源预留。

  • system-reserved:为操作系统进程预留的资源。(如 sshd、systemd,以及你自己安装的通过 system 管理的服务等)
  • kube-reserved:为kubelet、kube-proxy、容器运行时(Container Runtime)等核心系统进程预留的资源。
    • 这里的kube-reserved只为非pod形式启动的kube组件预留资源,假如组件要是以static pod(kubeadm)形式启动的,那并不在这个kube-reserved管理并限制的cgroup中,而是在kubepod这个cgroup中。
  • eviction-hard:触发驱逐Pod进行资源回收的资源低水位警戒线。

每个计算资源 Node 上的可分配计算资源量是 Node 资源总量减去上面3个分量的结果,预留资源越多,可分配资源越少,这是需要权衡考虑的。

1.2. cgroup

cgroup 是 Linux 内核的一个特性,用于限制、记录和隔离一个进程组群(process groups)所使用的物理资源(如 CPU、内存、磁盘 I/O、网络等)。

核心思想:将进程分组,然后对一组进程进行统一的资源监控和限制。

类比

  • 如果把服务器资源比作一个蛋糕。
  • 没有 cgroup:所有进程一起抢蛋糕吃,谁抢得快谁就吃得多,可能导致某些重要进程饿死。
  • 有 cgroup:蛋糕被预先切好。这一块给 Web 服务进程组,那一块给数据库进程组。每个组只能吃自己那份,不能抢别人的。

Cgroup 是如何工作的?(以 v2 为例)

内核通过一个虚拟文件系统(通常是 /sys/fs/cgroup/)暴露 cgroup 的接口。

操作流程通常是:

  • 创建 Cgroup:其实就是在这个文件系统下创建一个目录。
mkdir /sys/fs/cgroup/my_app_group
Bash
  • 配置限制:向该目录下的特定文件写入值。
# 限制该组内存最大使用为 500MB
echo "500M" > /sys/fs/cgroup/my_app_group/memory.max

# 设置 CPU 权重为 500(相对比例)
echo "500" > /sys/fs/cgroup/my_app_group/cpu.weight
Bash
  • 将进程加入 Cgroup:将进程的 PID 写入该目录下的 cgroup.procs 文件
echo <PID> > /sys/fs/cgroup/my_app_group/cgroup.procs
Bash

之后,这个进程及其创建的所有子进程都将受到你设置的资源限制的约束。

Cgroup 与 Kubernetes 的关系

Kubernetes 使用 cgroup 来实施 Pod 和容器的资源限制(limits)和请求(requests)。

层级结构示例:

在 Cgroup v2 下,Kubernetes 的 cgroup 路径通常像这样:

/sys/fs/cgroup/

├── kubepods.slice/                 # 所有 Pod 的根 Cgroup
   ├── kubepods-pod<pod_uid>.slice/ # 单个 Pod 的 Cgroup
      ├── cri-containerd-<container_id>.scope/  # Pod 中的第一个容器
      └── cri-containerd-<container_id>.scope/  # Pod 中的第二个容器
   └── ... 

├── system.slice/                   # 系统服务(kubelet, containerd等)的 Cgroup
   └── containerd.service/         

└── user.slice/                     # 用户会话的 Cgroup
Bash

当你设置 kube-reserved 时,Kubelet 就会将限制写入/sys/fs/cgroup/system.slice/containerd.service/memory.max 这样的文件。

在设置 system-reservedkube-reserved 时需要注意,需要事先创建好对应的 cgroup,k8s 不会自动创建。

1.3. 资源预留配置示例

配置 kube-reserved 预留资源

用 systemd 控制 kubelet 服务进程时,默认会把它放入 system.slice 这个 cgroup 控制组中,需要为 kubelet 单独创建一个 cgroup,这个 cgroup 的名称为“kubeonly.slice”,同时把它作为kube-reserved 的控制组。

  • 修改 kubelet 服务的描述文件 kubelet.service,通过 systemd 创建出 kubeonly.slice控制组
# 不要直接编辑 /usr/lib/systemd/system/kubelet.service 文件,因为该文件可能在包更新时被覆盖。
# 创建配置目录
mkdir -p /etc/systemd/system/kubelet.service.d/
# 创建配置文件
vim /etc/systemd/system/kubelet.service.d/slice.conf
[Service]
Slice=kubeonly.slice

# 重启kubelet服务
systemctl daemon-reload
systemctl restart kubelet.service

# 验证 cgroup 被创建
ll /sys/fs/cgroup/ | grep kubeonly.slice
drwxr-xr-x  2 root root 0 Sep  4 18:14 kubeonly.slice
Bash

其他 k8s 组件可以使用同样的方式加入 kubeonly.slice 组,重启服务有可能不生效,需要重启节点。

  • 修改 kubelet 配置文件,开启并设置 kube-reserved 预留资源的相关信息
# /var/lib/kubelet/config.yaml
kind: KubeletConfiguration
cgroupDriver: systemd
volumeStatsAggPeriod: 0s
enforceNodeAllocatable:
  - pods            # 系统内置默认的
  - kube-reserved   # 增加 kube-reserved 配置
# 为 Kubernetes 系统守护进程预留资源 (Kube Reserved)
# 预留 0.5 核 CPU、1Gi 内存和 1Gi 临时存储给 kubelet、容器运行时等
kubeReserved:
  cpu: 500m
  memory: 1Gi
  ephemeral-storage: 1Gi
kubeReservedCgroup: /kubeonly
YAML
  • 重启 kubelet 服务
  • 验证预留资源是否生效
kubectl describe nodes k8s-master-01
...
Capacity:
  cpu:                4
  ephemeral-storage:  28600Mi
  hugepages-1Gi:      0
  hugepages-2Mi:      0
  memory:             8127652Ki
  pods:               110
Allocatable:
  cpu:                3500m
  ephemeral-storage:  25916604372
  hugepages-1Gi:      0
  hugepages-2Mi:      0
  memory:             6976676Ki
  pods:               110


# Capacity:节点总资源
# Allocatable:pod 可用资源


# 可以通过 systemctl show 查看某个 cgroup 的配额信息
systemctl show kubeonly.slice
MemoryMax=1073741824  # 表示该 cgroup 的最大内存使用量被设置为 1073741824 字节 = 1 GiB。
CPUWeight=20  # 这是一个相对权重(范围 1-10000),用于在 CPU 繁忙时分配时间片。kubelet 根据 500m 自动配置的一个权重,
Bash

配置 system-reserved 预留资源

步骤基本相同,可以直接使用 system.slice 这个 cgroup 作为 system-reserved。

  • 修改 kubelet 配置文件
# /var/lib/kubelet/config.yaml
kind: KubeletConfiguration
cgroupDriver: systemd
volumeStatsAggPeriod: 0s
enforceNodeAllocatable:
  - pods            # 系统内置默认的
  - kube-reserved   # 增加 kube-reserved 配置
  - system-reserved
# 为 Kubernetes 系统守护进程预留资源 (Kube Reserved)
# 预留 0.5 核 CPU、1Gi 内存和 1Gi 临时存储给 kubelet、容器运行时等
kubeReserved:
  cpu: 500m
  memory: 1Gi
  ephemeral-storage: 1Gi
kubeReservedCgroup: /kubeonly  # 指定预留资源对应的 Cgroup
# 为操作系统守护进程预留资源 (System Reserved)
# 预留 0.5 核 CPU、1GB 内存和 5GB 临时存储给 sshd、systemd 等系统进程
systemReserved:
  cpu: "500m"
  memory: "1Gi"
  ephemeral-storage: "5Gi"
systemReservedCgroup: /system  # 指定预留资源对应的 Cgroup
YAML

配置 Pod 驱逐的预留资源

一个完整的 Pod 驱逐相关资源阈值配置示例:

# /var/lib/kubelet/config.yaml
kind: KubeletConfiguration
# 驱逐配置:当节点资源压力过大时,kubelet 如何通过驱逐 Pod 来回收资源

# 1. 软驱逐阈值(Soft Eviction Thresholds)
# 当资源使用量超过此阈值时,kubelet 会开始等待grace period,而不是立即驱逐。
# 这为系统提供了一个"缓冲期",允许Pod或运维人员在此期间内主动清理资源。
evictionSoft:
  memory.available: 300Mi  # 当节点可用内存低于 300MB 时触发软驱逐信号
  nodefs.available: 1Gi    # 当根文件系统(存放kubelet数据、日志等)可用空间低于 1GB 时触发
  imagefs.available: 1Gi   # 当容器镜像存储文件系统可用空间低于 1GB 时触发

# 2. 软驱逐宽限期(Soft Eviction Grace Periods)
# 定义资源持续超过软驱逐阈值多长时间后,kubelet才会真正开始驱逐Pod。
evictionSoftGracePeriod:
  memory.available: 1m30s  # 可用内存持续低于300MB达到1分30秒后开始驱逐
  nodefs.available: 2m     # 根文件系统空间持续不足达到2分钟后开始驱逐
  imagefs.available: 2m    # 镜像存储空间持续不足达到2分钟后开始驱逐

# 3. 硬驱逐阈值(Hard Eviction Thresholds)
# 当资源使用量超过此阈值时,kubelet 会立即驱逐Pod,没有宽限期。
# 这是防止系统完全崩溃的最后防线。
evictionHard:
  memory.available: 100Mi      # 可用内存低于100MB时立即驱逐(防止系统OOM被杀)
  nodefs.available: 500Mi      # 根文件系统可用空间低于500MB时立即驱逐(防止系统僵死)
  nodefs.inodesFree: 5%        # 根文件系统inode可用率低于5%时立即驱逐(防止因inode耗尽导致故障)
  imagefs.available: 500Mi     # 镜像存储可用空间低于500MB时立即驱逐(防止无法拉取新镜像)

# 4. 最小回收量(Eviction Minimum Reclaim)
# kubelet 每次执行驱逐时,至少尝试回收的资源量。
# 这可以防止kubelet在资源压力边界附近频繁进行小规模驱逐。
evictionMinimumReclaim:
  memory.available: 0Mi      # 对于内存,每次驱逐至少回收0Mi(通常按需驱逐,不设最低)
  nodefs.available: 100Mi    # 对于根文件系统,每次驱逐至少回收100MB空间
  imagefs.available: 200Mi   # 对于镜像存储,每次驱逐至少回收200MB空间

# 5. 最大Pod宽限期(Eviction Max Pod Grace Period)
# 在驱逐Pod时,kubelet 给予Pod正常终止的最大宽限时间(单位:秒)。
# 如果Pod在30秒内未自行终止,kubelet将强制杀死它。
evictionMaxPodGracePeriod: 30
YAML

2. Pod 的 QoS

2.1. 核心概念

Pod 的 QoS 等级是 Kubernetes 根据 Pod 内所有容器的 资源请求(requests 和 资源限制(limits 自动计算和分配的一个标签。它决定了当节点资源(如 CPU、内存)压力过大时,kubelet 驱逐 Pod 的先后顺序以及 CPU 调度的权重

QoS 是一种内置的机制,无需用户显式指定,完全由用户的资源请求配置决定。

k8s 根据 Pod 配置的 Requests 值来调度 Pod,Pod 在调度成功后会得到 Requests 定义的资源来运行。如果 Pod 所在的节点上资源有空余,则 Pod 可以申请更多的资源,最多不超过 Limits 的值。

2.2. 三种 QoS 等级

Kubernetes 为 Pod 分配三种 QoS 等级,优先级从高到低为:Guaranteed > Burstable > BestEffort

Guaranteed(保证等级) – 最高优先级

  • 定义条件(必须同时满足):
    • Pod 中的每一个容器都必须设置了 CPU 和内存的 requests
    • Pod 中的每一个容器都必须设置了 CPU 和内存的 limits
    • 对于每个容器,其 CPU 的 requests 值必须等于 CPU 的 limits 值。
    • 对于每个容器,其内存的 requests 值必须等于内存的 limits 值。
  • 配置示例
apiVersion: v1
kind: Pod
metadata:
  name: guaranteed-pod
spec:
  containers:
  - name: nginx
    image: nginx
    resources:
      requests:
        memory: "100Mi"
        cpu: "500m"   # CPU request == CPU limit
      limits:
        memory: "100Mi" # Memory request == Memory limit
        cpu: "500m"
YAML
  • 行为与特点
    • 最后被驱逐:当节点内存不足时,kubelet 会优先驱逐其他类型的 Pod,Guaranteed Pod 最安全。
    • CPU 资源保证:即使节点 CPU 繁忙,它们也能获得所请求的 CPU 时间。
    • 严格隔离:它们不会因为其他 Pod 消耗资源而受到影响,是最稳定的。
    • 适用场景:核心关键应用,如数据库、核心业务服务。

Burstable(突发等级) – 中等优先级

  • 定义条件
    • Pod 不符合 Guaranteed 标准
    • 但 Pod 中至少有一个容器设置了 requests 或 limits
  • 配置示例
apiVersion: v1
kind: Pod
metadata:
  name: burstable-pod
spec:
  containers:
  - name: nginx
    image: nginx
    resources:
      requests:
        memory: "100Mi"
        cpu: "500m"
      limits:
        memory: "200Mi" # Memory request != Memory limit
        cpu: "1000m"    # CPU request != CPU limit
YAML
  • 行为与特点
    • 中间顺序被驱逐:其驱逐优先级介于 Guaranteed 和 BestEffort 之间。
    • 可以获得承诺的资源:能够保证获得所请求(requests)的 CPU/内存量。
    • 可以突发使用资源:在资源空闲时,可以突破 requests,使用到 limits 所设定的上限(因此得名“突发”)。
    • 适用场景:大多数普通应用,既需要一定的资源保证,又希望能在空闲时利用更多资源提升性能。

BestEffort(尽力而为等级) – 最低优先级

  • 定义条件
    • Pod 中的所有容器没有设置任何 CPU 或内存的 requests 和 limits
  • 配置示例:
apiVersion: v1
kind: Pod
metadata:
  name: besteffort-pod
spec:
  containers:
  - name: nginx
    image: nginx
    # 没有任何 resources 字段
YAML
  • 行为与特点
    • 最先被驱逐:当节点内存不足时,BestEffort Pod 会第一个被杀死。
    • 资源竞争激烈:它们获得的 CPU 资源权重最低。当节点 CPU 繁忙时,它们几乎分不到什么时间片。
    • 无资源保证:它们可以使用节点上任何未被占用的资源,但当其他 Pod 需要资源时,它们会被立刻压缩。
    • 适用场景:非关键的非生产负载,如测试作业、批处理任务,可以容忍随时被重启或杀死。

2.3. QoS 的实际影响

  • 资源超卖与驱逐(Eviction)
    • QoS 的主要作用体现在节点资源压力过大时,尤其是内存压力
    • 驱逐顺序BestEffort -> Burstable -> Guaranteed
    • kubelet 会按照这个顺序选择 Pod 进行驱逐,以回收资源。例如,它会先清除所有 BestEffort Pod,如果资源还不够,再在 Burstable Pod 中寻找那些实际使用资源远超其 requests 的 Pod 进行驱逐。
  • CPU 调度
    • Guaranteed Pod:获得其 requests 所对应的完整份额。
    • Burstable Pod:获得其 requests 所对应的份额,但在空闲时可以使用更多。
    • BestEffort Pod:获得一个非常低的默认份额,CPU 繁忙时几乎无法运行。
  • 内存管理
    • 当节点上的进程开始耗尽内存时,Linux 内核 OOM Killer 会被触发。kubelet 为每个容器设置了一个 oom_score_adj 值,该值基于 QoS:
      • Guaranteed:-997 (最不可能被 OOM Kill)
      • Burstable:通常在 0 到 1000 之间,计算方式为:max(2, 1000 - (1000 * memoryRequest) / machineMemoryCapacity)。请求越少,分数越高,越容易被杀。
      • BestEffort:1000 (最有可能被 OOM Kill)

注意:Pod 优先级和 QoS 之间互不影响,当某 Pod 的资源用量未超过其请求时,kubelet 节点压力驱逐不会驱逐该 Pod。 如果优先级较低的 Pod 的资源使用量没有超过其请求,则不会被驱逐。 另一个优先级较高且资源使用量超过其请求的 Pod 可能会被驱逐。

总之抢占逻辑依据的是优先级高低,而不是 QoS;资源不足时驱逐 Pod 的逻辑依据的是 QoS。而不考虑优先级。

上一篇
下一篇