k8s编程operator——(1) client-go基础部分

K8S学习指南(65)-Operator介绍 Kubernetes Operator 是一种以自定义资源(Custom Resource,CR)为基础的自动化控制器。它的设计初衷是为了更好地支持应用程序的生命周期管理,使得开发者能够在 Kubernetes 上更轻松、更自动地部署、更新和管理应用。Operator 的工作方式类似于一个 K8S 控制器,但是它不仅仅关注于基本的资源管理,还关心应用程序的特定需求。通过引入 Operator,我们可以将关于应用的操作和管理逻辑进行抽象,以实现更高级的自动化。首先,我们需要定义Database。 阅读详情


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}
# 通过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

        通过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)
	}
}

运行结果:

在这里插入图片描述

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()
}

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
}
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)
}

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

在这里插入图片描述

 

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)
	}
}

        可以看到,使用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")
}

运行程序,可以看到,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: {}

在项目中创建一个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

然后我们可以在程序中读入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
}

运行代码后,也成功创建了一个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
}

创建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
}

        在创建各个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
}

 

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()
}

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

K8S学习指南(69)-客户端client-go的使用 client-go 是 Kubernetes 官方提供的 Go 客户端库,提供了便捷的方式进行 Kubernetes 资源的管理。通过创建客户端并使用相应的 API 客户端进行操作,我们可以轻松地进行 Pod、Deployment、Service 等资源的增删改查。在实际开发和运维中,合理利用 client-go 可以提高工作效率,确保应用程序和服务在 Kubernetes 集群中的正常运行。希望本文的示例和说明对你理解和使用 client-go 提供了有益的指导。 阅读详情

相关推荐

k8s-client-go基本使用(1)

Client-go 是负责与 k8s APIServer 服务进行交互的客户端库,利用 Client-go 与 k8s APIServer 进行的交互访问,以此来对 k8s 中的各类资源对象进行管理操作,包括内置的资源对象及未来自定义的CRD资源。

渔夫.AI 1469

K8s系列之:K8s OPERATOR是什么

Kubernetes 的 Operator 模式概念允许你在不修改 Kubernetes 自身代码的情况下, 通过为一个或多个自定义资源关联控制器来扩展集群的能力。Operator 是 Kubernetes API 的客户端, 充当自定义资源的控制器。部署 Operator 最常见的方法是将自定义资源及其关联的控制器添加到你的集群中。Operator 模式会封装你编写的(Kubernetes 本身提供功能以外的)任务自动化代码。如果生态系统中没有可以实现你目标的 Operator,你可以自己编写代码。

zhengzaifeidelushang的博客 5486

用client-go调用Kubernetes API

背景client-go是go语言访问Kubernetes API的一种新的框架。本文记录client-go访问Kubernetes的过程。最终实现了给服务器端发送container ID,服务器端会返回该container所在pod的pod Name与Spacename。我们Kubernetes集群中一个pod都只运行了一个container。 程序用httprouter包来作为http的访问接口

hty46565的博客 7232

来了解一下 k8s 的 Operator 模式

在 `Kubernetes` 生态中还有一种更灵活、编程友好的解决方案 -- `Operator`, 它能让使用者根据自己应用的特性定义应用对象和管理对象的控制器。

ChaITSimpleLove的博客 5334

从Go到K8s Operator:我的云原生全栈工程师转型实战路线(附避坑清单)

本文分享了从Go语言开发转型为云原生全栈工程师的实战经验,详细介绍了掌握云原生核心技术栈的完整路径,包括Go语言在云原生场景下的特殊编程范式、Docker与Kubernetes的深度实践、Operator开发的三个阶段以及避坑清单。文章特别强调了云原生思维的重要性,并提供了实用的开发技巧和生产环境注意事项。

Golden的专栏 323

gsplat在windows本地部署

3DGS的cuda提效和优化内存的版本gsplat,使得在有一定GPU计算能力的电脑上也能够尝尝3DGS的效果。我在2025年的开年之初尝试配置过gsplat,由于过程中遇到很多问题,最终还是有一些windows上字符编码的问题,没有跑通,最近看gsplat的github更新了3DGUT,优化了反射和二次折射,以及适用于非线性相机模型,如鱼眼相机等。在data目录下下载好了360_v2的数据,进入之后images文件夹是原始分辨率的图像,images_2是下采样系数为2的数据,以此类推。

yuleaf_csdn的博客 2367

Kubernetes operator系列:client-go篇

本文是 Kubernetes operator学习 系列第一篇,主要对client-go进行学习,从源码阅读角度,学习client-go各个组件的实现原理、如何协同工作等。

a1369760658的博客 3338

【Unity Shader】2D模型 3D模型混用的穿模问题(Spine在3D场景中出现前后穿模问题的解决办法)

