揭秘云原生混布资源调度器Koordinator (四)StatesInformer 状态同步机制

15 阅读10分钟

核心使命与设计理念

4.1 What - StatesInformer 是什么?

StatesInformer 是 Koordlet 中的状态同步和缓存模块,维护节点上所有 Pod、Node、NodeSLO 等资源的本地视图

核心职责

  1. Watch 并缓存本节点的所有 Pod 信息
  2. Watch 并缓存节点本身的信息(labels、capacity、allocatable)
  3. Watch 并缓存节点的 NodeSLO 配置
  4. 为其他模块提供高速的状态查询接口
  5. 维护状态一致性和增量同步

4.2 Why - 为什么需要状态同步?

问题 1:频繁查询 API Server 导致性能问题

对比分析:

不使用缓存方案:
┌────────────────────────────────┐
│ QOSManager 每秒工作循环        │
├────────────────────────────────┤
│ for pod in pods:               │
│   api_call() → 查询 API Server │
│   延迟: ~100ms                 │
│   × 200 个 Pod                 │
│ = 20s 延迟!无法按时执行      │
│                                │
│ 额外问题:                      │
│ ├─ API Server QPS 爆炸        │
│ ├─ etcd 压力增大              │
│ └─ 网络带宽浪费               │
└────────────────────────────────┘

使用缓存方案:
┌────────────────────────────────┐
│ QOSManager 每秒工作循环        │
├────────────────────────────────┤
│ for pod in cache.GetAllPods(): │
│   内存查询 → 延迟: <1ms       │
│   × 200 个 Pod                 │
│ = 200ms 延迟(可接受)        │
│                                │
│ 好处:                          │
│ ├─ API Server 压力小          │
│ ├─ 本地操作速度快             │
│ └─ 容错能力强                 │
└────────────────────────────────┘

性能对比:
查询 200 个 Pod 的时间
├─ 直接查询 API Server: ~20000ms
├─ 使用本地缓存: ~200ms
└─ 性能提升: 100

问题 2:API Server 故障时需要继续工作

场景时间线:

T=0:00   API Server 升级开始(不可用 5 分钟)
T=0:01   Koordlet 的 Watch 连接断开
         ├─ 不使用缓存: 无法继续工作
         └─ 使用缓存: 继续使用旧状态管理资源

T=0:02   新 Pod 创建请求来了
         ├─ 不使用缓存: 无法感知,无法配置隔离
         └─ 使用缓存: 根据旧状态,但基本资源管理继续进行

T=0:05   API Server 恢复,Watch 重连
         ├─ 不使用缓存: 需要完全重新初始化
         └─ 使用缓存: 增量同步新变化,快速恢复

总体影响:
├─ 无缓存: Pod 被驱逐、资源被误用、SLO 违反
└─ 有缓存: 短期内基于旧状态继续工作,影响较小

问题 3:需要追踪状态变化历史

应用场景:

RuntimeHooks 决策示例:
├─ 判断 Pod 是否刚创建
│  └─ 如果 < 1 秒前创建,可能还未完全初始化
│  └─ 需要使用保守的隔离参数
│
├─ 判断 Pod 是否即将被删除
│  └─ 如果有删除时间戳,可能即将关闭
│  └─ 不必进行复杂的资源配置
│
└─ 计算 Pod 的运行时长
   └─ 新 Pod (< 1min): 无历史数据,谨慎处理
   └─ 运行中 Pod (> 1hour): 有充分历史,正常处理

QOSManager 决策示例:
├─ 最近被驱逐的 Pod
│  └─ 如果上次驱逐后不到 1 分钟,不立即重新抑制
│
└─ Pod 的内存趋势
   └─ 需要看历史数据判断内存是否持续增长

4.3 How - 高效状态同步的实现


StatesInformer 的完整实现

4.4 核心数据结构

// StatesInformer 的主要组件
type StatesInformer interface {
    // Pod 查询接口
    GetPodByUID(uid types.UID) *corev1.Pod
    GetPodByNamespace(namespace, name string) *corev1.Pod
    GetPodsByNode(nodeName string) []*corev1.Pod
    GetAllPods() []*corev1.Pod
    
    // Node 查询接口
    GetNode() *corev1.Node
    GetNodeSLO() *slov1alpha1.NodeSLO
    
    // QoS 查询接口
    GetPodQoSClass(pod *corev1.Pod) extension.QoSClass
    GetPodPriority(pod *corev1.Pod) int32
    
    // 状态变化回调
    RegisterEventHandler(handler PodEventHandler)
}

