惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

腾讯CDC
Hacker News: Ask HN
Hacker News: Ask HN
S
Securelist
Security Latest
Security Latest
S
Schneier on Security
T
Threat Research - Cisco Blogs
Latest news
Latest news
Cyberwarzone
Cyberwarzone
A
Arctic Wolf
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
NISL@THU
NISL@THU
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
I
Intezer
T
The Exploit Database - CXSecurity.com
N
News and Events Feed by Topic
Simon Willison's Weblog
Simon Willison's Weblog
T
Tor Project blog
Blog — PlanetScale
Blog — PlanetScale
C
Cyber Attacks, Cyber Crime and Cyber Security
C
CERT Recently Published Vulnerability Notes
The Hacker News
The Hacker News
月光博客
月光博客
WordPress大学
WordPress大学
博客园 - 叶小钗
Hugging Face - Blog
Hugging Face - Blog
美团技术团队
量子位
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
C
Cisco Blogs
博客园 - 三生石上(FineUI控件)
Google DeepMind News
Google DeepMind News
Project Zero
Project Zero
Webroot Blog
Webroot Blog
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
Application and Cybersecurity Blog
Application and Cybersecurity Blog
云风的 BLOG
云风的 BLOG
L
LINUX DO - 最新话题
Schneier on Security
Schneier on Security
Engineering at Meta
Engineering at Meta
www.infosecurity-magazine.com
www.infosecurity-magazine.com
aimingoo的专栏
aimingoo的专栏
D
Docker
有赞技术团队
有赞技术团队
Google DeepMind News
Google DeepMind News
宝玉的分享
宝玉的分享
T
Troy Hunt's Blog
L
Lohrmann on Cybersecurity
T
The Blog of Author Tim Ferriss
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
L
LangChain Blog

博客园 - 左扬

Kubernetes 编程 / Operator 专题【左扬精讲】—— Client-go 源代码分析:生产级 Controller 实践:并发安全、资源清理与高可用设计 Kubernetes 编程 / Operator 专题【左扬精讲】—— Client-go 源代码分析: Controller 调试与诊断工具:从日志分析到问题定位 Kubernetes 编程 / Operator 专题【左扬精讲】—— Client-go 源代码分析:DynamicClient 操作 CRD:无需代码生成的动态操作 Kubernetes 编程 / Operator 专题【左扬精讲】—— Client-go 源代码分析:控制器与 APIServer 完整交互流程:从 Watch 到缓存同步 Kubernetes 编程 / Operator 专题【左扬精讲】—— Client-go 源代码分析:错误处理与重试机制:WorkQueue 限速器详解 Kubernetes 编程 / Operator 专题【左扬精讲】—— Client-go 源代码分析:Leader 选举机制:高可用控制器的必备技能 Kubernetes 编程 / Operator 专题【左扬精讲】—— Client-go 源代码分析:Controller 开发模式完整实战 Kubernetes 编程 / Operator 专题【左扬精讲】—— Client-go 源代码分析:SharedInformerFactory 与等待缓存同步 Kubernetes 编程 / Operator 专题【左扬精讲】—— Client-go 源代码分析:从认证配置到 Deployment 操作 Kubernetes 编程 / Operator 专题【左扬精讲】—— Client-go 源代码分析:Informer 源码深度解析:从底层原理到实战应用 Kubernetes 编程 / Operator 专题【左扬精讲】—— Client-go 源代码分析:Reflector 源码深度解析 Kubernetes 编程 / Operator 专题【左扬精讲】—— Client-go 源代码分析:ListWatcher 源码深度解析 Kubernetes 编程 / Operator 专题【左扬精讲】—— Client-go 源代码分析:Indexer 与 ThreadSafeStore 核心原理与源码深度剖析 Kubernetes 编程 / Operator 专题【左扬精讲】—— Client-go 源代码分析:DeltaFIFO 核心原理与源码深度剖析 Kubernetes 编程 / Operator 专题【左扬精讲】—— Client-go 源代码分析:workqueue 核心原理与实战 Kubernetes 编程 / Operator 专题【左扬精讲】—— runtime.Codec 资源编解码:serializer 与 codec 差异、编解码数据结构、codec 核心调用链路 Kubernetes 编程 / Operator 专题【左扬精讲】—— Scheme 资源注册机制全解 Kubernetes 编程 / Operator 专题【左扬精讲】—— Kubernetes 自定义资源的内部版本与外部版本:从源码看版本定义机制 Kubernetes 编程 / Operator 专题【左扬精讲】—— Kubernetes 1.36.1 核心 API 数据结构全解 Kubernetes 编程 / Operator 专题【左扬精讲】—— Kubernetes 构建过程 【AIOPS】一文读懂LLM【左扬精讲】:从诞生到普及,解锁大语言模型的核心密码 【AIOPS】AI Agent 专题【左扬精讲】核心功能篇:MCP-VictoriaMetrics Hooks 源码精讲:Hooks 可观测性的无侵入式实现 【AIOPS】AI Agent 专题【左扬精讲】核心功能篇:MCP-VictoriaMetrics Golang 配置解析源码精讲 ——SRE 自定义 Agent 核心技巧 【AIOPS】AI Agent 专题【左扬精讲】核心功能篇:MCP-VictoriaMetrics Golang 并发模型解析 ——SRE 应对高并发采集的调优思路 【AIOPS】AI Agent 专题【左扬精讲】基础架构篇:MCP-VictoriaMetrics Golang 源码整体架构拆解 ——SRE 必懂的核心模块与数据流 OpenTelemetry 开发实战【左扬精讲】—— 云原生可观测体系构建与分布式追踪二次开发 Kubernetes 编程 / Operator 专题【左扬精讲】—— Operator 开发实战项目 7 —— 基于流量预测模型的智能弹性扩缩容 Operator 实战(AIOps 模型训练与智能扩容(下篇)—— 预测式弹性扩缩容 Operator 落地实现) Kubernetes 编程 / Operator 专题【左扬精讲】—— Operator 开发实战项目 7 —— 基于流量预测模型的智能弹性扩缩容 Operator 实战(AIOps 模型训练与智能扩容(上篇)—— 时序预测模型构建与离线训练) Kubernetes 编程 / Operator 专题【左扬精讲】—— Operator 开发实战项目 6 —— 基于运维专家知识库的智能故障诊断与排查 Operator 实战 Kubernetes 编程 / Operator 专题【左扬精讲】—— Operator 开发实战项目 5 —— 基于大语言模型(LLM)的实时日志流智能监测 Operator 实现 Kubernetes 编程 / Operator 专题【左扬精讲】—— Operator 开发实战项目 4 —— 基于 Operator 实现大模型私有化部署与管理 Kubernetes 编程 / Operator 专题【左扬精讲】—— Operator 开发实战项目 3 —— 面向 AI / 算力调度场景:GPU 竞价实例资源池统一调度管理 Operator 开发 Kubernetes编程 / Operator专题【左扬精讲】—— Operator 开发实战项目 2 —— 面向零售 / 电商潮汐流量难题:多云多集群数据中心级全链路弹性伸缩 DataCenter Scaler Operator 从 0 到 1 全链路开发 Kubernetes编程 / Operator专题【左扬精讲】—— 深入理解Kubebuilder注解:为什么Operator开发离不开这些特殊注释 Kubernetes编程 / Operator专题【左扬精讲】—— Operator 开发实战项目1 —— Applicaion Operator(通用应用生命周期管理 Operator 实战) Pod 镜像拉取失败?kubectl edit pods修改镜像地址的底层原理与实操 (该方法仅为临时应急方案,并非长期解决方案) Kubernetes编程/Operator专题精讲—— 理解控制器模式 —— 控制器模式的核心原理与实现逻辑(从原理到实践) 【AIOPS】AI Agent 专题【左扬精讲】模型微调实战:一站式平台 LLaMA-Factory 【AIOPS】AI Agent 专题【左扬精讲】基于 k8s+vLLM+Ray 分布式部署全指南:架构设计、资源调度与性能优化 【AIOPS】AI Agent专题【左扬精讲】非量化版DeepSeek分布式部署全指南:精度保障、显存规划与Ollama/vLLM选型 【AIOPS】AI Agent 专题【左扬精讲】零开发框架实现 ReAct Agent(Go SRE友好)
Kubernetes 编程 / Operator 专题【左扬精讲】—— Client-go 源代码分析:版本对应、架构组件与组件关系
左扬 · 2026-06-13 · via 博客园 - 左扬