问题描述: 在3D场景中使用Spine模型(2D), 因为Spine需要像Billboard类似的处理,永远是垂直与相机的, 所以一般情况下2D模型能正常显示且前后的阻挡关系也是没有问题的,如下图所示,一切都很美好.(这里用一个四边形,图中的福字表示2D模型) 可是如果后面蓝色的格子比较高,或者2D角色本身也比较高,那么就会出现模型穿插的问题,如下图所示: 如果吧2D模型竖立起来,前后关系肯定是没有问题的,所以也能避免模型穿插的问题,可是就没有Billboard效果,圆形的福字已经被压扁了,如下图:

Vitens的专栏 4590

K8S中的operator

Kubernetes 是一个高度可扩展的系统,它可扩展的功能包括kubectlAPIServer、K8资源、Controller 、Scheduler 、CNICSI、CRI,虽然它的扩展点这么多,但是一般来说我们接触的比较多的还是CRCRD、准入控制,其他扩展功能也有大批的开源组件可供使用。这次讲的Operator就会涉及到CR、CRD、准入控制。Operator:Kubernetes 的Operator 模式。

weixin_44411745的博客 1808

使用同一个时钟源clki,做两个二分频时钟,得到div2_clk1和div2_clk2,这两个时钟都能跟源时钟clki同步,但是div2_clk1和div2_clk2的相位可能是相同的,也可能是反相

比如,触发器1的初始Q为0,而触发器2的初始Q为1(这可能由于芯片制造工艺的微小差异、上电过程的随机性、或没有全局复位信号导致)。这句话的意思是:虽然div2_clk1和div2_clk2都是由同一个妈妈(clki)生出来的,频率也都是妈妈的一半,但它们“起步”的时刻可能不同。所以,上面那段话是在提醒你注意这种潜在的、由分频产生的时钟相位关系的不确定性,这在异步电路设计或时钟管理中是必须考虑的关键因素。结果:div2_clk1和div2_clk2的波形完全重合,它们的上升沿和下降沿都发生在同一时刻。

m0_49540263的博客 591

Go(Golang)在 Kubernetes(K8s)编排中的应用

Go(Golang)是 Kubernetes(K8s)的核心开发语言,同时也是基于 K8s 进行编排、扩展和自动化的首选语言之一。其轻量级、高性能、原生支持并发(Goroutine)、编译型特性,以及丰富的 K8s 官方客户端库,使其成为构建 K8s 编排工具、自定义控制器、Operator、CI/CD 集成等场景的理想选择。Operator 是 Go 基于 K8s 扩展的核心场景,用于管理自定义资源(如 MySQL 集群、Elasticsearch 集群)的生命周期。

shao.bing的专栏 1373

干法刻蚀工艺进阶:如何通过调节DC自偏压优化刻蚀速率和各向异性

本文深入探讨了干法刻蚀工艺中DC自偏压的精细调控艺术。DC自偏压是影响刻蚀速率和各向异性的关键因素,文章通过实战案例,详细分析了如何通过调节射频功率、频率、腔室压力及气体成分等参数,间接而有效地控制DC自偏压,从而优化高深宽比硅沟槽等复杂结构的刻蚀效果,实现工艺性能的精准提升。

weixin_28308325的博客 325

k8s编程operator——(2) client-go中的informer

使用client-set可以很容易地对ApiServer中的数据进行增删改查。但是,如果每次都从ApiServer获取数据,特别是执行List操作时,ApiServer会查询并传输大量的数据,将会对其产生很大的压力。如果能将数据缓存到本地,并在数据变化时进行更新,获取数据时从本地获取,那么将会大大减轻ApiServer的压力。因此,本章将会对client-go中的informer进行一个探究,通过Infomer,我们可以实现将数据缓存到本地,并且当资源对象发生变化时,我们能获取到相应的变化,并进行处理。

Peerless__的博客 3306

[24] cuda 应用之 nppi 实现图像颜色通道变换

[24] cuda 应用之 nppi 实现图像颜色通道变换

yohnyang的博客 343

RT-DETR改进策略【注意力机制篇】| 2024 PPA 并行补丁感知注意模块,提高小目标关注度

本文记录的是利用改进检测精度,详细说明了优化原因,注意事项等。原论文在红外小目标检测任务中,小目标在多次下采样操作中容易丢失关键信息。通过替代编码器和解码器基本组件中的传统卷积操作,更好地保留小目标的重要信息。HCF-Net: Hierarchical Context Fusion Network for Infrared Small Object Detection采用多分支特征提取策略,通过不同分支提取不同尺度和层次的特征。利用局部、全局和串行卷积分支,对输入特征张量进行处理。通过控制 patch si

Limiiiing的博客 1646

k8s client-go 程序实现kubernetes Controller & Operator 使用CRD 学习总结

