Clientset是什么

结论:Clientset 是 一系列 RESTClient 的 集合。
使用 RESTClient 操作kubernetes资源,太麻烦了

  • 要操作 pods,需要指定config,给config设置 APIPath 为 “/api”、设置序列化器、设置 GroupVersion,最后还要调用 rest.RESTClientFor(config) 得到一个 用于操作pods的Clientset
  • 而如果我要操作 deployment,这个过程又需要写一遍,然后又得到一个 用于操作deployment的Clientset
  • 代码冗余,不优雅,而且到处创建Clientset,耗时又浪费资源
    因此,就有了事先创建 各种资源的RESTClient,存起来备用的需求。Clientset就是这样封装起来的一个set集合。

Clientset的结构体

位于 /kubernetes/clientset.go 中,Clientset结构体:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
type Clientset struct {
......
appsV1 *appsv1.AppsV1Client
appsV1beta1 *appsv1beta1.AppsV1beta1Client
appsV1beta2 *appsv1beta2.AppsV1beta2Client
authenticationV1 *authenticationv1.AuthenticationV1Client
authenticationV1alpha1 *authenticationv1alpha1.AuthenticationV1alpha1Client
authenticationV1beta1 *authenticationv1beta1.AuthenticationV1beta1Client
authorizationV1 *authorizationv1.AuthorizationV1Client
authorizationV1beta1 *authorizationv1beta1.AuthorizationV1beta1Client
autoscalingV1 *autoscalingv1.AutoscalingV1Client
autoscalingV2 *autoscalingv2.AutoscalingV2Client
autoscalingV2beta1 *autoscalingv2beta1.AutoscalingV2beta1Client
autoscalingV2beta2 *autoscalingv2beta2.AutoscalingV2beta2Client
batchV1 *batchv1.BatchV1Client
batchV1beta1 *batchv1beta1.BatchV1beta1Client
certificatesV1 *certificatesv1.CertificatesV1Client
certificatesV1beta1 *certificatesv1beta1.CertificatesV1beta1Client
certificatesV1alpha1 *certificatesv1alpha1.CertificatesV1alpha1Client
coordinationV1beta1 *coordinationv1beta1.CoordinationV1beta1Client
coordinationV1 *coordinationv1.CoordinationV1Client
coreV1 *corev1.CoreV1Client
......
}
  • 以 appsv1 的类型 *appsv1.AppsV1Client 举例:可以看到,内部包含了一个 restClient。这也进一步认证,Clientset 就是一系列 RESTClient 的集合。
    1
    2
    3
    type AppsV1Client struct {
    restClient rest.Interface
    }

Clientset的常用方法

1、NewForConfig()方法

  • 位于 /kubernetes/clientset.go 中,所以可以直接使用 kubernetes.NewForConfig() 使用
  • 用于创建一个Clientset,传入一个rest.Config配置对象
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    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
    }

    // 这个方法,就完成了所有 RESTClient 的创建
    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
    // 下面就是创建各种 RESTClient 了,创建结果,被保存到 cs 中
    cs.admissionregistrationV1, err = admissionregistrationv1.NewForConfigAndClient(&configShallowCopy, httpClient)
    if err != nil {
    return nil, err
    }
    cs.admissionregistrationV1alpha1, err = admissionregistrationv1alpha1.NewForConfigAndClient(&configShallowCopy, httpClient)
    if err != nil {
    return nil, err
    }
    cs.admissionregistrationV1beta1, err = admissionregistrationv1beta1.NewForConfigAndClient(&configShallowCopy, httpClient)
    if err != nil {
    return nil, err
    }
    ......
    return &cs, nil
    }

