初识 Client-go

文章介绍了RESTfulAPI作为前后端交互的规范,强调了其基于HTTP方法的资源操作特性。接着,文章详细讲解了Kubernetes中的Client-go库,包括RestClient、ClientSet、DynamicClient和DiscoveryClient,阐述了它们在操作Kubernetes资源时的不同作用和使用场景,展示了如何通过这些客户端进行资源的增删查改。
摘要由CSDN通过智能技术生成

在了解 Client-go 之前需要先理解一个概念叫 RESTful API

RESTful API

在主流公司的程序开发中,为了提高程序开发迭代的速度,基本都是前后端分离架构,而前端既包括网页、App、小程序等等,因此必须要有一个统一的规范用于约束前后端的通信,RESTful API则是目前比较成熟的API设计理论。

REST(Representational State Transfer):表现层状态转移

RESTful是一种风格而不是标准,而这个风格大致有以下几个主要特征:

以资源为基础 :资源可以是一个图片、音乐、一个XML格式、HTML格式或者JSON格式等网络上的一个实体,除了一些二进制的资源外普通的文本资源更多以JSON为载体、面向用户的一组数据(通常从数据库中查询而得到)。

统一接口: 对资源的操作包括获取、创建、修改和删除,这些操作正好对应HTTP协议提供的GET、POST、PUT和DELETE方法。换言而知,使用RESTful风格的接口但从接口上你可能只能定位其资源,但是无法知晓它具体进行了什么操作,需要具体了解其发生了什么操作动作要从其HTTP请求方法类型上进行判断。具体的HTTP方法和方法含义如下:

GET(SELECT):从服务器取出资源(一项或多项)。
POST(CREATE):在服务器新建一个资源。
PUT(UPDATE):在服务器更新资源(客户端提供完整资源数据)。
PATCH(UPDATE):在服务器更新资源(客户端提供需要修改的资源数据)。
DELETE(DELETE):从服务器删除资源。

简单概括就是:RESTful 设计风格 可以让用户通过HTTP加不同的操作方式实现对资源的增删查改。

Client-go

Kubernetes就是经典的 RESTful 架构,用户可以轻松的通过GET、POST等方式访问、操作Kubernetes中的资源,例如Deployments、Pods、namespaces等。包括Kubectl命令其本质也是通过HTTP请求 kubernetes API Server 实现对资源的操作

通过 RESTful API 将前端和后端分离,用户可以直接的通过 HTTP 协议提交表单来对操作资源。但是频繁的填写表单对于开发人员来说并不是一种高效的的方式。

于是client-go应运而生(除此之位还有client-java、client-python等),client-go中封装了HTTP连接,使开发人员可以更高效的操作资源。

在这里插入图片描述
client-go中一共有四种客户端,分别是:

RestClient:

RestClient是最基础的客户端,其结构中包含 HTTP连接、URL链接、GroupVersion、以及编解码方式等参数。
在这里插入图片描述
使用RestClient之前需要了解几个基本的概念,以Deployment资源为例:
假设Deployment资源的路径是:
http://192.168.1.1:8001/apis/apps/v1/deployments

http://192.168.1.1:8001 就是 kubernetes 的 API Server 暴露出来的一个服务地址,通过这个服务地址,用户便可以和 API Server 建立连接。RestClient中的Client便相当于行驶建立连接这个过程,当然,Client还需要经过鉴权、认证等操作才可以建立连接,这里只是类比。

/apis 是最基础的api路径,除了apis外,还有api这个路径,pod、node、namespace等资源便在api路径下。

/apps/v1 这里的apps则是资源的Group,v1则是资源的Version,他们一起组成了GroupVersion,并注册到了Scheme中。

下面给出两个demo,分别是通过 RestClient 部署、删除namespace和deployment

func restOptNs() {
	var kubeconfig = "{KUBECONFIG PATH}"

	config, err := clientcmd.BuildConfigFromFlags("", kubeconfig)
	if err != nil {
		panic(err.Error())
	}

	// 参考path : /api/v1/namespaces/{namespace}
	config.APIPath = "api"
	config.GroupVersion = &corev1.SchemeGroupVersion
	config.NegotiatedSerializer = scheme.Codecs

	restClient, err := rest.RESTClientFor(config)
	if err != nil {
		panic(err)
	}

	ns := &corev1.Namespace{
		ObjectMeta: metav1.ObjectMeta{
			Name: "test-client-go",
		},
		Spec: corev1.NamespaceSpec{},
	}

	result := &corev1.Namespace{}
	err = restClient.Post().
		Resource("namespaces").
		Body(ns).
		Do(context.TODO()).
		Into(result)
	if err != nil {
		panic(err)
	}
	fmt.Printf("namespace %s created !\n", ns.Name)

	time.Sleep(time.Second * 20)
	
	err = restClient.Delete().
		Resource("namespaces").
		Name(ns.Name).
		Do(context.TODO()).
		Error()
	if err != nil {
		panic(err)
	}
	fmt.Printf("namespace %s deleted !\n", ns.Name)

}