内部数据结构

┌─────────────────────────────────────────────┐
          StatesInformer 缓存结构            
├─────────────────────────────────────────────┤
                                             
 1. podMap                                  
    Key: Pod UID                            
    Value: Pod 对象 + 元数据                 
    Size: ~50 MB(200 Pod 节点)            
    Access: O(1)                            
                                             
 2. podByNamespace                          
    Key: namespace/name                     
    Value: Pod UID                          
    目的:快速按名称查询                     
                                             
 3. podListByNode                           
    Key: nodeName                           
    Value: [Pod UID 列表]                   
    目的:快速查询一个节点上的所有 Pod      
                                             
 4. nodeCache                               
    单个 Node 对象缓存                       
    Size: ~100 KB                           
                                             
 5. nodeSLOCache                            
    单个 NodeSLO 对象缓存                    
    Size: ~50 KB                            
                                             
 6. eventHandlers                           
    Pod 事件的回调列表                       
    ├─ Pod 创建回调                         
    ├─ Pod 更新回调                         
    └─ Pod 删除回调                         
                                             
└─────────────────────────────────────────────┘

4.5 Watch 机制的优化

Watch 流程

API Server
    │ 推送增量变化(Add/Update/Delete 事件)
    ↓
Informer Framework
    │ 事件缓冲、合并、去重
    ↓
EventHandler
    │ 业务逻辑处理
    │
    ├─→ OnAdd(pod)
    │    ├─ podMap[pod.UID] = pod
    │    ├─ podByNamespace[ns/name] = pod.UID
    │    ├─ podListByNode[node] += pod.UID
    │    └─ 触发 Pod 创建回调
    │
    ├─→ OnUpdate(oldPod, newPod)
    │    ├─ 检查关键字段是否变化
    │    │  ├─ 资源 request/limit
    │    │  ├─ QoS 标签
    │    │  └─ 优先级
    │    ├─ 更新缓存
    │    └─ 触发 Pod 更新回调(仅关键字段变化)
    │
    └─→ OnDelete(pod)
         ├─ podMap.delete(pod.UID)
         ├─ podByNamespace.delete(ns/name)
         ├─ podListByNode[node] -= pod.UID
         └─ 触发 Pod 删除回调
    ↓
StatesInformer 缓存保持最新状态

Watch 的关键优化

1. 增量更新机制
   ├─ 只同步变化的对象,不是全量重新加载
   └─ 减少网络带宽和 CPU 消耗

2. 事件合并
   ├─ 如果 Pod 快速连续更新多次
   ├─ 只保留最后的状态
   └─ 避免重复处理

3. 去重和验证
   ├─ 检测出 Pod 虽然更新但关键字段未变
   ├─ 不触发 ResourceExecutor 的重复执行
   └─ 保证幂等性

4. Resync 机制
   ├─ 周期性(默认 30 分钟)重新同步全量数据
   ├─ 用于检测 Watch 丢失的事件
   └─ 保证最终一致性

4.6 查询性能分析

查询操作的性能

操作                    时间复杂度    实际延迟     使用场景
────────────────────────────────────────────────────────────
GetPodByUID()          O(1)          <0.1ms     最常用,快速查询
GetPodsByNode()        O(n)          <1ms       获取节点上所有 Pod
GetAllPods()           O(n)          <5ms       遍历操作
GetPodByNamespace()    O(1)          <0.1ms     按名称查询
GetNode()              O(1)          <0.1ms     查询节点信息
GetNodeSLO()           O(1)          <0.1ms     查询 SLO 配置

