client-go 四种客户端详解

写给源码学习者


目录

  1. Kubernetes API 基础(读四种客户端前必知)
  2. 为什么需要「四种客户端」
  3. 总览:四种客户端对照表
  4. 架构关系图
  5. 选型决策
    9.~12. 共同模式 / fake / 跟读 / 自测
  6. 架构深入(设计/并发/性能/面试)

0. Kubernetes API 基础(读四种客户端前必知)

讲四种 client 之前,要先弄清:apiserver 上到底有哪些 API、URL 怎么组织、内置资源和 CRD 有何不同。否则「为什么 Pod 用 Clientset、CRD 用 Dynamic」会显得很随意。

0.1 一个 apiserver,多套 REST API

Kubernetes 集群对外(对你的 Go 程序而言)主要是 kube-apiserver 的 HTTPS REST API

你的程序  --HTTPS-->  kube-apiserver  --etcd-->  集群状态
  • 内置资源(Pod、Deployment、Service…)和 自定义资源(CRD)在 apiserver 上 同一套机制 注册、同一套 REST 语义(List/Get/Create/Update/Delete/Watch)。
  • client-go 四种客户端,是 访问这套 REST API 的不同 Go 封装层,不是四套不同的集群连接。

0.2 资源的「身份证」:GVK 与 GVR

每个 API 对象都有两组常见标识(初学先记这两个缩写):

