讲解 Kubernetes 源码是一个非常宏大的话题,因为它是一个拥有数百万行 Go 代码的巨型项目。直接深入某一个文件会让人迷失方向。

因此,我将采用一种从宏观到微观、从架构到实践的方式,为你提供一个清晰的源码阅读地图和导览。


第一部分:核心设计哲学与准备工作

在看代码之前,必须理解 Kubernetes 的核心设计思想,否则你会看不懂代码背后的“为什么”。

  1. 一切皆 API (API-Centric Design):Kubernetes 的核心是一个 RESTful API Server。所有的组件(kubelet, controller-manager, scheduler)都是 API Server 的客户端。它们通过 Watch/List API 监控资源变化,并通过 Create/Update/Delete API 修改资源状态。这是理解 K8s 的第一钥匙。

  2. 声明式 API 与最终一致性:你告诉 Kubernetes 你“想要”什么状态(spec字段),而不是“如何”达到这个状态。各个控制器(Controllers)负责工作,不断地将“当前状态”调整为“期望状态”。这个过程叫做Reconciliation Loop(调和循环)

  3. 控制器模式 (Controller Pattern):这是 K8s 的大脑。一个控制器通常只关注一种或几种资源。它会 Watch 相关的资源变化,然后执行相应的业务逻辑(比如,Deployment Controller 监听到 Deployment 创建,就会去创建 ReplicaSet)。

准备工作:

  • 语言:Kubernetes 主要用 Go 语言编写。你需要有扎实的 Go 基础。

  • 工具

    • 一个好的 IDE(如 GoLand 或 VS Code with Go extension)至关重要,它能帮你跳转定义、查找引用。

    • Git 和 GitHub 客户端。

    • grep, find 等命令行工具。

  • 源码获取:git clone https://github.com/kubernetes/kubernetes.git


第二部分:源码目录结构导览(The Map)

Kubernetes 的主仓库 kubernetes/kubernetes 是一个 Monorepo(单一代码库)。理解它的目录结构是第一步。

code Code

downloadcontent_copy

expand_less

    kubernetes/
├── api/             # 定义非核心版本的 API 对象(例如 v1beta1),最终会合并到 k8s.io/api
├── cmd/             # 所有组件的 main 函数入口,是程序的起点
│   ├── kube-apiserver/
│   ├── kube-controller-manager/
│   ├── kubelet/
│   ├── kube-proxy/
│   └── kube-scheduler/
├── pkg/             # 项目的核心逻辑代码,最值得花时间看的地方
│   ├── apis/        # 核心 API 对象的 Go 结构体定义 (v1/Pod, v1/Node 等)
│   ├── controller/  # 所有内置控制器的实现 (deployment, namespace, node 等)
│   ├── kubelet/     # Kubelet 的核心逻辑,包括 Pod 管理、CRI/CNI/CSI 交互等
│   ├── scheduler/   # 调度器的核心逻辑 (过滤、打分算法)
│   └── registry/    # API Server 与 etcd 交互的存储层抽象
├── staging/         # "稳定"且可被外部引用的代码库,是 K8s 解耦的成果
│   └── src/k8s.io/
│       ├── api/                # 核心 API 类型定义 (Pod, Node, etc.)
│       ├── apimachinery/       # API 相关的底层库 (runtime, scheme, watch)
│       ├── apiserver/          # 构建 API Server 的通用框架
│       ├── client-go/          # 官方的 Go 客户端库
│       └── ...                 # 还有很多其他独立库
├── vendor/          # 项目的 Go 依赖
├── hack/            # 各种构建、测试、发布的脚本
├── build/           # 构建相关的 Dockerfile 和脚本
└── test/            # 测试相关代码,特别是 e2e (端到端) 测试
  

关键解读:

  • 从 cmd/ 开始:任何一个组件的启动流程都从 cmd/ 目录下对应的 main.go 开始。这是阅读源码的绝佳入口。

  • 核心在 pkg/:绝大部分业务逻辑都在这里。比如你想知道 Deployment 的工作原理,就去 pkg/controller/deployment/。

  • staging/ 是宝藏:如果你想写一个 Operator 或者与 K8s API 交互的程序,那么 staging/src/k8s.io/ 下的 client-go, apimachinery, api 是你必须学习的库。K8s 自身也依赖这些库。


第三部分:实战演练:追踪一个 Pod 的创建过程