Kubernetes 编程 / Operator 专题【左扬精讲】—— Client-go 源代码分析:版本对应、架构组件与组件关系

当我们刚开始学习 Kubernetes Operator 开发时,往往会被 client-go 里一大堆概念砸得晕头转向:Clientset、DynamicClient、RESTClient 有什么区别?SharedInformer、SharedIndexInformer、SharedInformerFactory 三个" Informatin"到底是什么关系?为什么 Reflector 要和 DeltaFIFO 配合使用?更重要的是,client-go 的版本号和 Kubernetes 的版本号到底怎么对应?用错了版本会有什么后果?这些问题如果不搞清楚,后面写代码的时候就会处处踩坑。这篇文章我们就从源码出发,把这些问题全部讲清楚。

Kubernetes client-go Operator v1.36.1 Go 1.26

🔓 学习重点提示  — 建议先通读全文,再重点回顾标注内容

★ 重点掌握(必须)
   • 版本对应关系:client-go v0.32.x 对应 Kubernetes 1.32.x,这是外部项目引用的基础
   • 三种客户端差异:Clientset(typed、强类型)、DynamicClient(unstructured、动态类型)、RESTClient(底层 HTTP)
   • Informer 体系:SharedInformer → SharedIndexInformer → SharedInformerFactory 的继承关系和职责分工
   • 数据流:Reflector → DeltaFIFO → Indexer → 事件处理器 的完整数据流动路径

☆ 次重点(了解即可)
   • CachedDiscoveryClient 和 MemCacheClient 的缓存机制
   • WorkQueue 的限速和重试机制


目录

  1. 一、版本对应关系:client-go 和 Kubernetes 的版本号之谜
  2. 二、client-go 源代码目录结构
  3. 三、三种客户端:Clientset、DynamicClient、RESTClient 的差异与适用场景
  4. 四、Informer 体系:SharedInformer、SharedIndexInformer、SharedInformerFactory
  5. 五、核心数据结构:Reflector、DeltaFIFO、Indexer、WorkQueue
  6. 六、组件关系图:完整的数据流与调用链
  7. 七、常见问题 FAQ

一、版本对应关系:client-go 和 Kubernetes 的版本号之谜

很多初学者被 client-go 的版本号搞糊涂了——Kubernetes 都发布到 1.36 了,为什么 client-go 的版本号才到 0.32?它们之间到底是什么关系?