缩写 全称 含义 例子
GVK Group / Version / Kind 对象「是哪种类型」(YAML 里 apiVersion + kind apps/v1, Deployment
GVR Group / Version / Resource REST URL 里的资源复数名 apps, v1, deployments

对应关系示例:

kind(类型名) apiVersion GVR(REST 路径用) HTTP 列表示例
Pod v1 "", v1, pods GET /api/v1/pods
Deployment apps/v1 apps, v1, deployments GET /apis/apps/v1/namespaces/default/deployments
Service v1 "", v1, services GET /api/v1/namespaces/default/services

Kind vs ResourceDeployment(类型)→ URL 里写 deployments(复数、小写)。client-go typed client 的 .Resource("pods") 就是在填 Resource 名。

Go 里 GVR 常写成:

schema.GroupVersionResource{Group: "apps", Version: "v1", Resource: "deployments"}

0.3 两类 API 路径:/api/apis

apiserver 按 API Group 分前缀(rest/url_utils.go DefaultVersionedAPIPath):

API 类别 URL 前缀 Group 特点 例子
Core 组(遗留核心 API) /api/{version} Group 为空字符串 /api/v1/pods
具名 Group /apis/{group}/{version} Group 非空 /apis/apps/v1/deployments

client-go 里 typed client 创建时会设 APIPath + GroupVersion

// CoreV1 — core_client.go setConfigDefaults
config.APIPath = "/api"
config.GroupVersion = &{Version: "v1"}          // Group 为空 → /api/v1

// AppsV1 — 同理
config.APIPath = "/apis"
config.GroupVersion = &{Group: "apps", Version: "v1"}  // → /apis/apps/v1

模块 1 复习RESTClientversionedAPIPath 就是这里拼出来的;List Pod 的 /api/v1/pods 即 Core 组路径。

0.4 内置 API vs 自定义 API(CRD)

概念 是什么 谁定义 Go 里常见访问方式
内置 API K8s 自带的资源类型 Kubernetes 项目(k8s.io/api/... Clientset 强类型
CRD(CustomResourceDefinition) 用户在集群里 声明 的新资源类型 你安装的 CRD YAML Dynamic Client(或 code-gen 后的 typed client)
Aggregated API 通过聚合层扩展的 API 如 metrics-server、自定义 APIService 多为 Dynamic / REST

内置 API 例子(Clientset 直接支持):

  • Core:Pod, Service, Namespace, ConfigMap
  • Apps:Deployment, ReplicaSet, StatefulSet
  • Batch:Job, CronJob

自定义 API 例子(集群里装了 CRD 才有):

apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
  name: crontabs.stable.example.com
spec:
  group: stable.example.com
  versions:
    - name: v1
      served: true
      storage: true
  scope: Namespaced
  names:
    plural: crontabs      # ← Resource 名
    kind: CronTab         # ← Kind

装好后可用 GVR {stable.example.com, v1, crontabs} 做 CRUD——Clientset 里没有 CronTab() 方法,要用 Dynamic 或自己 code-gen。

0.5 一张图:从 YAML 到 URL 到 client-go

YAML 文件                         apiserver REST                    client-go
─────────────────────────────────────────────────────────────────────────────
apiVersion: v1                    GET /api/v1/namespaces/         clientset.CoreV1()
kind: Pod                           default/pods                    .Pods("default").List()
metadata: name: nginx

apiVersion: apps/v1               GET /apis/apps/v1/              clientset.AppsV1()
kind: Deployment                    namespaces/default/deployments   .Deployments("default").List()

apiVersion: stable.example.com/v1 GET /apis/stable.example.com/v1/ dynamic.NewForConfig
kind: CronTab                       namespaces/default/crontabs     .Resource(gvr).Namespace().List()

0.6 API 类型 ↔ 四种客户端(先建立直觉)

API 类型 是否编译期有 Go struct 首选客户端 备注
内置 Core/Apps/Batch… 有(k8s.io/api/... Clientset 第一阶段主力
CRD / 未知 GVR 默认无(用 Unstructured Dynamic dynamic-create-update-delete-deployment 对内置资源演示的是 同一套 API 形状
任意 GVR,只要 metadata PartialObjectMetadata Metadata 进阶优化
任意路径,手写 REST 自己解析 REST Client 模块 1 底层

易混点dynamic-create-update-delete-deployment 操作的是 内置 Deployment,是为了演示 Dynamic 的 GVR 用法;生产里操作内置资源仍优先 Clientset。操作 CRD 才是 Dynamic 的主场。

0.7 与 kubectl 的对应(帮助建立感性认识)

kubectl 涉及的 API client-go 大致对应
kubectl get pods 内置 Core/v1 clientset.CoreV1().Pods().List()
kubectl get deploy 内置 apps/v1 clientset.AppsV1().Deployments().List()
kubectl get crontabs.stable.example.com CRD dynamicClient.Resource(gvr).List()
kubectl api-resources 发现集群支持哪些 GVR discovery 客户端(本文不展开)

0.8 本节小结(读 §1 前自检)

  1. GVK 看 YAML 的 apiVersion + kindGVR 拼 REST URL 和 Dynamic 的 Resource(gvr)
  2. Core 资源走 /api/v1;具名 Group 走 /apis/{group}/{version}
  3. 内置 API → 有官方 Go 类型 → ClientsetCRD → 常无类型 → Dynamic
  4. 四种客户端共享 rest.Config 和同构的 Request.Do() HTTP 机制。

与模块 4 闭环:读完四种客户端选型后,跟 第一阶段指南 模块 4 把 GVK/GVR 对应到 k8s.io/api 里的 struct 和 Scheme。

自测(§0)
  1. Deployment 的 GVR 是什么?List 的 URL 路径前缀?
  2. Pod 属于 Core 组还是 apps 组?
  3. 集群里新装的 CRD 资源,第一阶段默认用哪种 client?

参考答案

  1. apps/v1/deployments/apis/apps/v1/...
  2. Core 组(/api/v1/pods
  3. Dynamic Client(或对该 CRD 做 code-gen 后的 typed client)

1. 为什么需要「四种客户端」

前置:请先读完 §0 Kubernetes API 基础

集群 API 分 内置自定义(CRD) 等(§0.4);访问都是 REST,但 Go 侧是否已有 *v1.Pod 这类类型不同。client-go 因此提供不同抽象层:

你的需求 更合适的客户端
操作 Pod、Deployment 等内置类型,要类型安全 Clientset(强类型)
操作 CRD 或运行时才知道 GVR Dynamic Client
只要 metadata(name/labels/uid),不要 spec/status Metadata Client
自己拼 URL、最底层控制 REST Client

四种客户端 不是四种连集群方式(连集群仍靠 rest.Config,见《调用链精读》§3.8~3.10)。
四种客户端是:在同一张 Config「门票」之上,选哪一层 API 封装


2. 总览:四种客户端对照表

# 名称 包 / 入口 创建函数 操作对象类型 典型调用
1 Clientset(强类型) k8s.io/client-go/kubernetes kubernetes.NewForConfig *v1.Pod*appsv1.Deployment clientset.CoreV1().Pods(ns).List(...)
2 Dynamic Client(动态) k8s.io/client-go/dynamic dynamic.NewForConfig *unstructured.Unstructured client.Resource(gvr).Namespace(ns).List(...)
3 Metadata Client(元数据) k8s.io/client-go/metadata metadata.NewForConfig *metav1.PartialObjectMetadata client.Resource(gvr).Namespace(ns).Get(...)
4 REST Client(底层) k8s.io/client-go/rest rest.RESTClientFor / RESTClientForConfigAndClient 原始 JSON / runtime.Object client.Get().Resource("pods").Do(ctx)

共同底座(模块 1 已学):

rest.Config
  → rest.HTTPClientFor        共享 TLS + Token(Transport 链)
  → rest.RESTClientForConfigAndClient   各客户端内部都会用到
  → rest.Request.Do()         真正发 HTTP

3. 架构关系图

3.1 纵向:抽象层从高到低

                    ┌─────────────────────────────────────┐
                    │         rest.Config(门票)          │
                    └─────────────────┬───────────────────┘
                                      │
          ┌───────────────────────────┼───────────────────────────┐
          │                           │                           │
          ▼                           ▼                           ▼
   kubernetes.NewForConfig    dynamic.NewForConfig      metadata.NewForConfig
          │                           │                           │
          ▼                           ▼                           ▼
      *Clientset                 *DynamicClient              metadata.Client
   CoreV1().Pods()...      Resource(gvr).Namespace()...   Resource(gvr).Get()
          │                           │                           │
          └───────────────────────────┼───────────────────────────┘
                                      ▼
                            rest.Interface(*RESTClient)
                                      │
                            rest.Request.Get/Post()...
                                      │
                                      ▼
                            client.Do(req)  ★ HTTP

3.2 横向:四种客户端与 RESTClient 关系

rest.Config

http.Client via HTTPClientFor

kubernetes.Clientset

dynamic.DynamicClient

metadata.Client

rest.RESTClient 直接使用

CoreV1Client / AppsV1Client ...

pods / deployments 等 typed 方法

rest.Interface

dynamicResourceClient

rest.Interface

metadata client 包装

rest.RESTClient 字段

Request.Do

kube-apiserver

3.3 四种客户端的 URL 拼装策略(设计分歧点)

四种客户端最终都到 rest.Request,但 路径从哪来 有两种根本不同的策略:

客户端 URL 来源 RESTClient 的 versionedAPIPath 单次请求如何定路径
Clientset Namespace + Resource + Name Builder 固定 /api/v1/apis/apps/v1 pathPrefix + namespaces/ns + pods
Dynamic AbsPath(makeURLSegments...) 占位 /if-you-see-this-search-for-the-break 运行时按 GVR 拼完整 path
Metadata AbsPath(makeURLSegments...) 占位 /this-value-should-never-be-sent 同 Dynamic + 特殊 Accept
REST Client 直接用 Builder 或 AbsPath 按 Group 绑定 完全手写

Clientset(编译期绑定 API 版本)

// core_client.go setConfigDefaults
config.APIPath = "/api"
config.GroupVersion = &{Version: "v1"}   // → RESTClient.pathPrefix = /api/v1
// pod.go List
Get().Namespace(ns).Resource("pods")     // → /api/v1/namespaces/default/pods

Dynamic(运行时 GVR 绑定)

// dynamic/simple.go:86-87 — 故意不设真实 APIPath
config.GroupVersion = &schema.GroupVersion{}
config.APIPath = "/if-you-see-this-search-for-the-break"

// Create 时用 AbsPath 覆盖 pathPrefix
Post().AbsPath(c.makeURLSegments(name)...)
// makeURLSegments → ["apis","apps","v1","namespaces","default","deployments","demo"]

Metadata(同 Dynamic 路径 + 内容协商)

Get().AbsPath(c.makeURLSegments(name)...).
    SetHeader("Accept", "...PartialObjectMetadata;g=meta.k8s.io;v=v1...")

apiserver 收到带 as=PartialObjectMetadata 的 Accept 后,只序列化 metadata 字段——这是 Metadata Client 省流量 的核心,不是少调几个字段那么简单。

3.4 共同执行底座:Do() vs Watch() 双路径

详见 模块 3 Watch 源码架构详解 §6.3

操作 入口 首次限流 成功时 body
List/Get/Create/… Request.Do()request() tryThrottle ReadAlltransformResponse
Watch Request.Watch() 独立循环 保留 body → StreamWatcher

四种客户端的 List/CRUD 全部走 Do() 路径;Watch 走独立路径。Dynamic/Metadata 也不例外。

3.5 四种客户端执行阶段对照(List 为例)

阶段 A — NewForConfig(四种相同)
  rest.Config → HTTPClientFor → RESTClientForConfigAndClient → 持有 rest.Interface

阶段 B — Builder(四种不同)
  Clientset:  Get().Namespace().Resource("pods")
  Dynamic:    Get().AbsPath(makeURLSegments...)
  Metadata:   Get().AbsPath(...).SetHeader(Accept: PartialObjectMetadata...)
  REST:       同 Clientset 或 AbsPath

阶段 C — Execute(List 时四种汇合)
  .Do(ctx) → request() → tryThrottle → newHTTPRequest → client.Do → transformResponse

阶段 D — Decode(四种不同)
  Clientset:  Into(&PodList{})           → scheme.Codecs → *v1.PodList
  Dynamic:    Raw() + UnstructuredJSONScheme.Decode
  Metadata:   Get() / Raw fallback       → *PartialObjectMetadataList
  REST:       Into 或 Raw 自行处理
apiserver http.Client request() rest.Request Typed/Dynamic/Metadata 用户代码 apiserver http.Client request() rest.Request Typed/Dynamic/Metadata 用户代码 List/Get/Create... 1 Builder 链(路径策略因客户端而异) 2 Do(ctx) 3 request(ctx, fn) 4 tryThrottle 5 newHTTPRequest 6 client.Do(req) 7 HTTPS 8 JSON body 9 transformResponse (ReadAll) 10 Result 11 Result 12 Into / Raw / Get(解码策略因客户端而异) 13 强类型 / Unstructured / PartialObjectMetadata 14

4. 客户端 ①:Clientset(强类型,最常用)

4.1 是什么

  • 结构体*kubernetes.Clientsetkubernetes/clientset.go
  • 特点:为每个 API Group 生成 typed 方法,编译期类型检查
  • 第一阶段主力:List / CRUD example 都用它

4.2 怎么创建

config, _ := clientcmd.BuildConfigFromFlags("", kubeconfig)
clientset, err := kubernetes.NewForConfig(config)

内部(模块 1):HTTPClientFor → 各 Group NewForConfigAndClient → 共享一个 http.Client

4.3 怎么调用(套娃)

clientset.CoreV1().Pods("default").List(ctx, metav1.ListOptions{})
clientset.AppsV1().Deployments("default").Create(ctx, dep, metav1.CreateOptions{})
层级 编译期类型 运行期类型 New 还是 Getter
Clientset *Clientset *Clientset 大工厂 NewForConfig
.CoreV1() CoreV1Interface *CoreV1Client Getter
.Pods(ns) PodInterface *pods 小包装 newPods
.List() (*pods).List 方法 → RESTClient.Get()...Do()

详见《第一阶段夯实基础指南》模块 2 §2.0。

4.4 适用场景

  • 操作 core/apps/batch 等内置 API
  • 需要 *v1.Pod 等强类型 struct
  • 控制器、业务代码 默认首选

4.5 源码入口

文件 关注
kubernetes/clientset.go NewForConfig
kubernetes/typed/core/v1/core_client.go CoreV1Client
kubernetes/typed/core/v1/pod.go List / Create 链式调用

4.6 Example

  • examples/out-of-cluster-client-configuration
  • examples/create-update-delete-deployment
  • examples/module1-debug

4.7 执行流程深入(List Pod)

阶段 B-Builderpod.go:94-98,零网络):

c.client.Get().Namespace(c.ns).Resource("pods").
    VersionedParams(&opts, scheme.ParameterCodec).Timeout(timeout)
  • pathPrefix 已在 NewRequest 时设为 /api/v1(来自 CoreV1Client 的 setConfigDefaults
  • VersionedParamslabelSelectorfieldSelector 等写入 query

阶段 C-ExecuteDo(ctx)request.go:965):

  1. tryThrottle — 默认 QPS=5(CoreV1Client 创建时 NewTokenBucketRateLimiter
  2. newHTTPRequestGET https://host:6443/api/v1/namespaces/default/pods?...
  3. client.Do — RoundTripper 链注入 Bearer Token
  4. transformResponseio.ReadAll(resp.Body) 读完整 JSON

阶段 D-DecodeInto(&PodList{})):

  • scheme.ParameterCodec / scheme.Codecs 来自 kubernetes/scheme
  • decoder 根据 Kind=PodList + apiVersion=v1 反序列化

与 Dynamic 的关键差异:Clientset 不需要 AbsPath;API 版本在 NewForConfig 时已绑定到 RESTClient,Resource 名在编译期由 client-gen 固定为 "pods"


5. 客户端 ②:Dynamic Client(动态,CRD 友好)

5.1 是什么

  • 结构体*dynamic.DynamicClientdynamic/simple.go:34
  • 特点:用 GVR(GroupVersionResource)指定资源,对象用 unstructured.Unstructured(类似 map[string]interface{}
  • 无编译期类型:CRD 未 code-gen 也能操作

5.2 怎么创建

config, _ := clientcmd.BuildConfigFromFlags("", kubeconfig)
client, err := dynamic.NewForConfig(config)

NewForConfig:71-78)同样:ConfigForHTTPClientForRESTClientForConfigAndClient

注意 Dynamic 的 Config 会把 GroupVersion 设为「占位」,真正 GVR 在调用 Resource(gvr) 时指定:86-87)。

5.3 怎么调用

gvr := schema.GroupVersionResource{Group: "apps", Version: "v1", Resource: "deployments"}

client.Resource(gvr).Namespace("default").Create(ctx, obj, metav1.CreateOptions{})
client.Resource(gvr).Namespace("default").Get(ctx, "demo-deployment", metav1.GetOptions{})

对象示例(examples/dynamic-create-update-delete-deployment/main.go):

deployment := &unstructured.Unstructured{
    Object: map[string]interface{}{
        "apiVersion": "apps/v1",
        "kind":       "Deployment",
        "metadata": map[string]interface{}{"name": "demo-deployment"},
        // spec: ...
    },
}

5.4 与 Clientset 的异同

Clientset Dynamic Client
资源定位 CoreV1().Pods() Resource(gvr).Namespace()
对象类型 *v1.Pod *unstructured.Unstructured
CRD 需 code-gen 或不用 typed 直接指定 GVR
底层 HTTP rest.Interface + Do() 相同simple.go:132-138

Dynamic 的 Create 内部仍是:

c.client.client.Post().AbsPath(...).Body(outBytes).Do(ctx)

与 typed client 同一套 rest.Request,只是 URL 用 AbsPath 拼 GVR 路径。

5.4a 执行流程深入(Create Deployment)

初始化dynamic/simple.go:83-93):

config.GroupVersion = &schema.GroupVersion{}  // 空占位
config.APIPath = "/if-you-see-this-search-for-the-break"
restClient, _ := rest.RESTClientForConfigAndClient(config, h)

RESTClient 的 pathPrefix 是占位符——真实路径每次 CRUD 由 makeURLSegments 决定

Builder + Executesimple.go:132-138):

result := c.client.client.Post().
    AbsPath(append(c.makeURLSegments(name), subresources...)...).
    SetHeader("Content-Type", application/json).
    Body(outBytes).   // runtime.Encode(UnstructuredJSONScheme, obj)
    SpecificallyVersionedParams(&opts, dynamicParameterCodec, versionV1).
    Do(ctx)

makeURLSegments("demo-deployment") 展开为:

["apis", "apps", "v1", "namespaces", "default", "deployments", "demo-deployment"]
→ POST /apis/apps/v1/namespaces/default/deployments/demo-deployment

Decode(与 Clientset 的 Into 不同):

retBytes, _ := result.Raw()   // 拿原始 JSON 字节
runtime.Decode(unstructured.UnstructuredJSONScheme, retBytes)

设计动机

  • 无编译期类型 → 不能 Into(&appsv1.Deployment{}),除非你知道类型
  • AbsPath → 一个 DynamicClient 实例可操作任意 GVR,不必像 Clientset 建几十个 Group client
  • UnstructuredJSONScheme → 对象存为 map[string]interface{},CRD 无 Go struct 也能 CRUD

5.5 适用场景

  • CRD、Aggregated API
  • 通用控制器、kubectl 类工具
  • 运行时才知道资源类型

5.6 Example

  • examples/dynamic-create-update-delete-deployment

6. 客户端 ③:Metadata Client(只要元数据)

6.1 是什么

  • 结构体metadata.Clientmetadata/metadata.go:56
  • 特点:只获取 PartialObjectMetadata(name、namespace、labels、uid 等),不拉完整 spec/status
  • 省流量 / 省解码:大规模 List 元数据时更高效

6.2 怎么创建

config, _ := clientcmd.BuildConfigFromFlags("", kubeconfig)
client, err := metadata.NewForConfig(config)

ConfigFor:64-72)使用 protobuf 为主的 Content-Type,与 Dynamic/Clientset 的 JSON 默认略有不同。

