Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

第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 cache34.5、34.6
做调度定制 / 扩展 scheduler调度框架 + plugin34.8
排查 Pod 起不来 / 节点问题kubelet PLEG + CRI34.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、资源版本、乐观并发。

阅读方法论(这套方法比具体代码更重要):

  1. 自顶向下找入口,自底向上理解机制。 先从 cmd/ 找到组件的 main(),看清启动流程和“主循环“在哪;再下钻到具体机制。
  2. 先读接口,再读实现。 K8s 满世界是 interface(runtime.Objectwatch.Interfacestorestorage.InterfaceFramework)。先看接口定义弄清职责边界,再用 IDE “Find Implementations” 跳到实现,否则会被一堆实现绕晕。
  3. 先走 happy path,再读 edge case。 任何组件都先追“正常路径“一条线到底,再回头看错误处理、重试、并发竞争。
  4. 用单测当文档。 K8s 的 *_test.go 是最好的用法说明书。读不懂某个函数时,先看它的测试怎么构造输入。
  5. 写最小 demo 验证理解。 读 client-go 就手写一个 Informer;读 scheduler 就注册一个空 plugin。能跑通才算读懂。
  6. 画图。 时序图(请求链路)、状态机(Pod 状态、调度队列流转)、依赖图(module 依赖)。不画图,读 apiserver 必丢。
  7. 固定 tag。 每次读之前 git checkout v1.36.x,不要在主干上读,否则文件一直在变。

34.3 仓库宏观地图:先建立全局坐标

K8s 源码最大的迷路原因是没有坐标感。先记住这张地图。

仓库演进与 staging 机制。 K8s 曾经是个单仓库(monorepo)kubernetes/kubernetes。为了让 client-go 等库能被外部项目独立依赖,引入了 staging 机制:这些库的源码物理上放在 kubernetes/kubernetesstaging/src/k8s.io/ 下,但各自是一个独立的 Go module(如 k8s.io/client-gok8s.io/apimachineryk8s.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.ObjectSchemewatch.Interfacemetav1.ObjectMeta。不先建立这层词汇表,后面每个文件都读不动。

入口包: staging/src/k8s.io/apimachinery/pkg/

阅读顺序与关键文件:

  1. 元类型 pkg/apis/meta/v1/types.goTypeMetaapiVersion+kind)、ObjectMetaname/namespace/uid/resourceVersion/labels/annotations/finalizers/...)、ListMetaresourceVersion/continue)、OwnerReference。这是所有 K8s 对象的公共字段来源。务必理解 resourceVersion 是什么——它是乐观并发和 watch 一致性的核心(见 34.6)。
  2. runtime.Object 接口 pkg/runtime/interfaces.go:所有 K8s 对象都实现它(GetObjectKind()DeepCopyObject())。它是 K8s 泛型处理的根基。
  3. runtime.Scheme pkg/runtime/scheme.go:GVK(Group/Version/Kind)<-> Go 类型的注册表,还管版本转换(conversion)和默认值(defaulter)。读 scheme.goRegisterVersionsAddKnownTypesConverter。apiserver 启动时会把所有内置资源类型注册进一个 Scheme。
  4. 序列化 pkg/runtime/serializer/:JSON、YAML、Protobuf codec。codec.goDecode/Encode。理解 runtime.Decoder 如何根据 content-type 选 codec。
  5. watch pkg/watch/watch.goInterfaceEventAdded/Modified/Deleted/Bookmark/Error)。这是 watch 机制的抽象,apiserver 和 client-go 都用它。
  6. 选择器 pkg/labels/pkg/fields/pkg/selection/:label selector 与 field selector,list/watch 过滤的基础。
  7. 转换 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-gendefaulter-genopenapi-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
  1. tools/cache/listers.goListWatch:封装 list + watch 两次调用。ListFunc/WatchFunc
  2. tools/cache/reflector.go:核心。Run() -> listAndWatch()。读它如何先 list 全量建立基线,再 watch 增量;relistResourceVersion 如何决定从哪开始;watchErrorHandler 如何处理断线重连。理解 resync 是什么(不是重新 list,而是把 Store 里现有对象按周期重新派发给 handler)。
  3. tools/cache/delta_fifo.goDeltaFIFODeltaSync/Added/Updated/Deleted + 对象)。它是 Reflector 和 Controller 之间的解耦队列。读 Pop() 的处理循环和 keyOf/dedup 逻辑。
  4. tools/cache/store.go / thread_safe_store.goIndexer 的实现,本地缓存 + 索引(indexers/indices)。理解 NamespaceIndex 等内置索引。
  5. tools/cache/controller.goControllerprocessLoop() 不停 Pop DeltaFIFO 并派发给 ResourceEventHandler。这是第30章控制循环的雏形。
  6. tools/cache/shared_informer.goSharedInformer。一个资源只跑一个 Reflector,但把事件广播给多个 handler(handlerstarted/syncHandlerHandleDeltas)。读 AddEventHandler 如何注册、Run 如何管理 lifecycle。
  7. tools/cache/shared_informer_factory.goSharedInformerFactory。统一管理多个 SharedInformer 的启动与 WaitForCacheSync

