API 体系概览
2.1 What - Koordinator API 设计的核心理念
Koordinator 的所有功能都通过 Kubernetes CRD(Custom Resource Definition) 暴露,遵循云原生设计哲学。而不是通过私有 API 或配置文件。
为什么选择 CRD?
- ✅ 与 Kubernetes 生态统一:用户可以使用 kubectl 管理 Koordinator 资源
- ✅ 声明式配置:一切都是 YAML,易于版本控制和自动化
- ✅ 原生支持 RBAC:权限管理原生集成
- ✅ Watch 机制:其他组件可以实时监听资源变化
- ✅ Validation 和 Conversion:API Server 原生支持数据验证和版本转换
2.2 Why - 为什么需要这些 CRD?
| 问题 | 传统方案的不足 | Koordinator CRD 的解决 |
|---|---|---|
| 节点配置差异 | ConfigMap 无法动态计算,所有节点配置相同 | NodeSLO:为每个节点计算不同的 QoS 参数 |
| 指标数据查询 | 无法统一查询节点和 Pod 的性能指标 | NodeMetric:提供标准化的指标查询接口 |
| 资源预留 | 难以提前为批量 Pod 预留资源 | Reservation:支持资源预留,批量调度 |
| 设备管理 | GPU/FPGA 调度无法精细化 | Device:统一的设备管理接口 |
| 弹性配额 | ResourceQuota 过于静态 | ElasticQuota:支持动态配额扩展 |
| Pod 迁移 | 无法管理 Pod 迁移任务 | PodMigrationJob:正式的迁移任务对象 |
2.3 How - API 的数据流
用户操作(kubectl apply)
↓
Webhook 验证和变更
↓
存储到 etcd
↓
各个组件通过 Watch 监听变化
↓
组件根据资源内容做出决策
↓
执行动作并更新资源状态
核心 CRD 详解
2.4 NodeSLO(节点服务级别目标)
2.4.1 What - NodeSLO 是什么?
NodeSLO 定义了单个节点的 QoS 策略配置和资源阈值策略。由 koord-manager 根据集群级策略动态计算和创建,Koordlet 监听并在节点上应用。
核心特点:
- 一个节点一个 NodeSLO(同名)
- 由控制面自动管理,用户通常不直接修改
- 支持节点级的差异化配置
2.4.2 完整结构示例
apiVersion: slo.koordinator.sh/v1alpha1
kind: NodeSLO
metadata:
name: node-001 # 与节点同名,一一对应
spec:
# 1. 资源阈值策略:定义各类 Pod 的资源保护边界
resourceThresholdStrategy:
enable: true
thresholds:
# CPU 相关
cpu-suppress-percent: 65 # CPU 使用率 > 65% 触发 BE 抑制
cpu-evict-threshold-percent: 85 # CPU 调度延迟 > 85% 触发驱逐
# 内存相关
memory-evict-threshold-percent: 75 # 内存 > 75% 触发驱逐
memory-request-utilization-percent: 80 # 内存请求利用率阈值
# 2. 资源 QoS 策略:针对每个 QoS 等级的详细参数
resourceQOSStrategy:
# LSR 级别:最强保护
lsrClass:
cpuQOS:
enable: true
groupIdentity: 0 # group identity,值越高优先级越高
memoryQOS:
enable: true
minLimitPercent: 100 # memory.min = request * 100%,完全保护
lowLimitPercent: 100 # memory.low = request * 100%
throttlingPercent: 120 # memory.high = limit * 120%
wmarkRatio: 95 # 异步回收水位(0-100)
wmarkScalePermill: 50 # 水位调整系数(1-1000)
wmarkMinAdj: -25 # 全局回收时的调整(-25 到 50)
blkIOQOS:
enable: false
# LS 级别:中等保护
lsClass:
cpuQOS:
enable: true
groupIdentity: 2
memoryQOS:
enable: true
minLimitPercent: 0
lowLimitPercent: 0
throttlingPercent: 100
wmarkRatio: 90
wmarkScalePermill: 30
wmarkMinAdj: -25
# BE 级别:最弱保护
beClass:
cpuQOS:
enable: true
groupIdentity: -1 # 最低优先级
memoryQOS:
enable: true
minLimitPercent: 0
lowLimitPercent: 0
throttlingPercent: 100
wmarkRatio: 80
wmarkScalePermill: 30
wmarkMinAdj: 50 # 最容易被全局内存回收
status:
# 节点上 NodeSLO 的应用状态
nodeMetric:
# 最后一次更新时间
updateTime: "2024-01-01T12:00:00Z"
2.4.3 Memory QoS 参数详解与生产调优
内存保护的三级机制:
当内存使用率上升时的处理流程:
1. 使用量 < memory.min
└─ 完全保护,不会被任何回收(全局回收也不行)
└─ 用于 LSR/LSE,确保关键数据在内存中
2. memory.min < 使用 < memory.low
└─ 尽力保护,优先级 > 其他 Pod
└─ 全局内存紧张时,其他低优先级 Pod 优先被回收
└─ 用于 LS,平衡保护和灵活性
3. memory.low < 使用 < memory.high
└─ 开始异步直接回收(不阻塞应用)
└─ 内核后台线程执行,应用继续运行
└─ 影响较小
4. memory.high < 使用 < limit
└─ 同步直接回收(可能阻塞应用)
└─ 应用执行内存分配时会被阻塞进行回收
└─ 可能导致延迟增加
5. 使用 > limit
└─ OOM Kill:进程被内核杀死
参数调优表:
| 参数 | LSR 推荐值 | LS 推荐值 | BE 推荐值 | 含义 |
|---|---|---|---|---|
minLimitPercent | 100 | 0-20 | 0 | memory.min = request * 百分比 |
lowLimitPercent | 100 | 0-20 | 0 | memory.low = request * 百分比 |
throttlingPercent | 120 | 100 | 80 | memory.high = limit * 百分比 |
wmarkRatio | 95 | 90 | 75 | 异步回收水位(高→晚回收→更多冲突) |
wmarkMinAdj | -25 | -25 | 50 | 全局回收调整(低→不容易被回收) |
生产调优案例:
# 案例:在线数据库(LSR)的配置
lsrClass:
memoryQOS:
enable: true
minLimitPercent: 100 # 数据库热数据必须在内存中
lowLimitPercent: 100 # 缓冲池完全保护
throttlingPercent: 120 # 允许超过 limit 一些(缓冲队列)
wmarkRatio: 95 # 水位很高,尽量避免回收
wmarkMinAdj: -25 # 全局回收时尽量不回收
# 案例:批处理任务(BE)的配置
beClass:
memoryQOS:
enable: true
minLimitPercent: 0 # 没有最小保护
lowLimitPercent: 0 # 也没有低保护
throttlingPercent: 80 # limit 的 80% 开始回收
wmarkRatio: 75 # 水位低,容易回收
wmarkMinAdj: 50 # 全局回收时最先被回收
2.4.4 CPU QoS 参数详解
CPU Group Identity 的工作原理:
Linux CGroup v2 中的 cpu.group_identity 机制:
内核调度器根据 group_identity 分配 CPU 时间片:
groupIdentity = 0 (LSR)
│ └─ 平时能占用 90% CPU 时间
│
groupIdentity = 2 (LS)
│ └─ 平时能占用 80% CPU 时间
│
groupIdentity = -1 (BE)
└─ 平时能占用 70% CPU 时间
当某个 group 的 Pod 被抑制时:
LSR groupIdentity 0 → 不受抑制,继续 90%
LS groupIdentity 2 → 被降低到 50%
BE groupIdentity -1 → 被降低到 30%
结果:高优先级 Pod 被保护,低优先级被抑制
2.5 NodeMetric(节点指标)
2.5.1 What - NodeMetric 是什么?
NodeMetric 包含节点和 Pod 的实时性能指标数据,由 Koordlet 采集并定期上报。调度器和其他组件通过查询 NodeMetric 来做出决策。
2.5.2 完整结构示例
apiVersion: slo.koordinator.sh/v1alpha1
kind: NodeMetric
metadata:
name: node-001 # 与节点同名
spec:
nodeName: node-001
collectionIntervalSeconds: 60 # 采集间隔
status:
updateTime: "2024-01-01T12:00:00Z"
# 节点级别的资源使用情况
nodeMetric:
usage:
cpu: "64000m" # 当前实际使用 64 core
memory: "64Gi" # 当前实际使用 64 GB
request:
cpu: "100000m" # 所有 Pod request 总和 100 core
memory: "100Gi" # 所有 Pod request 总和 100 GB
limit:
cpu: "128000m" # 所有 Pod limit 总和 128 core
memory: "128Gi" # 所有 Pod limit 总和 128 GB
# 统计汇总数据
beMetric: # BE Pod 的统计
usage:
cpu: "20000m" # BE Pod 实际使用 20 core
memory: "20Gi" # BE Pod 实际使用 20 GB
request:
cpu: "30000m"
memory: "30Gi"
lsMetric: # LS Pod 的统计
usage:
cpu: "44000m"
memory: "44Gi"
request:
cpu: "70000m"
memory: "70Gi"
# 各 Pod 的资源使用明细
podMetrics:
- podRef:
namespace: default
name: web-server-1
containers:
- name: app
usage:
cpu: "2000m" # 该容器使用 2 core
memory: "2Gi" # 该容器使用 2 GB
limit:
cpu: "4000m" # limit 4 core
memory: "4Gi" # limit 4 GB
2.5.3 调度中的应用示例
场景:koord-scheduler 在调度 Pod A(request 10 core)时
候选节点 1:
├─ 物理 CPU: 128 core
├─ 当前实际使用(来自 NodeMetric): 80 core
├─ 当前 request 总和: 100 core
├─ 可超卖比例: 30%
├─ 可超卖容量: (128 - 100) * 30% = 8.4 core
└─ Pod A 所需: 10 core
└─ 结果:8.4 < 10,不满足条件,Filter 阶段排除
候选节点 2:
├─ 物理 CPU: 128 core
├─ 当前实际使用: 50 core
├─ 当前 request 总和: 80 core
├─ 可超卖容量: (128 - 80) * 30% = 14.4 core
└─ Pod A 所需: 10 core
└─ 结果:14.4 > 10,满足条件,可以调度
优势:
✅ 基于实时使用情况(NodeMetric)调度,而不是静态的 request/limit
✅ 避免把 Pod 调到 request 充足但实际使用率很高的节点
✅ 更精确的资源利用决策
2.6 Reservation(资源预留)
2.6.1 What - Reservation 是什么?
Reservation 用于提前为某些工作负载预留资源。解决的问题是:批量 Pod(如 Spark Job、分布式训练)需要集群中有足够的连续空闲资源,才能同时调度所有副本。
2.6.2 完整结构示例
apiVersion: scheduling.koordinator.sh/v1alpha1
kind: Reservation
metadata:
name: spark-job-reservation-202401
spec:
# 定义要预留的资源模板
template:
spec:
containers:
- name: spark-executor
resources:
requests:
cpu: "8" # 预留 8 core
memory: "16Gi" # 预留 16 GB
limits:
cpu: "8"
memory: "16Gi"
# 定义谁可以使用这个预留
owners:
- labelSelector:
matchLabels:
job-id: "spark-123" # 匹配这些 Label 的 Pod 可以使用
app: "spark-executor"
# 预留的有效期
ttl: 10m # 10 分钟后过期
# 预分配:等待足够空闲资源后再预留
preAllocation: true
# 一次性使用:只有第一个 owner 可以使用
allocateOnce: true
status:
# 预留的状态阶段
phase: Available # Pending / Available / Succeeded / Failed
# 预留被调度到的节点
nodeName: node-001
# 当前使用者
currentOwners:
- name: spark-executor-1
namespace: default
- name: spark-executor-2
namespace: default
# 预留的资源和已分配资源
allocatable:
cpu: "8"
memory: "16Gi"
allocated:
cpu: "8"
memory: "16Gi" # 已有 1 个 Pod 使用,再有 7 core 可用
2.6.3 工作流程示例
时间轴:
T0:00 用户创建 Spark Job
└─ 同时创建一个 Reservation
├─ 预留 20 个 executor × 8 core = 160 core
└─ TTL = 10 分钟
T0:05 koord-scheduler 评估集群
├─ 找到 3 个节点,每个可以容纳 6-7 个 executor
├─ 总容量足够 20 个 executor
└─ Reservation 状态变为 Available
T0:10 Spark Job 的 Pod 开始创建(20 个 executor Pod)
├─ 第 1-10 个 Pod 创建
├─ 调度器识别 owner 匹配
├─ 优先在已预留的节点上调度
├─ Pod 绑定,从预留中分配资源
└─ Reservation.allocated 增加
T1:00 所有 20 个 Pod 都被调度成功
└─ Reservation 状态变为 Succeeded
T2:00 Spark Job 执行完成,Pod 被删除
├─ Reservation 自动清理
└─ 预留的资源释放回集群
优势:
✅ 保证批量 Pod 的调度成功率
✅ 避免部分 Pod 无法调度的尴尬局面
✅ 支持时间限制和预分配策略
2.6.4 生产案例
# 案例:Flink 离线处理 Job
apiVersion: scheduling.koordinator.sh/v1alpha1
kind: Reservation
metadata:
name: flink-job-batch-20240101
spec:
template:
spec:
containers:
- name: flink-taskmanager
resources:
requests:
cpu: "4"
memory: "8Gi"
owners:
- labelSelector:
matchLabels:
flink-job: "batch-20240101"
app: "flink-taskmanager"
ttl: 2h # Flink Job 预计运行 2 小时
preAllocation: true
allocateOnce: false # 可以多个 Job 共享(不是一次性)
---
# 对应的 Flink Job Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: flink-batch-job
spec:
replicas: 100 # 100 个 TaskManager
selector:
matchLabels:
app: flink-taskmanager
template:
metadata:
labels:
flink-job: "batch-20240101"
app: "flink-taskmanager"
spec:
containers:
- name: flink-taskmanager
resources:
requests:
cpu: "4"
memory: "8Gi"
2.7 ElasticQuota(弹性配额)
2.7.1 What - ElasticQuota 是什么?
传统 ResourceQuota 是静态的,无法根据集群空闲资源动态调整。ElasticQuota 支持基础配额保证 + 弹性扩展的模式。
2.7.2 完整结构示例
apiVersion: quota.koordinator.sh/v1alpha1
kind: ElasticQuota
metadata:
name: team-a-quota
namespace: team-a
spec:
# 硬配额:即使集群资源充足也不能超过
hard:
cpu: "100"
memory: "100Gi"
pods: "100"
# 最小配额:集群必须保证
min:
cpu: "50"
memory: "50Gi"
pods: "50"
# 最大可扩展配额:集群空闲时可以扩展到这个值
max:
cpu: "200"
memory: "150Gi"
pods: "150"
# 配额分配优先级(数字越大优先级越高)
priority: 50
status:
# 当前实际可用配额(动态变化)
allocatedQuota:
cpu: "150"
memory: "120Gi"
pods: "100"
# 当前使用量
used:
cpu: "120"
memory: "100Gi"
pods: "80"
# 已分配给 Pod 的配额
podAllocatedQuota:
cpu: "120"
memory: "100Gi"
pods: "80"
2.7.3 动态扩展的工作原理
场景:两个 Team 的配额管理
集群总资源:500 core, 500 GB
Team A 配置:
├─ min: 100 core (硬保证)
├─ max: 300 core (最多可获得)
└─ hard: 200 core (最高不能超过)
Team B 配置:
├─ min: 100 core (硬保证)
├─ max: 300 core (最多可获得)
└─ hard: 200 core (最高不能超过)
场景 1:集群充足(可用 300 core)
┌─────────────────────────────────┐
│ Team A 当前用: 80 core │
│ Team B 当前用: 80 core │
│ 可用资源: 340 core │
├─────────────────────────────────┤
│ Team A 可分配: 100(min) + 100(扩展) = 200 core │
│ Team B 可分配: 100(min) + 100(扩展) = 200 core │
│ → 都可以申请新 Pod │
└─────────────────────────────────┘
场景 2:集群紧张(只可用 50 core)
┌─────────────────────────────────┐
│ Team A 当前用: 90 core │
│ Team B 当前用: 100 core │
│ Team A 新增需求: +30 core │
│ 可用资源: 50 core │
├─────────────────────────────────┤
│ 保证最少配额: │
│ ├─ Team A min: 100 core (已用 90,还有 10) │
│ └─ Team B min: 100 core (已用 100,没有) │
│ │
│ Team A 新 Pod 只能分配: 10 core │
│ (min 剩余) + 40 core (扩展的一部分) │
│ = 50 core,但优先级更高的先拿 │
└─────────────────────────────────┘
场景 3:有一个 Team 优先级更高
┌─────────────────────────────────┐
│ Team A priority: 100 (高) │
│ Team B priority: 50 (低) │
│ │
│ 集群只剩 50 core 可用 │
│ Team A 请求 +50 core │
│ Team B 请求 +40 core │
│ │
│ 结果:Team A 获得全部 50 core │
│ Team B 无法获得扩展配额 │
└─────────────────────────────────┘
2.7.4 生产案例
# 案例:高价值业务 Team(在线服务)
apiVersion: quota.koordinator.sh/v1alpha1
kind: ElasticQuota
metadata:
name: critical-team-quota
namespace: critical-team
spec:
hard:
cpu: "500"
memory: "500Gi"
min:
cpu: "300" # 最少保证 300 core
memory: "300Gi"
max:
cpu: "500" # 最多可扩展到 500 core
memory: "500Gi"
priority: 100 # 最高优先级
---
# 案例:低优先级业务 Team(离线计算)
apiVersion: quota.koordinator.sh/v1alpha1
kind: ElasticQuota
metadata:
name: batch-team-quota
namespace: batch-team
spec:
hard:
cpu: "200"
memory: "200Gi"
min:
cpu: "100" # 最少保证 100 core
memory: "100Gi"
max:
cpu: "300" # 可扩展到 300 core
memory: "300Gi"
priority: 10 # 低优先级
2.8 Device(设备管理)
2.8.1 What - Device 是什么?
Device 用于管理 GPU、FPGA、RDMA 等特殊硬件设备。支持设备级的精细化调度和共享。
2.8.2 结构示例
apiVersion: scheduling.koordinator.sh/v1alpha1
kind: Device
metadata:
name: node-001
spec:
devices:
- deviceType: "gpu"
deviceModel: "nvidia-a100"
uuid: "gpu-uuid-001"
totalMemory: "40Gi" # GPU 显存 40GB
health: true # 设备健康状态
- deviceType: "gpu"
deviceModel: "nvidia-a100"
uuid: "gpu-uuid-002"
totalMemory: "40Gi"
health: true
- deviceType: "rdma"
deviceModel: "mellanox-ib"
uuid: "rdma-uuid-001"
totalMemory: "0" # RDMA 不需要显存
health: true
- deviceType: "fpga"
deviceModel: "xilinx-u250"
uuid: "fpga-uuid-001"
totalMemory: "0"
health: true
status:
# 各设备的使用情况
deviceAllocations:
- device:
deviceType: "gpu"
uuid: "gpu-uuid-001"
allocations:
- pod:
namespace: default
name: ml-training-1
container: training
allocated: "50%" # 该 Pod 分配了 50% 的 GPU 资源
- device:
deviceType: "gpu"
uuid: "gpu-uuid-002"
allocations:
- pod:
namespace: default
name: ml-training-2
container: training
allocated: "100%" # 独占该 GPU
总 - API 的完整数据流与交互
2.9 API 的端到端数据流
集群级配置
└─ ConfigMap (slo-controller-config)
│ 定义全局 QoS 策略
│
↓
koord-manager (NodeSLO Controller)
│ 监听 ConfigMap
│ 为每个节点计算对应参数
│
↓
NodeSLO CRD (节点级 QoS 配置)
│ 每个节点一个
│
├────────────→ Koordlet (StatesInformer 监听)
│ │ 应用 NodeSLO 配置
│ │
│ ├─→ QOSManager
│ │ 计算策略
│ │
│ ├─→ ResourceExecutor
│ │ 执行 CGroup 更新
│ │
│ └─→ 上报 NodeMetric
│
↓
NodeMetric CRD (节点实时指标)
│ 每个节点一个,每分钟更新
│
├────────────→ koord-scheduler
│ ├─ LoadAware Plugin
│ └─ 负载感知调度决策
│
├────────────→ koord-manager
│ └─ 聚合计算可超卖资源量
│
└────────────→ koord-descheduler
└─ 发现不合理调度
2.10 关键 API 调用示例
创建预留资源:
kubectl apply -f reservation.yaml
创建弹性配额:
kubectl apply -f elastic-quota.yaml
查询节点指标:
kubectl get nodemetric node-001 -o yaml
# 查看该节点的实时 CPU/内存使用情况
查询节点 SLO 配置:
kubectl get nodeslo node-001 -o yaml
# 查看该节点的 QoS 策略参数
总结 - CRD 设计的最佳实践
2.11 生产中的常见问题与解决
问题 1:NodeSLO 配置更新后没有立即生效
原因:Koordlet 的 Watch 有延迟(通常 1-10 秒)
解决:
1. 查看 Koordlet 日志确认是否监听到变化
2. 手动重启 Koordlet 加快生效
3. 增加 Watch 超时时间(如果是网络问题)
验证:
kubectl get nodeslo <node-name> -o yaml | grep -i status
问题 2:Reservation 一直处于 Pending 状态
原因:集群中没有足够的连续空闲资源
解决:
1. 检查集群可用资源
kubectl top nodes
2. 调整 Reservation 的 preAllocation 参数
3. 减少预留资源量
4. 等待其他 Pod 释放资源
查看细节:
kubectl describe reservation <name>
问题 3:ElasticQuota 的实际可用配额与预期不符
原因:其他 Team 的 Pod 占用了扩展配额
解决:
1. 查看各 Team 的实际配额使用
kubectl get elasticquota -A
2. 调整 hard 和 max 参数
3. 根据实际需求调整优先级
优化建议:
- 关键业务设置更高的 min(硬保证)
- 设置合理的 hard(防止单个 Team 过度使用)
- 根据业务特性调整 max 和 priority
2.12 API 版本管理与向后兼容
Koordinator API 版本历程:
v1alpha1 (当前稳定版本)
├─ NodeSLO
├─ NodeMetric
├─ Reservation
├─ ElasticQuota
├─ Device
└─ PodMigrationJob
向后兼容原则:
✅ 新增字段总是 Optional
✅ 删除字段通过 Deprecated 标记,保留一个大版本
✅ 字段类型更改通过新字段实现
✅ 支持版本间的自动转换(Conversion Webhook)
总结 - 章节关键要点
2.13 快速查询表
| CRD | 作用域 | 管理者 | 消费者 | 更新频率 |
|---|---|---|---|---|
| NodeSLO | Cluster | koord-manager | Koordlet | 不频繁(配置变化时) |
| NodeMetric | Cluster | Koordlet | koord-scheduler, koord-descheduler, koord-manager | 每分钟 |
| Reservation | Cluster | koord-scheduler | koord-scheduler (调度) | 创建/销毁时 |
| ElasticQuota | Namespaced | koord-manager | Webhook (准入), koord-scheduler | 动态 |
| Device | Cluster | 用户维护或自动发现 | koord-scheduler | 不频繁 |
本章要点:
- 理解 Koordinator API 的设计哲学
- 掌握各核心 CRD 的结构和使用方法
- 学会根据生产场景配置 NodeSLO、Reservation、ElasticQuota
- 了解 API 的完整数据流和最佳实践