6.3 怎么调用

gvr := schema.GroupVersionResource{Group: "apps", Version: "v1", Resource: "deployments"}

client.Resource(gvr).Namespace("default").Get(ctx, "demo-deployment", metav1.GetOptions{})
// 返回 *metav1.PartialObjectMetadata

API 形状与 Dynamic 类似(Resource(gvr).Namespace(ns)),但返回类型是 PartialObjectMetadata,不是 Unstructured 全对象。

6.3a 执行流程深入(Get Deployment metadata)

初始化metadata/metadata.go:103-114):

config.ContentType = "application/vnd.kubernetes.protobuf"  // 默认 protobuf
config.NegotiatedSerializer = metainternalversionscheme.Codecs.WithoutConversion()
config.APIPath = "/this-value-should-never-be-sent"       // 同 Dynamic 占位

Builder — 内容协商是核心metadata.go:185-188):

result := c.client.client.Get().
    AbsPath(append(c.makeURLSegments(name), subresources...)...).
    SetHeader("Accept",
        "application/vnd.kubernetes.protobuf;as=PartialObjectMetadata;g=meta.k8s.io;v=v1,"+
        "application/json;as=PartialObjectMetadata;g=meta.k8s.io;v=v1,...").
    SpecificallyVersionedParams(&opts, dynamicParameterCodec, versionV1).
    Do(ctx)

