一转眼,又是一年高考时,清晨赶考的紧张,笔尖沙沙作响的专注,还有交卷后如释重负的瞬间——那种感觉一定刻骨铭心!
高考已经结束,祝所有高考学子心想事成,金榜题名!无论结果如何,这段拼搏的青春都将闪闪发光,加油!
上周,有CILIKUBE社区里的小伙伴咨询,开发k8s管理平台的时候,客户端到底有什么作用,应该怎么用?不要着急,我们这就一起来学习探讨下。
想象一下,Kubernetes(K8s)集群是一个巨大的宇宙飞船,里面装满了资源(Node、Pod、Deployment 等),而你作为“舰长”,需要一个控制台来指挥这艘飞船。这个控制台就是 Kubernetes 客户端!通过客户端,你可以查看飞船状态(查询资源)、发射新模块(创建 Pod)、调整航线(更新 Deployment),甚至实时监控(Watch)。
在 Kubernetes 开发中,client-go 库是官方提供的“控制台”,包含多种客户端:RESTClient、Clientset、Dynamic Client,还有基于 client-go 的高级工具 Controller-runtime。这些客户端就像飞船上的不同仪表盘,各有特点,适用不同场景。那么,它们有什么区别?如何选择?
希里安将带你从“小白”到“舰长”,全面解析 Kubernetes 客户端的秘密!
Kubernetes 客户端是开发者与 Kubernetes API 服务器交互的桥梁。API 服务器提供 RESTful 接口(HTTP 请求),客户端通过这些接口管理集群资源。客户端的核心功能包括:
client-go 是 Kubernetes 官方的 Go 语言客户端库,被广泛用于 kubectl、Operator 和自定义管理平台,它提供了以下客户端:
接下来,我们逐一介绍这些客户端
RESTClient 是 client-go 中最原始、最底层(k8s.io/client-go/rest)的客户端。直接封装 HTTP 请求,与 Kubernetes API 服务器进行“原始对话”。它不提供类型化的资源操作,而是手动指定 API 路径和 JSON 数据。你可以把它想象成一个“半自动”的 HTTP 客户端,专门用于和符合 Kubernetes API 规范的 RESTful 接口进行通信。
如何使用?
RESTClient 需要手动构造请求路径和解析响应。以下是获取节点信息的示例:
package main
import (
"context"
"fmt"
"os"
"path/filepath"
corev1 "k8s.io/api/core/v1"
"k8s.io/client-go/kubernetes/scheme"
"k8s.io/client-go/rest"
"k8s.io/client-go/tools/clientcmd"
)
func main() {
// 获取 kubeconfig 文件路径(默认 ~/.kube/config)
kubeconfig := filepath.Join(os.Getenv("HOME"), ".kube", "config")
if envKubeconfig := os.Getenv("KUBECONFIG"); envKubeconfig != "" {
kubeconfig = envKubeconfig
}
// 从 kubeconfig 构建配置
config, err := clientcmd.BuildConfigFromFlags("", kubeconfig)
if err != nil {
fmt.Printf("无法加载 kubeconfig: %v\n", err)
os.Exit()
}
// 设置 GroupVersion 和 APIPath
config.GroupVersion = &corev1.SchemeGroupVersion // 设置为 core/v1
config.APIPath = "/api" // core 资源的 API 路径
config.NegotiatedSerializer = scheme.Codecs // 使用 scheme.Codecs 提供序列化器
// 创建 RESTClient
restClient, err := rest.RESTClientFor(config)
if err != nil {
fmt.Printf("无法创建 RESTClient: %v\n", err)
os.Exit()
}
// 获取节点列表
nodeList := &corev1.NodeList{}
err = restClient.Get().
AbsPath("/api/v1/nodes").
Do(context.TODO()).
Into(nodeList)
if err != nil {
fmt.Printf("无法获取节点列表: %v\n", err)
os.Exit()
}
// 打印所有节点名称
fmt.Println("集群中的节点:")
for _, node := range nodeList.Items {
fmt.Printf("- %s\n", node.Name)
}
}特点:
v1.Pod),它只负责构建 HTTP 请求(指定 Verb, Path, Body 等),然后发送给 API Server,并接收返回的 []byte 数据RESTClient 本身不负责将 JSON/Protobuf 数据转换成 Go 结构体。这个工作需要一个叫做 Scheme 的组件来配合完成RESTClient优缺点
注意:除非你在从零构建一个 Kubernetes 客户端库,否则不建议用!
使用场景:
说实话,你几乎永远不会直接使用它。它太底层了,使用起来非常繁琐。它的存在主要是为了给上层客户端提供统一的 HTTP 通信基础。了解它,是为了帮助我们理解 K8s 客户端的通信原理
Clientset 是 client-go 的静态客户端(k8s.io/client-go/kubernetes),为 Kubernetes 内置资源(如 Node、Pod、Deployment)提供类型化的 Go 接口。它基于 Clientset 结构体,包含所有 API 组的客户端(CoreV1、AppsV1 等),这是最广为人知的客户端,也是处理 K8s 内置资源的首选。
如何使用?
Clientset 通过 kubernetes.NewForConfig 创建,提供直观的方法。以下是获取节点的示例:
import (
"context"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
"fmt"
"k8s.io/apimachinery/pkg/apis/meta/v1"
)
func main() {
config, err := rest.InClusterConfig()
if err != nil {
panic(err)
}
clientset, err := kubernetes.NewForConfig(config)
if err != nil {
panic(err)
}
node, err := clientset.CoreV1().Nodes().Get(context.TODO(), "node-1", v1.GetOptions{})
if err != nil {
fmt.Printf("Error: %v\n", err)
return
}
fmt.Printf("Node: %s\n", node.Name)
}特点
优缺点
适用场景
当 clientset 面对未知的自定义资源(CRD)时,就无能为力了。这时,动态客户端闪亮登场。Dynamic Client 是 client-go 的动态客户端(k8s.io/client-go/dynamic),支持操作任意 Kubernetes 资源,包括自定义资源(CRD)。它使用 unstructured.Unstructured 表示资源,基于 JSON 的键值对
如何使用?
Dynamic Client 需要指定 GroupVersionResource(GVR)。以下是获取节点的示例:
import (
"context"
"k8s.io/client-go/dynamic"
"k8s.io/client-go/rest"
"k8s.io/apimachinery/pkg/runtime/schema"
"fmt"
)
func main() {
config, err := rest.InClusterConfig()
if err != nil {
panic(err)
}
dynamicClient, err := dynamic.NewForConfig(config)
if err != nil {
panic(err)
}
gvr := schema.GroupVersionResource{Group: "", Version: "v1", Resource: "nodes"}
node, err := dynamicClient.Resource(gvr).Get(context.TODO(), "node-1", v1.GetOptions{})
if err != nil {
fmt.Printf("Error: %v\n", err)
return
}
fmt.Printf("Node: %s\n", node.GetName())
}特点
如何工作:
它同样基于 RESTClient。但它不依赖 Scheme 进行强类型转换,而是将所有资源都处理为 unstructured.Unstructured 类型,这本质上是一个 map[string]interface{}。你需要手动从这个 map 中提取你需要的字段
注意:CRD 和通用工具的救星,但小心“灵活”的代价!
适用场景:
随着 Operator 模式的兴起,开发者发现直接使用 clientset 和 dynamic client 来编写复杂的控制器逻辑,需要处理大量的缓存、索引和错误重试等模板代码。于是,controller-runtime 项目应运而生。
controller-runtime/pkg/client 提供了一个更高层次的抽象客户端,它堪称是 K8s 客户端的“集大成者”。Controller-runtime 是基于 client-go 的高级库(sigs.k8s.io/controller-runtime),专为开发 Kubernetes 控制器和 Operator 设计。它封装了 Clientset 和 Dynamic Client,并引入了缓存机制和事件驱动模型。
核心组件:
优缺点 优点:智能缓存,统一接口,Operator 开发的最佳实践 缺点:有学习成本,需理解缓存和 Reconcile 机制
注意:Operator 开发的“智能大脑”,省心但需先学会!
特点:
client.Client 接口,可以无缝地处理内置资源和 CRD。你不再需要在代码中维护两个不同的客户端实例。GET 和 LIST 请求路由到一个本地的 Informer 缓存中,而不是直接请求 API Server。这极大地降低了 API Server 的负载,对于需要频繁读取资源的 Operator 来说是至关重要的。Create, Update, Delete)会绕过缓存,直接发送给 API Server,保证了数据的一致性。Scheme 紧密集成:在初始化时,你将所有需要处理的类型(内置和 CRD)注册到 Scheme 中,controller-runtime 客户端就能自动地、类型安全地处理它们。适用场景:
几乎是所有现代 K8s Operator 或控制器的不二之选。如果你在使用 Kubebuilder 或 Operator SDK 框架来构建你的应用,你接触到的就是这个客户端。它优雅地解决了直接使用低级客户端的各种痛点。
如何使用? Controller-runtime 通常与 Operator SDK 一起使用。以下是监控 Pod 变化的示例:
import (
"context"
"k8s.io/client-go/rest"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/client/config"
"sigs.k8s.io/controller-runtime/pkg/manager"
corev1 "k8s.io/api/core/v1"
"fmt"
)
func main() {
cfg, err := config.GetConfig()
if err != nil {
panic(err)
}
mgr, err := manager.New(cfg, manager.Options{})
if err != nil {
panic(err)
}
c, err := client.New(cfg, client.Options{})
if err != nil {
panic(err)
}
podList := &corev1.PodList{}
err = c.List(context.TODO(), podList, client.InNamespace("default"))
if err != nil {
fmt.Printf("Error: %v\n", err)
return
}
for _, pod := range podList.Items {
fmt.Printf("Pod: %s\n", pod.Name)
}
}Discovery Client(k8s.io/client-go/discovery)用于查询 Kubernetes API 服务器支持的 API 版本和资源类型,辅助其他客户端
如何使用? 以下是获取服务器版本的示例:
import (
"k8s.io/client-go/discovery"
"k8s.io/client-go/rest"
"fmt"
)
func main() {
config, err := rest.InClusterConfig()
if err != nil {
panic(err)
}
discoveryClient, err := discovery.NewDiscoveryClientForConfig(config)
if err != nil {
panic(err)
}
version, err := discoveryClient.ServerVersion()
if err != nil {
fmt.Printf("Error: %v\n", err)
return
}
fmt.Printf("Kubernetes Version: %s\n", version.GitVersion)
}特点
适用场景
客户端类型 | 主要特点 | 核心优势 | 核心劣势 | 一句话选择指南 |
|---|---|---|---|---|
RESTClient | 底层 HTTP 封装 | 究极灵活 | 使用极其繁琐,无类型 | (别用)除非你在从零构建一个 K8s 客户端库 |
Clientset | 强类型,面向内置资源 | 类型安全,IDE 友好,简单直接 | 无法操作 CRD | 操作 K8s 内置资源(Pod, Deployment 等) |
Dynamic Client | 弱类型,面向任意资源 | 无需编译时类型,通吃所有资源 | 无类型安全,易出错,体验差 | 需要操作不确定的 CRD 或编写通用工具 |
Controller-runtime | 高层抽象,内置缓存 | 统一接口,智能缓存,Operator 最佳实践 | 需要理解其缓存机制,略有学习成本 | 编写任何需要监听资源变化的 Operator/Controller |
Kubernetes 客户端是开发资源管理平台、控制器和 Operator 的核心工具。client-go 提供了从底层到高层的完整解决方案:
RESTClient 是地基,处理最底层的通信Clientset 和 Dynamic Client 是第一层楼,提供了两种不同风格的、可以直接使用的工具:一个专精,一个通用Controller-runtime Client 是顶层豪华套房,它整合了下层的能力,并增加了缓存等高级功能,为复杂的控制器开发提供了最佳实践行动起来:
如果你想实践以上k8s客户端,CILIKUBE适合你,还不知道CILIKUBE是什么?查看这篇文章CiliKube开源啦!让小白一次学会K8s 运维 + Web 开发 + k8s二次开发(Vue3+Go 全栈,免费开源),觉得 CILIKUBE对你有帮助,或者你对云原生和 Kubernetes 充满热情,诚挚邀请你:
GitHub 项目地址: https://github.com/ciliverse/cilikube
在线演示: https://cilikubedemo.cillian.website/