大纲这里使用client-go实现编写,相对于kubebuiler这些工具生成脚手架工程要麻烦一些,但是可以理解完整的原理。k8s 自定义operator 基本流程此例子中使用的CRD自定义资源定义基本和 《k8s java程序实现kubernetes Controller & Operator 使用CRD 学习总结》 文章中使用的CRD一致CRD自定义资源定义yaml文件内容如下 (yaml/crd-liuyijiang.yaml ) 资源定义完成后使用 kubectl apply -f crd-liuy

liuyij3430448的博客 1716

华为的C语言编程规范.pdf

华为技术有限公司内部技术规范。本规范制定了编写C语言程序的基本原则、规则和建议。从代码的清晰、简洁、可测试、安全、程序效率、可移植各个方面对C语言编程作出了具体指导。

Kubernetes-Operator篇-04-operator部署验证

1、make manifests生成manifests, 2、make install将crd安装到集群 3、make run在本地运行起controller 4、直接在ide内以debug模式运行controller 5、apply一个测试yaml做测试

一叶知秋 1943

TMS_WebGMaps_1.9.8.0_Full_Source

TMS_WebGMaps_1.9.8.0_Full_Source 全源码 delphi cbuilder

STM32F407中文手册

STM32f407中文手册 本参考手册面向应用开发人员,提供有关使用 STM32F405xx/07xx、STM32F415xx/17xx、 STM32F42xxx 和 STM32F43xxx 微控制器存储器与外设的完整信息。STM32F405xx/07xx、STM32F415xx/17xx、STM32F42xxx 和 STM32F43xxx 构成一个微 控制器系列,各产品具有不同的存储器大小、封装和外设。有关订购信息以及器件的机械与电气特性,请参见数据手册。有关 ARM Cortex™-M4F 内核的信息,请参见《Cortex™-M4F 技术参考手册》。

使用 Controller Runtime Client API 构建 Kubernetes Operator:Operator SDK 中的 CRUD 实战

本指南基于 Operator SDK 官方文档《Controller Runtime Client API》展开,聚焦 controller-runtime 的 `client.Client` 接口——它是 Kubernetes Operator 在调谐(Reconcile)循环中与 API Server 交互的核心通道。你将掌握:默认 split-client 的读写分离机制、如何注入到 Rec

gitblog_00913的博客 895

汉书HE8,好用的汇编代码编写工具

汉书HE8,好用的汇编代码编写工具早期编写汇编代码所用功能强大

别再死记硬背了!用这套Golang + K8s实战项目,打通你的云原生技能树

本文介绍了如何通过Golang和Kubernetes实战项目构建云原生技能树,从零实现一个K8s资源管理平台。通过详细的架构设计、核心组件选型、Operator模式实现及生产环境部署优化,帮助开发者掌握云原生技术的实际应用,提升开发效率和系统性能。

dhc65376的博客 458

Kubernetes 客户端库 Client-Go 教程

**Kubernetes Client-Go** 是一个用于开发 Kubernetes 应用程序的官方 Go 语言客户端库。它提供了与 Kubernetes 集群通信所需的接口和工具,允许开发者轻松地在 Go 代码中集成 Kubernetes 的功能。 ### 版本与兼容性 Client-Go 遵循 [语义版本](semver) 规范。主要版本号变更表示可能有不向后兼容的API更改,次要版本更

gitblog_00850的博客 1408

k8s交互桥梁:走进Client-Go

Client-Go通过分层抽象和高效缓存机制,为Kubernetes API交互提供了强大支撑。从类型化的Clientset到动态的DynamicClient,从实时监听的Informer到解耦处理的Workqueue,其设计既满足了开发便捷性,又保证了生产级性能。掌握Client-Go不仅是开发自定义控制器、Operator的基础,更是深入理解Kubernetes控制平面工作原理的关键。

zyun360的博客 948

使用client-go包访问Kubernetes CRD

Kubernetes API服务器可通过自定义资源定义轻松扩展。但是,用client-go库访问这些资源有点麻烦,官方也没有完整的文档。如kubebuilder operator-framework都能很方便的帮助我们去创建实现一个controller,但是封装的过于好导致我们并不清楚内部是怎么调用client-go的,很多场景我们是需要自己去调用接口操作CRD的而不是在controller中去访...

github_35614077的博客 3788

Alluxio Kubernetes Operator 源码构建:Go 开发环境与依赖管理实战指南

本篇技术指南围绕 Alluxio 仓库中 [Kubernetes Operator 开发文档](https://link.gitcode.com/i/88a41bd4424a6e2c7baee9bf98ae81e0) 展开,系统讲解从零搭建 Alluxio Operator(Dataset/Runtime 控制器与注入 Webhook)Go 开发环境的三个关键步骤——准备 client-go、更新

gitblog_00540的博客 217

【Kubernetes Operator】Kubernetes Operator 深度剖析 — 第一部分

Kubernetes Operator 深度剖析 — 第一部分

zhonglinzhang 739
上一篇: Kubernetes(K8S)学习笔记
下一篇: k8s权限控制RBAC
CoreDump丶
博客等级 码龄8年 451粉丝 70原创
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值