重新认识Clientset

1、介绍

Clientset 是调用 Kubernetes 资源对象最常用的客户端,可以操作所有的资源对象。

那么在 Clientset 中使如何用这些资源的呢?

因为在 staging/src/k8s.io/api 下面定义了各种类型资源的规范,然后将这些规范注册到了全局的 Scheme 中。这样就可以在Clientset中使用这些资源了。

2、示例

首先我们来看下如何通过 Clientset 来获取资源对象,我们这里来创建一个 Clientset 对象,然后通过该对象来获取默认命名空间之下的 Deployments 列表,代码如下所示:

package main

import (
"flag"
"fmt"
"os"
"path/filepath" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
"k8s.io/client-go/tools/clientcmd"
) func main() {
var err error
var config *rest.Config
var kubeconfig *string if home := homeDir(); home != "" {
kubeconfig = flag.String("kubeconfig", filepath.Join(home, ".kube", "config"), "(optional) absolute path to the kubeconfig file")
} else {
kubeconfig = flag.String("kubeconfig", "", "absolute path to the kubeconfig file")
}
flag.Parse() // 使用 ServiceAccount 创建集群配置(InCluster模式)
if config, err = rest.InClusterConfig(); err != nil {
// 使用 KubeConfig 文件创建集群配置
if config, err = clientcmd.BuildConfigFromFlags("", *kubeconfig); err != nil {
panic(err.Error())
}
} // 创建 clientset
clientset, err := kubernetes.NewForConfig(config)
if err != nil {
panic(err.Error())
} // 使用 clientsent 获取 Deployments
deployments, err := clientset.AppsV1().Deployments("default").List(metav1.ListOptions{})
if err != nil {
panic(err)
}
for idx, deploy := range deployments.Items {
fmt.Printf("%d -> %s\n", idx+1, deploy.Name)
} } func homeDir() string {
if h := os.Getenv("HOME"); h != "" {
return h
}
return os.Getenv("USERPROFILE") // windows
}

这是一个非常典型的访问 Kubernetes 集群资源的方式,通过 client-go 提供的 Clientset 对象来获取资源数据,主要有以下三个步骤:

  1. 使用 kubeconfig 文件或者 ServiceAccount(InCluster 模式)来创建访问 Kubernetes API 的 Restful 配置参数,也就是代码中的 rest.Config 对象
  2. 使用 rest.Config 参数创建 Clientset 对象,这一步非常简单,直接调用 kubernetes.NewForConfig(config) 即可初始化
  3. 然后是 Clientset 对象的方法去获取各个 Group 下面的对应资源对象进行 CRUD 操作

3、Clientset 对象

上面我们了解了如何使用 Clientset 对象来获取集群资源,接下来我们来分析下 Clientset 对象的实现。

上面我们使用的 Clientset 实际上是对各种资源类型的 Clientset 的一次封装:

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

// NewForConfig 通过给定的 config 创建一个新的 Clientset
func NewForConfig(c *rest.Config) (*Clientset, error) {
configShallowCopy := *c if configShallowCopy.UserAgent == "" {
configShallowCopy.UserAgent = rest.DefaultKubernetesUserAgent()
} // share the transport between all clients
httpClient, err := rest.HTTPClientFor(&configShallowCopy)
if err != nil {
return nil, err
} return NewForConfigAndClient(&configShallowCopy, httpClient)
} func NewForConfigAndClient(c *rest.Config, httpClient *http.Client) (*Clientset, error) {
configShallowCopy := *c
if configShallowCopy.RateLimiter == nil && configShallowCopy.QPS > 0 {
if configShallowCopy.Burst <= 0 {
return nil, fmt.Errorf("burst is required to be greater than 0 when RateLimiter is not set and QPS is set to greater than 0")
}
configShallowCopy.RateLimiter = flowcontrol.NewTokenBucketRateLimiter(configShallowCopy.QPS, configShallowCopy.Burst)
} var cs Clientset
var err error // 将其他 Group 和版本的资源的 RESTClient 封装到全局的 Clientset 对象中
cs.admissionregistrationV1, err = admissionregistrationv1.NewForConfigAndClient(&configShallowCopy, httpClient)
if err != nil {
return nil, err
} cs.appsV1, err = appsv1.NewForConfigAndClient(&configShallowCopy, httpClient)
if err != nil {
return nil, err
}
......
return &cs, nil
}

上面的 NewForConfig 函数最后调用了NewForConfigAndClient函数,NewForConfigAndClient里面就是将其他的各种资源的 RESTClient 封装到了全局的 Clientset 中,这样当我们需要访问某个资源的时候只需要使用 Clientset 里面包装的属性即可,比如 clientset.CoreV1() 就是访问 Core 这个 Group 下面 v1 这个版本的 RESTClient。这些局部的 RESTClient 都定义在 staging/src/k8s.io/client-go/typed///_client.go 文件中,比如 staging/src/k8s.io/client-go/kubernetes/typed/apps/v1/apps_client.go 这个文件中就是定义的 apps 这个 Group 下面的 v1 版本的 RESTClient,这里同样以 Deployment 为例:

