Appearance
源码阅读:从行为问题追到架构边界
专题索引 / 上一篇:扩展与兼容:稳定契约如何承载演进
读 Linux 和 Kubernetes,不要从目录根部一路向下读。几百万行代码没有天然阅读顺序;架构是由真实行为、状态归属和跨模块契约构成的。最有效的入口是一句可验证的问题。
text
用户行为或故障现象
-> 对外入口
-> 状态对象与持久化位置
-> 事件或调用链
-> 负责收敛/执行的模块
-> 底层资源与完成信号
-> 观测证据与失败分支例如,不问“Deployment controller 的代码在哪里”,而问:“把 Deployment 副本从 3 改为 5 后,为什么 API 返回成功仍可能没有 5 个 Ready Pod?”这会强迫阅读顺序沿机制展开。
1. 先把问题写成状态转换
任何源码阅读任务先固定四件事:
| 项目 | 要写清的内容 |
|---|---|
| 起点 | 谁触发了什么输入,例如 API update、系统调用、网卡中断 |
| 目标 | 什么状态必须真正成立,例如 availableReplicas=5、文件已持久化 |
| 中间状态 | 哪些队列、缓存、对象、锁、buffer 或资源会变化 |
| 失败证据 | 失败时由谁记录 condition、errno、Event、metric 或 trace |
这样可以立刻区分“请求被接受”和“系统已经完成”。没有这个区分,源码阅读会停留在 handler、CLI 或 YAML 表层。
2. Kubernetes 案例:Deployment 的一条可追踪路径
从 kubectl scale deployment web --replicas=5 开始:
text
REST request
-> API Server validation / persistence
-> watch event
-> Deployment controller informer
-> workqueue key
-> syncDeployment
-> ReplicaSet / Pod 对象变化
-> scheduler 与 kubelet
-> Pod Ready condition
-> Deployment status 更新阅读时分三层定位:
text
组装入口:cmd/kube-controller-manager/app/
控制器入口:pkg/controller/deployment/
通用机制:client-go/tools/cache/ 与 client-go/util/workqueue/每次跨文件不要只记“调用了谁”,还要写清:这个模块读取了什么对象,写入了什么对象,重试 key 是什么,成功以后靠哪条 watch 或 status 更新让下一层知道。
例如 controller callback 把对象 key 入队,而不是保存一份旧对象直接执行,这一处代码体现的是“重读当前事实、幂等收敛”的架构约束,不是普通性能优化。
3. Linux 案例:一次 open() 为什么不是文件系统函数调用
从应用的 openat() 开始:
text
用户态 libc / syscall ABI
-> syscall entry
-> fs/open.c
-> pathname lookup(fs/namei.c)
-> VFS inode / dentry / mount 语义
-> 目标文件系统实现
-> file descriptor 返回给进程这里最重要的不是背出函数名,而是看见层次:路径解析、权限检查、mount namespace、缓存和具体文件系统实现被分开。应用依赖的是文件描述符与 errno;VFS 依赖文件系统操作契约;ext4、XFS、NFS 或 FUSE 可以替换具体实现。
源码入口通常包括:
text
fs/open.c
fs/namei.c
fs/file_table.c
include/linux/fs.h
Documentation/filesystems/vfs.rst固定一个 Linux tag 后再追调用。内核内部函数、锁和字段会演进;先通过文档确认抽象,再在同一版本源码中验证实现,避免把不同版本的搜索结果拼成一条不存在的调用链。
4. 每轮阅读只产出一张机制卡
不要读完一个目录再总结。每次只留下可复盘的一张卡:
text
问题:PVC 已 Bound,为什么 Pod 仍卡在 ContainerCreating?
入口:Pod 被调度到 Node 后 kubelet volume manager 调谐
状态:PVC/PV、VolumeAttachment、staging path、Pod mount
执行者:CSI controller + CSI node plugin + kubelet
完成:容器 mount namespace 中路径可用
失败:Event、CSI 日志、kubelet mount 错误、后端卷状态
边界:Bound 不是 mounted;mounted 不是 write durable这张卡比一堆目录笔记更有价值,因为它能指导下一次排障、设计评审或代码修改。
5. 读到“架构结论”的检验标准
真正理解一个模块,至少应能回答:
- 它拥有哪份状态,状态的真源在哪里?
- 它接收什么输入,输出给谁?
- 它怎样处理重复、并发、超时和部分失败?
- 它依赖的稳定契约是什么,哪些实现可以替换?
- 它的完成信号是什么,用户和运维如何验证?
如果答案只能是“它调用了 A、B、C”,还没有读到架构。调用图是证据,不是结论。
6. 下一次阅读的练习
选一个问题,不要同时选三个:
- Kubernetes:
Service已有 EndpointSlice,为什么某个 Node 的新连接仍然失败?沿pkg/proxy/和目标 CNI 的 Node 数据面追一次。 - Linux:一次写入为何卡住?从
write()、page cache、文件系统、block layer 到设备完成事件追一次。 - 扩展:一个 CRD 已创建,为什么外部资源没有出现?追 API watch、controller workqueue、reconcile、status 和最终资源读回。
验收不是“看完源码”,而是写出一张机制卡,并能用日志、status、trace 或系统工具为每个关键跳转找到证据。
资料与源码入口
- Kubernetes:Kubernetes Components、Controllers,访问于 2026-08-03。
- Kubernetes 源码:
cmd/kube-controller-manager/app/、pkg/controller/deployment/、client-go/tools/cache/,访问于 2026-08-03。 - Linux:VFS documentation、
fs/open.c、fs/namei.c,访问于 2026-08-03。
本文的路径用于建立阅读入口,不能替代固定版本的源码证据。进行设计或故障结论前,应锁定 tag/commit,并保留对应的调用位置、运行时证据与版本信息。