WorkQueue(第30章重点): util/workqueue/delaying_queue.go(延迟入队)、rate_limiting_queue.go(限速重试)、parallelism.go。读 AddAfter/AddRateLimited/Forget 的语义,以及第30章讲的 priorityqueue(1.36 已有优先级队列)。

其他要点:

  • rest/config.gorest.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.goNewAPIServerCommand -> CreateServerChain

三条 apiserver 链: K8s 的 apiserver 其实是三个串联的 server:

  • aggregatorkube-aggregator):最外层,处理 APIService 聚合(如 metrics-server 注册到 /apis/metrics.k8s.io)。
  • kube-apiserverpkg/controlplane):内置资源(Pod/Deployment/Service…)。
  • apiextensions-apiserverstaging/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

按子层阅读:

  1. 启动与 handler chain staging/src/k8s.io/apiserver/pkg/server/config.goConfig 聚合所有配置)、server.gohooks.go。重点 BuildHandlerChainFunc(默认 DefaultBuildHandlerChainpkg/server/config.go)——它决定了 filter 顺序:AuthorizationAuthenticationImpersonationAuditRequestInfoMaxInFlightLimit 等。
  2. 路由 pkg/endpoints/go-restfulhandlers/get.gohandlers/create.go 等。看 InstallREST 如何把一个 RESTStorage 注册成 REST 路由。
  3. admission staging/src/k8s.io/apiserver/pkg/admission/chain.gochainAdmissionHandler)、plugins.go。mutating(MutatingAdmission)在对象写 etcd 前改对象,validating(ValidatingAdmission)只校验。Webhook admission(plugin/webhook/)是云原生扩展点。
  4. registry / storage staging/src/k8s.io/apiserver/pkg/registry/rest.goStandardStorage 接口:Create/Get/List/Update/Delete/Watch)、store.goStore 是通用实现,组合 CreateStrategy/UpdateStrategy/DeleteStrategy 等)。每个内置资源在 pkg/registry/<group>/<resource>/ 下有自己的 strategy(如 pkg/registry/core/pod/strategy.go),定义校验、默认值、允许的字段变更。
  5. etcd3 storage staging/src/k8s.io/apiserver/pkg/storage/etcd3/store.goCreate/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。读 CacherprocessEventdispatchEventstartDispatching。理解 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.gowatcher.goerrors.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.goDeploymentController 持有 Informerworkqueue.RateLimitingInterface
  • NewDeploymentController:注册 Pod、ReplicaSet、Deployment 的 EventHandler,Enqueue 入队。
  • Run:起 N 个 worker 跑 worker -> processNextWorkItem -> syncHandler(即 syncDeployment)。
  • syncDeployment.go:真正的调和逻辑——让 ReplicaSet 数量与期望一致。