func restK8sDeploy() {
	var kubeconfig = "{KUBECONFIG PATH}
	config, err := clientcmd.BuildConfigFromFlags("", kubeconfig)

	if err != nil {
		panic(err.Error())
	}

	// 参考path : /api/apps/v1/namespaces/{namespace}/deployments
	config.APIPath = "apis"
	config.GroupVersion = &appsv1.SchemeGroupVersion
	config.NegotiatedSerializer = scheme.Codecs

	restClient, err := rest.RESTClientFor(config)
	if err != nil {
		panic(err)
	}

	deploy := &appsv1.Deployment{}
	bytes, err := ioutil.ReadFile("./yamls/nginx.yaml")
	if err != nil {
		panic(err)
	}

	toJson, err := yaml.ToJSON(bytes)
	if err != nil {
		panic(err)
	}

	json.Unmarshal(toJson, deploy)

	
	var replicas int32 = 1
	deploy := &appsv1.Deployment{
	ObjectMeta: metav1.ObjectMeta{
			Name: "nginx",
			Labels: map[string]string{
	 			"app": "nginx",
	 		},
	 	},
	 	Spec: appsv1.DeploymentSpec{
	 		Replicas: &replicas,
	 		Selector: &metav1.LabelSelector{
	 			MatchLabels: map[string]string{
	 				"app": "nginx",
	 			},
	 		},
	 		Template: corev1.PodTemplateSpec{
	 			ObjectMeta: metav1.ObjectMeta{
	 				Name: "nginx",
	 				Labels: map[string]string{
	 					"app": "nginx",
	 				},
	 			},
	 			Spec: corev1.PodSpec{
	 				Containers: []corev1.Container{
	 					{
	 						Name:  "nginx",
	 						Image: "nginx:latest",
	 						Ports: []corev1.ContainerPort{
	 							{
	 								Name:          "http",
	 								Protocol:      corev1.ProtocolTCP,
									ContainerPort: 80,
	 							},
	 						},
	 					},
	 				},
	 			},
	 		},
	 	},
	 }

	err = restClient.Delete().
		Namespace(deploy.ObjectMeta.Namespace).
		Resource("deployments").
		Name(deploy.ObjectMeta.Name).
		Do(context.TODO()).
		Error()

	if err != nil {
		panic(err)
	}
	fmt.Printf("deployment %s deleted !\n", deploy.Name)
}

ClientSet

通过上文中的两个例子得知,在连接RestClient之前,需要配置基础路径(api、apis)以及资源的GroupVersion和编解码的方式,对于不同的资源(属于不同的GroupVersion)需要不同的配置,操作还是过于繁琐。
于是便出现了ClientSet,ClietSet实现了对RestClient的再封装。
在这里插入图片描述
其结构体中涵盖了所有的k8s资源,通过ClientSet,开发人员只需要建立一次连接,便可以通过不同的接口操作k8s中不同的资源,不用再重新定义GroupVersion、再重新建立连接。

func cliensetK8s() {
	var kubeconfig = "{KUBECONFIG PATH}"

	config, err := clientcmd.BuildConfigFromFlags("", kubeconfig)
	if err != nil {
		panic(err.Error())
	}

	clientset, err := kubernetes.NewForConfig(config)
	if err != nil {
		panic(err.Error())
	}

	ctx, cancel := context.WithTimeout(context.Background(), time.Duration(time.Second*100))
	defer cancel()

	listPod(clientset, ctx, "")
}

// 获取 Pod 列表
func listPod(clientset *kubernetes.Clientset, ctx context.Context, ns string) {
	pods, err := clientset.CoreV1().Pods("").List(ctx, metav1.ListOptions{})
	if err != nil {
		panic(err.Error())
	}
	fmt.Printf("There are %d pods in the k8s cluster\n", len(pods.Items))

	// 获取指定 namespace 中的 Pod 列表信息
	namespace := ns
	pods, err = clientset.CoreV1().Pods(namespace).List(ctx, metav1.ListOptions{})
	if err != nil {
		panic(err)
	}
	fmt.Printf("\nThere are %d pods in namespaces %s\n", len(pods.Items), namespace)
	for _, pod := range pods.Items {
		// fmt.Printf("Name: %s, NameSpace: %s, Status: %s, CreateTime: %s\n", pod.ObjectMeta.Name, pod.ObjectMeta.Namespace, pod.Status.Phase, pod.ObjectMeta.CreationTimestamp)
		listPodInfo(clientset, ctx, pod.ObjectMeta.Namespace, pod.ObjectMeta.Name)
	}
}

