第34章 Kubernetes 源码学习路线
版本基线:本章以
kubernetes/kubernetes主干及client-go v0.36.0(对应 K8s 1.36)为参照。K8s 是个高速演进的庞然大物,包路径和大文件会随版本微调,但本章给出的“分层 + 入口 + 调用链“骨架在近几个 minor 里是稳定的;读到具体行号时请固定到一个 tag 上。本章不是 API 教程,而是教你怎么把这几百万行 Go 代码啃下来。
前面五章已经把 K8s 体系里最贴近业务的部分讲透了:第29章的 client-go Informer/WorkQueue、第30章的 Reconcile 模式、第31章的 controller-runtime 与 Operator、第32章的指标暴露、第33章的工程范式。它们回答了“怎么用 Go 写 K8s 应用“。本章回答另一个问题:怎么读 K8s 自己的 Go 源码——从一个 Pod 被提交到它跑起来,中间 apiserver、etcd、scheduler、controller-manager、kubelet 各自做了什么、代码在哪、按什么顺序读才不迷路。
34.1 先想清楚为什么读、读到什么程度
K8s 源码不是一个“从头读到尾“的目标,读法因人而异:
| 你的目标 | 建议深度 | 入手层 |
|---|---|---|
| 写好 Operator / 控制器 | client-go + controller 机制吃透即可 | 34.3、34.7、34.11 |
| 排查线上 apiserver 异常 | 请求链路 + storage + watch cache | 34.5、34.6 |
| 做调度定制 / 扩展 scheduler | 调度框架 + plugin | 34.8 |
| 排查 Pod 起不来 / 节点问题 | kubelet PLEG + CRI | 34.9 |
| 面试 / 系统设计 | 全链路主干 + 关键机制 | 全章主干 |
| 给上游提 PR | 锁定一个 SIG 的子模块 + 测试体系 | 34.13、34.17 |
定好目标再开读,否则很容易在 apiserver 的 handler chain 里转两周后放弃。一个可执行的建议:给自己一个“能讲清楚一个 Pod 从 kubectl apply 到 Running 的全链路“的验收标准,以此倒推要读哪些层。
34.2 前置知识与阅读方法论
知识前置(缺哪块读哪块会卡):
- Go 进阶:接口、反射、并发(第12章、第13章、第15章)、内存与 GC(第20章、第21章)。K8s 大量用 channel 做控制循环、用 context 做取消传播、用 interface 做解耦,这些不过关读不动。
- Linux:进程、网络(iptables/ipvs/netlink)、cgroup、namespace、文件系统。kubelet 和 kube-proxy 直接操作这些。
- 分布式系统基础:一致性、 leases、watch、最终一致。
- etcd:raft 基本概念、key-value + watch + lease + transaction 模型。apiserver 的存储层就是 etcd v3 的封装。
- API 设计:RESTful、资源版本、乐观并发。
阅读方法论(这套方法比具体代码更重要):
- 自顶向下找入口,自底向上理解机制。 先从
cmd/找到组件的main(),看清启动流程和“主循环“在哪;再下钻到具体机制。 - 先读接口,再读实现。 K8s 满世界是 interface(
runtime.Object、watch.Interface、store、storage.Interface、Framework)。先看接口定义弄清职责边界,再用 IDE “Find Implementations” 跳到实现,否则会被一堆实现绕晕。 - 先走 happy path,再读 edge case。 任何组件都先追“正常路径“一条线到底,再回头看错误处理、重试、并发竞争。
- 用单测当文档。 K8s 的
*_test.go是最好的用法说明书。读不懂某个函数时,先看它的测试怎么构造输入。 - 写最小 demo 验证理解。 读 client-go 就手写一个 Informer;读 scheduler 就注册一个空 plugin。能跑通才算读懂。
- 画图。 时序图(请求链路)、状态机(Pod 状态、调度队列流转)、依赖图(module 依赖)。不画图,读 apiserver 必丢。
- 固定 tag。 每次读之前
git checkout v1.36.x,不要在主干上读,否则文件一直在变。
34.3 仓库宏观地图:先建立全局坐标
K8s 源码最大的迷路原因是没有坐标感。先记住这张地图。
仓库演进与 staging 机制。 K8s 曾经是个单仓库(monorepo)kubernetes/kubernetes。为了让 client-go 等库能被外部项目独立依赖,引入了 staging 机制:这些库的源码物理上放在 kubernetes/kubernetes 的 staging/src/k8s.io/ 下,但各自是一个独立的 Go module(如 k8s.io/client-go、k8s.io/apimachinery、k8s.io/apiserver)。由 hack/update-vendor.sh / publishing-rules 定期把它们同步(sync)到独立仓库 k8s.io/<name>。你 clone 主仓后,client-go 的代码不在 vendor/ 里读,而要读 staging/src/k8s.io/client-go/。 这是新人第一个坑。
顶层目录速查:
| 目录 | 内容 |
|---|---|
cmd/ | 各组件入口:kube-apiserver/、kubelet/、kube-scheduler/、kube-controller-manager/、kube-proxy/、kubectl/ |
pkg/ | 组件主体逻辑(非 staging 部分):pkg/kubelet/、pkg/scheduler/、pkg/controller/、pkg/proxy/ 等 |
staging/src/k8s.io/ | 独立 module 的源码:client-go/、apimachinery/、apiserver/、api/、apiextensions-apiserver/、kubectl/、component-base/、code-generator/ 等 |
plugin/ | 插件:pkg/admission/、pkg/auth/、pkg/scheduler/(部分) |
hack/ | 构建/代码生成脚本:hack/update-codegen.sh 等 |
test/ | e2e 与集成测试 |
keps/ | KEP(Kubernetes Enhancement Proposals)设计文档,读源码卡住时回去翻设计意图 |
module 依赖图(从底层到上层):
apimachinery ← 地基:类型、Scheme、runtime、watch、labels
↑
apiserver ← apiserver 框架:handler chain、storage、admission
↑
kube-apiserver / apiextensions-apiserver / kube-aggregator
↑
client-go ← 客户端:informer、workqueue、clientset
↑
kubelet / scheduler / controller-manager / kube-proxy / kubectl
记住一句话:apimachinery 是地基,apiserver 是服务端框架,client-go 是客户端库,三者构成“铁三角“,所有组件都站在它们上面。 读源码的顺序也应大致遵循这个依赖方向:先 apimachinery,再 client-go 或 apiserver,最后各组件。
34.4 第 0 层:apimachinery(地基,最先读)
为什么先读它: 无论读 apiserver、client-go 还是任何组件,都会撞上 runtime.Object、Scheme、watch.Interface、metav1.ObjectMeta。不先建立这层词汇表,后面每个文件都读不动。
入口包: staging/src/k8s.io/apimachinery/pkg/
阅读顺序与关键文件:
- 元类型
pkg/apis/meta/v1/types.go:TypeMeta(apiVersion+kind)、ObjectMeta(name/namespace/uid/resourceVersion/labels/annotations/finalizers/...)、ListMeta(resourceVersion/continue)、OwnerReference。这是所有 K8s 对象的公共字段来源。务必理解resourceVersion是什么——它是乐观并发和 watch 一致性的核心(见 34.6)。 runtime.Object接口pkg/runtime/interfaces.go:所有 K8s 对象都实现它(GetObjectKind()、DeepCopyObject())。它是 K8s 泛型处理的根基。runtime.Schemepkg/runtime/scheme.go:GVK(Group/Version/Kind)<-> Go 类型的注册表,还管版本转换(conversion)和默认值(defaulter)。读scheme.go的RegisterVersions、AddKnownTypes、Converter。apiserver 启动时会把所有内置资源类型注册进一个 Scheme。- 序列化
pkg/runtime/serializer/:JSON、YAML、Protobuf codec。codec.go的Decode/Encode。理解runtime.Decoder如何根据 content-type 选 codec。 watchpkg/watch/watch.go:Interface、Event(Added/Modified/Deleted/Bookmark/Error)。这是 watch 机制的抽象,apiserver 和 client-go 都用它。- 选择器
pkg/labels/、pkg/fields/、pkg/selection/:label selector 与 field selector,list/watch 过滤的基础。 - 转换
pkg/conversion/:版本间转换(如apps/v1<->apps/v1beta1)的底层机制。
配套代码生成器(staging/src/k8s.io/code-generator/,别去读生成产物,但要懂它生成了什么):deepcopy-gen(生成 DeepCopyObject/DeepCopy)、client-gen(生成 clientset)、informer-gen(生成 Informer)、lister-gen(生成 Lister)、conversion-gen、defaulter-gen、openapi-gen。// +k8s:... 注释是触发标记。
时间预估: 2 天。验收: 能说清一个 Deployment 对象如何被 Scheme 识别、如何被序列化成 JSON 存进 etcd。
34.5 第 1 层:client-go(客户端库,对应第29章)
client-go 是读 K8s 源码性价比最高的一层:它是业务代码直接用的库,机制集中,且有第29章打底。这里只补“读源码“的视角。
入口包: staging/src/k8s.io/client-go/
阅读顺序(这是 Informer 体系的经典调用链,务必一次走通):
ListWatch → Reflector → DeltaFIFO → Indexer/Store → Controller → SharedInformer → 你的 EventHandler
tools/cache/listers.go的ListWatch:封装 list + watch 两次调用。ListFunc/WatchFunc。tools/cache/reflector.go:核心。Run()->listAndWatch()。读它如何先 list 全量建立基线,再watch增量;relistResourceVersion如何决定从哪开始;watchErrorHandler如何处理断线重连。理解resync是什么(不是重新 list,而是把 Store 里现有对象按周期重新派发给 handler)。tools/cache/delta_fifo.go:DeltaFIFO。Delta(Sync/Added/Updated/Deleted+ 对象)。它是 Reflector 和 Controller 之间的解耦队列。读Pop()的处理循环和keyOf/dedup 逻辑。tools/cache/store.go/thread_safe_store.go:Indexer的实现,本地缓存 + 索引(indexers/indices)。理解NamespaceIndex等内置索引。tools/cache/controller.go:Controller。processLoop()不停PopDeltaFIFO 并派发给ResourceEventHandler。这是第30章控制循环的雏形。tools/cache/shared_informer.go:SharedInformer。一个资源只跑一个 Reflector,但把事件广播给多个 handler(handler的started/syncHandler、HandleDeltas)。读AddEventHandler如何注册、Run如何管理 lifecycle。tools/cache/shared_informer_factory.go:SharedInformerFactory。统一管理多个 SharedInformer 的启动与WaitForCacheSync。
WorkQueue(第30章重点): util/workqueue/。delaying_queue.go(延迟入队)、rate_limiting_queue.go(限速重试)、parallelism.go。读 AddAfter/AddRateLimited/Forget 的语义,以及第30章讲的 priorityqueue(1.36 已有优先级队列)。
其他要点:
rest/config.go:rest.Config(kubeconfig 解析后产物)。tools/clientcmd/:kubeconfig 文件解析。kubernetes/typed/:clientset(client-gen 生成,别精读,知道结构即可)。dynamic/:dynamic client(操作任意 GVK,写 Operator 时常用)。tools/record/:Event recorder。
时间预估: 3 天。验收: 能手写一个监听 Pod 变化并打印的 Informer,并说清 list、watch、resync、DeltaFIFO、Indexer 各自的职责边界。对应: 第29章。
34.6 第 2 层:kube-apiserver(最复杂,重点投入)
apiserver 是 K8s 最庞大也最值得读的组件。它的难点是请求链路极长,所以第一要务是抓住一条链路。
入口: cmd/kube-apiserver/ -> app/server.go 的 NewAPIServerCommand -> CreateServerChain。
三条 apiserver 链: K8s 的 apiserver 其实是三个串联的 server:
- aggregator(
kube-aggregator):最外层,处理 APIService 聚合(如 metrics-server 注册到/apis/metrics.k8s.io)。 - kube-apiserver(
pkg/controlplane):内置资源(Pod/Deployment/Service…)。 - apiextensions-apiserver(
staging/src/k8s.io/apiextensions-apiserver):CRD 资源。
请求按 path/version 路由到对应的 server。读时先只盯 kube-apiserver 这一链。
一条请求的全链路(GET /api/v1/namespaces/default/pods 为例):
HTTP 请求
→ cobra 命令 → server 启动(BuildHandlerChainFunc 装配 filter 链)
→ handler chain:filter(authn 鉴权 → authz 授权 → audit → impersonation → requestinfo → ...)
→ go-restful 路由 → Resource handler(pkg/endpoints/handlers/)
→ admission chain(mutating → ... )(写请求才有)
→ storage layer(pkg/registry/ 的 store + strategy)
→ etcd3 store(pkg/storage/etcd3/store.go)
→ etcd
按子层阅读:
- 启动与 handler chain
staging/src/k8s.io/apiserver/pkg/server/:config.go(Config聚合所有配置)、server.go、hooks.go。重点BuildHandlerChainFunc(默认DefaultBuildHandlerChain在pkg/server/config.go)——它决定了 filter 顺序:Authorization、Authentication、Impersonation、Audit、RequestInfo、MaxInFlightLimit等。 - 路由
pkg/endpoints/与go-restful:handlers/get.go、handlers/create.go等。看InstallREST如何把一个RESTStorage注册成 REST 路由。 - admission
staging/src/k8s.io/apiserver/pkg/admission/:chain.go(chainAdmissionHandler)、plugins.go。mutating(MutatingAdmission)在对象写 etcd 前改对象,validating(ValidatingAdmission)只校验。Webhook admission(plugin/webhook/)是云原生扩展点。 - registry / storage
staging/src/k8s.io/apiserver/pkg/registry/:rest.go(StandardStorage接口:Create/Get/List/Update/Delete/Watch)、store.go(Store是通用实现,组合CreateStrategy/UpdateStrategy/DeleteStrategy等)。每个内置资源在pkg/registry/<group>/<resource>/下有自己的 strategy(如pkg/registry/core/pod/strategy.go),定义校验、默认值、允许的字段变更。 - etcd3 storage
staging/src/k8s.io/apiserver/pkg/storage/etcd3/store.go:Create/Get/Update/Watch的 etcd 落地实现。读Update如何用resourceVersion做乐观并发(CAS)。
watch cache(重点): staging/src/k8s.io/apiserver/pkg/storage/cacher/cacher.go。apiserver 不会把每个 client 的 watch 都打到 etcd;它维护一个 watch cache,订阅 etcd 的 watch,再 multicast 给所有 client。读 Cacher 的 processEvent、dispatchEvent、startDispatching。理解 watch 的 resourceVersion 一致性保证(“从某 RV 之后的所有变更”)和 ServeWatch 如何从 cache 回放历史 + 续接实时。
建议第一个走通的链路: ConfigMap 的 CRUD(最简单的资源,无 status 子资源、无复杂 strategy)。从 pkg/registry/core/configmap/ 入手,追到 etcd 再回来。
时间预估: 5-7 天,是全书最值得花时间的部分。验收: 能画出写一个 ConfigMap 的完整时序图,标注每一步经过的包;能解释 watch cache 为什么不让 client 直连 etcd。
34.7 第 3 层:etcd 集成与 watch 一致性
这一层是 apiserver 的延伸,单独拎出来因为它是分布式一致性的关键。
关键概念:
- etcd revision:etcd 全局单调递增的整数,每次写分配一个新 revision。
resourceVersion:K8s 对象的resourceVersion来自 etcd revision(内置资源)或自增计数(CRD 在老版本里)。它是对象版本的“逻辑时钟“。- 乐观并发:
Update时带上旧resourceVersion,etcd 用mod_revision做 CAS;不匹配返回Conflict,客户端重试(这就是第31章RetryOnConflict的根因)。 - list 的
resourceVersion语义:list 传不同 RV 有不同语义——不传(最新)、传具体值(不晚于该 RV 的快照)、传"0"(任何缓存即可,允许 served from cache)。这是性能优化热点,细节见 KEP “Consistent Reads from Cache”。 - watch 一致性:watch 从某 RV 开始,保证不丢事件、按序送达;若 etcd 历史被压缩(compaction)导致无法从该 RV 续上,返回
410 Gone,client 需重新 list。
关键文件: staging/src/k8s.io/apiserver/pkg/storage/etcd3/(store.go、watcher.go、errors.go)。
时间预估: 1-2 天。
34.8 第 4 层:controller-manager 与 controller 机制(对应第30章)
第30章已经从 controller-runtime 的角度讲了 Reconcile;这里看 K8s 内置 controller 的原生写法(informer + workqueue,没有 controller-runtime 的抽象,更贴近“机制本体“)。
入口: cmd/kube-controller-manager/app/controllermanager.go。它注册几十个 controller,按依赖顺序启动。cmd/kube-controller-manager/app/core.go 等分文件管理各类 controller 的启动函数。
一个内置 controller 的标准结构(以 deployment 为例,pkg/controller/deployment/):
deployment_controller.go:DeploymentController持有Informer、workqueue.RateLimitingInterface。NewDeploymentController:注册 Pod、ReplicaSet、Deployment 的 EventHandler,Enqueue入队。Run:起 N 个 worker 跑worker->processNextWorkItem->syncHandler(即syncDeployment)。syncDeployment.go:真正的调和逻辑——让 ReplicaSet 数量与期望一致。
建议阅读顺序(由易到难):
pkg/controller/replication/:最简单的 controller,只管副本数。先读它建立模板。pkg/controller/deployment/:引入 ReplicaSet 层级关系。pkg/controller/endpoint/:Service->Endpoints 映射,理解 slice endpoints 的演进可跳。pkg/controller/node/:节点生命周期与状态、taint。pkg/controller/garbagecollector/:基于OwnerReference的级联删除,最难也最精彩,依赖图 + finalizer。
leader election: staging/src/k8s.io/client-go/tools/leaderelection/。controller-manager 多实例时用 Lease 抢主,只有主才跑 controller。读 leaderelection.go 的 acquire/renew 循环。
时间预估: 3-4 天。验收: 能对照 第30章 说清“原生 controller 写法“和“controller-runtime 写法“的对应关系(syncHandler ≈ Reconcile、workqueue ≈ PriorityQueue、Informer ≈ cache.Cache)。
34.9 第 5 层:kube-scheduler
入口: cmd/kube-scheduler/app/server.go -> Setup -> scheduler.New。
核心模型(调度框架 / Scheduling Framework,v1.19+): 调度被拆成一串扩展点(QueueSort、Filter、Score、Bind、Reserve、Permit、PreBind、PostBind…),每个扩展点可插多个 plugin。这让调度器从“硬编码 predicate/priority“变成“plugin 组合“。
关键目录:
pkg/scheduler/scheduler.go:主循环Run->scheduleOne。pkg/scheduler/framework/:框架接口(Framework、Plugin、各扩展点接口FilterPlugin/ScorePlugin/…)。pkg/scheduler/framework/plugins/:内置 plugin(noderesources/、nodeaffinity/、podtopologyspread/、interpodaffinity/、tainttoleration/、defaultbinder/等)。pkg/scheduler/internal/queue/:三队列——activeQ(待调度)、backoffQ(退避中)、unschedulableQ(无法调度),以及它们之间的流转。pkg/scheduler/internal/cache/:scheduler cache(节点上已有 Pod 的快照,Snapshot)。
两阶段调度主流程:
scheduleOne:
从 activeQ pop 一个 Pod
→ 运行 Filter plugins(预选,筛掉不满足的节点)
→ 运行 Score plugins(优选,给剩余节点打分)
→ 选最高分节点
→ Reserve / Permit / PreBind
→ Bind(写回 Pod.Spec.NodeName,经 apiserver)
→ 失败则入 backoffQ/unschedulableQ
阅读建议: 先读 scheduler.go 的 scheduleOne 拿到主流程,再挑一个最简单的 plugin(如 nodename)完整读它的 Filter/Score 实现,理解 plugin 如何注册和被调用。然后读队列流转(NextPod、backoff 逻辑)。
扩展实践: 写一个自定义调度 plugin 是理解调度器最快的路径——参考 pkg/scheduler/framework/plugins/ 下任意 plugin 的骨架,在 --config 里挂上。
时间预估: 3-4 天。验收: 能说出一个 Pod 从入队到 bind 经过的扩展点和队列流转。
34.10 第 6 层:kubelet(最复杂的单组件之一)
kubelet 的复杂度在于它是一个事件驱动的多 manager 协调器,对接一堆外部接口(CRI/CNI/CSI)。
入口: cmd/kubelet/app/server.go -> Run -> startKubelet -> kubelet.Run。
核心循环与机制:
pkg/kubelet/kubelet.go的syncLoop:kubelet 的主循环,靠一组 channel 驱动(plegCh、syncCh、livenessManager.Updates()、housekeepingCh…)。每个事件把对应 Pod 入队podWorkers。pkg/kubelet/pod_workers.go:podWorkers保证每个 Pod 串行syncPod,并发不同 Pod。读UpdatePod。pkg/kubelet/pleg/pleg.go:PLEG(Pod Lifecycle Event Generator)。它周期性从 CRI 查容器状态,对比上次快照,产生PodLifecycleEvent喂给syncLoop。这是“容器实际状态变化感知到 kubelet“的关键。syncPod(kubelet.go/pkg/kubelet/pod_workers.go):Pod 同步的核心,按计算变更集 → 创建/更新 Pod 的 sandbox → 启动 init 容器 → 启动普通容器。
各 manager(各管一摊,按需读):
pkg/kubelet/status/:status manager,把 Pod 状态异步回写 apiserver。pkg/kubelet/prober/:liveness/readiness/startup probe 探测,结果喂回syncLoop。pkg/kubelet/volumemanager/:卷的 attach/mount 时机管理,对接 CSI。pkg/kubelet/images/:镜像 GC 与拉取。pkg/kubelet/eviction/:节点资源压力下的驱逐。pkg/kubelet/container/runtime.go与pkg/kubelet/cri/:CRI 接口调用(RuntimeService、ImageService)。
CRI / CNI / CSI: 这三个接口把 kubelet 与运行时、网络、存储解耦。CRI(k8s.io/cri-api / pkg/kubelet/cri/)是 gRPC,kubelet 作为客户端调用容器运行时(containerd/CRI-O)。读 kubelet 时把 CRI 当黑盒接口即可,需要时再看具体运行时实现。
静态 Pod 与 mirror pod: pkg/kubelet/config/ 读取静态 Pod 配置文件,并在 apiserver 创建对应的 mirror pod(只读镜像)。
时间预估: 4-5 天。验收: 能讲清“容器在节点上挂了,kubelet 怎么感知并重启它“的完整链路(PLEG -> syncLoop -> syncPod -> CRI)。
34.11 第 7 层:kube-proxy 与网络
入口: cmd/kube-proxy/app/server.go。
两种数据面模式: iptables(默认)与 ipvs。kube-proxy 本质是监听 Service/Endpoints 变化,把规则同步到内核。
关键目录:
pkg/proxy/:Proxier接口与实现(iptables/proxier.go、ipvs/proxier.go)。pkg/proxy/iptables/:proxier.go的syncProxyRules把 Service->Endpoints 翻译成 iptables 链(KUBE-SERVICES、KUBE-SVC-*、KUBE-SEP-*)。pkg/proxy/ipvs/:用 ipvs service + iptables 兜底 masquerade。
阅读建议: 读 syncProxyRules 一条线,理解一个 Service 的 ClusterIP/NodePort/DNAT 规则如何生成。配合 iptables-save / ipvsadm -L 在真机对照看规则,最快。
时间预估: 1-2 天。
34.12 第 8 层:CRD 与 apiextensions(对应第31章)
第31章讲了用 controller-runtime 写 Operator;这里看CRD 本身是怎么被 apiserver 支撑的——即 apiextensions-apiserver。
入口: staging/src/k8s.io/apiextensions-apiserver/。
关键点:
pkg/apis/apiextensions/types.go:CustomResourceDefinition类型本身。pkg/registry/apiextensions/crd/:CRD 资源的 strategy。pkg/controller/crd/:crd_finalizer_controller、establishing_controller——CRD 创建后如何“建立“(生成对应的 REST handler)。pkg/server/:CRD 资源如何被动态注册进 apiserver 的路由(CustomResourceDefinitions一变,就为新 GVK 装一组 handler)。validation与conversion:基于 OpenAPI schema 校验 CR、版本间转换(conversion-gen或 webhook)。
和第31章的衔接: 你写的 Operator(controller-runtime)消费的是 CRD 资源;而让这个 CRD 资源能在 apiserver 上 CRUD,就是 apiextensions-apiserver 干的。两者拼起来才是完整链路。
时间预估: 2 天。
34.13 第 9 层:kubectl(可选,工程范式宝库)
kubectl 的代码质量很高,visitor 模式与 apply 三方合并值得一看。
入口: cmd/kubectl/ 与 staging/src/k8s.io/kubectl/。
要点:
- cobra 命令装配:
pkg/cmd/。 apply的3-way merge(last-applied / local / live 三方合并):pkg/util/apply/、pkg/cmd/apply/。visitor模式:pkg/visit/(visitor 遍历一组资源对象)。- generators:
generate.go生成资源对象。
时间预估: 1 天(按兴趣)。
34.14 阅读工具与方法论(落地)
| 工具 / 方法 | 用法 |
|---|---|
IDE Go to Definition / Find Implementations / Call Hierarchy | K8s 接口多,必须靠 IDE 找实现;Call Hierarchy 找调用链 |
grep / git grep | 按符号、GVK 字符串、func 签名定位 |
dlv(Delve)断点调试 | 起一个本地 apiserver/kubelet,断点走 happy path,比纯看代码快十倍 |
读 *_test.go | 单测是最好的用法文档;构造输入看断言理解行为 |
| 读 e2e / 集成测试 | 理解组件间真实交互,test/e2e/ |
| 看 KEP | keps/ 目录,卡在设计意图时回去翻提案 |
看官方文档 Resources 与 API Conventions | kubernetes/community 的 contributors/devel/sig-architecture/ 有 API 约定 |
| 画图 | 时序图(apiserver 链路)、状态机(Pod 状态、调度队列)、依赖图 |
| 固定 tag 读 | git checkout v1.36.x,别在主干读 |
一个高效读法:起本地单机集群 + 断点。 用 kind 或 minikube 起集群,把改过的组件以一定方式跑起来(或直接在测试里 kubelet.NewTestKubelet 之类构造一个),用 dlv attach,发一个请求/创建一个 Pod,断点单步。这是理解控制流最快的方式,远胜通读源码。
34.15 各组件源码阅读清单(速查表)
| 组件 | 入口 | 关键包/文件 | 主循环/入口函数 | 阅读重点 | 预估时间 |
|---|---|---|---|---|---|
| apimachinery | — | apimachinery/pkg/runtime/、pkg/apis/meta/v1/ | — | Scheme、Object、watch、序列化 | 2 天 |
| client-go | — | client-go/tools/cache/、util/workqueue/ | Reflector.Run、SharedInformer.Run | Informer 调用链、workqueue | 3 天 |
| kube-apiserver | cmd/kube-apiserver/app/server.go | apiserver/pkg/server/、/registry/、/storage/、/admission/ | BuildHandlerChain、Store.Create | 请求链路、admission、watch cache | 5-7 天 |
| etcd 集成 | — | apiserver/pkg/storage/etcd3/、/cacher/ | store.Update、Cacher.dispatchEvent | RV 一致性、乐观并发、watch cache | 1-2 天 |
| controller-manager | cmd/kube-controller-manager/app/ | pkg/controller/<name>/ | DeploymentController.syncDeployment | 原生 controller 模式、GC、leader election | 3-4 天 |
| kube-scheduler | cmd/kube-scheduler/app/server.go | pkg/scheduler/、framework/、framework/plugins/ | scheduleOne | 调度框架、plugin、三队列 | 3-4 天 |
| kubelet | cmd/kubelet/app/server.go | pkg/kubelet/kubelet.go、pleg/、pod_workers.go | syncLoop、syncPod | PLEG、多 manager、CRI | 4-5 天 |
| kube-proxy | cmd/kube-proxy/app/server.go | pkg/proxy/iptables/、/ipvs/ | syncProxyRules | Service->规则同步 | 1-2 天 |
| apiextensions | apiextensions-apiserver/ | pkg/registry/apiextensions/、pkg/controller/crd/ | CRD establish | CRD 动态注册、schema | 2 天 |
| kubectl | cmd/kubectl/ | kubectl/pkg/cmd/、pkg/util/apply/ | apply 三方合并 | visitor、3-way merge | 1 天 |
34.16 进阶专题(读透主干后选方向)
- API 机制深挖:
resourceVersion一致性模型、limit=continue分页、listrv=0缓存读、finalizer 与 GC、admission webhook、Server-Side Apply(字段所有权)。 - 调度进阶: 调度框架 plugin 编写、
gang scheduling(需 kube-scheduler 无法直接做,靠调度器扩展或 Volcano)、优先级与抢占(preemption)、descheduler。 - 存储: CSI 三阶段(Provision/Attach/Mount)、
VolumeSnapshot、InlineVolume、卷扩缩容。 - 网络: CNI 插件链、
NetworkPolicy实现、Service Mesh与 kube-proxy 的关系、Gateway API。 - 安全: RBAC(
pkg/apis/rbac/+ authorizer)、Pod Security Admission(替代 PSP)、静态 pod 与节点安全、Secret 静态加密(kms/v2)。 - 稳定性与性能: apiserver
Panic/Timeouthandler、MaxInFlightLimit、APIPriorityAndFairness(公平排队)、WatchCache容量调优、etcd compaction 策略。
34.17 常见卡壳点与排雷
| 卡壳点 | 排雷 |
|---|---|
| 找不到 client-go 代码 | 不在 vendor/,在 staging/src/k8s.io/client-go/ |
满屏 xxx_generated.go | 这些是 codegen 产物,不要读,读手写的 .go |
runtime.Object/store/storage.Interface 找不到实现 | 用 IDE Find Implementations,别靠眼睛搜 |
| apiserver handler chain 看不懂 | 从 DefaultBuildHandlerChain 倒着看 filter 顺序,画图 |
Scheme 转换链太绕 | 先只看一个 GVK 的注册与 codec,版本转换留到进阶 |
kubelet syncLoop 多 channel 乱 | 把每个 channel 的生产者/消费者列成表,看清谁喂谁 |
| scheduler plugin 执行顺序 | 看 --config 的 plugin 配置和 framework 的扩展点调度逻辑,不是代码里写死的 |
看 resourceVersion 越看越晕 | 先读 34.7,把 etcd revision / 乐观并发 / watch 续接三件事分清 |
| 不知道某个 controller 为啥被触发 | 看 NewXxxController 里注册了哪些 Informer 的 EventHandler |
34.18 从读到写:配套实践
光读不写,理解停在表面。建议按难度递进做三件事:
- 写一个 controller(原生写法): 不用 controller-runtime,用裸 client-go 的 Informer + workqueue 写一个监听 ConfigMap 变化打日志的小控制器。对照第30章理解 controller-runtime 帮你省了什么。
- 写一个 Operator(controller-runtime): 参考第31章的完整示例,定义一个 CRD + Reconcile,部署到本地集群。
- 给上游提 PR: 从
good first issue入手;或改进测试文档。走完一次 CI(pull-kubernetes-*job)能让你对 K8s 工程体系有质的理解。贡献流程见kubernetes/community的contributors/guide/。
34.19 版本与代码获取
- 选哪个 release: 建议读最新稳定版(如 v1.36.x)主干;想理解演进,可对比一两个老版本(如 v1.20 前后的 apiserver 差异、v1.19 调度框架引入前后)。
- 获取:
git clone https://github.com/kubernetes/kubernetes,git checkout v1.36.x。client-go 单独用:go get k8s.io/client-go@v0.36.0。 - 本地构建: 看
hack/目录,make系列。构建要求 Go 版本见go.mod(K8s 各 release 有严格 Go 版本约束)。只读源码不必本地构建全组件,用kind/minikube跑现成集群即可。
本章小结
- K8s 源码读法因目标而异:先定目标,再定深度,别盲目通读。
- 铁三角优先:apimachinery(地基)→ client-go(客户端)/ apiserver(服务端)→ 各组件。这条依赖方向就是阅读方向。
- 抓链路:apiserver 的请求链路、client-go 的 Informer 链路、kubelet 的 PLEG->syncLoop 链路、scheduler 的
scheduleOne——每个组件先走通一条 happy path。 - 接口优先于实现,单测当文档,断点单步优于通读,固定 tag。
- 和前五章呼应:client-go 对应第29章,controller 机制对应第30章,CRD/Operator 对应第31章,指标与工程对应第32章、第33章。
- 附录里的“Kubernetes 源码阅读路线“是便签式速查,本章是其完整展开版——卡壳时查附录,系统学时读本章。
读完这一章,你应该有了一张地图:知道每个组件代码在哪、入口是谁、主循环是什么、按什么顺序读。剩下的事没有捷径——打开 IDE,固定一个 tag,从 cmd/kube-apiserver/app/server.go 的 main 开始,一步一步走。