这是串联起所有知识点的最佳方式。让我们看看执行 kubectl apply -f my-pod.yaml 后,源码世界里发生了什么。

1. kubectl -> API Server
  • kubectl 是 client-go 的一个典型应用。它解析 YAML,构建成 Go 的 v1.Pod 结构体对象。

  • 通过 client-go 的 clientset,它向 API Server 发起一个 POST /api/v1/namespaces/{namespace}/pods 的 HTTP 请求。

2. API Server 的处理流程
  • 入口:cmd/kube-apiserver/apiserver.go 的 main() 函数。它会初始化并启动一个基于 generic-apiserver 框架的 HTTP 服务器。

  • 请求处理链:一个请求进来,会经过一连串的 HTTP Handler Chain

    1. Authentication (认证):验证用户是谁(例如,通过 TLS 证书、Bearer Token)。代码在 k8s.io/apiserver/pkg/authentication。

    2. Authorization (授权):验证用户有没有权限创建 Pod(例如,通过 RBAC)。代码在 k8s.io/apiserver/pkg/authorization。

    3. Admission Control (准入控制):在对象持久化之前,执行一系列检查和修改。比如 LimitRanger 检查资源限制,DefaultStorageClass 自动添加存储类。代码在 k8s.io/apiserver/pkg/admission。

  • 写入 etcd:通过所有检查后,API Server 会将 Pod 对象序列化成 JSON,然后通过 pkg/registry 层的代码存入 etcd。这时,Pod 的 status.phase 是 Pending。

3. Scheduler 的工作
  • 入口:cmd/kube-scheduler/scheduler.go

  • 核心逻辑:pkg/scheduler/scheduler.go 的 scheduleOne 方法。

    1. Watch:Scheduler 通过 client-go 的 Informer 机制 Watch 所有 spec.nodeName 为空的 Pod。

    2. 调度周期 (Scheduling Cycle)

      • Filtering (过滤):遍历所有 Node,过滤掉不满足 Pod 要求的节点(如资源不足、端口冲突、污点不匹配等)。相关代码在 pkg/scheduler/framework/plugins/。

      • Scoring (打分):为通过过滤的 Node 打分,选出最优的节点(如资源最空闲、镜像已存在等)。相关代码也在 pkg/scheduler/framework/plugins/。

    3. Bind:Scheduler 选出最佳 Node 后,并不是直接去操作 Kubelet,而是向 API Server 发起一个 Update 请求,将 Pod 对象的 spec.nodeName 字段更新为选中的节点名。

4. Kubelet 的工作
  • 入口:cmd/kubelet/kubelet.go

  • 核心逻辑:pkg/kubelet/kubelet.go

    1. Watch:每个节点上的 Kubelet 只 Watch 分配给自己的 Pod(通过 spec.nodeName 字段识别)。

    2. SyncLoop:Kubelet 内部有一个核心的同步循环(syncLoop),它会处理 Pod 的增删改。当它发现一个新的 Pod 被分配过来时,会调用 syncPod 方法。

    3. Pod Worker:syncPod 会为每个 Pod 启动一个 "worker",这个 worker 负责管理该 Pod 的整个生命周期。

    4. 与 CRI/CNI/CSI 交互:Kubelet 本身不直接创建容器。它遵循插件化设计:

      • CRI (Container Runtime Interface):调用容器运行时(如 containerd, CRI-O)去创建和管理容器。代码在 pkg/kubelet/cri。

      • CNI (Container Network Interface):调用网络插件(如 Calico, Flannel)为 Pod 配置网络。

      • CSI (Container Storage Interface):调用存储插件为 Pod 挂载存储卷。

    5. 更新状态:Kubelet 创建容器后,会定期向 API Server 汇报 Pod 的状态(status 字段),例如 IP 地址、容器状态等。最终将 status.phase 更新为 Running。

至此,一个 Pod 的创建流程在代码层面就完整了。


第四部分:核心数据结构与机制

