一、前言

      client-go是一个调用kubernetes集群资源对象API的客户端,即通过client-go实现对kubernetes集群中资源对象(包括deployment、service、ingress、replicaSet、pod、namespace、node等)的增删改查等操作。大部分对kubernetes进行前置API封装的二次开发都通过client-go这个第三方包来实现。Kubernetes官方从2016年8月份开始,将Kubernetes资源操作相关的核心源码抽取出来,独立出来一个项目Client-go,作为官方提供的Go client。client-go支持RESTClient、ClientSet、DynamicClient、DiscoveryClient四种客户端与Kubernetes Api Server进行交互。

client-go官方文档:https://github.com/kubernetes/client-go

二、知识点

1、结构图

kubernetes client kubernetes client go_kubernetes client

2、package包的功能说明

  • kubernetes: 访问 Kubernetes API的一系列的clientset
  • discovery:通过Kubernetes API 进行服务发现
  • dynamic:对任意Kubernetes对象执行通用操作的动态client
  • transport:启动连接和鉴权auth
  • tools/cache:controllers控制器

3、Client客户端

  • RESTClient:RESTClient是最基础的,相当于的底层基础结构,可以直接通过 是RESTClient提供的RESTful方法如Get(),Put(),Post(),Delete()进行交互
  • 同时支持Json 和 protobuf
  • 支持所有原生资源和CRDs
  • 但是,一般而言,为了更为优雅的处理,需要进一步封装,通过Clientset封装RESTClient,然后再对外提供接口和服务
  • Clientset:Clientset是调用Kubernetes资源对象最常用的client,可以操作所有的资源对象,包含RESTClient。需要指定Group、指定Version,然后根据Resource获取
  • 优雅的姿势是利用一个controller对象,再加上Informer
  • DynamicClient:Dynamic client 是一种动态的 client,它能处理 kubernetes 所有的资源。不同于 clientset,dynamic client 返回的对象是一个 map[string]interface{},如果一个 controller 中需要控制所有的 API,可以使用dynamic client,目前它在 garbage collector 和 namespace controller中被使用。
  • 只支持JSON
  • DiscoveryClient

      DiscoveryClient是发现客户端,主要用于发现Kubernetes API Server所支持的资源组、资源版本、资源信息。除此之外,还可以将这些信息存储到本地,用户本地缓存,以减轻对Kubernetes API Server访问的压力。
kubectl的api-versions和api-resources命令输出也是通过DiscoversyClient实现的。

三、RESTClient客户端

     RESTClient是最基础的客户端,其它的客户端都是基于RESTClient实现的。RESTClient对HTTP Request进行了封装,实现了RESTFful风格的API,具有很高的灵活性,数据不依赖于方法和资源,因此RESTClient能够处理多种类型的调用,返回不同的数据格式。

案例:列出default命名空间的Pod

package main

import (
 "context"
 "fmt"
 "log"

 coreV1 "k8s.io/api/core/v1"
 metaV1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 "k8s.io/client-go/kubernetes/scheme"
 "k8s.io/client-go/rest"
 "k8s.io/client-go/tools/clientcmd"
)

func main() {
 configPath := "etc/kube.conf"
 config, err := clientcmd.BuildConfigFromFlags("", configPath)
 config.APIPath = "api"
 config.GroupVersion = &coreV1.SchemeGroupVersion
 config.NegotiatedSerializer = scheme.Codecs

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

 result := &coreV1.PodList{}
 err = restClient.Get().Namespace("default").Resource("pods").VersionedParams(&metaV1.ListOptions{}, scheme.ParameterCodec).Do(context.TODO()).Into(result)
 if err != nil {
  log.Fatal(err)
 }
 for _, d := range result.Items {
  fmt.Printf("NameSpace:%v \t Name:%v \t Status:%+v\n", d.Namespace, d.Name, d.Status.Phase)
 }
}

四、ClientSet客户端

ClientSet是在RESTClient的基础上封装了对Resource和Version的管理方法。每一个Resource可以理解为一个客户端,而ClientSet是多个客户端的集合,每一个Resource和 Version都以函数的方式暴露出来。
ClientSer仅能访问Kubernetes自身内置的资源,不能直接访问CRD自定义的资源。如果要想ClientSet访问CRD自定义资源,可通过client-gin代码生成器重新生成ClientSet。

package main