apiserver 侧行为:收到 as=PartialObjectMetadata 的 Accept 后,通过 Table/PartialObjectMetadata 转换 只返回 metadata(name、namespace、labels、uid、ownerReferences 等),不序列化 spec/status

Decode — 双路径 fallbackmetadata.go:192-216):

obj, err := result.Get()   // 优先走 NegotiatedSerializer
if runtime.IsNotRegisteredError(err) {
    rawBytes, _ := result.Raw()
    json.Unmarshal(rawBytes, &partial)  // 老 apiserver 或 CRD:全量 JSON 再抽 metadata
}

与 Dynamic 对比

Dynamic Get Metadata Get
Accept 默认 JSON/Pod 全对象 PartialObjectMetadata
返回 *unstructured.Unstructured 全字段 *metav1.PartialObjectMetadata
流量 完整 spec/status 仅 metadata
URL 拼法 makeURLSegments 相同

6.4 适用场景

  • 控制器只关心 labels / ownerReferences
  • 全集群扫描资源「名单」
  • 与 Dynamic 类似,支持 任意 GVR(含 CRD)

6.5 第一阶段建议

知道存在即可;日常 CRUD 用 Clientset,CRD 全对象用 Dynamic。Metadata 在 Informer/控制器优化场景更常见。


7. 客户端 ④:REST Client(最底层)