1.1 版本对应规则:client-go 版本号 = Kubernetes 主版本号 - 4

这个规则的背后有一个明确的设计决策:Kubernetes 官方保证"主版本号 - 4"的 client-go 版本能够兼容当前 Kubernetes 版本。也就是说,Kubernetes 1.36 对应 client-go 0.32,Kubernetes 1.35 对应 client-go 0.31,以此类推。为什么要减 4?因为 Kubernetes 采用"主版本号减 4"的策略来给外部依赖留出兼容空间。

我们来从源码中验证这个规则。先看 Kubernetes 1.36.1 的 go.mod 文件:

// staging/src/k8s.io/client-go/go.mod

module k8s.io/client-go

go 1.26.0

require (
    k8s.io/api v0.0.0
    k8s.io/apimachinery v0.0.0
    k8s.io/klog/v2 v2.140.0
    ...
)

replace (
    k8s.io/api => ../api
    k8s.io/apimachinery => ../apimachinery
    k8s.io/streaming => ../streaming
)

在 Kubernetes 源码仓库内部,client-go、api、apimachinery 这些 staging 包都通过 replace 指令指向本地目录,所以版本号显示为 v0.0.0。但当我们把这些包单独发布(publish)到 GitHub 时,版本号就会遵循 "Kubernetes 主版本号 - 4" 的规则。

1.2 版本对应表

Kubernetes 版本client-go 版本适用场景
v1.36 v0.32 最新 Kubernetes Operator 开发
v1.35 v0.31 生产环境推荐
v1.29 v0.29 稳定版本
v1.28 v0.28 长期维护版本
v1.26 v0.26 经典版本

🌟 实用技巧
在 go.mod 中使用 client-go 时,推荐写法是:
require k8s.io/client-go v0.32.0
注意使用精确版本号(v0.32.0),而不是 v0.32.x 或 v0.32/latest,这样能确保每次构建的可重复性。

1.3 版本不匹配的后果

如果你的 Operator 使用了 client-go v0.28,但连接的是 Kubernetes 1.35 集群,会发生什么?主要有三类问题:

  • API 类型不匹配:新版本 K8s 新增的字段在旧版 client-go 中没有定义,序列化/反序列化会失败
  • Watch 版本不支持:K8s 1.35 可能启用了某些新的 Watch 机制,旧版 client-go 无法正确处理
  • 元数据缺失:新版本对象可能缺少旧版 client-go 认识的某些注解(Annotations)和标签(Labels)

反过来,如果用 client-go v0.32 连接 Kubernetes 1.29 集群,一般情况下是兼容的——client-go 通常能支持"更旧"的 Kubernetes 版本。但如果用到新版本独有的 API 字段,还是会报错。所以最安全的做法是让 client-go 版本和集群版本保持一致,或者让 client-go 版本略高于集群版本。


二、client-go 源代码目录结构

在 Kubernetes 源码仓库中,client-go 位于 staging/src/k8s.io/client-go 目录。理解这个目录结构,是后续理解各组件关系的基础。

staging/src/k8s.io/client-go/
├── kubernetes/                 # typed 客户端集合(Clientset)
│   ├── typed/                # 按 API 组和版本分类的强类型客户端
│   │   ├── core/v1/         # Pod、Service、Namespace 等核心资源
│   │   ├── apps/v1/         # Deployment、StatefulSet、DaemonSet
│   │   ├── networking/v1/    # Ingress、NetworkPolicy
│   │   └── ...              # 其他 API 组
│   └── clientset.go         # Interface 接口定义,组合所有 typed 客户端
├── dynamic/                  # DynamicClient(动态客户端)
│   ├── simple.go            # DynamicClient 主实现
│   └── dynamicinformer/     # DynamicInformer 相关
├── rest/                    # RESTClient(底层 REST 客户端)
│   ├── config.go           # Config 配置结构体
│   ├── client.go           # RESTClient 实现
│   └── request.go          # 请求构建器
├── discovery/               # DiscoveryClient(服务发现)
│   ├── client.go           # DiscoveryClient 主实现
│   └── cached/             # 缓存实现(磁盘缓存、内存缓存)
├── informers/               # SharedInformerFactory + 各种资源 Informer
│   ├── factory.go          # SharedInformerFactory 主实现
│   ├── generic.go          # GenericInformer 通用接口
│   └── internalinterfaces/  # 工厂内部接口
├── listers/                 # Lister(本地缓存读取器)
│   ├── core/v1/            # 按资源类型分类
│   ├── apps/v1/
│   └── ...
├── tools/                   # 核心工具:Informer、Reflector、Cache 等
│   ├── cache/              # 缓存层核心实现
│   │   ├── shared_informer.go      # SharedInformer 接口
│   │   ├── shared_index_informer.go # SharedIndexInformer
│   │   ├── reflector.go             # Reflector:ListAndWatch
│   │   ├── delta_fifo.go            # DeltaFIFO:增量队列
│   │   ├── fifo.go                  # FIFO 队列
│   │   ├── index.go                 # Indexer + ThreadSafeStore
│   │   └── store.go                 # Store 接口
│   ├── clientcmd/           # kubeconfig 解析
│   └── record/              # 事件记录器
├── util/                    # 工具函数
│   ├── workqueue/          # 工作队列(限速、重试)
│   ├── apply/              # Server-Side Apply
│   └── flowcontrol/        # 限速器
├── scale/                  # Scale 子资源客户端
├── openapi/                # OpenAPI 客户端
├── plugin/                 # 插件(client auth exec 等)
└── examples/              # 示例代码