n = 节点上的 Pod 数量(通常 100-500

内存占用分析

资源            单位大小      200 Pod 节点    500 Pod 节点
────────────────────────────────────────────────────────────
Pod 对象        ~5 KB        1 MB            2.5 MB
Pod 元数据      ~2 KB        0.4 MB          1 MB
索引(UID)     ~40 bytes    8 KB            20 KB
索引(名称)    ~60 bytes    12 KB           30 KB
索引(节点)    ~40 bytes    8 KB            20 KB
Node 缓存       ~100 KB      100 KB          100 KB
NodeSLO 缓存    ~50 KB       50 KB           50 KB
──────────────────────────────────────────────────────────
总计                        ~52 MB          ~64 MB

4.7 事件处理的生产案例

案例:Pod 创建到资源配置的完整流程

时间线:

T=0:00   用户执行 kubectl apply -f pod.yaml
         └─ Pod 对象发送到 API Server

T=0:05   API Server 持久化 Pod,创建事件
         └─ Watch 推送 Pod Added 事件

T=0:10   Koordlet StatesInformer 收到事件
         ├─ EventHandler.OnAdd(pod)
         ├─ podMap[uid] = pod
         ├─ podByNamespace[ns/name] = uid
         ├─ podListByNode[node] += uid
         └─ 触发已注册的回调函数们:
            │
            ├─→ RuntimeHooks.OnPodAdded()
            │    └─ 计算该 Pod 的隔离参数
            │    └─ 为容器创建准备数据
            │
            ├─→ MetricAdvisor.OnPodAdded()
            │    └─ 注册该 Pod 的指标采集
            │
            └─→ QOSManager.OnPodAdded()
                 └─ 更新 Pod 集合
                 └─ 下次周期时应用 QoS 策略

T=0:15   kubelet 监听到 Pod 变化,开始创建容器
         └─ 调用 CRI 创建容器

T=0:20   如果启用了 RuntimeProxy/NRI,会在容器创建时
         └─ 注入 StatesInformer 提供的隔离参数

T=1:00   Pod 正常运行,StatesInformer 继续监听变化
         ├─ 如果 Pod 资源请求变化(通常不变)
         └─ OnUpdate 事件,触发重新配置

T=N:00   Pod 被删除
         └─ OnDelete 事件
         └─ 清理缓存、停止监控

案例:API Server 故障恢复

时间线:

T=10:00  API Server 正常,StatesInformer 同步状态
         ├─ 缓存中有 200 个 Pod
         └─ Watch 连接活跃

T=10:05  API Server 开始升级,Watch 连接断开
         ├─ 网络层错误,连接关闭
         └─ StatesInformer 保持旧缓存,标记为 "Stale"

T=10:06-10:10 API Server 升级期间
         ├─ StatesInformer 使用旧缓存继续工作
         ├─ 新的 Pod 请求可能无法感知(最多 5 分钟)
         ├─ QOSManager 仍基于旧状态执行
         └─ 但 Pod 基本的资源配置保持稳定

T=10:11  API Server 升级完成,Watch 重连成功
         ├─ 开始接收增量事件
         ├─ 检测新创建的 Pod(在故障期间)
         ├─ 检测被删除的 Pod
         └─ 5 分钟内的所有变化被同步

T=10:30  Resync 触发(30 分钟周期)
         ├─ 全量重新同步所有 Pod
         ├─ 对比缓存和 API Server 状态
         ├─ 修复任何不一致的状态
         └─ 恢复到完全一致

故障影响评估:
├─ 无缓存系统: 5 分钟内无法管理,SLO 严重违反
└─ 有缓存系统: 基本继续工作,5 分钟后完全恢复

与其他模块的协作

4.8 StatesInformer 被谁使用?

┌──────────────────────┐
│ StatesInformer 缓存  │
└──────────────────────┘
        ↑ Watch 更新
        │
┌─────────────────────────────────────────────┐
│         Koordlet 各模块的使用        │
├─────────────────────────────────────────────┤
│                                             │
│ 1. RuntimeHooks                            │
│    GetPodByUID() → 获取 Pod request/limit  │
│    GetPodQoSClass() → 获取 QoS 等级       │
│    用途:容器创建时注入隔离参数            │
│                                             │
│ 2. MetricAdvisor                           │
│    GetAllPods() → 遍历所有 Pod            │
│    GetPodQoSClass() → 按 QoS 分类采集    │
│    用途:采集时知道哪些 Pod 是 LS/BE     │
│                                             │
│ 3. QOSManager                              │
│    GetAllPods() → 每秒查询所有 Pod 状态    │
│    GetPodQoSClass() → 判断如何抑制/驱逐   │
│    GetPodPriority() → 驱逐时的优先级      │
│    用途:制定 QoS 策略                    │
│                                             │
│ 4. ResourceExecutor                        │
│    GetPodByUID() → 查询 Pod 的容器 ID     │
│    用途:执行 CGroup 更新时定位容器       │
│                                             │
│ 5. PredictServer                           │
│    GetAllPods() → 历史数据分析             │
│    用途:基于 Pod 历史预测未来需求        │
│                                             │
└─────────────────────────────────────────────┘

4.9 一致性保证

缓存与 API Server 的一致性

三级一致性保证:

1. 最终一致性
   └─ 高频事件: 通过 Watch 增量同步
   └─ 延迟: 通常 < 100ms
   └─ 保证: 一定会同步,但可能有延迟

2. 周期同步
   └─ 每 30 分钟 Resync 全量数据
   └─ 用于修复 Watch 丢失的事件
   └─ 保证: 最坏情况 30 分钟内恢复一致

3. 主动验证
   └─ 关键操作前,可选性验证 API Server
   └─ 如果缓存不确定,查询 API Server
   └─ 保证: 关键决策的准确性

实际应用:
├─ 正常情况: 使用缓存(快速)
├─ 关键决策: 可选验证 API Server(准确)
└─ 故障恢复: 优先用缓存,等待同步(可靠)

生产调优指南

4.10 常见性能问题与优化

问题 1:Watch 延迟过大

症状:Koordlet 感知 Pod 变化延迟 > 1 秒

可能原因:
├─ API Server 负载过高
├─ 网络延迟
├─ Watch 缓冲区满

诊断方法:
$ kubectl logs pod/koordlet-xxx -n koordinator-system | \
  grep "watch\|delay" | tail -20

$ # 查看 API Server 的 QPS
$ kubectl top pod -n kube-system | grep apiserver

解决方案:
1. 增加 API Server 资源
2. 减少 Pod 变动频率
3. 调整 Watch 缓冲区大小:
   --watch-buffer-size=2000

问题 2:内存占用过高

症状:StatesInformer 内存占用 > 200 MB

可能原因:
├─ 节点上 Pod 过多(> 1000 个)
├─ 缓存保留的历史数据过多
├─ 内存泄漏

诊断方法:
$ kubectl top pod koordlet-xxx -n koordinator-system

解决方案:
1. 减少单节点 Pod 数量(分散到多节点)
2. 减少缓存保留时间:
   --cache-ttl=600(从 3600 改为 600)
3. 定期 GC:
   --gc-interval=30m

问题 3:查询响应时间长

症状:GetAllPods() 耗时 > 50ms

可能原因:
├─ Pod 数量过多(> 1000)
├─ 频繁的全量遍历

优化方案:
1. 使用索引查询而不是遍历:
   GetPodsByNode(nodeName)  ✅
   GetAllPods()             ❌ (低效)

2. 缓存查询结果(如果数据不需要实时)
3. 按 QoS 等级分组查询:
   GetPodsByQoS(qos) // 内部实现优化

4.11 生产配置建议

# Koordlet StatesInformer 配置
spec:
  containers:
  - name: koordlet
    args:
    # Watch 配置
    - --watch-timeout=5m           # Watch 超时时间
    - --watch-buffer-size=2000     # Watch 缓冲区大小
    
    # Resync 配置
    - --resync-interval=30m        # 多久全量同步一次
    
    # 缓存配置
    - --cache-ttl=3600             # 缓存数据保留时间
    - --gc-interval=30m            # GC 周期
    
    # 性能配置
    - --query-timeout=1s           # 查询超时
    - --batch-update-size=100      # 批量更新大小

4.12 监控指标

# Koordlet StatesInformer 关键指标

# Pod 缓存大小
koordlet_pod_cache_size

# 最后一次同步的时间(Age)
koordlet_states_informer_sync_age_seconds

# Watch 连接状态(0=断开,1=连接)
koordlet_watch_connection_status

# 事件处理延迟
koordlet_event_handler_latency_milliseconds

# 查询操作延迟
koordlet_cache_query_latency_milliseconds{operation="GetAllPods"}
koordlet_cache_query_latency_milliseconds{operation="GetPodByUID"}

总结 - 章节要点汇总

4.13 关键概念速查

概念含义性能指标
缓存本地内存中 Pod/Node/NodeSLO 的副本~50-100 MB
WatchAPI Server 推送的增量事件流< 100ms 延迟
Resync定期全量同步以修复不一致30 分钟周期
最终一致性缓存与 API Server 最终一致最坏 30 分钟
事件回调Pod 变化时的处理函数< 10ms 执行

4.14 最佳实践

  • 总是使用缓存查询,避免直接查询 API Server
  • 在关键决策前,可选性验证 API Server
  • 使用索引查询而不是全量遍历
  • 定期检查缓存与 API Server 的一致性
  • 监控 Watch 连接状态,及时发现问题

本章要点

  • 理解 StatesInformer 的核心职责和设计理念
  • 掌握高效的状态同步机制
  • 学会性能优化和故障排查
  • 理解缓存的一致性保证机制