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
YAML2. 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
值。
- Pod 中的每一个容器都必须设置了 CPU 和内存的
- 配置示例:
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
。
- Pod 不符合
- 配置示例:
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
。
- Pod 中的所有容器都没有设置任何 CPU 或内存的
- 配置示例:
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)
- 当节点上的进程开始耗尽内存时,Linux 内核 OOM Killer 会被触发。kubelet 为每个容器设置了一个 oom_score_adj 值,该值基于 QoS:
注意:Pod 优先级和 QoS 之间互不影响,当某 Pod 的资源用量未超过其请求时,kubelet 节点压力驱逐不会驱逐该 Pod。 如果优先级较低的 Pod 的资源使用量没有超过其请求,则不会被驱逐。 另一个优先级较高且资源使用量超过其请求的 Pod 可能会被驱逐。
总之抢占逻辑依据的是优先级高低,而不是 QoS;资源不足时驱逐 Pod 的逻辑依据的是 QoS。而不考虑优先级。