核心使命与设计理念
10.1 What - ResourceExecutor 是什么?
ResourceExecutor 是 Koordlet 中的资源操作执行引擎,负责将 QOSManager 的决策转化为具体的 CGroup 操作指令,并确保指令正确执行。
核心职责:
- 接收执行指令:从 QOSManager 接收资源隔离和限流指令
- 管理 CGroup:更新 Pod 对应的 CGroup 配置文件
- 批量执行:支持批量和增量更新,提高效率
- 错误处理:处理执行失败,提供重试机制
- 状态同步:同步执行结果,反馈给 QOSManager
- 幂等性:确保重复执行相同指令不会产生副作用
执行层次:
yaml
┌─────────────────────────────────────────────┐
│ ResourceExecutor 的执行层次 │
├─────────────────────────────────────────────┤
│ │
│ L1: 策略层(QOSManager) │
│ ├─ CPU 压力 > 90 │
│ ├─ 决策: 驱逐 3 个 BE Pod │
│ └─ 调用 ResourceExecutor │
│ │
│ L2: 指令层(ResourceExecutor) │
│ ├─ 指令 1: 更新 BE Pod 1 的 quota │
│ ├─ 指令 2: 更新 BE Pod 2 的 quota │
│ └─ 指令 3: 驱逐 BE Pod 3 │
│ │
│ L3: 操作层(CGroup 文件系统) │
│ ├─ 写入 /sys/fs/cgroup/cpu/.../quota │
│ ├─ 写入 /sys/fs/cgroup/memory/.../min │
│ └─ 删除 Pod 对应的 CGroup │
│ │
│ L4: 内核层(Linux Kernel) │
│ ├─ CFS 调度器应用新 quota │
│ ├─ 内存管理器执行回收 │
│ └─ 进程被限流或终止 │
│ │
└─────────────────────────────────────────────┘
10.2 Why - 为什么需要专门的执行引擎?
问题 1:直接修改 CGroup 容易出错
markdown
低效的方式(逐个修改):
for each pod in pods_to_update:
write(pod.cgroup_cpu_quota, new_quota)
write(pod.cgroup_memory_min, new_min)
write(pod.cgroup_io_weight, new_weight)
问题:
├─ 每个 Pod 3 次文件写操作 → 频繁系统调用
├─ 写入期间不一致 → 某些参数已更新,某些未更新
├─ 如果中间出错 → Pod 处于不完整的状态
└─ 无法回滚 → 无法恢复到之前的状态
ResourceExecutor 的方案:
1. 批量收集所有指令
2. 验证指令的一致性
3. 原子性执行(全部成功或全部失败)
4. 保存执行历史(便于调试和回滚)
问题 2:CGroup 操作的复杂性和陷阱
yaml
CGroup 操作的常见陷阱:
陷阱 1: 内存限制的顺序
├─ 错误: 先设置 memory.min,后设置 memory.max
│ ├─ 如果 min > max,操作会失败
│ └─ 导致设置不成功
│
├─ 正确: 先确保 max >= min
│ └─ 避免顺序导致的失败
陷阱 2: CPU quota 的边界值
├─ quota 范围: 1000 ~ 18446744073709551615 (2^64-1)
├─ 错误: quota=0 或 quota=-1(无限制)不同含义
└─ 需要: 明确区分有限制和无限制的情况
陷阱 3: CGroup v1 和 v2 的差异
├─ v1: cpu/cpuacct 分开
├─ v2: unified 子系统
├─ 需要: 同时支持两个版本
└─ 不能混淆 API
陷阱 4: 权限问题
├─ 普通用户无法修改 CGroup
├─ 需要: root 权限或特殊配置
└─ RuntimeProxy: 在容器创建时注入参数
ResourceExecutor 的设计目标:
└─ 统一处理这些复杂性和陷阱
└─ 向上层提供简单、安全的 API
问题 3:需要支持高效的批量和增量操作
ini
场景:200 个 Pod 的节点,每秒更新参数
不优化的方案:
├─ 200 个 Pod × 3 个参数 = 600 次文件操作
├─ 每次操作 1ms → 总耗时 600ms
├─ 频繁的系统调用,CPU 占用高
└─ 可能无法按时完成
优化方案(ResourceExecutor):
1. 增量更新
├─ 只更新有变化的 Pod
├─ 例:仅 50 个 Pod 的参数变化
└─ 50 × 3 = 150 次操作
2. 批量操作
├─ 合并同一 CGroup 的多个参数更新
├─ 例:cpuset 和 cpu.shares 一起更新
└─ 减少文件操作次数
3. 异步更新
├─ 不阻塞 QOSManager 的决策循环
├─ 在后台线程执行
└─ QOSManager 继续做下一轮决策
4. 优先级队列
├─ 优先执行关键操作(驱逐、防 OOM)
├─ 延迟执行非关键操作(轻微抑制)
└─ 有限的执行资源,优先处理紧急任务
性能对比:
场景: 200 Pod,50 个需要更新,3 个参数/Pod
不优化:
├─ 更新所有: 600 次 I/O × 1ms = 600ms
└─ 无法按时完成 1s 的决策周期
优化后:
├─ 增量: 150 次 I/O × 1ms = 150ms
├─ 批量: 100 次 I/O × 1ms = 100ms
├─ 异步: 在后台,不阻塞主循环
└─ 总耗时: < 100ms,轻松完成
10.3 How - 执行引擎的实现架构
yaml
┌──────────────────────────────────────────────┐
│ ResourceExecutor 工作流程 │
├──────────────────────────────────────────────┤
│ │
│ 1. 指令收集(来自 QOSManager) │
│ ├─ UpdateRequest: 更新 Pod 的资源参数 │
│ ├─ EvictionRequest: 驱逐 Pod │
│ ├─ ReservationRequest: 资源预留 │
│ └─ 批量收集,避免逐个处理 │
│ │
│ 2. 指令验证 │
│ ├─ 检查指令的合法性 │
│ ├─ 检查参数范围 │
│ ├─ 检查 Pod 是否存在 │
│ └─ 过滤无效指令 │
│ │
│ 3. 指令去重和优化 │
│ ├─ 同一 Pod 的多个更新 → 合并 │
│ ├─ 无效指令 → 删除 │
│ ├─ 优先级排序 → 紧急指令优先 │
│ └─ 增量计算 → 只更新变化的部分 │
│ │
│ 4. 执行前检查 │
│ ├─ 检查 CGroup 是否存在 │
│ ├─ 检查目标路径的权限 │
│ ├─ 读取当前值,对比变化 │
│ └─ 避免不必要的写入 │
│ │
│ 5. 执行操作 │
│ ├─ 按优先级执行 │
│ ├─ 一个 Pod 的多个参数按顺序执行 │
│ ├─ 支持重试(瞬时故障) │
│ └─ 记录执行日志 │
│ │
│ 6. 错误处理 │
│ ├─ 捕获执行异常 │
│ ├─ 分类处理(可重试/不可重试) │
│ ├─ 回滚已执行的操作(可选) │
│ └─ 记录错误,通知上层 │
│ │
│ 7. 结果反馈 │
│ ├─ 记录执行结果 │
│ ├─ 统计执行速度和成功率 │
│ ├─ 更新 Pod 的资源状态 │
│ └─ 通知 QOSManager │
│ │
└──────────────────────────────────────────────┘
分 - ResourceExecutor 的实现细节
10.4 指令的数据结构和优先级
执行指令的类型:
go
// ResourceUpdateRequest: 更新资源参数
type ResourceUpdateRequest struct {
PodUID string
ResourceType string // "cpu", "memory", "io", "network"
ParameterName string // "quota", "shares", "min", "max"
ParameterValue interface{} // 参数值
Priority int // 0-10, 越小越紧急
DeadlineNano int64 // 执行截止时间(纳秒)
Reason string // 执行原因,用于日志
}
// EvictionRequest: 驱逐 Pod
type EvictionRequest struct {
PodUID string
Namespace string
PodName string
Reason string // 驱逐原因
Priority int // 驱逐优先级
GracePeriodSecond int // 优雅关闭时间
}
// 优先级定义
const (
PriorityEmergency = 0 // 紧急(SLO 违反)
PriorityHigh = 3 // 高(CPU 压力 > 90)
PriorityMedium = 5 // 中等(CPU 压力 60-90)
PriorityLow = 8 // 低(日常调整)
PriorityBatch = 10 // 批处理(可延迟)
)
优先级排序规则:
ini
同一批次内的指令排序:
1. Priority 值(越小越优先)
├─ PriorityEmergency (0): 驱逐、防 OOM
├─ PriorityHigh (3): CPU 紧急抑制
├─ PriorityMedium (5): CPU 正常抑制
├─ PriorityLow (8): 日常参数调整
└─ PriorityBatch (10): 非关键更新
2. 截止时间(越早越优先)
├─ 即将超时的指令优先执行
└─ 避免关键指令被延迟
3. 资源类型(关键资源优先)
├─ CPU 驱逐 > 内存驱逐 > 其他
└─ CPU 是最关键的资源
4. Pod 数量(影响范围大的优先)
├─ 影响 100 个 Pod 的操作优先
├─ 影响 10 个 Pod 的操作其次
└─ 影响 1 个 Pod 的操作最后
示例排序:
原始列表:
├─ Request 1: CPU.quota, Priority=5, Deadline=3000ms (中等,3秒)
├─ Request 2: Memory.min, Priority=0, Deadline=5000ms (紧急,5秒)
├─ Request 3: CPU.shares, Priority=8, Deadline=6000ms (低,6秒)
└─ Request 4: CPU.quota, Priority=0, Deadline=2000ms (紧急,2秒)
排序后:
├─ Request 4: Priority=0, Deadline=2000ms ← 紧急且即将超时
├─ Request 2: Priority=0, Deadline=5000ms ← 紧急但时间充足
├─ Request 1: Priority=5, Deadline=3000ms ← 中等
└─ Request 3: Priority=8, Deadline=6000ms ← 低
10.5 批量操作和增量更新
增量计算的实现:
ini
增量更新的目标:
└─ 只更新有变化的参数
└─ 避免不必要的 CGroup 写操作
算法流程:
步骤 1: 读取当前值
current_quota = read_from_cgroup(pod_cgroup_path)
步骤 2: 对比新值
if current_quota == new_quota:
skip_update()
else:
execute_update()
示例:
场景: 200 个 Pod,每秒更新
全量更新(无优化):
├─ 200 个 Pod × 3 个参数 = 600 次写操作
└─ 600ms (每次 1ms)
增量更新(有优化):
├─ 检查当前值 200 × 3 = 600 次读操作(并行,< 100ms)
├─ 发现 150 个 Pod 需要更新
├─ 执行 150 × 3 = 450 次写操作(< 450ms)
└─ 总计: < 550ms(比全量快 50ms)
批量操作的优化:
不优化:
for each pod in pods:
for each param in params:
write(cgroup_path, param, value)
优化后:
batch = []
for each pod in pods:
for each param in params:
batch.append((cgroup_path, param, value))
execute_batch(batch) # 一次性执行,减少系统调用
性能提升:
├─ 系统调用从 600 次 → 1 次(减少 99.8%)
├─ 用户态/内核态切换从 1200 次 → 2 次
└─ 总耗时从 600ms → 100ms
生产案例:批量操作的性能提升
ini
场景:1024 核节点,500 个 Pod,每秒更新
不使用批量优化:
┌─────────────────────────────────────┐
│ 执行时间分析: │
├─────────────────────────────────────┤
│ 读取 Pod 信息: 50ms │
│ 生成指令: 20ms │
│ 修改 CGroup(500 × 3): │
│ ├─ 用户态准备: 200ms │
│ ├─ 系统调用: 1200 × 1ms = 1200ms │
│ ├─ 内核执行: 300ms │
│ └─ 返回用户态: 200ms │
│ 验证结果: 30ms │
│ │
│ 总计: 2000ms > 1000ms (超时!) │
│ │
│ 问题: │
│ ├─ QOSManager 的 1s 决策周期无法完成│
│ ├─ 执行延迟,导致决策滞后 │
│ └─ 资源隔离不及时 │
└─────────────────────────────────────┘
使用批量优化:
┌─────────────────────────────────────┐
│ 执行时间分析: │
├─────────────────────────────────────┤
│ 读取 Pod 信息: 50ms │
│ 生成指令: 20ms │
│ 修改 CGroup(批量): │
│ ├─ 用户态准备: 200ms │
│ ├─ 系统调用: 1 次 × 1ms = 1ms │
│ ├─ 内核执行: 300ms │
│ └─ 返回用户态: 10ms │
│ 验证结果: 30ms │
│ │
│ 总计: 611ms < 1000ms (满足!) │
│ │
│ 改进: │
│ ├─ 执行延迟从 2000ms 降到 611ms │
│ ├─ 系统调用从 1200 次降到 1 次 │
│ ├─ 缓冲充足,可以执行其他操作 │
│ └─ 资源隔离及时有效 │
└─────────────────────────────────────┘
关键优化点:
├─ 批量 CGroup 操作 → 减少系统调用
├─ 增量更新 → 只更新变化的 Pod
├─ 异步执行 → 不阻塞 QOSManager
└─ 优先级排序 → 关键操作优先
10.6 错误处理和重试机制
错误分类:
go
CGroup 操作的错误类型:
可重试错误:
├─ EIO (I/O error): 瞬时 I/O 故障
│ └─ 解决: 等待后重试
│
├─ EAGAIN (Resource temporarily unavailable): 资源暂时不可用
│ └─ 解决: 等待后重试
│
├─ EINTR (Interrupted system call): 系统中断
│ └─ 解决: 重试系统调用
│
└─ 超时: 操作超过设定时间
└─ 解决: 增加超时时间后重试
不可重试错误:
├─ ENOENT (No such file or directory): CGroup 不存在
│ └─ 原因: Pod 已删除或路径错误
│ └─ 处理: 记录日志,跳过
│
├─ EACCES (Permission denied): 权限不足
│ └─ 原因: 进程权限不足
│ └─ 处理: 记录错误,需要提权
│
├─ EINVAL (Invalid argument): 参数无效
│ └─ 原因: 参数超出范围或格式错误
│ └─ 处理: 记录日志,跳过
│
└─ ERANGE (Numerical result out of range): 数值范围外
└─ 原因: 内存限制值不合理(min > max)
└─ 处理: 调整参数后重试
重试策略:
func ExecuteWithRetry(request Request) error {
max_retries := 3
retry_delay := 100ms
for attempt := 0; attempt < max_retries; attempt++ {
err := Execute(request)
if err == nil {
return nil // 成功
}
if !IsRetryable(err) {
return err // 不可重试,返回错误
}
if attempt < max_retries - 1 {
wait(retry_delay)
retry_delay *= 2 // 指数退避
}
}
return errors.New("max retries exceeded")
}
生产案例:处理 CGroup 不存在
ini
场景:Pod 在执行操作期间被删除
时间线:
T=10:00:00 QOSManager 检测到 CPU 压力
├─ 决策: 更新 Pod A 的 quota
└─ 发送更新请求到 ResourceExecutor
T=10:00:01 ResourceExecutor 收到请求
├─ 验证 Pod A 存在(检查成功)
└─ 加入执行队列
T=10:00:02 Pod A 被驱逐
├─ Kubernetes 删除 Pod
├─ CGroup 也被删除
└─ Pod A 的资源释放
T=10:00:03 ResourceExecutor 开始执行
├─ 尝试打开 CGroup 路径
├─ 文件不存在 → ENOENT 错误
└─ 该如何处理?
不使用错误处理:
┌─────────────────────────────────────┐
│ 执行失败,异常抛出 │
│ ├─ ResourceExecutor crash │
│ ├─ 之后的所有操作停止 │
│ ├─ 其他 Pod 的参数无法更新 │
│ └─ 整个资源隔离系统停止工作 │
└─────────────────────────────────────┘
使用错误处理:
┌─────────────────────────────────────┐
│ 捕获 ENOENT 错误 │
│ ├─ 检查:Pod 是否还存在? │
│ │ └─ 检查 StatesInformer 的缓存 │
│ │ └─ 发现 Pod A 已被删除 │
│ │ └─ 确认是 Pod 删除,不是路径错误 │
│ │ │
│ ├─ 处理:略过该 Pod │
│ │ └─ 从执行队列中移除 Pod A 的请求 │
│ │ └─ 记录日志: "Pod A 已删除,跳过" │
│ │ │
│ └─ 继续执行下一个 Pod(Pod B) │
│ │
│ 结果: │
│ ├─ ResourceExecutor 继续工作 │
│ ├─ 其他 Pod 的参数正常更新 │
│ ├─ 系统稳定 │
│ └─ 仅记录一条日志,无其他影响 │
└─────────────────────────────────────┘
错误分析的流程:
ENOENT 错误
↓
检查 Pod 是否在 StatesInformer 中
├─ 存在 → 路径错误,重新计算路径
├─ 不存在 → Pod 已删除,安全跳过
└─ 不确定 → 记录日志,跳过
↓
继续执行下一个操作
优势:
├─ 自动适应 Pod 的生命周期变化
├─ 不因为 Pod 删除而崩溃
└─ 提高系统鲁棒性
10.7 执行性能优化
并行执行的策略:
go
CGroup 操作的并行可行性:
可以并行的操作:
├─ 不同 Pod 的操作 → 完全独立
├─ 不同 CGroup 子系统的操作 → 独立
└─ 读操作 → 可以并行读
不能并行的操作:
├─ 同一文件的读-写 → 可能不一致
├─ 内存的 min/max 操作 → 有依赖关系
│ └─ 必须确保 min <= max
└─ 同一 Pod 的多个参数更新 → 需要原子性
并行执行的实现:
type UpdateBatch struct {
requests []UpdateRequest
// 按 Pod 分组
by_pod := group_by(requests, pod_uid)
// 按资源类型分组
by_resource := group_by(requests, resource_type)
}
ExecuteParallel(batch) {
// 步骤 1: 内存操作(有依赖)
memory_requests := filter(batch, resource="memory")
for request in memory_requests {
ExecuteMemoryUpdate(request) // 串行
}
// 步骤 2: CPU 操作(独立)
cpu_requests := filter(batch, resource="cpu")
// 创建 goroutine 池
workers := 4 // 4 个并行 worker
work_queue := make(chan UpdateRequest)
// 启动 worker
for i := 0; i < workers; i++ {
go Worker(work_queue)
}
// 分配任务
for request in cpu_requests {
work_queue <- request
}
close(work_queue)
wait_for_all()
}
性能提升:
场景: 200 个 Pod,4 个 CPU 并行 worker
串行执行:
├─ 内存操作: 50 Pod × 2 params × 1ms = 100ms
├─ CPU 操作: 200 Pod × 2 params × 1ms = 400ms
└─ 总计: 500ms
并行执行(4 workers):
├─ 内存操作: 50 × 2 × 1ms = 100ms(串行)
├─ CPU 操作: 200 × 2 × 1ms / 4 = 100ms(并行)
└─ 总计: 200ms(提升 60%)
生产调优指南
10.8 ResourceExecutor 配置
yaml
# ResourceExecutor 配置示例
apiVersion: v1
kind: ConfigMap
metadata:
name: koordlet-config
namespace: koordinator-system
data:
resource-executor-config.yaml: |
resourceExecutor:
# 执行线程池
threadPoolSize: 4 # 并行 worker 数
queueSize: 1000 # 等待队列大小
# 执行参数
batchSize: 100 # 单次批处理大小
batchInterval: 100ms # 批处理间隔
# 超时配置
executionTimeout: 5s # 单个操作超时
overallTimeout: 30s # 整批操作超时
# 重试配置
maxRetries: 3 # 最大重试次数
retryDelay: 100ms # 初始重试延迟
maxRetryDelay: 2s # 最大重试延迟
# 优先级队列
priorityQueueEnabled: true # 启用优先级队列
# 增量更新
incrementalUpdate: true # 启用增量更新
changeThreshold: 0 # 变化阈值(0=全部更新)
# 日志和监控
verboseLogging: false # 详细日志
metricsEnabled: true # 启用监控指标
slowLogThreshold: 500ms # 慢操作日志阈值
10.9 常见问题排查
问题 1:CGroup 操作超时
shell
诊断:
1. 检查执行延迟
$ kubectl logs koordlet-xxx | grep "execution timeout"
如果频繁超时:
└─ 系统 I/O 过载或权限问题
2. 检查系统负载
$ top
$ iostat -x 1
如果 I/O wait > 50%:
└─ 磁盘 I/O 过载
3. 检查 CGroup 文件系统
$ df /sys/fs/cgroup
$ ls -la /sys/fs/cgroup/cpu/...
如果文件操作慢:
└─ CGroup 文件系统问题
解决方案:
├─ 增加 executionTimeout: 5s → 10s
├─ 减少 batchSize: 100 → 50
├─ 增加 batchInterval: 100ms → 200ms
└─ 并行 worker 数不要过多
问题 2:内存限制设置失败
matlab
诊断:
1. 检查错误日志
$ kubectl logs koordlet-xxx | grep "EINVAL\|ERANGE"
如果显示 memory.min > memory.max:
└─ 参数设置顺序错误
2. 验证当前 CGroup 配置
$ cat /sys/fs/cgroup/memory/kubepods/pod-uid/memory.min
$ cat /sys/fs/cgroup/memory/kubepods/pod-uid/memory.max
如果 min > max:
└─ 之前的操作部分失败
解决方案:
├─ 确保操作顺序: min <= soft_limit <= high <= max
├─ 先调大 max,再调小 min
├─ 使用事务性操作确保一致性
问题 3:Pod 删除期间的操作失败
shell
诊断:
1. 检查 ENOENT 错误频率
$ promql: koordlet_resource_executor_errors{error="ENOENT"}
如果频率高:
└─ Pod 删除频率高,正常现象
2. 检查日志中的错误处理
$ kubectl logs koordlet-xxx | grep "ENOENT"
如果只有日志,无 crash:
└─ 错误处理正确
解决方案:
├─ 确保 ENOENT 错误被正确处理
├─ 不应该导致 ResourceExecutor crash
└─ 监控错误率,异常告警
10.10 监控指标
promql
# ResourceExecutor 关键指标
# 执行延迟
koordlet_resource_executor_latency_milliseconds{operation="update"}
koordlet_resource_executor_latency_milliseconds{operation="evict"}
# 执行成功率
koordlet_resource_executor_success_total
koordlet_resource_executor_errors_total{error_type="ENOENT"}
# 队列深度
koordlet_resource_executor_queue_depth
# 批量操作的大小
koordlet_resource_executor_batch_size
# 并行 worker 的负载
koordlet_resource_executor_worker_busy_ratio
# 重试次数
koordlet_resource_executor_retries_total
# 慢操作日志
koordlet_resource_executor_slow_operations_total
总结 - 章节要点汇总
10.11 关键概念速查
| 概念 | 含义 | 重要性 |
|---|---|---|
| 增量更新 | 只更新变化的参数 | 高 |
| 批量操作 | 合并多个操作,减少系统调用 | 高 |
| 优先级队列 | 关键操作优先执行 | 中 |
| 并行执行 | 多线程并行处理独立操作 | 中 |
| 错误重试 | 可重试错误自动重试 | 高 |
10.12 最佳实践
- 启用增量更新,避免不必要的 CGroup 操作
- 使用优先级队列,关键操作优先执行
- 正确处理 ENOENT 错误,不应该导致 crash
- 并行执行不相关的操作,提高吞吐量
- 监控执行延迟和成功率,及时发现问题