建议阅读顺序(由易到难):

  1. pkg/controller/replication/:最简单的 controller,只管副本数。先读它建立模板。
  2. pkg/controller/deployment/:引入 ReplicaSet 层级关系。
  3. pkg/controller/endpoint/:Service->Endpoints 映射,理解 slice endpoints 的演进可跳。
  4. pkg/controller/node/:节点生命周期与状态、taint。
  5. pkg/controller/garbagecollector/:基于 OwnerReference 的级联删除,最难也最精彩,依赖图 + finalizer。

leader election: staging/src/k8s.io/client-go/tools/leaderelection/。controller-manager 多实例时用 Lease 抢主,只有主才跑 controller。读 leaderelection.goacquire/renew 循环。

时间预估: 3-4 天。验收: 能对照 第30章 说清“原生 controller 写法“和“controller-runtime 写法“的对应关系(syncHandlerReconcileworkqueuePriorityQueueInformercache.Cache)。

34.9 第 5 层:kube-scheduler

入口: cmd/kube-scheduler/app/server.go -> Setup -> scheduler.New

核心模型(调度框架 / Scheduling Framework,v1.19+): 调度被拆成一串扩展点QueueSortFilterScoreBindReservePermitPreBindPostBind…),每个扩展点可插多个 plugin。这让调度器从“硬编码 predicate/priority“变成“plugin 组合“。

