集群计算资源管理

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-reserved 和 kube-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。而不考虑优先级。

上一篇
下一篇