← 返回文章列表
K8s 资源超卖方案:原理与实现
10 分钟阅读
字号
K8s 资源超卖方案:原理与实现
本文介绍 K8s 资源超卖的概念、解决的问题,以及四个核心组件的实现方法。
一、为什么需要资源超卖
Kubernetes 资源上报的默认逻辑
Kubelet 在创建 Pod 时,会根据 requests 值向 kube-apiserver 汇报 Pod 所需的资源量。调度器调度 Pod 时,依据的是 requests 值而非实际使用量。
resources:
requests:
cpu: "2"
memory: "4Gi"
limits:
cpu: "4"
memory: "8Gi"默认逻辑的问题
假设一个节点有 32 核 CPU、128Gi 内存:
| Pod | CPU Request | Memory Request |
|---|---|---|
| Pod A | 4核 | 16Gi |
| Pod B | 4核 | 16Gi |
| Pod C | 4核 | 16Gi |
| Pod D | 4核 | 16Gi |
调度器认为已用 16核/64Gi,剩余 16核/64Gi,还可以再调度 4 个 Pod。
但实际上:这些 Pod 可能只用了 50% 的资源,节点已经快撑不住了。
资源超卖的核心思路
用真实使用量替代 requests 作为调度依据,从而在保证节点稳定的前提下,提高资源利用率。
二、核心概念
PDB(Pod Disruption Budget)
保障 Pod 最小可用数量的资源对象,防止一次性驱逐过多 Pod。
apiVersion: policy/v1
kind: PodDisruptionBudget
spec:
minAvailable: 3 # 最少保留 3 个 Pod
# 或者
maxUnavailable: 30% # 最多驱逐 30%优先级与抢占
K8s 支持 Pod 优先级(PriorityClass),高优先级 Pod 可以抢占低优先级 Pod。
三、四个核心组件
组件总览
┌─────────────────────────────────────────────────────────┐
│ 资源超卖架构 │
├─────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────────┐ │
│ │ kube-node 采集器 │ → 获取节点真实资源使用率 │
│ └────────┬─────────┘ │
│ ▼ │
│ ┌──────────────────┐ │
│ │ Oversold-Injector│ → 修正 kubelet 上报值 │
│ │ (Webhook) │ │
│ └────────┬─────────┘ │
│ ▼ │
│ ┌──────────────────┐ │
│ │ Matrix 保护控制器 │ → 节点过载时驱逐低优先级 Pod │
│ └────────┬─────────┘ │
│ ▼ │
│ ┌──────────────────┐ │
│ │ DW-Scheduler │ → 调度时考虑真实利用率 │
│ │ 增强调度器 │ │
│ └──────────────────┘ │
│ │
└─────────────────────────────────────────────────────────┘1. kube-node 采集器
作用:定时调用 kubelet API 获取节点真实资源使用率。
实现方法:
- 部署一个 DaemonSet 到每个节点
- 每隔一段时间(如 10 秒)调用 kubelet 的 Summary API
GET /api/v1/nodes/{nodeName}/proxy/stats/summary - 解析返回的 JSON,提取真实 CPU/内存使用量:
{ "node": { "cpu": { "usageNanoCores": 15000000000 }, // 15核 "memory": { "usageBytes": 50000000000 } // 约 50Gi } } - 将数据上报到 Matrix 保护控制器
关键点:
- 调用 kubelet 不需要认证,因为 DaemonSet 和 kubelet 在同一节点
- 采集频率要合适:太频繁增加负载,太稀疏数据不及时
2. Oversold-Injector(Webhook)
作用:拦截 kubelet 上报的 metrics,修正为真实使用量,让调度器看到更准确的数据。
实现方法:
- 创建一个 MutatingWebhookConfiguration
- 监听
nodes/status资源变更 - 当 kubelet 上报节点 allocatable 时,用真实使用量替换 requests
apiVersion: admissionregistration.k8s.io/v1
kind: MutatingWebhookConfiguration
metadata:
name: oversold-injector
webhooks:
- name: oversold-injector.example.com
clientConfig:
service:
name: oversold-injector
namespace: default
path: "/mutate-node"
rules:
- operations: ["UPDATE"]
apiGroups: [""]
apiVersions: ["v1"]
resources: ["nodes/status"]修正逻辑:
| 原始值 | 修正值 |
|---|---|
| node.status.allocatable.cpu | 节点真实剩余 CPU × 调整系数 |
| node.status.allocatable.memory | 节点真实剩余 Memory × 调整系数 |
为什么不直接改 Pod 的 requests:
- Pod 的 requests 影响 Limit 计算
- 直接改 Pod 可能导致已运行 Pod 的 Limit 收缩,引发不稳定
- 改节点 allocatable 更干净,让新调度过来的 Pod 自然用更少资源
3. Matrix 保护控制器
作用:监控节点真实使用率,当超过阈值时,主动驱逐低优先级 Pod 释放资源。
实现方法:
- 部署一个 Controller,持续监控所有节点的使用率
- 当节点使用率超过阈值(如 80%)时,触发保护逻辑:
- 找出该节点上所有 低优先级 Pod
- 计算需要驱逐多少 Pod 才能降到安全线
- 结合 PDB 驱逐 Pod(保证 PDB 约束)
- 触发 cgroup 限流 限制低优先级 Pod 资源使用
驱逐流程:
节点使用率 > 80%
│
▼
┌──────────────────┐
│ 1. 标记低优先级 Pod │ ← 找出所有低优先级 Pod
└────────┬─────────┘
▼
┌──────────────────┐
│ 2. 计算需驱逐数量 │ ← 目标:降到 70% 以下
└────────┬─────────┘
▼
┌──────────────────┐
│ 3. 检查 PDB 约束 │ ← 保证 PDB minAvailable
└────────┬─────────┘
▼
┌──────────────────┐
│ 4. 执行驱逐 │ ← kubectl delete pod
└────────┬─────────┘
▼
┌──────────────────┐
│ 5. 触发 cgroup │ ← 限流保护
│ 限流 │
└──────────────────┘cgroup 限流:
- Linux cgroup 可以限制进程组的 CPU/内存使用
- Controller 通过 systemd 或 docker config 动态调整低优先级 Pod 的 cgroup 限制
- 快速降低资源争抢,无需等待 Pod 真正被驱逐
4. DW-Scheduler 增强调度器
作用:在调度阶段就考虑节点真实利用率,避免把 Pod 调度到即将过载的节点。
实现方法:
修改 kube-scheduler 配置,在 Filter 和 Score 阶段加入真实利用率考量。
Filter 阶段(预选)
过滤掉不符合条件的节点。增强后:
- 原始条件:节点 allocatable >= Pod requests
- 增强条件:节点 allocatable >= Pod requests 且 节点当前使用率 < 阈值(如 85%)
// Filter 增强伪代码
func OversoldFilter(pod *v1.Pod, node *v1.Node) bool {
// 原条件:节点资源够不够
if !originalFilter(pod, node) {
return false
}
// 新条件:节点使用率是否在安全范围内
realUsage := getNodeRealUsage(node.Name)
if realUsage.CPUPercent > 85 || realUsage.MemoryPercent > 85 {
return false // 拒绝调度到过载节点
}
return true
}Score 阶段(优选)
对符合条件的节点打分。增强后:
- 原始分数:基于 allocatable 计算
- 增强分数:真实使用率越低,分数越高(优先调度到资源更充裕的节点)
// Score 增强伪代码
func OversoldScore(pod *v1.Pod, nodes []*v1.Node) map[string]int {
scores := originalScore(pod, nodes)
for _, node := range nodes {
realUsage := getNodeRealUsage(node.Name)
// 真实使用率越低,分数加成越高
usageBonus := 100 - realUsage.CPUPercent
scores[node.Name] += usageBonus * 0.5 // 加权系数 0.5
}
return scores
}四、完整流程
Pod 调度到运行的完整链路
1. 用户提交 Pod
│
▼
2. kube-apiserver 接收请求
│
▼
3. Scheduler Filter 阶段
├─ 检查节点 allocatable
└─ 检查节点真实使用率 < 85% ← 增强点
│
▼
4. Scheduler Score 阶段
└─ 真实使用率越低分数越高 ← 增强点
│
▼
5. Pod 调度到目标节点
│
▼
6. kubelet 创建 Pod
│
▼
7. kube-node 采集器上报真实使用量 ← 持续运行
│
▼
8. Oversold-Injector 修正节点 allocatable ← 持续运行
│
▼
9. Matrix 保护控制器监控
├─ 使用率 > 80% → 驱逐低优先级 Pod
└─ 触发 cgroup 限流Pod 抢占与驱逐
高优先级 Pod A 等待调度
│
▼
Matrix 检测到节点紧张
│
▼
┌──────────────────┐
│ 抢占低优先级 Pod B │ ← B 被标记为待驱逐
└────────┬─────────┘
│
▼
PDB 保护检查
├─ B 所在 PDB minAvailable=2
└─ 当前有 3 个 B 类 Pod → 可以驱逐 1 个
│
▼
Pod B 被驱逐
│
▼
Pod A 调度成功五、与传统超卖的区别
| 维度 | 传统超卖 | K8s 资源超卖方案 |
|---|---|---|
| 依据 | 固定比例 | 真实使用率动态调整 |
| 保护 | 无 | PDB + cgroup 多层保护 |
| 调度 | 无感知 | Scheduler 增强,提前规避 |
| 响应 | 被动 | 主动预防 + 被动拦截 |
六、总结
┌─────────────────────────────────────────────────────────┐
│ K8s 资源超卖核心要点 │
├─────────────────────────────────────────────────────────┤
│ │
│ 四个组件: │
│ ├─ kube-node 采集器 → 获取真实使用率 │
│ ├─ Oversold-Injector → 修正 kubelet 上报值 │
│ ├─ Matrix 保护控制器 → 过载时驱逐低优先级 Pod │
│ └─ DW-Scheduler 增强 → 调度时预判节点负载 │
│ │
│ 核心思想: │
│ ├─ 用真实使用量替代 requests 作为调度依据 │
│ ├─ PDB 保护低优先级 Pod 最小可用数量 │
│ └─ cgroup 限流快速降低资源争抢 │
│ │
│ 效果: │
│ └─ 提高资源利用率的同时保障节点稳定性 │
│ │
└─────────────────────────────────────────────────────────┘分享