Skip to content

控制回路、调度与 cgroup ​

专题索引 / 下一篇:Pod 网络:从 socket 到 Service

本页把 Kubernetes 的 controller、Linux scheduler 和 cgroup 放进同一张图。它们都在处理资源和并发,但时间尺度、状态对象和正确性条件不同。

text
Kubernetes:声明期望 -> 观察事实 -> 计算差异 -> 执行一小步 -> 再观察
Linux:线程可运行 -> 选择 CPU -> 上下文切换 -> 阻塞或抢占 -> 再选择
cgroup:把进程归组 -> 在内核执行 CPU、内存、I/O、PID 的资源规则

1. Kubernetes:把一次操作改成持续收敛 ​

把 Deployment 从 3 个副本改为 5 个,API 请求成功只说明期望被保存,不说明 5 个 Pod 已运行。

text
spec.replicas = 5
  -> API Server 写入 etcd
  -> watch 通知 controller
  -> informer 更新本地 cache
  -> callback 将 namespace/name 放进 workqueue
  -> worker 执行 sync(key)
  -> 读取最新 Deployment、ReplicaSet、Pod
  -> 只执行缩小差异的一步
  -> 新事件进入下一轮 reconcile

这里有三种不能混淆的状态:

状态归属作用
spec用户或上层 controller声明想要什么
status负责该对象的 controller报告观察到什么
实际资源API Server、Node、运行时表示集群现在真实存在什么

informer 的 callback 不应直接创建 Pod。事件可能重复、乱序到达,处理时对象也可能已经变化;callback 只投递 key,worker 再按 key 读取当前事实并重新计算。DeltaFIFO 服务于本地 cache 的增删改维护;controller 的 workqueue 服务于业务对象的收敛,两者放的不是同一种东西。

sync(key) 的边界 ​

一次 sync(key) 应是从当前状态得出的计算,不是重放上一轮的操作计划。

text
读取对象失败且不存在 -> 清理本 controller 拥有的关联状态,或结束
读取对象成功 -> 找到 ownerReference 指向自己的子资源
计算 desired 与 actual -> 创建、更新或删除一个安全的小差异
API conflict / 短暂错误 -> 限速后重新排队
成功 -> 清除该 key 的旧失败退避

ownerReference 让 Deployment、ReplicaSet、Pod 的资源树可判定;没有它,controller 无法安全地区分“自己管理的 Pod”和外部 Pod。resourceVersion 则防止旧读覆盖新写:收到 409 Conflict 后要重新读、重新算,不是原样再发一次 update。

2. Linux:调度器不做业务收敛 ​

Linux 的调度器面对的是已经可运行的任务。它不维护“服务最终应该有几个副本”,也不理解 Pod;它决定下一刻某个 CPU 应运行哪一个线程。

text
I/O、锁、定时器或中断完成
  -> wake_up
  -> task 进入某个 CPU 的 runqueue
  -> 调度器按调度类别和可运行状态选择任务
  -> context switch
  -> 新任务运行、阻塞或被抢占

每个 CPU 维护自己的 runqueue,热路径多数时候不需要扫描全机任务或争用一把全局锁。负载均衡、CPU 亲和性、NUMA 和迁移在需要时才跨 CPU 协调。

普通应用一般使用 fair 调度类;内核还需要为 deadline、real-time、idle 等不同约束提供不同调度规则。现代 Linux 的普通任务选择使用 EEVDF 思路,结合任务权重、已获得的 CPU 时间和可运行资格挑选候选任务。具体算法与内部字段会随内核版本变化,稳定的架构目标是:调度决策必须足够快、可抢占,并尽量兼顾公平性、延迟与缓存局部性。

比较维度Linux schedulerKubernetes controller
状态单位task_struct 与 runqueueAPI 对象与关联资源
典型时间尺度微秒到毫秒秒到分钟
主机制唤醒、抢占、切换、迁移watch、队列、reconcile、重试
正确性重点时序、延迟、并发安全幂等、所有权、最终收敛

3. cgroup:控制面和执行面接起来 ​

Kubernetes scheduler 根据 request 做 Node 放置决策;Pod 真正运行后,kubelet 与 runtime 把容器进程放进 Linux cgroup,由内核执行资源限制。

text
PodSpec resources
  -> scheduler 用 request 评估可放置性
  -> kubelet / CRI runtime 建立容器进程与 cgroup
  -> Linux scheduler、内存回收、I/O 子系统执行规则

request 不是物理保留的 CPU;它首先是调度账本。limit 才是节点执行面的强约束来源之一。以 cgroup v2 为例,常见对应关系是:

文件含义常见现象
cpu.weightCPU 竞争时的相对份额压力下获得 CPU 更少
cpu.max周期内最多可用的 CPU 时间quota 用尽后 throttling,P99 延迟上升
memory.high内存回收与压力阈值延迟先变差
memory.maxcgroup 内存上限分配失败或 cgroup OOM

具体 request/limit 到 cgroup 文件的映射受 cgroup v1/v2、kubelet、CRI runtime 和节点配置影响,不能只读 YAML 下结论。排障应同时查看对象配置、进程所在 cgroup 和 /sys/fs/cgroup 中的实际统计,例如 cpu.stat、memory.current、memory.events。

4. 可迁移的设计规则 ​

  1. 把快路径和慢路径分开:Kubernetes callback 只入队;Linux 中断和唤醒路径也避免承担长工作。
  2. 每个决策都基于可验证的当前状态:controller 重读对象;调度器检查 runqueue;资源限制读 cgroup 统计。
  3. 明确所有权:Kubernetes 用 ownerReferences;Linux 用任务、地址空间、文件描述符和 cgroup 的归属关系。
  4. 失败不能靠猜测:超时、冲突、OOM、throttling 都要保留可观察证据,并选择重试、重算、降级或终止。

源码与资料 ​

学习规划与研究资料库