import (
 "context"
 "fmt"
 "log"

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

func main() {
 configPath := "etc/kube.conf"
 config, err := clientcmd.BuildConfigFromFlags("", configPath)
 if err != nil {
  log.Fatal(err)
 }

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

 //获取nodes
 podClient := clientset.CoreV1().Pods(coreV1.NamespaceDefault)

 list, err := podClient.List(context.TODO(), metaV1.ListOptions{})

 if err != err {
  log.Fatal(err)
 }

 for _, pod := range list.Items {
  fmt.Printf("NameSpace:%v \t Name:%v \t Status:%+v\n", pod.Name, pod.Namespace, pod.Status.Phase)
 }
}

五、DynamicClient客户端

DynamicClient是一个动态客户端,可以对任意Kubernetes资源进行RESTFful操作,包括CRD自定义资源。DynamicClient与ClientSet操作类似,同样是封装了RESTClient。DynamicClient与ClientSet最大的不同就是,ClientSet仅能访问Kubernetes自带的资源,不能直接访问CRD自定义资源。ClientSet需要预先实现没种Resource和Version的操作,期内部的数据都是结构化数据。而DynamicClient内部实现了Unstructured,用于处理非结构化数据结构,这是能够处理CRD自定义资源的关键。DynamicClient不是类型安全的,因此访问CRD自定义资源时需要特别注意。

示例:

package main

import (
	"context"
	"flag"
	"fmt"
	apiv1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/apimachinery/pkg/runtime/schema"
	"k8s.io/client-go/dynamic"
	"k8s.io/client-go/tools/clientcmd"
	"k8s.io/client-go/util/homedir"
	"path/filepath"
)

func main() {

	var kubeconfig *string

	// home是家目录,如果能取得家目录的值,就可以用来做默认值
	if home:=homedir.HomeDir(); home != "" {
		// 如果输入了kubeconfig参数,该参数的值就是kubeconfig文件的绝对路径,
		// 如果没有输入kubeconfig参数,就用默认路径~/.kube/config
		kubeconfig = flag.String("kubeconfig", filepath.Join(home, ".kube", "config"), "(optional) absolute path to the kubeconfig file")
	} else {
		// 如果取不到当前用户的家目录,就没办法设置kubeconfig的默认目录了,只能从入参中取
		kubeconfig = flag.String("kubeconfig", "", "absolute path to the kubeconfig file")
	}

	flag.Parse()

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

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

	dynamicClient, err := dynamic.NewForConfig(config)

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

	// dynamicClient的唯一关联方法所需的入参
	gvr := schema.GroupVersionResource{Version: "v1", Resource: "pods"}

	// 使用dynamicClient的查询列表方法,查询指定namespace下的所有pod,
	// 注意此方法返回的数据结构类型是UnstructuredList
	unstructObj, err := dynamicClient.
		Resource(gvr).
		Namespace("kube-system").
		List(context.TODO(), metav1.ListOptions{Limit: 100})

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

	// 实例化一个PodList数据结构,用于接收从unstructObj转换后的结果
	podList := &apiv1.PodList{}

	// 转换
	err = runtime.DefaultUnstructuredConverter.FromUnstructured(unstructObj.UnstructuredContent(), podList)

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

	// 表头
	fmt.Printf("namespace\t status\t\t name\n")

	// 每个pod都打印namespace、status.Phase、name三个字段
	for _, d := range podList.Items {
		fmt.Printf("%v\t %v\t %v\n",
			d.Namespace,
			d.Status.Phase,
			d.Name)
	}
}

六、DiscoveryClient客户端

DiscoveryClient是发现客户端,主要用于发现Kubernetes API Server所支持的资源组、资源版本、资源信息。除此之外,还可以将这些信息存储到本地,用户本地缓存,以减轻对Kubernetes API Server访问的压力。
kubectl的api-versions和api-resources命令输出也是通过DiscoversyClient实现的。

package main

import (
	"flag"
	"fmt"
	"k8s.io/apimachinery/pkg/runtime/schema"
	"k8s.io/client-go/discovery"
	"k8s.io/client-go/tools/clientcmd"
	"k8s.io/client-go/util/homedir"
	"path/filepath"
)

func main() {

	var kubeconfig *string

	// home是家目录,如果能取得家目录的值,就可以用来做默认值
	if home:=homedir.HomeDir(); home != "" {
		// 如果输入了kubeconfig参数,该参数的值就是kubeconfig文件的绝对路径,
		// 如果没有输入kubeconfig参数,就用默认路径~/.kube/config
		kubeconfig = flag.String("kubeconfig", filepath.Join(home, ".kube", "config"), "(optional) absolute path to the kubeconfig file")
	} else {
		// 如果取不到当前用户的家目录,就没办法设置kubeconfig的默认目录了,只能从入参中取
		kubeconfig = flag.String("kubeconfig", "", "absolute path to the kubeconfig file")
	}

	flag.Parse()

	// 从本机加载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)
		}
	}
}

七、参考文章

1、参考文章1:https://www.jianshu.com/p/d17f70369c35

2、参考文章2:https://www.huweihuang.com/kubernetes-notes/develop/client-go.html