2、Clientset的实例方法

  • Clientset 实现了 /kubernetes/clientset.go 下的 Interface接口,将自己内部的 私有属性 供外部使用

  • Interface 接口源码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    type Interface interface {
    ......
    AppsV1() appsv1.AppsV1Interface
    AppsV1beta1() appsv1beta1.AppsV1beta1Interface
    AppsV1beta2() appsv1beta2.AppsV1beta2Interface
    AuthenticationV1() authenticationv1.AuthenticationV1Interface
    AuthenticationV1alpha1() authenticationv1alpha1.AuthenticationV1alpha1Interface
    AuthenticationV1beta1() authenticationv1beta1.AuthenticationV1beta1Interface
    AuthorizationV1() authorizationv1.AuthorizationV1Interface
    AuthorizationV1beta1() authorizationv1beta1.AuthorizationV1beta1Interface
    AutoscalingV1() autoscalingv1.AutoscalingV1Interface
    AutoscalingV2() autoscalingv2.AutoscalingV2Interface
    AutoscalingV2beta1() autoscalingv2beta1.AutoscalingV2beta1Interface
    AutoscalingV2beta2() autoscalingv2beta2.AutoscalingV2beta2Interface
    BatchV1() batchv1.BatchV1Interface
    BatchV1beta1() batchv1beta1.BatchV1beta1Interface
    CertificatesV1() certificatesv1.CertificatesV1Interface
    CertificatesV1beta1() certificatesv1beta1.CertificatesV1beta1Interface
    CertificatesV1alpha1() certificatesv1alpha1.CertificatesV1alpha1Interface
    CoordinationV1beta1() coordinationv1beta1.CoordinationV1beta1Interface
    CoordinationV1() coordinationv1.CoordinationV1Interface
    CoreV1() corev1.CoreV1Interface
    ......
    }
  • 以 AppsV1() 方法为例,返回值是接口 appsv1.AppsV1Interface 的实现类 appsv1.AppsV1Client 的对象

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    // 接口
    type AppsV1Interface interface {
    RESTClient() rest.Interface
    ControllerRevisionsGetter
    DaemonSetsGetter
    DeploymentsGetter
    ReplicaSetsGetter
    StatefulSetsGetter
    }

    // 实现类
    type AppsV1Client struct {
    restClient rest.Interface
    }

    // AppsV1Client 实现 AppsV1Interface 接口的方法
    func (c *AppsV1Client) RESTClient() rest.Interface {
    if c == nil {
    return nil
    }
    return c.restClient
    }
  • 以 appsv1.AppsV1Client.Deployments() 方法举例

    • Deployments() 方法源码
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      // 返回值是DeploymentInterface 
      func (c *AppsV1Client) Deployments(namespace string) DeploymentInterface {
      // 实际上,返回值是 DeploymentInterface 的实现类 deployments 的对象
      return newDeployments(c, namespace)
      }

      // 构造一个 deployments 的对象
      func newDeployments(c *AppsV1Client, namespace string) *deployments {
      return &deployments{
      client: c.RESTClient(),
      ns: namespace,
      }
      }

返回值:DeploymentInterface 接口源码,可以看到包含操作Deployment的各种方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
type DeploymentInterface interface {
Create(ctx context.Context, deployment *v1.Deployment, opts metav1.CreateOptions) (*v1.Deployment, error)
Update(ctx context.Context, deployment *v1.Deployment, opts metav1.UpdateOptions) (*v1.Deployment, error)
UpdateStatus(ctx context.Context, deployment *v1.Deployment, opts metav1.UpdateOptions) (*v1.Deployment, error)
Delete(ctx context.Context, name string, opts metav1.DeleteOptions) error
DeleteCollection(ctx context.Context, opts metav1.DeleteOptions, listOpts metav1.ListOptions) error
Get(ctx context.Context, name string, opts metav1.GetOptions) (*v1.Deployment, error)
List(ctx context.Context, opts metav1.ListOptions) (*v1.DeploymentList, error)
Watch(ctx context.Context, opts metav1.ListOptions) (watch.Interface, error)
Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts metav1.PatchOptions, subresources ...string) (result *v1.Deployment, err error)
Apply(ctx context.Context, deployment *appsv1.DeploymentApplyConfiguration, opts metav1.ApplyOptions) (result *v1.Deployment, err error)
ApplyStatus(ctx context.Context, deployment *appsv1.DeploymentApplyConfiguration, opts metav1.ApplyOptions) (result *v1.Deployment, err error)
GetScale(ctx context.Context, deploymentName string, options metav1.GetOptions) (*autoscalingv1.Scale, error)
UpdateScale(ctx context.Context, deploymentName string, scale *autoscalingv1.Scale, opts metav1.UpdateOptions) (*autoscalingv1.Scale, error)
ApplyScale(ctx context.Context, deploymentName string, scale *applyconfigurationsautoscalingv1.ScaleApplyConfiguration, opts metav1.ApplyOptions) (*autoscalingv1.Scale, error)