关键目录:

  • pkg/scheduler/scheduler.go:主循环 Run -> scheduleOne
  • pkg/scheduler/framework/:框架接口(FrameworkPlugin、各扩展点接口 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.goscheduleOne 拿到主流程,再挑一个最简单的 plugin(如 nodename)完整读它的 Filter/Score 实现,理解 plugin 如何注册和被调用。然后读队列流转(NextPodbackoff 逻辑)。

扩展实践: 写一个自定义调度 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

核心循环与机制:

  1. pkg/kubelet/kubelet.gosyncLoop:kubelet 的主循环,靠一组 channel 驱动(plegChsyncChlivenessManager.Updates()housekeepingCh…)。每个事件把对应 Pod 入队 podWorkers
  2. pkg/kubelet/pod_workers.gopodWorkers 保证每个 Pod 串行 syncPod,并发不同 Pod。读 UpdatePod
  3. pkg/kubelet/pleg/pleg.go:PLEG(Pod Lifecycle Event Generator)。它周期性从 CRI 查容器状态,对比上次快照,产生 PodLifecycleEvent 喂给 syncLoop。这是“容器实际状态变化感知到 kubelet“的关键。
  4. syncPodkubelet.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.gopkg/kubelet/cri/:CRI 接口调用(RuntimeServiceImageService)。

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.goipvs/proxier.go)。
  • pkg/proxy/iptables/proxier.gosyncProxyRules 把 Service->Endpoints 翻译成 iptables 链(KUBE-SERVICESKUBE-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.goCustomResourceDefinition 类型本身。
  • pkg/registry/apiextensions/crd/:CRD 资源的 strategy。
  • pkg/controller/crd/crd_finalizer_controllerestablishing_controller——CRD 创建后如何“建立“(生成对应的 REST handler)。
  • pkg/server/:CRD 资源如何被动态注册进 apiserver 的路由(CustomResourceDefinitions 一变,就为新 GVK 装一组 handler)。
  • validationconversion:基于 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/
  • apply3-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 HierarchyK8s 接口多,必须靠 IDE 找实现;Call Hierarchy 找调用链
grep / git grep按符号、GVK 字符串、func 签名定位
dlv(Delve)断点调试起一个本地 apiserver/kubelet,断点走 happy path,比纯看代码快十倍
*_test.go单测是最好的用法文档;构造输入看断言理解行为
读 e2e / 集成测试理解组件间真实交互,test/e2e/
看 KEPkeps/ 目录,卡在设计意图时回去翻提案
看官方文档 ResourcesAPI Conventionskubernetes/communitycontributors/devel/sig-architecture/ 有 API 约定
画图时序图(apiserver 链路)、状态机(Pod 状态、调度队列)、依赖图
固定 tag 读git checkout v1.36.x,别在主干读

一个高效读法:起本地单机集群 + 断点。kindminikube 起集群,把改过的组件以一定方式跑起来(或直接在测试里 kubelet.NewTestKubelet 之类构造一个),用 dlv attach,发一个请求/创建一个 Pod,断点单步。这是理解控制流最快的方式,远胜通读源码。

34.15 各组件源码阅读清单(速查表)

组件入口关键包/文件主循环/入口函数阅读重点预估时间
apimachineryapimachinery/pkg/runtime/pkg/apis/meta/v1/Scheme、Object、watch、序列化2 天
client-goclient-go/tools/cache/util/workqueue/Reflector.RunSharedInformer.RunInformer 调用链、workqueue3 天
kube-apiservercmd/kube-apiserver/app/server.goapiserver/pkg/server//registry//storage//admission/BuildHandlerChainStore.Create请求链路、admission、watch cache5-7 天
etcd 集成apiserver/pkg/storage/etcd3//cacher/store.UpdateCacher.dispatchEventRV 一致性、乐观并发、watch cache1-2 天
controller-managercmd/kube-controller-manager/app/pkg/controller/<name>/DeploymentController.syncDeployment原生 controller 模式、GC、leader election3-4 天
kube-schedulercmd/kube-scheduler/app/server.gopkg/scheduler/framework/framework/plugins/scheduleOne调度框架、plugin、三队列3-4 天
kubeletcmd/kubelet/app/server.gopkg/kubelet/kubelet.gopleg/pod_workers.gosyncLoopsyncPodPLEG、多 manager、CRI4-5 天
kube-proxycmd/kube-proxy/app/server.gopkg/proxy/iptables//ipvs/syncProxyRulesService->规则同步1-2 天
apiextensionsapiextensions-apiserver/pkg/registry/apiextensions/pkg/controller/crd/CRD establishCRD 动态注册、schema2 天
kubectlcmd/kubectl/kubectl/pkg/cmd/pkg/util/apply/apply 三方合并visitor、3-way merge1 天

34.16 进阶专题(读透主干后选方向)

  • API 机制深挖: resourceVersion 一致性模型、limit=continue 分页、list rv=0 缓存读、finalizer 与 GC、admission webhook、Server-Side Apply(字段所有权)。
  • 调度进阶: 调度框架 plugin 编写、gang scheduling(需 kube-scheduler 无法直接做,靠调度器扩展或 Volcano)、优先级与抢占(preemption)、descheduler
  • 存储: CSI 三阶段(Provision/Attach/Mount)、VolumeSnapshotInlineVolume、卷扩缩容。
  • 网络: CNI 插件链、NetworkPolicy 实现、Service Mesh 与 kube-proxy 的关系、Gateway API
  • 安全: RBAC(pkg/apis/rbac/ + authorizer)、Pod Security Admission(替代 PSP)、静态 pod 与节点安全、Secret 静态加密(kms/v2)。
  • 稳定性与性能: apiserver Panic/Timeout handler、MaxInFlightLimitAPIPriorityAndFairness(公平排队)、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 从读到写:配套实践

光读不写,理解停在表面。建议按难度递进做三件事:

  1. 写一个 controller(原生写法): 不用 controller-runtime,用裸 client-go 的 Informer + workqueue 写一个监听 ConfigMap 变化打日志的小控制器。对照第30章理解 controller-runtime 帮你省了什么。
  2. 写一个 Operator(controller-runtime): 参考第31章的完整示例,定义一个 CRD + Reconcile,部署到本地集群。
  3. 给上游提 PR:good first issue 入手;或改进测试文档。走完一次 CI(pull-kubernetes-* job)能让你对 K8s 工程体系有质的理解。贡献流程见 kubernetes/communitycontributors/guide/

34.19 版本与代码获取

  • 选哪个 release: 建议读最新稳定版(如 v1.36.x)主干;想理解演进,可对比一两个老版本(如 v1.20 前后的 apiserver 差异、v1.19 调度框架引入前后)。
  • 获取: git clone https://github.com/kubernetes/kubernetesgit 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.gomain 开始,一步一步走。