Appearance
控制回路、调度与 cgroup
本页把 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 scheduler | Kubernetes controller |
|---|---|---|
| 状态单位 | task_struct 与 runqueue | API 对象与关联资源 |
| 典型时间尺度 | 微秒到毫秒 | 秒到分钟 |
| 主机制 | 唤醒、抢占、切换、迁移 | 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.weight | CPU 竞争时的相对份额 | 压力下获得 CPU 更少 |
cpu.max | 周期内最多可用的 CPU 时间 | quota 用尽后 throttling,P99 延迟上升 |
memory.high | 内存回收与压力阈值 | 延迟先变差 |
memory.max | cgroup 内存上限 | 分配失败或 cgroup OOM |
具体 request/limit 到 cgroup 文件的映射受 cgroup v1/v2、kubelet、CRI runtime 和节点配置影响,不能只读 YAML 下结论。排障应同时查看对象配置、进程所在 cgroup 和 /sys/fs/cgroup 中的实际统计,例如 cpu.stat、memory.current、memory.events。
4. 可迁移的设计规则
- 把快路径和慢路径分开:Kubernetes callback 只入队;Linux 中断和唤醒路径也避免承担长工作。
- 每个决策都基于可验证的当前状态:controller 重读对象;调度器检查 runqueue;资源限制读 cgroup 统计。
- 明确所有权:Kubernetes 用 ownerReferences;Linux 用任务、地址空间、文件描述符和 cgroup 的归属关系。
- 失败不能靠猜测:超时、冲突、OOM、throttling 都要保留可观察证据,并选择重试、重算、降级或终止。
源码与资料
- Kubernetes controller 机制:Controllers、client-go cache、client-go workqueue,访问于 2026-07-31。前两者是机制和 API 文档;具体 controller 策略仍需结合目标版本源码。
- Kubernetes 源码入口:
kubernetes/pkg/controller/、client-go/tools/cache、client-go/util/workqueue,访问于 2026-07-31。 - Linux 调度文档:Linux scheduler documentation,以及源码
kernel/sched/,访问于 2026-07-31。调度算法和字段以阅读时选定的内核版本为准。 - cgroup v2:Control Group v2,访问于 2026-07-31。