client-go四种客户端详解
client-go 四种客户端详解
写给源码学习者
- 代码分支:
release-1.28- 前置文档:
- client-go调用链精读-从kubeconfig到ListPods.md(连接 Config → Clientset)
- client-go第一阶段夯实基础指南.md(模块 1 rest 层、模块 2 CRUD)
- 本文回答:client-go 里常见的 四种 API 客户端 分别是什么、怎么选、底层是否同一套 HTTP
- 建议阅读顺序:先读 §0 Kubernetes API 基础 → 再读四种客户端
目录
- Kubernetes API 基础(读四种客户端前必知)
- 为什么需要「四种客户端」
- 总览:四种客户端对照表
- 架构关系图
- §3.3 URL 拼装策略
- §3.4 Do vs Watch 双路径
- §3.5 执行阶段对照
4.~7. 四种客户端逐详解(含 §4.7 / §5.4a / §6.3a / §7.5a 执行深入)
- 选型决策
9.~12. 共同模式 / fake / 跟读 / 自测 - 架构深入(设计/并发/性能/面试)
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 Resource:Deployment(类型)→ 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 复习:RESTClient 的 versionedAPIPath 就是这里拼出来的;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 前自检)
- GVK 看 YAML 的
apiVersion+kind;GVR 拼 REST URL 和 Dynamic 的Resource(gvr)。 - Core 资源走
/api/v1;具名 Group 走/apis/{group}/{version}。 - 内置 API → 有官方 Go 类型 → Clientset;CRD → 常无类型 → Dynamic。
- 四种客户端共享
rest.Config和同构的Request.Do()HTTP 机制。
与模块 4 闭环:读完四种客户端选型后,跟 第一阶段指南 模块 4 把 GVK/GVR 对应到 k8s.io/api 里的 struct 和 Scheme。
- Deployment 的 GVR 是什么?List 的 URL 路径前缀?
- Pod 属于 Core 组还是 apps 组?
- 集群里新装的 CRD 资源,第一阶段默认用哪种 client?
参考答案
apps/v1/deployments;/apis/apps/v1/...- Core 组(
/api/v1/pods) - 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 关系
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() 双路径
| 操作 | 入口 | 首次限流 | 成功时 body |
|---|---|---|---|
| List/Get/Create/… | Request.Do() → request() |
有 tryThrottle |
ReadAll → transformResponse |
| 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 自行处理
4. 客户端 ①:Clientset(强类型,最常用)
4.1 是什么
- 结构体:
*kubernetes.Clientset(kubernetes/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-configurationexamples/create-update-delete-deploymentexamples/module1-debug
4.7 执行流程深入(List Pod)
阶段 B-Builder(pod.go:94-98,零网络):
c.client.Get().Namespace(c.ns).Resource("pods").
VersionedParams(&opts, scheme.ParameterCodec).Timeout(timeout)
pathPrefix已在NewRequest时设为/api/v1(来自 CoreV1Client 的setConfigDefaults)VersionedParams把labelSelector、fieldSelector等写入 query
阶段 C-Execute(Do(ctx) → request.go:965):
tryThrottle— 默认 QPS=5(CoreV1Client创建时NewTokenBucketRateLimiter)newHTTPRequest—GET https://host:6443/api/v1/namespaces/default/pods?...client.Do— RoundTripper 链注入 Bearer TokentransformResponse—io.ReadAll(resp.Body)读完整 JSON
阶段 D-Decode(Into(&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.DynamicClient(dynamic/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)同样:ConfigFor → HTTPClientFor → RESTClientForConfigAndClient。
注意 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 + Execute(simple.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.Client(metadata/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 — 双路径 fallback(metadata.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.RESTClient(rest/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/v1(core_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. 四种客户端选型决策
第一阶段学习顺序建议:
- §0 API 基础(内置 / CRD、GVK/GVR、
/apivs/apis) - Clientset(必学,模块 1~2)
- REST Client(理解模块 1 时已间接学过)
- Dynamic Client(模块 5 选读 + dynamic example;操作 CRD 时必用)
- 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)
- Pod 和 Deployment 的 REST 路径前缀有何不同?
- GVK 和 GVR 分别对应 YAML 里什么字段?
- CRD 安装后,Clientset 为何没有
CronTab()方法?
四种客户端
- 四种客户端分别用什么函数创建?
- 操作 CRD 全对象,选 Clientset 还是 Dynamic?
- 四种客户端发 HTTP 的最终共同点是什么?
Clientset.CoreV1()和dynamic.NewForConfig哪个是「大工厂」?- Metadata Client 返回的典型类型是什么?
§0
- Pod:
/api/v1/...(Core);Deployment:/apis/apps/v1/...。 - GVK →
apiVersion+kind;GVR → Group + Version + 复数 resource 名(如deployments)。 - CRD 是用户扩展的类型,未编入
kubernetes.Clientset;需 Dynamic 或 code-gen。
四种客户端
kubernetes.NewForConfig/dynamic.NewForConfig/metadata.NewForConfig/rest.RESTClientFor(AndClient)。- Dynamic(除非对 CRD 做了 code-gen)。
- 最终都到
rest.Request的.Do()→http.Client.Do(模块 1)。 - 只有
kubernetes.NewForConfig组装整棵 Clientset。 *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 · 配合第一阶段学习使用
更多推荐



所有评论(0)