这个目录结构本身就透露了很多信息。比如,kubernetes/typed 目录下为什么有那么多子目录?因为 Kubernetes 的 API 就是按 API 组(Group)和版本(Version)组织的。apps/v1 里面有 Deployment、StatefulSet、DaemonSet,它们都属于 apps 这个 API 组,版本是 v1。而 tools/cache 目录下集中了所有缓存相关的核心实现,这正是 Informer 机制的心脏。


三、三种客户端:Clientset、DynamicClient、RESTClient 的差异与适用场景

这是最容易混淆的知识点。client-go 提供了三种客户端,它们的抽象层次从高到低分别是:Clientset(最高)、DynamicClient(中等)、RESTClient(最低)。理解它们之间的差异,是选择正确客户端的基础。

3.1 Clientset(Typed Client,强类型客户端)

Clientset 是抽象层次最高的客户端,它的每一个方法都对应一个具体的 Kubernetes 资源类型。我们先看它的 Interface 接口定义:

// staging/src/k8s.io/client-go/kubernetes/clientset.go

// kubernetes.Interface 是所有 Kubernetes 客户端的顶层接口
type Interface interface {
    Discovery() discovery.DiscoveryInterface
    AdmissionregistrationV1() admissionregistrationv1.AdmissionregistrationV1Interface
    AppsV1() appsv1.AppsV1Interface
    AutoscalingV1() autoscalingv1.AutoscalingV1Interface
    CoreV1() corev1.CoreV1Interface
    NetworkingV1() networkingv1.NetworkingV1Interface
    PolicyV1() policyv1.PolicyV1Interface
    RbacV1() rbacv1.RbacV1Interface
    // ... 其他 API 组
}

这个接口的每个方法都返回一个对应 API 组的客户端接口。比如 CoreV1() 返回 CoreV1Interface,里面包含了 Pods()、Services()、Namespaces() 等方法。以 Pods() 为例,它返回 PodInterface,PodInterface 里定义了 Create、Update、Delete、Get、List、Watch 等方法——每一个方法签名都接收或返回强类型的 Pod 结构体。

// staging/src/k8s.io/client-go/kubernetes/typed/core/v1/interface.go

// CoreV1Interface 是 core/v1 API 组的客户端接口
type CoreV1Interface interface {
    RESTClient() rest.Interface
    Pods(namespace string) PodInterface
    PodTemplates(namespace string) PodTemplateInterface
    Services(namespace string) ServiceInterface
    Namespaces() NamespaceInterface
    Nodes() NodeInterface
    Secrets(namespace string) SecretInterface
    ConfigMaps(namespace string) ConfigMapInterface
    // ... 更多资源
}

使用 Clientset 的典型场景是:你的代码需要操作已知的、固定的 Kubernetes 资源类型,比如 Deployment、Pod、Service。在这种情况下,Clientset 提供了最好的类型安全——编译器会在编译期就检查你是否传入了正确的参数、是否正确处理了返回值。

3.2 DynamicClient(动态客户端)

DynamicClient 是为操作 CRD(Custom Resource Definition,自定义资源)设计的。它的核心特点是"动态"——不需要在编译期知道你要操作的是什么资源类型,运行期通过 GVR(GroupVersionResource)来指定。

// staging/src/k8s.io/client-go/dynamic/simple.go

// DynamicClient 内部持有的是一个通用的 REST Interface
type DynamicClient struct {
    client rest.Interface
}

var _ Interface = &DynamicClient{}

// Resource 方法接收一个 GroupVersionResource,返回可操作该资源的客户端
func (c *DynamicClient) Resource(resource schema.GroupVersionResource) NamespaceableResourceInterface {
    return &dynamicResourceClient{client: c, resource: resource}
}

// dynamicResourceClient 持有了资源的 GVR 信息
type dynamicResourceClient struct {
    client    *DynamicClient
    namespace string
    resource  schema.GroupVersionResource
}

注意看,dynamicResourceClient 里的 Create、Update、Delete 等方法,接收和返回的类型都是 unstructured.Unstructured——这是一个通用的 map[string]interface{} 结构,所有的 K8s 对象都可以用它来表示。这就是 DynamicClient 的核心设计:用通用的数据结构,承载任意的资源类型。

3.3 RESTClient(底层客户端)

RESTClient 是最底层的客户端,它本质上就是一个 HTTP 客户端,提供了 Get()、Post()、Put()、Patch()、Delete() 等方法,构建和发送 HTTP 请求到 API Server。

// staging/src/k8s.io/client-go/rest/config.go

// Config 保存了连接 API Server 所需的所有配置信息
type Config struct {
    Host string              // API Server 地址,如 "https://192.168.1.10:6443"
    APIPath string          // API 路径,如 "/api" 或 "/apis/apps/v1"
    ContentConfig ContentConfig  // 内容配置(序列化相关)
    Username string          // Basic Auth 用户名
    Password string          // Basic Auth 密码
    BearerToken string      // Bearer Token
    TLSClientConfig         // TLS 配置
    UserAgent string        // User-Agent 头
    // ... 更多字段
}

RESTClient 的使用方式非常底层,你需要手动构建 URL、设置参数、处理响应。通常不推荐直接使用 RESTClient,除非你有非常特殊的需求,比如需要精确控制 HTTP 请求的每个细节。