func listPodInfo(clientset *kubernetes.Clientset, ctx context.Context, ns string, podName string) {
	namespace := ns
	pod, err := clientset.CoreV1().Pods(namespace).Get(ctx, podName, metav1.GetOptions{})

	if errors.IsNotFound(err) {
		fmt.Printf("Pod %s in namespace %s not found\n", podName, namespace)
	} else if statusError, isStatus := err.(*errors.StatusError); isStatus {
		fmt.Printf("Error getting pod %s in namespace %s: %v\n", podName, namespace, statusError.ErrStatus.Message)
	} else if err != nil {
		panic(err.Error())
	} else {
		fmt.Printf("\nFound pod %s in namespace %s\n", podName, namespace)
		maps := map[string]interface{}{
			"Name":        pod.ObjectMeta.Name,
			"Namespaces":  pod.ObjectMeta.Namespace,
			"NodeName":    pod.Spec.NodeName,
			"Annotations": pod.ObjectMeta.Annotations,
			"Labels":      pod.ObjectMeta.Labels,
			"Uid":         pod.ObjectMeta.UID,
			"Status":      pod.Status.Phase,
			"IP":          pod.Status.PodIP,
			"Image":       pod.Spec.Containers[0].Image,
		}
		prettyPrint(maps)
	}
}

在这里:

pods, err := clientset.CoreV1().Pods("").List(ctx, metav1.ListOptions{})

Pod 的 GroupVersion 便已经封装到了 .CoreV1() 接口中,从而简化了连接配置

DynamicClient

DynamicClient是一种动态客户端它可以对任何资源进行restful操作包括crd自定义资源,不同于 clientset,dynamic client 返回的对象是一个 map[string]interface{},如果一个 controller 中需要控制所有的 API,可以使用dynamic client,目前它在 garbage collector 和 namespace controller中被使用。
DynamicClient的处理过程将Resource例如podlist转换为unstructured类型,k8s的所有resource都可以转换为这个结构类型,处理完之后在转换为podlist,整个转换过程类似于接口转换就是通过interface{}的断言。
Dynamic client 是一种动态的 client,它能处理 kubernetes 所有的资源,只支持JSON。

func dynamicK8s() {
	var kubeconfig = "{KUBECONFIG PATH}"
	config, err := clientcmd.BuildConfigFromFlags("", kubeconfig)
	if err != nil {
		panic(err)
	}

	dynamicClient, err := dynamic.NewForConfig(config)
	if err != nil {
		panic(err)
	}
	// 定义组版本资源
	gvr := schema.GroupVersionResource{Version: "v1", Resource: "pods"}
	unStructObj, err := dynamicClient.Resource(gvr).Namespace(namespace).List(metav1.ListOptions{})
	if err != nil {
		panic(err)
	}
	podList := &apiv1.PodList{}

	if err = runtime.DefaultUnstructuredConverter.FromUnstructured(unStructObj.UnstructuredContent(), podList); err != nil {
		panic(err)
	}

	for _, v := range podList.Items {
		fmt.Printf("namespaces:%v  name:%v status:%v \n", v.Namespace, v.Name, v.Status.Phase)
	}
}

DiscoverClient

DiscoveryClient是发现客户端,主要用于发现api server支持的资源组 资源版本 资源信息.

func discover() {

	var kubeconfig = "{KUBECONFIG PATH}"

	// 从本机加载kubeconfig配置文件,因此第一个参数为空字符串
	config, err := clientcmd.BuildConfigFromFlags("", kubeconfig)

	// kubeconfig加载失败就直接退出了
	if err != nil {
		panic(err.Error())
	}

	// 新建discoveryClient实例
	discoveryClient, err := discovery.NewDiscoveryClientForConfig(config)

	if err != nil {
		panic(err.Error())
	}

	// 获取所有分组和资源数据
	APIGroup, APIResourceListSlice, err := discoveryClient.ServerGroupsAndResources()

	if err != nil {
		panic(err.Error())
	}

	// 先看Group信息
	fmt.Printf("APIGroup :\n\n %v\n\n\n\n", APIGroup)

	// APIResourceListSlice是个切片,里面的每个元素代表一个GroupVersion及其资源
	for _, singleAPIResourceList := range APIResourceListSlice {

		// GroupVersion是个字符串,例如"apps/v1"
		groupVerionStr := singleAPIResourceList.GroupVersion

		// ParseGroupVersion方法将字符串转成数据结构
		gv, err := schema.ParseGroupVersion(groupVerionStr)

		if err != nil {
			panic(err.Error())
		}

		fmt.Println("*****************************************************************")
		fmt.Printf("GV string [%v]\nGV struct [%#v]\nresources :\n\n", groupVerionStr, gv)

		// APIResources字段是个切片,里面是当前GroupVersion下的所有资源
		for _, singleAPIResource := range singleAPIResourceList.APIResources {
			fmt.Printf("%v\n", singleAPIResource.Name)
		}
	}
}

至此,四种Client-go客户端就已经介绍完毕

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值