• k8s编程operator——client-go基础部分



    k8s编程operator系列:
    k8s编程operator——(1) client-go基础部分
    k8s编程operator——(2) client-go中的informer
    k8s编程operator——(3) 自定义资源CRD
    k8s编程operator——(4) kubebuilder & controller-runtime
    k8s编程operator实战之云编码平台——①架构设计
    k8s编程operator实战之云编码平台——②controller初步实现
    k8s编程operator实战之云编码平台——③Code-Server Pod访问实现
    k8s编程operator实战之云编码平台——④web后端实现
    k8s编程operator实战之云编码平台——⑤项目完成、部署
     

    1、client-go简介

             client-go是一个golang的client,我们可以通过client-go与K8S apiServer进行交互,对k8s集群中资源对象,包括内置资源(例如:Pod、Deployment、Service等)和CRD进行增删改查等操作。

    client-go地址: https://github.com/kubernetes/client-go

    在这里插入图片描述

     

    目录结构:

    • kubernetes: 该包包含了可以访问kubernetes集群的api,通过这些API可以与apiServer进行通信,对集群的资源对象进行增删改查。比如,我们可以通过kubectl来创建一个deployment,同样的,我们可以使用kubernetes中的clientset来创建一个Deployment。
    • discovery: 该包用于发现Kubernetes apiServer支持的API。
    • dynamic:该包包含一个动态客户端,可以对任意Kubernetes API对象执行通用操作。
    • plugin/pkg/client/auth:该包包含可选的身份验证插件,用于从外部源获取凭证。
    • transport:该包用于设置认证并启动连接。
    • informers:每种k8s资源的informer实现。
    • listers:为每一个k8s资源提供list功能,将数据缓存到本地,然后get和list时从本地获取,减轻apiServer的压力。
    • tools:提供常用工具,例如SharedInformer、Reflector、DeltaFifo已经Indexers等。提供client查询和缓存机制,主要子目录为cache。
    • util:提供常用方法。例如WorkQueue工作队列,Certificate证书管理等。

     

    2、GVK和GVR

    kubernetes API是通过http协议以Restful的形式提供的,同时支持json和protobuf的数据格式。protobuf是为了方便集群内部调用而支持的,我们自己平时调用kubernetes接口,一般都是使用json格式。

            在kubernetes API中一般使用GVR或GVK来区分特定的资源,根据不同的分组、版本和资源,进行URL的定义。有了分组和多版本的支持,即使是在后续的版本中,需要去掉资源对象的某些字段或者重构API资源,也可以保证版本之间的兼容性。

            kubernetes API的分组可以分为无组名资源有组名资源无组名资源也被称为核心资源组,core group。在后面的client-go中可以看到,无组名资源都在core下面。

    有组名资源组:

    在这里插入图片描述

    无组名资源组:

    在这里插入图片描述

    GVR/GVK的含义:

    G(Group组):资源组,包含一组资源操作的集合,比如apps下面有deployment、demonset等。

    V(Version版本):资源版本,用于区分不同API的稳定程度和兼容性,比如v1、v1alpha1等。

    R(Resources资源):资源信息,用于区别不同的资源API,k8s中有很多资源,比如pod、deployment、ingress等。

    K(Kind类别):资源对象的类型,每个资源对象都需要Kind来区分它自身代表的资源类型。

    一般在调用接口时,我们只需要知道GVR即可,通过GVR操作对应的资源对象。

    通过GVR组成Restful API的请求路径,例如下面的url可以获取pod信息:

    Get /api/v1/namespaces/{namespace}/pods/{name}
    
    • 1
    # 通过kubectl来开启一个proxy
    [root@master .kube]# kubectl proxy
    Starting to serve on 127.0.0.1:8001
    
    # 通过proxy来访问k8s的资源对象
    # 获取dev下的pods
    curl http://127.0.0.1:8001/api/v1/namespaces/dev/pods
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

            通过GVK信息可以获取要读取的资源对象的GVR,进而构建Restful API请求获取对应的资源。这种GVK和GVR的映射叫做RESTMapper。
            RESTMapper主要作用是在ListerWatcher时,根据Scheme定义的类型GVK解析出GVR,向APIServer发起HTTP请求获取资源,然后watch。

     

    3、client-go中的client

    client-go中提供了四种与k8s apiServer交互的客户端,分别是 Rest Client、ClientSet、 Discovery Client和Dynamic Client:

    • RestClient:最基础的client,底层是对标准库的net/http的封装,下面的client都是对rest client的封装。
    • ClientSet:基于Rest Client进行了封装,通过clientset可以更加方便地操作K8S地资源对象。
    • DiscoveryClient:发现客户端,负责发现apiServer支持地资源组、资源版本和资源信息,相当于使用kubectl api-resources
    • DynamicClient:动态客户端,可以对任意的K8S资源对象进行操作,包括CRD.

    在这里插入图片描述

     

    3.1 Rest Client

    使用:go get go get k8s.io/client-go

    或者 go get k8s.io/client-go@v0.20.4

    talk is cheap,show me the code,先来一段代码来看rest client怎么使用,然后在对其中的内容进行详解:

    package main
    
    import (
    	"context"
    	"fmt"
    	v1 "k8s.io/api/core/v1"
    	"k8s.io/client-go/kubernetes/scheme"
    	"k8s.io/client-go/rest"
    	"k8s.io/client-go/tools/clientcmd"
    	"log"
    )
    
    func main() {
    	// 1. 构造访问config的配置,从文件中加载,将 home目录下的 .kube/config拷贝到当前./conf/下
    	config, err := clientcmd.BuildConfigFromFlags("", "./conf/config")
    	if err != nil {
    		panic(err)
    	}
    	config.GroupVersion = &v1.SchemeGroupVersion
    	config.NegotiatedSerializer = scheme.Codecs.WithoutConversion()
    	config.APIPath = "/api"
    
    	// 2. 创建rest client
    	client, err := rest.RESTClientFor(config)
    	if err != nil {
    		panic(err)
    	}
    
    	// 3. 查找命名空间dev下的pod
    	var podList v1.PodList
    	err = client.Get().Namespace("dev").Resource("pods").Do(context.Background()).Into(&podList)
    	if err != nil {
    		log.Printf("get pods error:%v\n", err)
    		return
    	}
    
    	fmt.Println("dev pod count:", len(podList.Items))
    	for _, pod := range podList.Items {
    		fmt.Printf("name: %s\n", pod.Name)
    	}
    }
    
    • 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

    运行结果:

    在这里插入图片描述

    rest client的使用步骤如下:

    1、创建config,创建config的方法有两种:

            (1) 使用clientcmd.BuildConfigFromFlags从配置文件中读取,读取的正是home目录下.kube/config中的内容。因此,需要将该文件拷贝到程序目录下。

            (2) 使用rest.InClusterConfig(),该函数返回一个配置对象,它使用kubernetes提供给pods的服务账户。这个API是为运行在k8s的pod中的服务而设计的。如果你写的应用将来要跑在pod中,那么就可以使用这个方式。如果没有运行在kubernetes环境中的进程调用,它将会返回ErrNotInCluster。当我们的程序要在 pod中运行时,我们需要给其创建一个account,然后k8s会将需要的信息放入pod中,我们的程序就可以读取到了。

            注意:使用restclient时还要自己设置config的GroupVersion和NegotiatedSerializer字段,如果不设置,则会在创建rest client时会返回错误。同时也要设置APIPath,否则就查询不到 对应的资源。

    2、使用config来创建restclient

    3、使用restclient来对资源对象进行操作,比如查找指定命名空间下的pod等

    3.1.1 加载配置

    BuildConfigFromFlags的源码如下:

            如果两个字符串都为空字符串,那么将会尝试使用InClusterConfig来读取。否则,就从指定的config文件中读取

    func BuildConfigFromFlags(masterUrl, kubeconfigPath string) (*restclient.Config, error) {
        // 如果kubeconfigPath和masterUrl都为空字符串,那么将尝试使用InClusterConfig来读取配置
    	if kubeconfigPath == "" && masterUrl == "" {
    		klog.Warning("Neither --kubeconfig nor --master was specified.  Using the inClusterConfig.  This might not work.")
    		kubeconfig, err := restclient.InClusterConfig()
    		if err == nil {
    			return kubeconfig, nil
    		}
    		klog.Warning("error creating inClusterConfig, falling back to default config: ", err)
    	}
        
        // 否则从指定的config文件中读取
    	return NewNonInteractiveDeferredLoadingClientConfig(
    		&ClientConfigLoadingRules{ExplicitPath: kubeconfigPath},
    		&ConfigOverrides{ClusterInfo: clientcmdapi.Cluster{Server: masterUrl}}).ClientConfig()
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    InClusterConfig的源码如下:

            InClusterConfig会从文件中读取token和ca,并从环境变量中取得集群入口的host和port

    func InClusterConfig() (*Config, error) {
        // 如果再pod中运行,k8s会将指定账户的toeknFile和rootCAFile放入pod中的指定文件中,因此我们的程序就可以去读取token和ca文件
    	const (
    		tokenFile  = "/var/run/secrets/kubernetes.io/serviceaccount/token"
    		rootCAFile = "/var/run/secrets/kubernetes.io/serviceaccount/ca.crt"
    	)
        // 从环境变量中读取host和port
    	host, port := os.Getenv("KUBERNETES_SERVICE_HOST"), os.Getenv("KUBERNETES_SERVICE_PORT")
    	if len(host) == 0 || len(port) == 0 {
    		return nil, ErrNotInCluster
    	}
    
    	token, err := ioutil.ReadFile(tokenFile)
    	if err != nil {
    		return nil, err
    	}
    
    	tlsClientConfig := TLSClientConfig{}
    
    	if _, err := certutil.NewPool(rootCAFile); err != nil {
    		klog.Errorf("Expected to load root CA config from %s, but got err: %v", rootCAFile, err)
    	} else {
    		tlsClientConfig.CAFile = rootCAFile
    	}
    
    	return &Config{
    		// TODO: switch to using cluster DNS.
    		Host:            "https://" + net.JoinHostPort(host, port),
    		TLSClientConfig: tlsClientConfig,
    		BearerToken:     string(token),
    		BearerTokenFile: tokenFile,
    	}, nil
    }
    
    • 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
    3.1.2 创建restclient

    RESTClientFor的源码如下:

            在RESTClientFor中会对config的GroupVersion和NegotiatedSerializer进行检查,如果为nil,将会panic,因此我们需要收到设置这两个字段的值。

    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)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    如果不知道怎么设置这两个字段的值,我们可以搜索该方法调用的地方来查看官方是怎么设置的:

    在这里插入图片描述

     

    3.2 ClientSet

    package main
    
    import (
    	"context"
    	"fmt"
    	v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    	"k8s.io/client-go/kubernetes"
    	"k8s.io/client-go/tools/clientcmd"
    	"log"
    )
    
    func main() {
    	// 1. 构造访问config的配置,从文件中加载,将 home目录下的 .kube/config拷贝到当前./conf/下
    	config, err := clientcmd.BuildConfigFromFlags("", "./conf/config")
    	if err != nil {
    		panic(err)
    	}
    
    	// 2. 创建clientset
    	clientset, err := kubernetes.NewForConfig(config)
    	if err != nil {
    		panic(err)
    	}
    
    	// 3.对资源对象进行操作
    	podList, err := clientset.CoreV1().Pods("dev").List(context.Background(), v1.ListOptions{})
    	if err != nil {
    		log.Printf("list pods error:%v\n", err)
    		return
    	}
    
    	fmt.Println("dev pod count:", len(podList.Items))
    	for _, pod := range podList.Items {
    		fmt.Printf("name: %s\n", pod.Name)
    	}
    }
    
    • 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

            可以看到,使用clientset要简单的多,而且我们无需自己为config中的字段进行赋值了,因为clientset对各种资源对象的操作进行了封装。使用步骤如下:

    1、构造config配置

    2、使用kubernetes的NewForConfig和config来创建clientset

    3、使用clientset对资源对象进行操作

    3.2.1 创建pod

    接下来我们来创建一个nginx的pod:

    package main
    
    import (
    	"context"
    	corev1 "k8s.io/api/core/v1"
    	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    	"k8s.io/client-go/kubernetes"
    	"k8s.io/client-go/tools/clientcmd"
    	"log"
    )
    
    func main() {
    	// 1. 构造访问config的配置,从文件中加载,将 home目录下的 .kube/config拷贝到当前./conf/下
    	config, err := clientcmd.BuildConfigFromFlags("", "./conf/config")
    	if err != nil {
    		panic(err)
    	}
    
    	// 2. 创建clientset
    	clientset, err := kubernetes.NewForConfig(config)
    	if err != nil {
    		panic(err)
    	}
    
    	// 3.创建一个pod对象并填上字段
    	pod := corev1.Pod{
    		TypeMeta: metav1.TypeMeta{
    			APIVersion: "v1",
    			Kind:       "Pod",
    		},
    		ObjectMeta: metav1.ObjectMeta{
    			Name:      "nginx",
    			Namespace: "default",
    			Labels: map[string]string{
    				"run": "nginx",
    			},
    		},
    		Spec: corev1.PodSpec{
    			Containers: []corev1.Container{
    				{
    					Image: "nginx",
    					Name:  "nginx-container",
    					Ports: []corev1.ContainerPort{
    						{
    							ContainerPort: 80,
    						},
    					},
    				},
    			},
    		},
    	}
    
    	// 4. 创建pod
    	_, err = clientset.CoreV1().Pods("default").Create(context.Background(), &pod, metav1.CreateOptions{})
    	if err != nil {
    		log.Printf("create pod error:%v\n", err)
    		return
    	}
    	log.Printf("create pod success\n")
    }
    
    
    • 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
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61

    运行程序,可以看到,pod已经被创建。但是由于我的集群只启动了master,master默认是有污点的,因此该pod调度不上去,就是Pending状态。

    在这里插入图片描述

    3.2.2 从模板中创建pod

    可以看到,上面的创建pod对象并设置字段非常麻烦,下面将介绍从文件模板中构造一个k8s的pod资源对象:

    # 使用该命令生成一个创建pod的yaml
    [root@master .kube]# kubectl run nginx --image=nginx --port=80 --dry-run=client -o yaml
    apiVersion: v1
    kind: Pod
    metadata:
      creationTimestamp: null
      labels:
        run: nginx
      name: nginx
    spec:
      containers:
      - image: nginx
        name: nginx
        ports:
        - containerPort: 80
        resources: {}
      dnsPolicy: ClusterFirst
      restartPolicy: Always
    status: {}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    在项目中创建一个template的文件夹,并将创建一个pod.yaml文件,内容如下:

    {{.Name}}为模板语法,可以将程序中结构的Name字段渲染到模板中的对应位置

    apiVersion: v1
    kind: Pod
    metadata:
      name: {{.Name}}
      namespace: {{.Namespace}}
      labels:
        run: pod
    spec:
      containers:
        - image: {{.Image}}
          name: {{.ContainerName}}
          ports:
            - containerPort: 80
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    然后我们可以在程序中读入yaml模板文件,并对模板进行实例化,然后解析到pod对象中,再来创建pod,代码如下:

    package main
    
    import (
    	"bytes"
    	"context"
    	corev1 "k8s.io/api/core/v1"
    	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    	"k8s.io/apimachinery/pkg/util/yaml"
    	"k8s.io/client-go/kubernetes"
    	"k8s.io/client-go/tools/clientcmd"
    	"log"
    	"text/template"
    )
    
    func main() {
    	// 1. 构造访问config的配置,从文件中加载,将 home目录下的 .kube/config拷贝到当前./conf/下
    	config, err := clientcmd.BuildConfigFromFlags("", "./conf/config")
    	if err != nil {
    		panic(err)
    	}
    
    	// 2. 创建clientset
    	clientset, err := kubernetes.NewForConfig(config)
    	if err != nil {
    		panic(err)
    	}
    
    	// 3. 从模板文件中构造pod
    	spec := PodSpec{
    		Name:          "nginx-pod-demo",
    		Image:         "nginx",
    		Namespace:     "default",
    		ContainerName: "nginx",
    	}
    	var pod corev1.Pod
    	tmpl, err := ParseTemplate("./template/pod.yaml", &spec)
    	if err != nil {
    		panic(err)
    	}
    	err = yaml.Unmarshal(tmpl, &pod)
    	if err != nil {
    		panic(err)
    	}
    
    	// 4. 创建pod
    	_, err = clientset.CoreV1().Pods("default").Create(context.Background(), &pod, metav1.CreateOptions{})
    	if err != nil {
    		log.Printf("create pod error:%v\n", err)
    		return
    	}
    	log.Printf("create pod success\n")
    }
    
    type PodSpec struct {
    	Name          string `json:"name"`
    	Image         string `json:"image"`
    	Namespace     string `json:"namespace"`
    	ContainerName string `json:"container_name"`
    }
    
    func ParseTemplate(name string, item *PodSpec) ([]byte, error) {
    	tmpl, err := template.ParseFiles(name)
    	if err != nil {
    		return nil, err
    	}
    	buf := bytes.Buffer{}
    	err = tmpl.Execute(&buf, item)
    	if err != nil {
    		return nil, err
    	}
    
    	return buf.Bytes(), nil
    }
    
    • 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
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73

    运行代码后,也成功创建了一个pod。

    3.2.3 client源码分析

    clientset的结构:

            可以看到,ClientSet为一个结构体,里面包含了各种操作各个GVR的client。其中我们的pod在corev1下

    type Clientset struct {
    	*discovery.DiscoveryClient
    	admissionregistrationV1      *admissionregistrationv1.AdmissionregistrationV1Client
    	admissionregistrationV1beta1 *admissionregistrationv1beta1.AdmissionregistrationV1beta1Client
    	internalV1alpha1             *internalv1alpha1.InternalV1alpha1Client
    	appsV1                       *appsv1.AppsV1Client
    	appsV1beta1                  *appsv1beta1.AppsV1beta1Client
    	appsV1beta2                  *appsv1beta2.AppsV1beta2Client
    	authenticationV1             *authenticationv1.AuthenticationV1Client
    	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
    	coordinationV1beta1          *coordinationv1beta1.CoordinationV1beta1Client
    	coordinationV1               *coordinationv1.CoordinationV1Client
    	coreV1                       *corev1.CoreV1Client
    	discoveryV1                  *discoveryv1.DiscoveryV1Client
    	discoveryV1beta1             *discoveryv1beta1.DiscoveryV1beta1Client
    	eventsV1                     *eventsv1.EventsV1Client
    	eventsV1beta1                *eventsv1beta1.EventsV1beta1Client
    	extensionsV1beta1            *extensionsv1beta1.ExtensionsV1beta1Client
    	flowcontrolV1alpha1          *flowcontrolv1alpha1.FlowcontrolV1alpha1Client
    	flowcontrolV1beta1           *flowcontrolv1beta1.FlowcontrolV1beta1Client
    	flowcontrolV1beta2           *flowcontrolv1beta2.FlowcontrolV1beta2Client
    	networkingV1                 *networkingv1.NetworkingV1Client
    	networkingV1alpha1           *networkingv1alpha1.NetworkingV1alpha1Client
    	networkingV1beta1            *networkingv1beta1.NetworkingV1beta1Client
    	nodeV1                       *nodev1.NodeV1Client
    	nodeV1alpha1                 *nodev1alpha1.NodeV1alpha1Client
    	nodeV1beta1                  *nodev1beta1.NodeV1beta1Client
    	policyV1                     *policyv1.PolicyV1Client
    	policyV1beta1                *policyv1beta1.PolicyV1beta1Client
    	rbacV1                       *rbacv1.RbacV1Client
    	rbacV1beta1                  *rbacv1beta1.RbacV1beta1Client
    	rbacV1alpha1                 *rbacv1alpha1.RbacV1alpha1Client
    	schedulingV1alpha1           *schedulingv1alpha1.SchedulingV1alpha1Client
    	schedulingV1beta1            *schedulingv1beta1.SchedulingV1beta1Client
    	schedulingV1                 *schedulingv1.SchedulingV1Client
    	storageV1beta1               *storagev1beta1.StorageV1beta1Client
    	storageV1                    *storagev1.StorageV1Client
    	storageV1alpha1              *storagev1alpha1.StorageV1alpha1Client
    }
    
    • 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
    • 45
    • 46
    • 47
    • 48
    • 49

    创建clientset:

            首先会创建http.Client,该client用于与apiServer进行通信。然后,创建出各个GVR的client。

    // 利用config来创建clientset
    func NewForConfig(c *rest.Config) (*Clientset, error) {
    	configShallowCopy := *c
    
    	if configShallowCopy.UserAgent == "" {
    		configShallowCopy.UserAgent = rest.DefaultKubernetesUserAgent()
    	}
    
        // 创建net/http.Client,该client将在各个GVR的client中共享
    	httpClient, err := rest.HTTPClientFor(&configShallowCopy)
    	if err != nil {
    		return nil, err
    	}
    	
        // 创建各个GVR的client
    	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
        
        // 创建各种GVR的client
    	...
    	cs.appsV1beta1, err = appsv1beta1.NewForConfigAndClient(&configShallowCopy, httpClient)
    	if err != nil {
    		return nil, err
    	}
    	...
    	cs.coreV1, err = corev1.NewForConfigAndClient(&configShallowCopy, httpClient)
    	if err != nil {
    		return nil, err
    	}
    	cs.discoveryV1, err = discoveryv1.NewForConfigAndClient(&configShallowCopy, httpClient)
    	if err != nil {
    		return nil, err
    	}
    	cs.discoveryV1beta1, err = discoveryv1beta1.NewForConfigAndClient(&configShallowCopy, httpClient)
    	if err != nil {
    		return nil, err
    	}
    	...
    
    	cs.DiscoveryClient, err = discovery.NewDiscoveryClientForConfigAndClient(&configShallowCopy, httpClient)
    	if err != nil {
    		return nil, err
    	}
    	return &cs, nil
    }
    
    • 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
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57

            在创建各个GVR的client的时候会调用NewForConfigAndClient,而在NewForConfigAndClient中又会调用RESTClientForConfigAndClient来创建rest client

            同时在NewForConfigAndClient中会调用setConfigDefaults来为config的几个字段赋值。

    // 以CoreV1Client为例,可以看到其中包含了restClient
    type CoreV1Client struct {
        restClient rest.Interface
    }
    
    func NewForConfigAndClient(c *rest.Config, h *http.Client) (*CoreV1Client, error) {
    	config := *c
        // 为config中的字段设置默认值
    	if err := setConfigDefaults(&config); err != nil {
    		return nil, err
    	}
        // 创建restClient
    	client, err := rest.RESTClientForConfigAndClient(&config, h)
    	if err != nil {
    		return nil, err
    	}
    	return &CoreV1Client{client}, nil
    }
    
    
    // 在restClient中我们需要手动为config中的GroupVersion、NegotiatedSerializer和APIPath设置值,
    // 但是使用clientSet我们无需自己设置,因为在创建各个GVR的client时会自动指定不同的值
    func setConfigDefaults(config *rest.Config) error {
    	gv := v1.SchemeGroupVersion
    	config.GroupVersion = &gv
    	config.APIPath = "/api"
    	config.NegotiatedSerializer = scheme.Codecs.WithoutConversion()
    
    	if config.UserAgent == "" {
    		config.UserAgent = rest.DefaultKubernetesUserAgent()
    	}
    
    	return nil
    }
    
    • 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

     

    3.2.4 创建、更新、查询、删除Deployment

    接下来将使用client-go,实现一个deployment的创建、更新和删除操作。代码依据官方的例子修改而来:

    package main
    
    import (
    	"bufio"
    	"context"
    	"fmt"
    	appsv1 "k8s.io/api/apps/v1"
    	corev1 "k8s.io/api/core/v1"
    	v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    	"k8s.io/client-go/kubernetes"
    	appsresv1 "k8s.io/client-go/kubernetes/typed/apps/v1"
    	"k8s.io/client-go/tools/clientcmd"
    	"k8s.io/client-go/util/retry"
    	"k8s.io/klog/v2"
    	"os"
    )
    
    func main() {
    	// 1、创建配置文件
    	config, err := clientcmd.BuildConfigFromFlags("", "./conf/config")
    	if err != nil {
    		panic(err)
    	}
    
    	// 2、创建clientset
    	clientset, err := kubernetes.NewForConfig(config)
    	if err != nil {
    		panic(err)
    	}
    	deployClient := clientset.AppsV1().Deployments(corev1.NamespaceDefault)
        
    	// 3、创建deployment
    	CreateDeploy(deployClient)
    
    	prompt()
    	// 4、更新deployment
    	UpdateDeploy(deployClient)
    
    	prompt()
    	// 5、查询deployment
    	ListDeploy(deployClient)
    
    	prompt()
    	// 6、删除deployment
    	DeleteDeploy(deployClient)
    }
    
    func CreateDeploy(client appsresv1.DeploymentInterface) {
    	klog.Info("CreateDeploy...........")
    	replicas := int32(2)
    	deploy := appsv1.Deployment{
    		TypeMeta: v1.TypeMeta{
    			Kind:       "Deployment",
    			APIVersion: "apps/v1",
    		},
    		ObjectMeta: v1.ObjectMeta{
    			Name:      "deploy-nginx-demo",
    			Namespace: corev1.NamespaceDefault,
    		},
    		Spec: appsv1.DeploymentSpec{
    			Replicas: &replicas,
    			Selector: &v1.LabelSelector{
    				MatchLabels: map[string]string{
    					"app": "nginx",
    				},
    			},
    			Template: corev1.PodTemplateSpec{
    				ObjectMeta: v1.ObjectMeta{
    					Name: "nginx",
    					Labels: map[string]string{
    						"app": "nginx",
    					},
    				},
    				Spec: corev1.PodSpec{
    					Containers: []corev1.Container{
    						{
    							Name:  "web",
    							Image: "nginx:1.12",
    							Ports: []corev1.ContainerPort{
    								{
    									Protocol:      corev1.ProtocolTCP,
    									ContainerPort: 80,
    								},
    							},
    						},
    					},
    				},
    			},
    		},
    	}
    
    	dep, err := client.Create(context.Background(), &deploy, v1.CreateOptions{})
    	if err != nil {
    		klog.Errorf("create deployment error:%v", err)
    		return
    	}
    	klog.Infof("create deployment success, name:%s", dep.Name)
    }
    
    func UpdateDeploy(client appsresv1.DeploymentInterface) {
    	klog.Info("UpdateDeploy...........")
    	// 当有多个客户端对同一个资源进行操作时,可能会发生错误。使用RetryOnConflict来重试,重试相关参数由DefaultRetry来提供
    	err := retry.RetryOnConflict(retry.DefaultRetry, func() error {
    		// 查询要更新的deploy
    		deploy, err := client.Get(context.Background(), "deploy-nginx-demo", v1.GetOptions{})
    		if err != nil {
    			klog.Errorf("can't get deployment, err:%v", err)
    			return nil
    		}
    
    		// 修改参数后进行更新
    		replicas := int32(1)
    		deploy.Spec.Replicas = &replicas
    		deploy.Spec.Template.Spec.Containers[0].Image = "nginx:1.13"
    
    		_, err = client.Update(context.Background(), deploy, v1.UpdateOptions{})
    		if err != nil {
    			klog.Errorf("update deployment error, err:%v", err)
    		}
    		return err
    	})
    
    	if err != nil {
    		klog.Errorf("update deployment error, err:%v", err)
    	} else {
    		klog.Infof("update deployment success")
    	}
    
    }
    
    func ListDeploy(client appsresv1.DeploymentInterface) {
    	klog.Info("ListDeploy...........")
    	deplist, err := client.List(context.Background(), v1.ListOptions{})
    	if err != nil {
    		klog.Errorf("list deployment error, err:%v", err)
    		return
    	}
    
    	for _, dep := range deplist.Items {
    		klog.Infof("deploy name:%s, replicas:%d, container image:%s", dep.Name, *dep.Spec.Replicas, dep.Spec.Template.Spec.Containers[0].Image)
    	}
    }
    
    func DeleteDeploy(client appsresv1.DeploymentInterface) {
    	klog.Info("DeleteDeploy...........")
    	// 删除策略
    	deletePolicy := v1.DeletePropagationForeground
    	err := client.Delete(context.Background(), "deploy-nginx-demo", v1.DeleteOptions{PropagationPolicy: &deletePolicy})
    	if err != nil {
    		klog.Errorf("delete deployment error, err:%v", err)
    	} else {
    		klog.Info("delete deployment success")
    	}
    }
    
    func prompt() {
    	fmt.Printf("-> Press Return key to continue.")
    	scanner := bufio.NewScanner(os.Stdin)
    	for scanner.Scan() {
    		break
    	}
    	if err := scanner.Err(); err != nil {
    		panic(err)
    	}
    	fmt.Println()
    }
    
    • 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
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166

    运行结果:
    在这里插入图片描述

  • 相关阅读:
    【问题解决】Android JDK版本不匹配导致崩溃踩坑记录
    SRC逻辑漏洞-忘记密码/邮箱密码找回/链接token时间戳参数可逆
    12.2 实现键盘模拟按键
    Unity中的两种ScriptingBackend
    Skywalking APM监控系列(一丶.NET5.0+接入Skywalking监听)
    Type android.support.v4.app.INotificationSideChannel is defined multiple times
    常回家看看之house_of_cat
    领域驱动设计
    18_ue4捡钥匙进房间
    有来实验室|第一篇:Seata1.5.2版本部署和开源全栈商城订单支付业务实战
  • 原文地址:https://blog.csdn.net/Peerless__/article/details/127814142