要深入源码,必须理解几个 client-go 和 apimachinery 中定义的关键数据结构和模式。

  • Scheme (k8s.io/apimachinery/pkg/runtime/scheme.go)

    • 它是一个注册表,用于管理 API 对象的 Go 类型(v1.Pod)和它们的 GVK(Group, Version, Kind)之间的映射关系。序列化和反序列化都依赖它。

  • Informer/Lister/SharedInformerFactory (k8s.io/client-go/tools/cache/)

    • 这是所有控制器工作的基础。直接频繁地 List API Server 会造成巨大压力。

    • Informer 通过 Watch 机制,在本地维护一个特定资源(如 Pods)的全量缓存(Cache)。

    • 当资源发生变化时,Informer 会调用你注册的 EventHandler 函数(Add, Update, Delete)。

    • 控制器通过 Lister 从本地缓存中读取数据,极大地减轻了 API Server 的负担。

  • WorkQueue (k8s.io/client-go/util/workqueue/)

    • EventHandler 在收到事件后,通常不会立即处理,而是将对象的 namespace/name 作为一个 key 放入工作队列。

    • 控制器有一个或多个 worker goroutine,不断地从队列中取出 key,然后执行真正的调和逻辑(Reconcile)

    • 这样做的好处是:

      • 解耦:事件发现和事件处理解耦。

      • 合并事件:短时间内对同一个对象的多次变更,在队列里只会有一个 key,防止重复处理。

      • 重试:如果处理失败,可以把 key 重新放回队列,稍后重试。

一个典型的控制器伪代码结构:

code Go

downloadcontent_copy

expand_less

IGNORE_WHEN_COPYING_START

IGNORE_WHEN_COPYING_END

    // 1. 创建 InformerFactory 和具体的 Informer
informerFactory := informers.NewSharedInformerFactory(client, resyncPeriod)
podInformer := informerFactory.Core().V1().Pods()

// 2. 创建 WorkQueue
queue := workqueue.NewRateLimitingQueue(...)

// 3. 注册事件处理函数
podInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
    AddFunc: func(obj interface{}) {
        key, _ := cache.MetaNamespaceKeyFunc(obj)
        queue.Add(key) // 将 key 放入队列
    },
    // ... UpdateFunc, DeleteFunc
})

// 4. 启动 Informer
go informerFactory.Start(stopCh)

// 5. 启动 worker goroutine
for i := 0; i < threadiness; i++ {
    go wait.Until(runWorker, time.Second, stopCh)
}

// 6. worker 逻辑
func runWorker() {
    for processNextWorkItem() {
    }
}

func processNextWorkItem() bool {
    key, quit := queue.Get()
    // ...
    defer queue.Done(key)

    // 核心调和逻辑
    err := syncHandler(key.(string))
    if err != nil {
        // 处理错误,可能需要重试
        queue.AddRateLimited(key)
    } else {
        queue.Forget(key)
    }
    return true
}

// 7. 真正的业务逻辑
func syncHandler(key string) error {
    namespace, name, _ := cache.SplitMetaNamespaceKey(key)
    
    // 从 Informer 的本地缓存中获取对象
    pod, err := podLister.Pods(namespace).Get(name)
    
    // ... 实现你的业务逻辑 ...
    
    return nil
}
  

几乎所有 Kubernetes 内置控制器都遵循这个模式。


第五部分:如何开始你的源码阅读之旅

  1. 从一个简单组件的 main 函数开始:选择 kube-scheduler 或 kube-controller-manager。跟着 NewCommand() -> Run() 一路往下看,理解它的初始化流程、如何创建 Informer、如何启动 worker。

  2. 选择一个简单的控制器:不要一开始就挑战 deployment-controller 或 statefulset-controller。可以从 namespace-controller (pkg/controller/namespace/) 或 node-controller (pkg/controller/node/) 开始。它们的逻辑相对简单直白。

  3. 带着问题去读:比如“一个 Pod 的 terminationGracePeriodSeconds 是如何实现的?”然后用 IDE 全局搜索这个字段,找到 Kubelet 中处理 Pod 终止的逻辑。

  4. 善用调试器:在本地编译并运行一个组件(比如 kube-scheduler),然后用 Go 的调试器(如 Delve)挂载上去,设置断点,单步跟踪,观察变量变化。这是最强大的学习工具。

  5. 阅读社区文档和博客:有大量的开发者分享了他们的源码分析文章,可以作为参考。同时,kubernetes/community 仓库里有各个 SIG(特别兴趣小组)的设计文档,可以帮助你理解功能背后的设计考量。

总结:
Kubernetes 源码是一座宝库,但也是一个迷宫。不要期望一口气读完。最好的方法是:
理解核心架构 -> 熟悉目录结构 -> 追踪一个核心流程(如Pod创建)-> 深入一个简单的控制器 -> 带着具体问题去探索。

希望这份导览能为你打开探索 Kubernetes 源码世界的大门。祝你旅途愉快!

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