# go-client **Repository Path**: duyus/go-client ## Basic Information - **Project Name**: go-client - **Description**: No description available - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2025-08-04 - **Last Updated**: 2025-08-04 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # go-client学习 ## 核心概念 一个可以很好帮助理解k8s的观点是: * 可以把k8s看成一个资源对象的管理平台。k8s的核心就是将运维常用到的资源做了统一的抽象。 * node对k8s来说是一个对象,pod也是一个对象。用户对k8s上的操作本质上可以看成对象的操作。 * 通过对于对象的抽象,k8s实现了不同的增删改查等逻辑(业务的尽头是crud)。 * 就像我们开发一个图书管理系统,我们设计获取图书接口的url可以是Get方法下的ip:port/book。 既然是crud那我们肯定有接口,我们也肯定要用请求url,我们可以通过`kubectl get node -v 9` 这条命令看看kubectl在背地里做了什么,是不是符合我们的分析,输入命令后,我们可以看到如下日志: ```go I0524 15:32:06.433289 23846 loader.go:395] Config loaded from file: /Users/icecool/.kube/config I0524 15:32:06.445627 23846 round_trippers.go:466] curl -v -XGET -H "User-Agent: kubectl/v1.30.1 (darwin/arm64) kubernetes/6911225" -H "Accept: application/json;as=Table;v=v1;g=meta.k8s.io,application/json;as=Table;v=v1beta1;g=meta.k8s.io,application/json" 'https://175.27.192.40:443/api/v1/nodes?limit=500' I0524 15:32:06.448543 23846 round_trippers.go:510] HTTP Trace: Dial to tcp:175.27.192.40:443 succeed I0524 15:32:06.643821 23846 round_trippers.go:553] GET https://175.27.192.40:443/api/v1/nodes?limit=500 200 OK in 198 milliseconds I0524 15:32:06.643866 23846 round_trippers.go:570] HTTP Statistics: DNSLookup 0 ms Dial 0 ms TLSHandshake 102 ms ServerProcessing 90 ms Duration 198 ms ``` 简单看一下,首先读文件,然后发送了curl请求请求了一个接口,和我们分析的一样。而请求地址就是我们的apiserver ,实时上这些url都可以在官方文档中找到 [Node | Kubernetes](https://kubernetes.io/zh-cn/docs/reference/kubernetes-api/cluster-resources/node-v1/#Operations) 因此,对k8s集群的操作本质上还是网络请求,我们通过与apiserver作交互,然后对对象资源进行操作,不管是client-go还是kubectl,它们本质都是这个逻辑。因此我们也可以在一些架构图中看到这对华文华武都作为客户端放在最上层。 那么随着k8s的版本迭代,诞生了很多对象,具体怎么做区别呢,我们引入第一个概念:GKV ## GKV 通过gvk我们可以确定我们请求的具体资源的url了 [参考链接](https://blog.csdn.net/qq_24433609/article/details/122999327) ### 客户端类型 知道了请求地址,我们当然就可以去进行访问了,对于写代码来说,我们写http请求的时候往往会封装一个工具类,方便我们使用,client-go就有一个原始客户端是干这个活的 RESTClient:这个客户端提供了最简单的rest请求封装, 我们可以看到在请求时需要显示声明请求的对象Resource和apiVersion看起来非常不方便,既然我们是在对对象进行操作,可以将这些比较固定的信息做进一步的封装呢。 ### ClientSet ClientSet 是比较常用的一个 client,常用于对 K8s 内部资源做 CRUD 或查询当前集群拥有什么资源,我们的demo就使用了ClientSet,非常的简洁和方便,Clientset是所有Group和Version组合对象的集合,新手同学可以会感慨这么多api,我该怎么快速的知道要调啥,其实很简单,打开官方文档[工作负载资源 | Kubernetes](https://kubernetes.io/zh-cn/docs/reference/kubernetes-api/workload-resources/) ### DynamicClient ClientSet虽好,但是它只能操作k8s的原生对象,对于需要二开的同学来说可以选择DynamicClient DynamicClient是一种动态客户端,它可以对任意Kubernetes资源进行RESTful操作,包括CRD自定义资源。 DynamicClient与ClientSet操作类似,同样封装了RESTClient,同样提供了Create、Update、Delete、Get、List、Watch、Patch等方法。 ### DiscoveryClient Kubernetes API Server暴露出/api和/apis接口。 DiscoveryClient通过RESTClient分别请求/api和/apis接口,从而获取Kubernetes API Server所支持的资源组、资源版本、资源信息。 DiscoveryClient可以将资源相关信息存储于本地,默认存储位置为~/.kube/cache和~/.kube/http-cache。 缓存可以减轻client-go对Kubernetes API Server的访问压力。默认每10分钟与Kubernetes API Server同步一次,同步周期较长,因为资源组、源版本、资源信息一般很少变动。 ## client-go的使用注意事项 k8s 的集群存储信息存储信息有二级缓存效果,即 api-server 有 etcd的缓存,其它组件(cm,scheduler,kubelet)有api-server的缓存,各组件通过list&watch机制跟踪对象的更新,从而保证在减少对apiserver和etcd的直接请求损耗与数据时效性。etcd是整个k8s集群运营的重中之重,一旦被打崩,会引发集群崩溃。 因此,在大部分场景下,各组件应该去请求api-server而不是etcd。使用client-go的程序更要注意这一原则。在list请求时需要加上resourveVersion=0的参数,这个参数与etcd的Revision 机制对应,对于每个key,etcd都会记录他们的reversion(创建,更新),当Revision = 0时会返回该key的最新数据。如果没有加resourveVersion=0 参数,则会导致请求etcd,且在条件查询场景下,因为etcd是单纯的kv数据库,识别不出k8s中的label等元信息,就到导致apiserver会先去etcd list全量数据,再在内存里进行过滤操作,在集群规模庞大的情况下可能会耗尽资源,具体可参考下面这篇博客: https://arthurchiao.art/blog/k8s-reliability-list-data-zh/#client_code_empty_rv 这里主要看一下k8s判断是否需要走etcd的代码: ```go // staging/src/k8s.io/apiserver/pkg/util/flowcontrol/request/list_work_estimator.go func shouldDelegateList(opts storage.ListOptions) bool { resourceVersion := opts.ResourceVersion pred := opts.Predicate match := opts.ResourceVersionMatch consistentListFromCacheEnabled := utilfeature.DefaultFeatureGate.Enabled(features.ConsistentListFromCache) // Serve consistent reads from storage if ConsistentListFromCache is disabled consistentReadFromStorage := resourceVersion == "" && !consistentListFromCacheEnabled // Watch cache doesn't support continuations, so serve them from etcd. hasContinuation := len(pred.Continue) > 0 // Serve paginated requests about revision "0" from watch cache to avoid overwhelming etcd. hasLimit := pred.Limit > 0 && resourceVersion != "0" // Watch cache only supports ResourceVersionMatchNotOlderThan (default). unsupportedMatch := match != "" && match != metav1.ResourceVersionMatchNotOlderThan return consistentReadFromStorage || hasContinuation || hasLimit || unsupportedMatch } ``` 可以看到有4个条件,只要有一个为true,就会走etcd,总结如下: 1. 一致性读取从存储中:当 resourceVersion 为空且 ConsistentListFromCache 特性未启用时。 2. 有继续标记:当请求包含继续标记(pred.Continue 的长度大于 0)时。 3. 有分页限制:当请求是分页的(pred.Limit 大于 0)且 resourceVersion 不等于 "0" 时。 4. 不支持的资源版本匹配:当 match 不为空且不等于 metav1.ResourceVersionMatchNotOlderThan 时。 因此在查询场景中需要注意规避这4点,调整查询格式。 假如对数据的时效性确实有频繁且准确的需求,则可以使用ListWatch/informer的形式。