DeploymentExpansion
}
  • 挑选 DeploymentInterface.Create 方法,查看 实现类 deployments 的 Create实现

    • 可以看出,Create方法的内容,就跟我们4.2.4中使用 RESTClient 的方式差不多
    • 这更加印证了,Clientset 就是 对各种 GroupVersion 的 RESTClient 的封装
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      func (c *deployments) Create(ctx context.Context, deployment *v1.Deployment, opts metav1.CreateOptions) (result *v1.Deployment, err error) {
      result = &v1.Deployment{}
      err = c.client.Post().
      Namespace(c.ns).
      Resource("deployments").
      VersionedParams(&opts, scheme.ParameterCodec).
      Body(deployment).
      Do(ctx).
      Into(result)
      return
      }
  • 实际上,这些方法都不是人工写的,都是 code-generator 自动生成的

    • code-generator 提供了很多工具用于为k8s中的资源生成相关代码,其中包括一个 client-gen
    • client-gen:可以为资源生成标准的操作方法(get;list;watch;create;update;patch;delete)
    • 比如,在kuberentes源码 staging/src/k8s.io/api/core/v1/types.go 中,可以看到 type Pod struct 注释上,就使用了 genclient 的标记
      1
      2
      3
      4
      5
      6
      // +genclient
      // +genclient:method=UpdateEphemeralContainers,verb=update,subresource=ephemeralcontainers
      // ......
      type Pod struct {
      ......
      }
  • client-gen 常用标记

    1
    2
    3
    4
    5
    // +genclient - 生成默认的客户端动作函数(create, update, delete, get, list, update, patch, watch以及 是否生成updateStatus取决于.Status字段是否存在)。
    // +genclient:nonNamespaced - 所有动作函数都是在没有名称空间的情况下生成
    // +genclient:onlyVerbs=create,get - 指定的动作函数被生成.
    // +genclient:skipVerbs=watch - 生成watch以外所有的动作函数.
    // +genclient:noStatus - 即使.Status字段存在也不生成updateStatus动作函数

Clientset使用示例

  • 需求:获取default命名空间下的pod列表,并获取kube-system命名空间下的deploy列表
  • 从下面代码来看,创建了一个 clientset,就可以操作不同 GroupVersion 下的 不同资源,也无需再去手动指定 APIPath 等值了
  • 代码编写
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    package main

    import (
    "context"
    v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    "k8s.io/client-go/kubernetes"
    "k8s.io/client-go/tools/clientcmd"
    )

    func main() {
    // 同样是先 创建一个客户端配置config
    config, err := clientcmd.BuildConfigFromFlags("", clientcmd.RecommendedHomeFile)
    if err != nil {
    panic(err)
    }

    // 使用 kubernetes.NewForConfig(),创建一个ClientSet对象
    clientSet, err := kubernetes.NewForConfig(config)
    if err != nil {
    panic(err)
    }

    // 1、从 clientSet 中调用操作pod的 RESTClient,获取default命名空间下的pod列表
    pods, err := clientSet.CoreV1().Pods(v1.NamespaceDefault).List(context.TODO(), v1.ListOptions{})
    if err != nil {
    panic(err)
    }
    // 打印pod名称
    for _, pod := range pods.Items {
    println(pod.Name)
    }

    println("------")

    // 2、从 clientSet 中调用操作 deploy 的 RESTClient,获取kube-system命名空间下的deploy列表
    deploys, err := clientSet.AppsV1().Deployments("kube-system").List(context.TODO(), v1.ListOptions{})
    if err != nil {
    panic(err)
    }
    // 打印 deploy 名称
    for _, deploy := range deploys.Items {
    println(deploy.Name)
    }
    }

输出结果

1
2
3
4
5
6
7
8
9
10
cassandra-5hbf7
liveness-exec
mysql-87pgn
myweb-7f8rh
myweb-rjblc
nginx-pod-node1
------
coredns
default-http-backend
metrics-server