// staging/src/k8s.io/client-go/kubernetes/typed/apps/v1/apps_client.go

func NewForConfig(c *rest.Config) (*AppsV1Client, error) {
config := *c
if err := setConfigDefaults(&config); err != nil {
return nil, err
}
httpClient, err := rest.HTTPClientFor(&config)
if err != nil {
return nil, err
} // 最后还是通过调用 NewForConfigAndClient 函数,返回AppsV1这个资源对象的Clientset
return NewForConfigAndClient(&config, httpClient)
} func setConfigDefaults(config *rest.Config) error {
// 资源对象的GroupVersion
gv := v1.SchemeGroupVersion
config.GroupVersion = &gv
// 资源对象的根目录
config.APIPath = "/apis"
// 使用注册的资源类型 Scheme 对请求和响应进行编解码,Scheme 就是资源类型的规范
config.NegotiatedSerializer = scheme.Codecs.WithoutConversion() if config.UserAgent == "" {
config.UserAgent = rest.DefaultKubernetesUserAgent()
} return nil
} func (c *AppsV1Client) Deployments(namespace string) DeploymentInterface {
return newDeployments(c, namespace)
} // staging/src/k8s.io/client-go/kubernetes/typed/apps/v1/deployment.go // deployments implements DeploymentInterface
// deployments 实现了 DeploymentInterface 接口
type deployments struct {
client rest.Interface
ns string
} // newDeployments returns a Deployments
// newDeployments 实例化 deployments 对象
func newDeployments(c *AppsV1Client, namespace string) *deployments {
return &deployments{
client: c.RESTClient(),
ns: namespace,
}
}

通过上面代码我们就可以很清晰的知道可以通过 clientset.AppsV1().Deployments("default")来获取一个 deployments 对象,然后该对象下面定义了 deployments 对象的 CRUD 操作,比如我们调用的 List() 函数:

// staging/src/k8s.io/client-go/kubernetes/typed/apps/v1/deployment.go

// List takes label and field selectors, and returns the list of Deployments that match those selectors.
func (c *deployments) List(ctx context.Context, opts metav1.ListOptions) (result *v1.DeploymentList, err error) {
var timeout time.Duration
if opts.TimeoutSeconds != nil {
timeout = time.Duration(*opts.TimeoutSeconds) * time.Second
}
result = &v1.DeploymentList{}
err = c.client.Get().
Namespace(c.ns).
Resource("deployments").
VersionedParams(&opts, scheme.ParameterCodec).
Timeout(timeout).
Do(ctx).
Into(result)
return
}

从上面代码可以看出最终是通过 c.client 去发起的请求,也就是局部的 restClient 初始化的函数中通过 rest.RESTClientFor(&config) 创建的对象,也就是将 rest.Config 对象转换成一个 Restful 的 Client 对象用于网络操作:

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

// RESTClientFor返回一个满足客户端Config对象上所要求属性的RESTClient。
// 请注意,在初始化客户端时,一个RESTClient可能需要一些可选的字段。
// 由该方法创建的RESTClient是通用的
// 它期望在遵循Kubernetes惯例的API上操作,但可能不是Kubernetes的API。
// RESTClientFor等同于调用RESTClientForConfigAndClient(config, httpClient)
// 其中httpClient是用HTTPClientFor(config)生成的。
func RESTClientFor(config *Config) (*RESTClient, error) {
if config.GroupVersion == nil {
return nil, fmt.Errorf("GroupVersion is required when initializing a RESTClient")
}
if config.NegotiatedSerializer == nil {
return nil, fmt.Errorf("NegotiatedSerializer is required when initializing a RESTClient")
} // Validate config.Host before constructing the transport/client so we can fail fast.
// ServerURL will be obtained later in RESTClientForConfigAndClient()
_, _, err := defaultServerUrlFor(config)
if err != nil {
return nil, err
} httpClient, err := HTTPClientFor(config)
if err != nil {
return nil, err
} return RESTClientForConfigAndClient(config, httpClient)
}

到这里我们就知道了 Clientset 是基于 RESTClient 的,RESTClient 是底层的用于网络请求的对象,可以直接通过 RESTClient 提供的 RESTful 方法如 Get()、Put()、Post()、Delete() 等和 APIServer 进行交互。

同时支持JSON和protobuf两种序列化方式,支持所有原生资源。

当主食化RESTClient过后,就可以发起网络请求了,比如对于Deployment的List操作:

// staging/src/k8s.io/client-go/kubernetes/typed/apps/v1/deployment.go

func (c *deployments) List(ctx context.Context, opts metav1.ListOptions) (result *v1.DeploymentList, err error) {
var timeout time.Duration
if opts.TimeoutSeconds != nil {
timeout = time.Duration(*opts.TimeoutSeconds) * time.Second
}
result = &v1.DeploymentList{}
err = c.client.Get().
Namespace(c.ns).
Resource("deployments").
VersionedParams(&opts, scheme.ParameterCodec).
Timeout(timeout).
Do(ctx).
Into(result)
return
}

上面通过调用 RestClient 发起网络请求,真正发起网络请求的代码如下所示:

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

// request connects to the server and invokes the provided function when a server response is
// received. It handles retry behavior and up front validation of requests. It will invoke
// fn at most once. It will return an error if a problem occurred prior to connecting to the
// server - the provided function is responsible for handling server errors.
func (r *Request) request(ctx context.Context, fn func(*http.Request, *http.Response)) error {
//Metrics for total request latency
start := time.Now()
defer func() {
metrics.RequestLatency.Observe(ctx, r.verb, *r.URL(), time.Since(start))
}() if r.err != nil {
klog.V(4).Infof("Error in request: %v", r.err)
return r.err
} if err := r.requestPreflightCheck(); err != nil {
return err
} // 初始化网络客户端
client := r.c.Client
if client == nil {
client = http.DefaultClient
} // Throttle the first try before setting up the timeout configured on the
// client. We don't want a throttled client to return timeouts to callers
// before it makes a single request.
if err := r.tryThrottle(ctx); err != nil {
return err
} // 超时处理
if r.timeout > 0 {
var cancel context.CancelFunc
ctx, cancel = context.WithTimeout(ctx, r.timeout)
defer cancel()
} isErrRetryableFunc := func(req *http.Request, err error) bool {
// "Connection reset by peer" or "apiserver is shutting down" are usually a transient errors.
// Thus in case of "GET" operations, we simply retry it.
// We are not automatically retrying "write" operations, as they are not idempotent.
if req.Method != "GET" {
return false
}
// For connection errors and apiserver shutdown errors retry.
if net.IsConnectionReset(err) || net.IsProbableEOF(err) {
return true
}
return false
} // Right now we make about ten retry attempts if we get a Retry-After response.
// 重试机制
retry := r.retryFn(r.maxRetries)
for {
if err := retry.Before(ctx, r); err != nil {
return retry.WrapPreviousError(err)
} // 构造请求对象
req, err := r.newHTTPRequest(ctx)
if err != nil {
return err
} // 发起网络请求
resp, err := client.Do(req)
updateURLMetrics(ctx, r, resp, err)
// The value -1 or a value of 0 with a non-nil Body indicates that the length is unknown.
// https://pkg.go.dev/net/http#Request
if req.ContentLength >= 0 && !(req.Body != nil && req.ContentLength == 0) {
metrics.RequestSize.Observe(ctx, r.verb, r.URL().Host, float64(req.ContentLength))
}
retry.After(ctx, r, resp, err) done := func() bool {
defer readAndCloseResponseBody(resp) // if the the server returns an error in err, the response will be nil.
f := func(req *http.Request, resp *http.Response) {
if resp == nil {
return
}
fn(req, resp)
} if retry.IsNextRetry(ctx, r, req, resp, err, isErrRetryableFunc) {
return false
} f(req, resp)
return true
}()
if done {
return retry.WrapPreviousError(err)
}
}
}

到这里就完成了一次完整的网络请求。

其实 Clientset 对象也就是将 rest.Config 封装成了一个 http.Client 对象而已,最终还是利用 golang 中的 http 库来执行一个正常的网络请求而已。