7.1 是什么

  • 结构体*rest.RESTClientrest/client.go:81
  • 特点:直接提供 Get() / Post() / Put() / Delete()*rest.Request
  • Clientset / Dynamic / Metadata 最终都委托它(或同构的 rest.Interface

7.2 怎么创建

方式 A(单独创建,需自己设 GroupVersion):

config, _ := clientcmd.BuildConfigFromFlags("", kubeconfig)
// 必须设置 GroupVersion、NegotiatedSerializer 等
client, err := rest.RESTClientFor(config)
// 或
client, err := rest.RESTClientForConfigAndClient(config, httpClient)

方式 B(间接使用,最常见):

clientset, _ := kubernetes.NewForConfig(config)
// 内部已创建 RESTClient;typed client 的 c.client 就是 rest.Interface

CoreV1 绑定 /api/v1core_client.go setConfigDefaults)。

7.3 怎么调用

client.Get().
    Namespace("default").
    Resource("pods").
    Do(ctx)

这就是模块 1 的 Request 链,无 Pods() 这种 typed 封装。

7.4 适用场景

  • client-gen 之前的原型、调试 HTTP
  • 实现 新的 typed client(client-gen 生成的代码底层就是 RESTClient)
  • 非标准 API 路径(如 AbsPath

7.5 与 http.Client 的分工(模块 1 复习)

组件 职责
http.Client TLS、Token、连接池
RESTClient API 版本路径、限流、编解码器
Request 单次 verb/URL/body
client.Do(req) 发 HTTP

7.5a 执行流程深入:REST Client 在栈中的位置

REST Client 不是「第四种连法」,而是 三种高层客户端的公共底座

kubernetes.Clientset
  └─ CoreV1Client.restClient : rest.Interface  → *RESTClient
dynamic.DynamicClient
  └─ client : rest.Interface                 → *RESTClient(占位 APIPath)
metadata.Client
  └─ client : *RESTClient                    → 直接持有(非 Interface)

Typed client 的 List 等价于手写

// 等价于 pod.go List 的核心
restClient.Get().
    Namespace("default").Resource("pods").
    VersionedParams(&opts, scheme.ParameterCodec).
    Do(ctx).Into(&v1.PodList{})

何时直接用 REST Client

  • 调试 HTTP(对照 curl)
  • 实现 client-gen 未覆盖的 API
  • 非标准 subresource 路径

读源码定位:任何 typed 方法的 c.client.Get/Post/... 最终都是 rest.Interface 上的 Builder;跟模块 1 的 request() 即可,不必另学一套 HTTP。


8. 四种客户端选型决策

否 / CRD

需要访问 K8s API

先确认:内置 API 还是 CRD?
见 §0.4

资源是内置类型
Pod/Deploy/Service?

需要 spec/status
完整对象?

是 CRD 或
运行时 GVR?

只要 labels/name
等元数据?

在写 client-gen
或调试 HTTP?

① Clientset

③ Metadata Client

② Dynamic Client

④ REST Client

第一阶段学习顺序建议

  1. §0 API 基础(内置 / CRD、GVK/GVR、/api vs /apis
  2. Clientset(必学,模块 1~2)
  3. REST Client(理解模块 1 时已间接学过)
  4. Dynamic Client(模块 5 选读 + dynamic example;操作 CRD 时必用
  5. Metadata Client(知道即可,进阶再学)

9. 共同模式:NewForConfig 家族

四种客户端创建函数 名字高度相似,这是嵌套晕的来源之一。记住模式即可:

函数模式 含义
Xxx.NewForConfig(config) 内部 HTTPClientFor + RESTClientForConfigAndClient
Xxx.NewForConfigAndClient(config, httpClient) 注入已有 httpClient(Clientset 共享用)
Xxx.NewForConfigOrDie(config) 失败 panic(example 少用)
Xxx.New(rest.Interface) 已有 RESTClient,只包一层

只有 kubernetes.NewForConfig 是「大工厂」——组装所有 Group。
Dynamic / Metadata 只创建 单个 客户端实例。


10. 与 fake client 的区别

四种正式客户端 kubernetes/fake
目的 连真 apiserver 单元测试,内存假集群
网络 有(除 fake)
第一阶段 Clientset 必学 知道 fake.NewSimpleClientset() 即可

Fake 不是第五种生产客户端,测试替身。


11. 源码跟读路线(按学习阶段)

11.1 第一阶段(Clientset + REST 底座的理解)

1. kubernetes/clientset.go:460          NewForConfig
2. kubernetes/typed/core/v1/pod.go:88   List → RESTClient.Get()...Do()
3. rest/client.go:81                    RESTClient 结构体
4. rest/request.go:1023                 client.Do(req)

11.2 扩展(Dynamic 对比)

5. dynamic/simple.go:71               NewForConfig
6. dynamic/simple.go:112              Create → Post().AbsPath().Do()
7. examples/dynamic-create-update-delete-deployment/main.go

11.3 进阶(Metadata)

8. metadata/metadata.go:91            NewForConfig
9. metadata/metadata.go:125           Resource(gvr) API

12. 自测

API 基础(§0)

  1. Pod 和 Deployment 的 REST 路径前缀有何不同?
  2. GVK 和 GVR 分别对应 YAML 里什么字段?
  3. CRD 安装后,Clientset 为何没有 CronTab() 方法?

四种客户端

  1. 四种客户端分别用什么函数创建?
  2. 操作 CRD 全对象,选 Clientset 还是 Dynamic?
  3. 四种客户端发 HTTP 的最终共同点是什么?
  4. Clientset.CoreV1()dynamic.NewForConfig 哪个是「大工厂」?
  5. Metadata Client 返回的典型类型是什么?
参考答案

§0

  1. Pod:/api/v1/...(Core);Deployment:/apis/apps/v1/...
  2. GVK → apiVersion + kind;GVR → Group + Version + 复数 resource 名(如 deployments)。
  3. CRD 是用户扩展的类型,未编入 kubernetes.Clientset;需 Dynamic 或 code-gen。

四种客户端

  1. kubernetes.NewForConfig / dynamic.NewForConfig / metadata.NewForConfig / rest.RESTClientFor(AndClient)
  2. Dynamic(除非对 CRD 做了 code-gen)。
  3. 最终都到 rest.Request.Do()http.Client.Do(模块 1)。
  4. 只有 kubernetes.NewForConfig 组装整棵 Clientset。
  5. *metav1.PartialObjectMetadata

13. 架构深入(按 source_code / client-go rules)

本节按 .cursor/skills/source_code.md.cursor/rules/client-go.mdc 框架,补全四种客户端的设计动机、并发、性能、调试、面试维度。

13.1 为什么需要四种抽象(而非一种万能 Client)

若没有分层 问题
只有 REST Client 每次手写 URL/解码;无编译期类型安全
只有 Clientset CRD 无法操作;每加一个 CRD 要 code-gen 或改 Clientset
只有 Dynamic 内置资源失去 IDE 补全;controller 易写错字段
只有 Metadata 无法读 spec/status;不能替代 CRUD

当前方案:共享 rest.Config + http.Client + Request.Do(),在 对象类型URL 策略 上分叉——这是「同一 REST API、多种 Go 封装」的工程折中。

13.2 设计模式对照

模式 体现 原因
Factory NewForConfig 家族 统一从 Config 建客户端
Facade Clientset 包几十 Group 隐藏 REST 细节,暴露业务 API
Builder Get().Namespace().Resource().Do() 分步填 Request,.Do() 发网
Strategy Into vs Raw vs PartialObjectMetadata 解码 按客户端选解码策略
Adapter Dynamic AbsPath 适配任意 GVR 运行时资源定位
Decorator RoundTripper 链 TLS/Token 与业务解耦

13.3 并发与共享

对象 是否线程安全 说明
*Clientset / *DynamicClient 可并发复用 底层 http.Client 连接池线程安全
*rest.Request 不可跨 goroutine 共享 每次 Do 前 Builder 填参;Builder 非并发
watch.Interface 单 consumer 一个 ResultChan 通常一个 goroutine 读

实践:多 goroutine 共享同一个 clientset;每个 goroutine 自己调 List/Get,不要共享正在 Builder 中的 *Request

13.4 性能与限流

机制 Clientset Dynamic Metadata
QPS 限流 RESTClient 默认 5 QPS 同左 同左
连接池 共享 http.Client 可共享(若注入同一 client) 同左
payload 全对象 JSON 全对象 JSON PartialObjectMetadata 更小
Watch 限流 不走 tryThrottle 同左 同左

Metadata 在大集群 List 全量资源 名字+标签 时,带宽和解码 CPU 显著低于 Dynamic List 全对象。

13.5 调试断点(四种客户端各一处)

客户端 推荐断点 观察
Clientset List pod.go:99 .Do(ctx) Resource("pods") + pathPrefix
Dynamic Create simple.go:138 .Do(ctx) AbsPath 完整 GVR URL
Metadata Get metadata.go:188 .Do(ctx) Accept 含 PartialObjectMetadata
共同 request.go:1023 client.Do 四种最终汇合点

13.6 面试题(进阶)

源码级:Dynamic 为何把 APIPath 设为 /if-you-see-this-search-for-the-break
→ 强迫每次请求用 AbsPath 显式拼 GVR 路径;RESTClient 的 versionedAPIPath 对 Dynamic 无意义。

架构级:四种客户端能否共用一个 http.Client
→ 可以;NewForConfigAndClient(config, httpClient) 注入同一实例即可共享连接池与 TLS session。

对比级:操作 CRD 全对象 vs 只要 labels,选谁?
→ 全对象 Dynamic;只要 metadata Metadata Client

13.7 一句话总结

四种客户端是 同一张 rest.Config 门票、同一条 Request.Do HTTP 通路 上的四种 Go 抽象:Clientset 用编译期类型 + 固定 Resource 名;Dynamic 用 GVR + Unstructured;Metadata 用 GVR + 内容协商只取 metadata;REST Client 是前三者的公共底座。


14. 相关文档

文档 关系
调用链精读 Clientset + List 主线;/api/v1 路径来源
第一阶段夯实基础指南 模块 1 rest 层、模块 2 CRUD
第一阶段指南 模块 4 GVK/GVR ↔ Go struct ↔ Scheme 闭环
本文 §0 内置 API / CRD / GVK / GVR 基础
Watch 源码架构详解 Watch 与 List 分叉、StreamWatcher
面试题集 模块 B 面试题(B-01~B-12)
Kubernetes API Concepts 官方 API 概念

client-go 四种客户端详解 · release-1.28 · 配合第一阶段学习使用

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