3.4 三种客户端的对比总结

对比维度ClientsetDynamicClientRESTClient
类型安全 ★★★★★ 编译期检查 ★★ 运行期检查 ★ 无类型检查
适用场景 内置资源(Pod/Svc/Deploy) CRD / 未知类型 底层 HTTP 控制
使用难度 简单 中等 复杂
代码提示 完整(IDE 自动补全) 有限(需查文档) 无(手写参数)
典型使用 Operator 开发 通用控制器 kubectl 内部

💡 提示
实际开发中,最常见的组合是:Clientset 用于操作内置资源(Deployment、Pod),DynamicClient 用于操作 CRD 自定义资源。RESTClient 基本上是 client-go 内部的实现细节,开发者很少直接用到。


四、Informer 体系:SharedInformer、SharedIndexInformer、SharedInformerFactory

Informer 是 Kubernetes 控制器开发中最核心的概念。理解它的体系结构,是写出高效控制器的关键。很多初学者分不清 SharedInformer、SharedIndexInformer、SharedInformerFactory 之间的关系,这一节我们从源码出发,把它们讲清楚。

4.1 SharedInformer(共享事件监听器的基础接口)

SharedInformer 是整个体系的顶层接口,它定义了"事件监听器"的基本行为。先看接口定义:

// staging/src/k8s.io/client-go/tools/cache/shared_informer.go(行 144-220)