重新认识Clientset的更多相关文章

  1. client-go实战之三:Clientset

    欢迎访问我的GitHub https://github.com/zq2599/blog_demos 内容:所有原创文章分类汇总及配套源码,涉及Java.Docker.Kubernetes.DevOPS ...

  2. redis并发问题

    redis中的并发问题 使用redis作为缓存已经很久了,redis是以单进程的形式运行的,命令是一个接着一个执行的,一直以为不会存在并发的问题,直到今天看到相关的资料,才恍然大悟~~ 具体问题实例 ...

  3. redis cluster php 客户端 predis

    php有redis的扩展,目前来说,还不支持redis cluster,推荐一下predis,功能比较全,从单个,到主从,到cluster都是支持的.效率怎么样,要靠自己去测试一下. 1,下载pred ...

  4. [PHP]Yaf + composer 引起大幅性能下降

    composer.json 文件可以用命令 composer init 创建,命令是交互式的. 也可以直接编辑一个 json 文件,如下: repositories 中 url 使用中国全量镜像地址. ...

  5. php修改和增加xml结点属性

    <?xml version="1.0" encoding="UTF-8" ?> <clientSet> <server url=& ...

  6. php 修改、增加xml结点属性的实现代码

    php修改xml结点属性,增加xml结点属性的代码,有需要的朋友可以参考下 php 修改 增加xml结点属性的代码,供大家学习参考.php修改xml结点属性,增加xml结点属性的代码,有需要的朋友,参 ...

  7. inno setup 执行SQL

    参考之:1.可将导入数据的功能写入一个小程序,再外部调用(楼上已经说了):2.可用程序代码:[Setup] AppName=科發醫院管理系統 AppVerName=科發醫院管理系統4.0 AppPub ...

  8. redis 扩展安装使用

    Redis的php客户端库非常之多, Redis推荐客户端链接是:http://redis.io/clients 推荐用phpredis,下载地址:https://github.com/nicolas ...

  9. mfc socket编程

    socket编程用法---- 随着计算机网络化的深入,计算机网络编程在程序设计的过程中变得日益重要.由于C++语言对底层操作的优越性,许多文章都曾经介绍过用VC++进行Socket编程的方法.但由于都 ...

  10. Kubernetes e2e test and test framework

    前言 Kubernetes的成功少不了大量工程师的共同参与,而他们之间如何高效的协作,非常值得我们探究.最近研究和使用了他们的e2e测试和框架,还是挺有启发的. 怎样才是好的e2e测试? 不同的人写出 ...

随机推荐

  1. animation 与transform一起使用时 transofrm不起作用

    先占坑 干完活再解决 先占坑 干完活再解决先占坑 干完活再解决先占坑 干完活再解决先占坑 干完活再解决先占坑 干完活再解决先占坑 干完活再解决先占坑 干完活再解决先占坑 干完活再解决先占坑 干完活再解 ...

  2. 免费的编程连字等宽字体:Fira Code

    免费的编程连字等宽字体:Fira Code 介绍和特征 介绍 Fira 是 Mozilla 公司 主推的字体系列.Fira Code 专为写程序而生,开源免费.除了具有等宽等基本属性外,还加入了编程连 ...

  3. goland Cannot resolve import 'google/api/annotations.proto'

    前言 kratos 项目出现 import 标红,但是 $GOPATH/src/google/api/annotations.proto 已经存在了. 解决 路径:Goland > Settin ...

  4. Joker 智能开发平台再推重磅新品,引领开发新潮流

    自 Joker 智能开发平台的前端可视化版本惊艳亮相后,便迅速在行业内掀起热潮,其创新的理念与出色的性能,为开发者和企业打造了高效.便捷的开发新路径,备受瞩目与好评.如今,Joker 智能开发平台即将 ...

  5. 团队小规模本地大模型服务平台搭建 - Ubuntu

    实现目标和考虑因素 部署一个支持多用户同时使用.多模型运行的离线局域网大模型服务器 需要考虑以下几个关键因素: 大模型的加载和管理.使用一个基础大模型,根据实战需要创建多个专用模型,模型管理方便可靠. ...

  6. Sa-Token v1.41.0 发布 🚀,来看看有没有令你心动的功能!

    Sa-Token 是一个轻量级 Java 权限认证框架,主要解决:登录认证.权限认证.单点登录.OAuth2.0.微服务网关鉴权 等一系列权限相关问题. 目前最新版本 v1.41.0 已推送至 Mav ...

  7. Effective Java理解笔记系列-第2条-何时考虑用构建器?

    为什么写这系列博客? 在阅读<Effective Java>这本书时,我发现有许多地方需要仔细认真地慢慢阅读并且在必要时查阅相关资料才能彻底搞懂,相信有些读者在阅读此书时也有类似感受:同时 ...

  8. 国产化-内存数据库tendis-单机安装(完美替代redis)

    挺好的产品腾讯能开源还是体现了大厂的担当和格局,赞一个.阿里也开源了一些不错的产品后面讲. Tendis 介绍 Tendis 是腾讯公司开源的一款高性能分布式存储系统,基于 Redis 协议开发,具有 ...

  9. 【SpringCloud】SpringCloud Alibaba Sentinel实现熔断与限流

    SpringCloud Alibaba Sentinel实现熔断与限流 限流与降级 限流 blockHandler 降级 fallback 降级需要运行时出现异常才会触发,而限流一旦触发,你连运行的机 ...

  10. Windows Server 2012 配置 FTP

    环境 Windows Server 2012 安装步骤 打开服务器管理器 管理 - 添加角色和功能 开始之前 选择安装类型 服务器选择 选择对应的服务器... 服务器角色 功能 确认 安装进度 配置步 ...