// SharedInformer 提供最终一致的方式来追踪某个资源集合的变化
// 关键保证:
// 1. 本地缓存最终会与权威状态一致
// 2. 缓存中对象的 key 格式:namespace/name(命名空间资源)或 name(集群级资源)
// 3. 对同一 informer 添加的多个事件处理器,共享同一份缓存
type SharedInformer interface {
    // AddEventHandler 添加一个事件处理器,接收 Added/Updated/Deleted 事件
    // 返回一个 registration handle,可用于移除处理器
    AddEventHandler(handler ResourceEventHandler) (ResourceEventHandlerRegistration, error)

    // AddEventHandlerWithResyncPeriod 添加带重同步周期的事件处理器
    // resyncPeriod=0 表示不重同步
    AddEventHandlerWithResyncPeriod(handler ResourceEventHandler, resyncPeriod time.Duration) (ResourceEventHandlerRegistration, error)

    // GetStore 返回本地缓存(Store 接口)
    GetStore() Store

    // Run 启动 informer,stopCh 关闭时停止
    Run(stopCh

SharedInformer 的核心设计理念是"共享"(Shared)。在一个复杂的控制器中,可能有多个组件都需要监听同一个资源的变化。如果没有共享机制,每个组件都会独立地向 API Server 发起 List + Watch 请求,造成大量重复流量。SharedInformer 确保所有的事件处理器共享同一份缓存、同一份到 API Server 的 Watch 连接。

4.2 SharedIndexInformer(带索引的共享监听器)

SharedIndexInformer 在 SharedInformer 的基础上增加了索引能力。我们看它的接口定义:

// staging/src/k8s.io/client-go/tools/cache/shared_informer.go(行 284-288)

// SharedIndexInformer 在 SharedInformer 基础上增加了索引能力
// 索引可以加速按标签或字段查询对象的速度
type SharedIndexInformer interface {
    SharedInformer
    // AddIndexers 添加自定义索引函数
    AddIndexers(indexers Indexers) error
    // GetIndexer 返回索引器,用于按标签或字段快速查找对象
    GetIndexer() Indexer
}

两者的继承关系非常清晰:SharedIndexInformer extends SharedInformer。也就是说,所有的 SharedIndexInformer 都是 SharedInformer,但反过来不一定成立——普通的 SharedInformer 没有索引能力。

4.3 SharedInformerFactory(工厂模式的生产者)

SharedInformerFactory 是工厂类,负责创建和管理多个 SharedIndexInformer 实例。它的设计目标是:在同一个进程内,同一种资源类型只创建一个 Informer 实例,所有需要监听该资源的组件都使用这个共享的 Informer。

// staging/src/k8s.io/client-go/informers/factory.go(行 125-140)

// SharedInformerFactory 主实现
type sharedInformerFactory struct {
    client           clientset.Interface     // Kubernetes 客户端
    namespace        string                  // 监控的命名空间(v1.NamespaceAll 表示所有)
    defaultResync    time.Duration           // 默认重同步周期
    informers        map[reflect.Type]cache.SharedIndexInformer  // 按类型缓存的 informer
    startedInformers map[reflect.Type]bool   // 已启动的 informer
    customResync     map[reflect.Type]time.Duration  // 自定义重同步周期
}

// 工厂的核心方法:根据对象类型获取或创建 informer
func (f *sharedInformerFactory) InformerFor(obj runtime.Object, newFunc internalinterfaces.NewInformerFunc) cache.SharedIndexInformer {
    f.lock.Lock()
    defer f.lock.Unlock()

    informerType := reflect.TypeOf(obj)
    informer, exists := f.informers[informerType]
    if exists {
        return informer  // 命中缓存,直接返回
    }

    // 不存在则创建新的
    resyncPeriod, exists := f.customResync[informerType]
    if !exists {
        resyncPeriod = f.defaultResync
    }

    informer = newFunc(f.client, resyncPeriod)
    f.informers[informerType] = informer
    return informer
}

SharedInformerFactory 的 InformerFor 方法就是"单例模式"的实现。第一次请求某个资源的 Informer 时,它会创建新实例并缓存起来;后续请求直接返回缓存的实例。这就保证了同一进程内同一资源只有一个 Informer。

4.4 三者的关系总结

┌─────────────────────────────────────────────────────────────┐
│                  SharedInformerFactory                        │
│  角色:工厂模式,生产和管理 SharedIndexInformer 实例          │
│  关键方法:                                                   │
│    - InformerFor(obj, newFunc) -> SharedIndexInformer        │
│    - Start(stopCh) -> 启动所有 informers                    │
│    - WaitForCacheSync(stopCh) -> 等待缓存同步完成             │
└───────────────────────┬─────────────────────────────────────┘
                        │ 创建/获取
                        ▼
┌─────────────────────────────────────────────────────────────┐
│                   SharedIndexInformer                         │
│  角色:带索引的共享事件监听器                                 │
│  继承自:SharedInformer                                       │
│  扩展能力:                                                   │
│    + AddIndexers() 添加自定义索引                            │
│    + GetIndexer() 获取索引器                                  │
│  内部组件:                                                   │
│    - Indexer(本地缓存,存储所有对象)                       │
│    - Controller(控制循环,驱动数据流动)                     │
│    - Processor(事件分发,通知所有事件处理器)                 │
└───────────────────────┬─────────────────────────────────────┘
                        │ 继承
                        ▼
┌─────────────────────────────────────────────────────────────┐
│                    SharedInformer                             │
│  角色:共享事件监听器的基础接口                              │
│  核心职责:                                                  │
│    - 管理事件处理器(AddEventHandler)                       │
│    - 提供本地缓存(GetStore)                                 │
│    - 控制生命周期(Run/HasSynced)                            │
└─────────────────────────────────────────────────────────────┘

五、核心数据结构:Reflector、DeltaFIFO、Indexer、WorkQueue

Informer 背后的四大核心组件:Reflector 负责从 API Server 拉取数据,DeltaFIFO 负责增量事件的排序和管理,Indexer 负责本地缓存的存储和索引,WorkQueue 负责接收事件后的去重和限速处理。这四个组件串起来,就构成了完整的 Informer 数据流。

5.1 Reflector(List + Watch 的封装)

Reflector 是整个 Informer 体系的数据源。它封装了"先 List 再 Watch"的逻辑,持续不断地从 API Server 获取最新数据。我们看它的核心结构:

// staging/src/k8s.io/client-go/tools/cache/reflector.go(行 105-171)

// Reflector 监听指定资源的所有变化,并反映到给定的 Store 中
type Reflector struct {
    logger klog.Logger
    name string  // 用于日志标识,如 "deployment-controller"

    // expectedType 是期望放入 store 的对象类型
    expectedType reflect.Type

    // store 是目标 store,通常是 DeltaFIFO
    store ReflectorStore

    // listerWatcher 负责执行 List 和 Watch 操作
    listerWatcher ListerWatcherWithContext

    // resyncPeriod 是重同步周期
    resyncPeriod time.Duration

    // lastSyncResourceVersion 是最后一次同步的资源版本(用于 Watch)
    lastSyncResourceVersion string

    // useWatchList 是否使用 WatchList 流式拉取(更高效)
    useWatchList bool

    // paginatedResult 记录 List 是否支持分页
    paginatedResult bool
}

Reflector 的核心方法是 ListAndWatch,它的工作流程是:

  1. 1List 阶段:首次启动时,发起一次全量 LIST 请求,获取所有资源对象,用返回的 ResourceVersion 初始化 Watch
  2. 2Watch 阶段:使用 List 得到的 ResourceVersion 发起 Watch 请求,持续接收增量变化事件
  3. 3重试逻辑:Watch 因网络问题断开时,自动 backoff 重连,ResourceVersion 过期时会重新 LIST
  4. 4写入 Store:每个事件(Added/Modified/Deleted/Bookmark)都通过 store 的 Add/Update/Delete 方法写入 DeltaFIFO

5.2 DeltaFIFO(增量事件的先进先出队列)

DeltaFIFO 是 Informer 体系中最独特的数据结构。它的名字里"FIFO"表示先进先出队列,"Delta"表示它存储的不是完整对象,而是对象的变化(增量)。

// staging/src/k8s.io/client-go/tools/cache/delta_fifo.go

// DeltaType 表示变化的类型
type DeltaType string

const (
    Added   DeltaType = "Added"     // 对象被添加
    Updated DeltaType = "Updated"   // 对象被更新
    Deleted DeltaType = "Deleted"   // 对象被删除
    Replaced DeltaType = "Replaced" // 全量替换(来自 Replace() 调用)
    Sync DeltaType = "Sync"        // 同步(历史原因,行为同 Replaced)
)

// Delta 包含一个变化类型和变化的对象
type Delta struct {
    Type   DeltaType  // 变化的类型
    Object interface{} // 对象本身或 DeletedFinalStateUnknown
}

// Deltas 是某个对象的多个变化的列表(按时间顺序)
type Deltas []Delta

// DeltaFIFO 维护了每个对象的 Deltas 列表
type DeltaFIFO struct {
    lock sync.RWMutex
    cond sync.Cond
    items map[string]Deltas  // key -> Deltas(该对象的所有变化)
    queue []string           // FIFO 顺序
    knownObjects KeyListerGetter  // 用于判断是否是新增
    // ...
}

DeltaFIFO 的关键特性是"去重":同一个对象连续发生多次变化,只会保留最后一次。比如某个 Pod 在 1 秒内被更新了 10 次,DeltaFIFO 只会保留最后一次的 Updated 事件,之前 9 次会被丢弃。这样做的好处是避免事件处理器处理大量无意义的重复事件。

5.3 Indexer(本地缓存存储)

Indexer 是 SharedIndexInformer 的本地缓存层,负责存储所有已知对象,并提供索引查询能力。

Indexer 基于 ThreadSafeStore(线程安全存储)实现,内部维护了一个 key 到对象的 map,以及多个索引函数(Indexers)。通过索引,可以按标签选择器(label selector)或字段选择器(field selector)快速查找对象,而不需要遍历整个缓存。

5.4 WorkQueue(工作队列)

WorkQueue 在 Informer 体系中扮演的是"事件处理器和 Informer 之间的缓冲层"。当 DeltaFIFO 中的事件被 Pop 出来之后,不会直接调用用户的处理器,而是先写入 WorkQueue。这样做的好处是:用户的处理器可以异步执行,且 WorkQueue 提供了限速、重试、去重的能力,防止事件处理过载。


六、组件关系图:完整的数据流与调用链

现在我们来把所有的组件串起来,看一看一个资源变化从 API Server 到用户处理器,经历了怎样的完整数据流:

┌──────────────────────────────────────────────────────────────────┐
│                          API Server                              │
│  ┌──────────────┐  ┌──────────────┐  ┌──────────────┐           │
│  │   /api/v1    │  │  /apis/apps  │  │ /apis/crd    │           │
│  │   /pods      │  │  /deployments│  │ /customs     │           │
│  └──────┬───────┘  └──────┬───────┘  └──────┬───────┘           │
└─────────┼─────────────────┼─────────────────┼────────────────────┘
          │                 │                 │
          │ List + Watch    │ List + Watch    │ List + Watch
          │ (HTTP/REST)     │ (HTTP/REST)      │ (HTTP/REST)
          ▼                 ▼                 ▼

┌──────────────────────────────────────────────────────────────────┐
│                      SharedInformerFactory                        │
│  负责创建和管理多个 SharedIndexInformer(同类型只创建一个)       │
│  - SharedInformerFactory.InformerFor(obj, newFunc)               │
│  - SharedInformerFactory.Start(stopCh)                           │
│  - SharedInformerFactory.WaitForCacheSync(stopCh)                │
└───────────────────────────┬──────────────────────────────────────┘
                            │ 创建 SharedIndexInformer
                            ▼

┌──────────────────────────────────────────────────────────────────┐
│                    SharedIndexInformer                             │
│  ┌────────────────────────────────────────────────────────────┐ │
│  │                     Controller (Reflector)                  │ │
│  │  核心职责:                                                  │ │
│  │    1. List 全量拉取 -> 填充 DeltaFIFO                        │ │
│  │    2. Watch 增量监听 -> 持续写入 DeltaFIFO                   │ │
│  │    3. 处理 Watch 断开 -> backoff 重连                       │ │
│  │    4. 处理 ResourceVersion 过期 -> 重新 List               │ │
│  └─────────────────────────────┬────────────────────────────────┘ │
│                                │                                  │
│                                ▼                                  │
│  ┌────────────────────────────────────────────────────────────┐   │
│  │                       DeltaFIFO                             │   │
│  │  存储对象的所有增量变化(Added/Updated/Deleted/Replaced)   │   │
│  │  关键特性:                                                  │   │
│  │    - 去重:同对象连续变化只保留最后一次                      │   │
│  │    - 先进先出:按事件发生顺序处理                            │   │
│  │    - KnownObjects:区分新增和已有对象                       │   │
│  └─────────────────────────────┬────────────────────────────────┘   │
│                                │ Pop()                            │
│                                ▼                                  │
│  ┌────────────────────────────────────────────────────────────┐   │
│  │                        Indexer                              │   │
│  │  本地缓存存储层(ThreadSafeStore + 索引函数)                │   │
│  │    - Add/Update/Delete/Replace 操作缓存                     │   │
│  │    - 按标签/字段索引查询对象                                │   │
│  │    - SharedIndexInformer.GetIndexer() 获取                 │   │
│  └─────────────────────────────┬────────────────────────────────┘   │
│                                │                                  │
│                                ▼                                  │
│  ┌────────────────────────────────────────────────────────────┐   │
│  │                      Processor                              │   │
│  │  事件分发器:                                                 │   │
│  │    - 接收 DeltaFIFO 的每个事件                              │   │
│  │    - 分发给所有注册的 ResourceEventHandler                  │   │
│  │    - 支持多个 handler 共享同一份缓存                        │   │
│  └────────────────────────────────────────────────────────────┘   │
└──────────────────────────────────────────────────────────────────┘
                                │
                                │ 事件通知
                                ▼

┌──────────────────────────────────────────────────────────────────┐
│                    用户的 ResourceEventHandler                      │
│  ┌───────────────┐  ┌───────────────┐  ┌───────────────┐         │
│  │ OnAdd(obj)    │  │ OnUpdate(o,n)│  │ OnDelete(obj) │         │
│  │ 资源新增时调用 │  │ 资源更新时调用 │  │ 资源删除时调用 │         │
│  └───────┬───────┘  └───────┬───────┘  └───────┬───────┘         │
└──────────┼─────────────────┼─────────────────┼────────────────────┘
           │                 │                 │
           ▼                 ▼                 ▼

┌──────────────────────────────────────────────────────────────────┐
│                         WorkQueue                                │
│  限速工作队列(通常配合 Controller 使用):                        │
│    - 去重:同一对象的多次变化只入队一次                            │
│    - 限速:控制处理速率,防止过载                                  │
│    - 重试:处理失败后自动重试                                      │
└──────────────────────────────────────────────────────────────────┘

整个数据流的核心逻辑是:Reflector 持续不断地从 API Server 拉取数据(先 List 再 Watch),每次有变化就写入 DeltaFIFO。DeltaFIFO 负责去重和排序,然后通过 Pop 操作把事件交给 Processor。Processor 更新 Indexer 中的缓存,并通知所有注册的 ResourceEventHandler。Handler 收到通知后,可以直接处理,也可以先把 key 写入 WorkQueue,异步处理。


七、常见问题 FAQ

▼ Q: client-go 一定要和 Kubernetes 源码一起用吗?

A: 不一定。client-go 作为独立的 Go 模块发布在 GitHub(github.com/kubernetes/client-go),你可以单独 import 使用。在 Kubernetes 源码仓库内部,staging/src/k8s.io/client-go 是源码目录,通过 go.work 工作区文件关联到其他 staging 包。外部项目引用时,直接 go get k8s.io/client-go@v0.32.0 即可。内部的 staging 目录主要用于 Kubernetes 自身的开发。


▼ Q: SharedInformer 和 SharedIndexInformer 到底用哪个?

A: 在实际开发中,SharedInformerFactory 返回的类型是 SharedIndexInformer(向上转型为 SharedInformer 使用)。你不需要纠结用哪个——只需要用 SharedInformerFactory 创建 informer,然后注册事件处理器。如果你的控制器需要按标签或字段快速查询对象,就调用 GetIndexer() 获取索引能力;如果不需要查询,直接用 GetStore() 获取缓存即可。


▼ Q: HasSynced() 返回 true 是什么意思?为什么 WaitForCacheSync 必须等它?

A: HasSynced() 返回 true,表示 Informer 已经完成了首次全量 LIST,本地缓存已经和 API Server 的当前状态一致了。在控制器启动时,必须先等所有 informer 的 HasSynced() 返回 true,才能开始 Reconcile 循环。否则,如果缓存还没同步完就开始处理业务逻辑,可能会读到不完整的数据,导致错误的决策。比如 DeploymentController 在启动时等待缓存同步,就是为了避免处理到启动前的历史状态。


▼ Q: DynamicClient 和 Clientset 的 Watch 方法都能监听变化,有什么区别?

A: 关键区别在于:Clientset 的 Watch 返回的是强类型的 Watch Interface(watch.Interface),事件是强类型的结构体(如 watch.Event{Type: watch.Added, Object: *v1.Pod});DynamicClient 的 Watch 返回的是 watch.Interface,但事件对象是 unstructured.Unstructured(map[string]interface{})。如果你需要操作 CRD 且不想生成代码,用 DynamicClient;如果你需要最好的类型安全和 IDE 代码提示,用 Clientset。


▼ Q: DeltaFIFO 的 Replaced 事件什么时候会产生?和 Updated 有什么区别?

A: Replaced 事件在 Reflector 重新 LIST 全量数据后产生。当你调用 DeltaFIFO 的 Replace() 方法(Reflector 会调用)时,会产生 Replaced 事件。相比之下,Updated 来自 Watch 流中的修改事件。两者的语义不同:Updated 是增量变化,Replaced 是全量替换。在处理事件时,如果你的 handler 需要区分"对象被修改"和"对象被全量替换",可以通过 Delta.Type 来判断。在新版 client-go 中,Replace 默认产生 Replaced 事件。


▼ Q: DiscoveryClient 是做什么用的?和 Clientset 有什么关系?

A: DiscoveryClient 用于发现集群支持的 API 资源——它会调用 API Server 的 /apis 和 /api 端点,返回集群中安装了哪些 API 组、哪些版本、哪些资源类型。这在写通用工具时特别有用,比如 kubectl 就是用 DiscoveryClient 来发现"kubectl get xxx" 能支持哪些资源类型。DiscoveryClient 和 Clientset 是两个独立的功能:Clientset 用于操作资源,DiscoveryClient 用于查询"集群支持哪些资源"。


本篇文章我们从版本对应关系出发,深入 client-go 的源代码结构,详细讲解了三种客户端的差异、Informer 体系的组件关系、以及核心数据结构的工作原理。理解这些内容,是后续开发 Kubernetes Operator 和控制器的基础。

本文学到的要点:

  • 版本对应:client-go 版本号 = Kubernetes 主版本号 - 4,v0.32 对应 Kubernetes 1.36
  • 三种客户端:Clientset(强类型)、DynamicClient(动态类型)、RESTClient(底层 HTTP)各有适用场景
  • Informer 体系:SharedInformerFactory 生产 SharedIndexInformer,后者继承 SharedInformer 接口
  • 数据流:Reflector → DeltaFIFO → Indexer → Processor → ResourceEventHandler
  • HasSynced:控制器启动前必须等待缓存同步完成

Kubernetes 编程 / Operator 专题【左扬精讲】—— client-go 源码全解 · 来源:Kubernetes 1.36.1 源码分析

相关阅读:
   • kubernetes/client-go GitHub 仓库
   • client-go tools/cache 源码目录
   • Kubernetes Informer 设计提案
   • sample-controller 示例项目