目录

client-go库使用-k8s四种客户端介绍

简介

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官方文档

知识点

package包的功能说明

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

安装

1
2
3
4
#下载client-go库,注意kubernetes的版本号
C:\Users\xx\Desktop\git\client>go get -u -v k8s.io/client-go@kubernetes-1.18.3
#下载kubernetes的api库,注意对应的版本号
C:\Users\xx\Desktop\git\client>go get k8s.io/api@v0.18.3

client客户端

RESTClient

RESTClient是最基础的,相当于最底层的基础结构,可以直接通过RESTClient提供的RESTful方法如Get()、Put()、Post()、Delete()进行交互。

  • 同时支持json和protobuf
  • 支持所有原生资源和CRDs
  • 但是,一般而言,为了更为优雅的处理,需要进一步封装,通过Clientset封装RESTClient,然后再对外提供接口和服务。

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

案例 列出dev名称空间下所有的pod

  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
#安装用于格式打印的库
C:\Users\xx\Desktop\git\client>go get github.com/olekukonko/tablewriter

package main

import (
	"context"
	"flag"
	"fmt"
	"github.com/olekukonko/tablewriter"
	"k8s.io/client-go/deprecated/scheme"
	"k8s.io/client-go/rest"
	"k8s.io/client-go/tools/clientcmd"
	"os"
	"path/filepath"
	corev1 "k8s.io/api/core/v1"
	"strconv"
	"time"
)

//获取家目录的环境变量值
func homeDir()string{
	if h:= os.Getenv("HOME");h!= ""{
		return h //linux家目录
	}
	return os.Getenv("USERPROFILE") //windows家目录
}

func main(){
	var kubeconfig *string

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

	//从本机加载kubeconfig配置文件,因此第一个参数为空字符串
	config , err := clientcmd.BuildConfigFromFlags("",*kubeconfig)
	if err != nil{
		fmt.Println("load kubeconfig failed!err:",err)
		panic(err.Error())
	}

	//参考path : /api/v1/namespace/{namespace}/pods
	config.APIPath = "api"
	//pod的group是空字符串
	/*
	   // GroupName is the group name use in this package
	   const GroupName = ""
	   // SchemeGroupVersion is group version used to register these objects
	   var SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: "v1"}
	*/

	config.GroupVersion = &corev1.SchemeGroupVersion
	//指定序列化工具
	config.NegotiatedSerializer = scheme.Codecs

	//根据配置信息构建restClient示例
	restClient,err := rest.RESTClientFor(config)
	if err != nil{
		fmt.Println("init restClient failed ! err: ",err)
		panic(err.Error())
	}

	//保存pod结果的数据结构实例
	result := &corev1.PodList{}

	//指定namespace
	namespace := "dev"

	//设置请求参数,然后发起请求

	//GET请求
	err = restClient.Get().
		//指定namespace,参考path: /api/v1/namespace/{namespace}/pods
		Namespace(namespace).
		// 查找多个pod,参考path: /api/v1/namespace/{namespace}/pods
		Resource("pods").
		//指定大小限制和序列化工具
		// 限制指定返回结果条目为100条
		//VersionedParams(&metav1.ListOptions{Limit:100}, scheme.ParameterCodec).
		//使用字段选择器选择只返回metadata.name为coredns-64dc4c69b-v8t4m的pod
		//VersionedParams(&metav1.ListOptions{Limit:100,FieldSelector:"metadata.name=coredns-64dc4c69b-v8t4m"}, scheme.ParameterCodec).
		//使用字段选择器选择只返回状态(status.phase)为Running的pod
		//VersionedParams(&metav1.ListOptions{Limit:100,FieldSelector:"status.phase=Running"}, scheme.ParameterCodec).
		//使用标签选择器选择标签k8s-app=kube-dns的pod
		//VersionedParams(&metav1.ListOptions{Limit:100,LabelSelector:"k8s-app=kube-dns"}, scheme.ParameterCodec).
		//请求
		Do(context.TODO()).
		//将结果存入result
		Into(result)

	if err != nil{
		fmt.Println("RESTClient Get failed ! err: ",err)
		panic(err.Error())
	}
	data := make([][]string,0,len(result.Items))
	for i,_ := range result.Items{
		var count int
		for _,v := range result.Items[i].Status.ContainerStatuses{
			if v.Ready{
				count ++
			}
		}
		var avg string
		subHoure := time.Now().Sub(result.Items[i].ObjectMeta.CreationTimestamp.Time).Hours()
		if  subHoure < 24{
			avg = fmt.Sprintf("%dh", subHoure)
		}else{
			hours := int(subHoure)
			days := hours/24
			if (hours % 24)>0{
				days += 1
			}
			avg = fmt.Sprintf("%dd", days)
		}

		data = append(data, []string{result.Items[i].Namespace,result.Items[i].Name,fmt.Sprintf("%d/%d",count,len(result.Items[i].Status.ContainerStatuses)),
			string(result.Items[i].Status.Phase),strconv.Itoa(int(result.Items[i].Status.ContainerStatuses[0].RestartCount)),avg})
	}
	table := tablewriter.NewWriter(os.Stdout)
	table.SetHeader([]string{"NAMESPACE", "NAME", "READY", "STATUS","RESTART","AGE"})
	table.SetAutoWrapText(false)
	table.SetAutoFormatHeaders(true)
	table.SetHeaderAlignment(tablewriter.ALIGN_LEFT)
	table.SetAlignment(tablewriter.ALIGN_LEFT)
	table.SetCenterSeparator("")
	table.SetColumnSeparator("")
	table.SetRowSeparator("")
	table.SetHeaderLine(false)
	table.SetBorder(false)
	table.SetTablePadding("\t") // pad with tabs
	table.SetNoWhiteSpace(true)
	table.AppendBulk(data) // Add Bulk Data
	table.Render()
}

输出如下
C:\Users\xx\Desktop\git\client>go run main.go --kubeconfig inner-config
NAMESPACE       NAME                                                            READY   STATUS  RESTART AGE
dev             chat-cloud-apis-chtapp-app-recipient-server-deployment-5b45hxkb 1/1     Running 0       11d
dev             chat-cloud-apis-chtapp-chat-server-deployment-774f598bb5-2g4lm  1/1     Running 0       11d
dev             chat-cloud-apis-chtapp-chat-server-deployment-774f598bb5-4dcxx  1/1     Running 0       11d
dev             chat-cloud-apis-chtapp-chat-server-deployment-774f598bb5-5gwbs  1/1     Running 0       11d
...

Clientset

Clientset是调用Kubernetes资源对象最常用的client,可以操作所有的资源对象,包含RESTClient。需要制定Group、Version,然后根据Resource获取

  • 优雅的姿势是利用一个controller对象,再加上informer

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

案例 获取dev名称空间所有的pod

  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
package main

import (
	"context"
	"flag"
	"fmt"
	"github.com/olekukonko/tablewriter"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/client-go/kubernetes"
	"k8s.io/client-go/tools/clientcmd"
	"os"
	"path/filepath"
	"strconv"
	"time"
)

//获取家目录的环境变量值
func homeDir()string{
	if h:= os.Getenv("HOME");h!= ""{
		return h //linux家目录
	}
	return os.Getenv("USERPROFILE") //windows家目录
}

func main(){
	var kubeconfig *string

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

	//从本机加载kubeconfig配置文件,因此第一个参数为空字符串
	config , err := clientcmd.BuildConfigFromFlags("",*kubeconfig)
	if err != nil{
		fmt.Println("load kubeconfig failed!err:",err)
		panic(err.Error())
	}

	//实例化一个clientset对象
	clientset,err := kubernetes.NewForConfig(config)
	if err != nil{
		fmt.Println("init clientset failed ! err: ",err)
		panic(err.Error())
	}

	//获取podClient客户端,corev1.NamespaceAll 为空字符串,实际如果为空字符串,那么拿到的是所有名称空间的pod资源
	//podClient := clientset.CoreV1().Pods(corev1.NamespaceAll)
	podClient := clientset.CoreV1().Pods("dev")

	//使用podclient客户端,列出名称空间内所有pod资源
	result,err := podClient.List(context.TODO(),metav1.ListOptions{})
	if err != nil{
		fmt.Println("podclient get pods failed! err: ",err)
		panic(err.Error())
	}

	data := make([][]string,0,len(result.Items))
	for i,_ := range result.Items{
		var count int
		for _,v := range result.Items[i].Status.ContainerStatuses{
			if v.Ready{
				count ++
			}
		}
		var avg string
		subHoure := time.Now().Sub(result.Items[i].ObjectMeta.CreationTimestamp.Time).Hours()
		if  subHoure < 24{
			avg = fmt.Sprintf("%dh", subHoure)
		}else{
			hours := int(subHoure)
			days := hours/24
			if (hours % 24)>0{
				days += 1
			}
			avg = fmt.Sprintf("%dd", days)
		}

		data = append(data, []string{result.Items[i].Namespace,result.Items[i].Name,fmt.Sprintf("%d/%d",count,len(result.Items[i].Status.ContainerStatuses)),
			string(result.Items[i].Status.Phase),strconv.Itoa(int(result.Items[i].Status.ContainerStatuses[0].RestartCount)),avg})
	}
	table := tablewriter.NewWriter(os.Stdout)
	table.SetHeader([]string{"NAMESPACE", "NAME", "READY", "STATUS","RESTART","AGE"})
	table.SetAutoWrapText(false)
	table.SetAutoFormatHeaders(true)
	table.SetHeaderAlignment(tablewriter.ALIGN_LEFT)
	table.SetAlignment(tablewriter.ALIGN_LEFT)
	table.SetCenterSeparator("")
	table.SetColumnSeparator("")
	table.SetRowSeparator("")
	table.SetHeaderLine(false)
	table.SetBorder(false)
	table.SetTablePadding("\t") // pad with tabs
	table.SetNoWhiteSpace(true)
	table.AppendBulk(data) // Add Bulk Data
	table.Render()
}


DynamicClient

Dynamic client是一种动态的client,它能处理kubernetes所有的资源。不同于clientset,dynamic client返回的对象是一个map[string]interface{},如果一个controller中需要控制所有的API,可以使用dynamic client,目前它在garbage collector和namespace controller中被使用。

  • 只支持json

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

示例,获取dev名称空间内的所有pod(limit 100,限制显示100个)

  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
package main

import (
	"context"
	"flag"
	"fmt"
	"github.com/olekukonko/tablewriter"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/apimachinery/pkg/runtime/schema"
	"k8s.io/client-go/dynamic"
	corev1 "k8s.io/api/core/v1"
	"k8s.io/client-go/tools/clientcmd"
	"os"
	"path/filepath"
	"strconv"
	"time"
)

//获取家目录的环境变量值
func homeDir()string{
	if h:= os.Getenv("HOME");h!= ""{
		return h //linux家目录
	}
	return os.Getenv("USERPROFILE") //windows家目录
}

func main(){
	var kubeconfig *string

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

	//从本机加载kubeconfig配置文件,因此第一个参数为空字符串
	config , err := clientcmd.BuildConfigFromFlags("",*kubeconfig)
	if err != nil{
		fmt.Println("load kubeconfig failed!err:",err)
		panic(err.Error())
	}

	//实例化一个DynamicClient对象
	dynamicClient,err := dynamic.NewForConfig(config)
	if err != nil{
		fmt.Println("init dynamicClient failed ! err: ",err)
		panic(err.Error())
	}

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

	//使用dynamicClient的查询列表方法,查询指定namespace下的所有pod
	//注意此方法返回的数据结构类型是UnstructuredList
	unstructObjList,err := dynamicClient.
		//Resource是dynamicClient唯一的一个方法,参数为gvr
		Resource(gvr).
		//指定查询的namespace
		Namespace("dev").
		//以list列表的方式查询
		List(context.TODO(),metav1.ListOptions{Limit: 100})

	if err != nil{
		fmt.Println("dynamicClient list pods failed ! err :",err)
		panic(err.Error())
	}

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

	//转换
	err = runtime.DefaultUnstructuredConverter.FromUnstructured(unstructObjList.UnstructuredContent(),result)
	if err != nil{
		fmt.Println("unstructured failed ! err: ",err)
		panic(err.Error())
	}

	data := make([][]string,0,len(result.Items))
	for i,_ := range result.Items{
		var count int
		for _,v := range result.Items[i].Status.ContainerStatuses{
			if v.Ready{
				count ++
			}
		}
		var avg string
		subHoure := time.Now().Sub(result.Items[i].ObjectMeta.CreationTimestamp.Time).Hours()
		if  subHoure < 24{
			avg = fmt.Sprintf("%dh", subHoure)
		}else{
			hours := int(subHoure)
			days := hours/24
			if (hours % 24)>0{
				days += 1
			}
			avg = fmt.Sprintf("%dd", days)
		}

		data = append(data, []string{result.Items[i].Namespace,result.Items[i].Name,fmt.Sprintf("%d/%d",count,len(result.Items[i].Status.ContainerStatuses)),
			string(result.Items[i].Status.Phase),strconv.Itoa(int(result.Items[i].Status.ContainerStatuses[0].RestartCount)),avg})
	}
	table := tablewriter.NewWriter(os.Stdout)
	table.SetHeader([]string{"NAMESPACE", "NAME", "READY", "STATUS","RESTART","AGE"})
	table.SetAutoWrapText(false)
	table.SetAutoFormatHeaders(true)
	table.SetHeaderAlignment(tablewriter.ALIGN_LEFT)
	table.SetAlignment(tablewriter.ALIGN_LEFT)
	table.SetCenterSeparator("")
	table.SetColumnSeparator("")
	table.SetRowSeparator("")
	table.SetHeaderLine(false)
	table.SetBorder(false)
	table.SetTablePadding("\t") // pad with tabs
	table.SetNoWhiteSpace(true)
	table.AppendBulk(data) // Add Bulk Data
	table.Render()
}

DiscoveryClient

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

DiscoveryClient是发现客户端,主要用于发现Kubernetes API Server所支持的资源组、资源版本、资源信息。除此之外,还可以将这些信息存储到本地,用户本地缓存,以减轻Api Server访问压力。

kubectl的api-versions和api-resources命令输出也是通过DiscoverysyClient实现的。

从kubernetes查询所有的Group、Version、Resource信息

  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
167
168
169
170
171
172
173
174
package main

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

//获取家目录的环境变量值
func homeDir()string{
	if h:= os.Getenv("HOME");h!= ""{
		return h //linux家目录
	}
	return os.Getenv("USERPROFILE") //windows家目录
}

func main(){
	var kubeconfig *string

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

	//从本机加载kubeconfig配置文件,因此第一个参数为空字符串
	config , err := clientcmd.BuildConfigFromFlags("",*kubeconfig)
	if err != nil{
		fmt.Println("load kubeconfig failed!err:",err)
		panic(err.Error())
	}

	//实例化一个discoveryClient对象
	discoveryClient,err := discovery.NewDiscoveryClientForConfig(config)
	if err != nil{
		fmt.Println("init discoveryClient failed ! err: ",err)
		panic(err.Error())
	}

	//获取所有分组的资源数据
	APIGroup,APIResourceListSlice,err := discoveryClient.ServerGroupsAndResources()
	if err != nil{
		fmt.Println("get apigroup resource failed! err: ",err)
		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)
		}
	}
}

输出结果
C:\Users\xx\Desktop\git\client>go run main.go --kubeconfig inner-config
APIGroup :

 [&APIGroup{Name:,Versions:[]GroupVersionForDiscovery{GroupVersionForDiscovery{GroupVersion:v1,Version:v1,},},PreferredVersion:GroupVersionForDiscovery{GroupVersion:v1,Version:v1,},ServerAddressByClientCIDRs:[]ServerAddressByClientC
IDR{},} &APIGroup{Name:apiregistration.k8s.io,Versions:[]GroupVersionForDiscovery{GroupVersionForDiscovery{GroupVersion:apiregistration.k8s.io/v1,Version:v1,},GroupVersionForDiscovery{GroupVersion:apiregistration.k8s.io/v1beta1,Vers
ion:v1beta1,},},PreferredVersion:GroupVersionForDiscovery{GroupVersion:apiregistration.k8s.io/v1,Version:v1,},ServerAddressByClientCIDRs:[]ServerAddressByClientCIDR{},} &APIGroup{Name:extensions,Versions:[]GroupVersionForDiscovery{G
roupVersionForDiscovery{GroupVersion:extensions/v1beta1,Version:v1beta1,},},PreferredVersion:GroupVersionForDiscovery{GroupVersion:extensions/v1beta1,Version:v1beta1,},ServerAddressByClientCIDRs:[]ServerAddressByClientCIDR{},} &APIG
roup{Name:apps,Versions:[]GroupVersionForDiscovery{GroupVersionForDiscovery{GroupVersion:apps/v1,Version:v1,},GroupVersionForDiscovery{GroupVersion:apps/v1beta2,Version:v1beta2,},GroupVersionForDiscovery{GroupVersion:apps/v1beta1,Ve
rsion:v1beta1,},},PreferredVersion:GroupVersionForDiscovery{GroupVersion:apps/v1,Version:v1,},ServerAddressByClientCIDRs:[]ServerAddressByClientCIDR{},} &APIGroup{Name:events.k8s.io,Versions:[]GroupVersionForDiscovery{GroupVersionFo
rDiscovery{GroupVersion:events.k8s.io/v1beta1,Version:v1beta1,},},PreferredVersion:GroupVersionForDiscovery{GroupVersion:events.k8s.io/v1beta1,Version:v1beta1,},ServerAddressByClientCIDRs:[]ServerAddressByClientCIDR{},} &APIGroup{Na
me:authentication.k8s.io,Versions:[]GroupVersionForDiscovery{GroupVersionForDiscovery{GroupVersion:authentication.k8s.io/v1,Version:v1,},GroupVersionForDiscovery{GroupVersion:authentication.k8s.io/v1beta1,Version:v1beta1,},},Preferr
edVersion:GroupVersionForDiscovery{GroupVersion:authentication.k8s.io/v1,Version:v1,},ServerAddressByClientCIDRs:[]ServerAddressByClientCIDR{},} &APIGroup{Name:authorization.k8s.io,Versions:[]GroupVersionForDiscovery{GroupVersionFor
Discovery{GroupVersion:authorization.k8s.io/v1,Version:v1,},GroupVersionForDiscovery{GroupVersion:authorization.k8s.io/v1beta1,Version:v1beta1,},},PreferredVersion:GroupVersionForDiscovery{GroupVersion:authorization.k8s.io/v1,Versio
n:v1,},ServerAddressByClientCIDRs:[]ServerAddressByClientCIDR{},} &APIGroup{Name:autoscaling,Versions:[]GroupVersionForDiscovery{GroupVersionForDiscovery{GroupVersion:autoscaling/v1,Version:v1,},GroupVersionForDiscovery{GroupVersion
:autoscaling/v2beta1,Version:v2beta1,},GroupVersionForDiscovery{GroupVersion:autoscaling/v2beta2,Version:v2beta2,},},PreferredVersion:GroupVersionForDiscovery{GroupVersion:autoscaling/v1,Version:v1,},ServerAddressByClientCIDRs:[]Ser
verAddressByClientCIDR{},} &APIGroup{Name:batch,Versions:[]GroupVersionForDiscovery{GroupVersionForDiscovery{GroupVersion:batch/v1,Version:v1,},GroupVersionForDiscovery{GroupVersion:batch/v1beta1,Version:v1beta1,},},PreferredVersion
:GroupVersionForDiscovery{GroupVersion:batch/v1,Version:v1,},ServerAddressByClientCIDRs:[]ServerAddressByClientCIDR{},} &APIGroup{Name:certificates.k8s.io,Versions:[]GroupVersionForDiscovery{GroupVersionForDiscovery{GroupVersion:cer
tificates.k8s.io/v1beta1,Version:v1beta1,},},PreferredVersion:GroupVersionForDiscovery{GroupVersion:certificates.k8s.io/v1beta1,Version:v1beta1,},ServerAddressByClientCIDRs:[]ServerAddressByClientCIDR{},} &APIGroup{Name:networking.k
8s.io,Versions:[]GroupVersionForDiscovery{GroupVersionForDiscovery{GroupVersion:networking.k8s.io/v1,Version:v1,},GroupVersionForDiscovery{GroupVersion:networking.k8s.io/v1beta1,Version:v1beta1,},},PreferredVersion:GroupVersionForDi
scovery{GroupVersion:networking.k8s.io/v1,Version:v1,},ServerAddressByClientCIDRs:[]ServerAddressByClientCIDR{},} &APIGroup{Name:policy,Versions:[]GroupVersionForDiscovery{GroupVersionForDiscovery{GroupVersion:policy/v1beta1,Version
:v1beta1,},},PreferredVersion:GroupVersionForDiscovery{GroupVersion:policy/v1beta1,Version:v1beta1,},ServerAddressByClientCIDRs:[]ServerAddressByClientCIDR{},} &APIGroup{Name:rbac.authorization.k8s.io,Versions:[]GroupVersionForDisco
very{GroupVersionForDiscovery{GroupVersion:rbac.authorization.k8s.io/v1,Version:v1,},GroupVersionForDiscovery{GroupVersion:rbac.authorization.k8s.io/v1beta1,Version:v1beta1,},},PreferredVersion:GroupVersionForDiscovery{GroupVersion:
rbac.authorization.k8s.io/v1,Version:v1,},ServerAddressByClientCIDRs:[]ServerAddressByClientCIDR{},} &APIGroup{Name:storage.k8s.io,Versions:[]GroupVersionForDiscovery{GroupVersionForDiscovery{GroupVersion:storage.k8s.io/v1,Version:v
1,},GroupVersionForDiscovery{GroupVersion:storage.k8s.io/v1beta1,Version:v1beta1,},},PreferredVersion:GroupVersionForDiscovery{GroupVersion:storage.k8s.io/v1,Version:v1,},ServerAddressByClientCIDRs:[]ServerAddressByClientCIDR{},} &A
PIGroup{Name:admissionregistration.k8s.io,Versions:[]GroupVersionForDiscovery{GroupVersionForDiscovery{GroupVersion:admissionregistration.k8s.io/v1beta1,Version:v1beta1,},},PreferredVersion:GroupVersionForDiscovery{GroupVersion:admi
ssionregistration.k8s.io/v1beta1,Version:v1beta1,},ServerAddressByClientCIDRs:[]ServerAddressByClientCIDR{},} &APIGroup{Name:apiextensions.k8s.io,Versions:[]GroupVersionForDiscovery{GroupVersionForDiscovery{GroupVersion:apiextension
s.k8s.io/v1beta1,Version:v1beta1,},},PreferredVersion:GroupVersionForDiscovery{GroupVersion:apiextensions.k8s.io/v1beta1,Version:v1beta1,},ServerAddressByClientCIDRs:[]ServerAddressByClientCIDR{},} &APIGroup{Name:scheduling.k8s.io,V
ersions:[]GroupVersionForDiscovery{GroupVersionForDiscovery{GroupVersion:scheduling.k8s.io/v1,Version:v1,},GroupVersionForDiscovery{GroupVersion:scheduling.k8s.io/v1beta1,Version:v1beta1,},},PreferredVersion:GroupVersionForDiscovery
{GroupVersion:scheduling.k8s.io/v1,Version:v1,},ServerAddressByClientCIDRs:[]ServerAddressByClientCIDR{},} &APIGroup{Name:coordination.k8s.io,Versions:[]GroupVersionForDiscovery{GroupVersionForDiscovery{GroupVersion:coordination.k8s
.io/v1,Version:v1,},GroupVersionForDiscovery{GroupVersion:coordination.k8s.io/v1beta1,Version:v1beta1,},},PreferredVersion:GroupVersionForDiscovery{GroupVersion:coordination.k8s.io/v1,Version:v1,},ServerAddressByClientCIDRs:[]Server
AddressByClientCIDR{},} &APIGroup{Name:node.k8s.io,Versions:[]GroupVersionForDiscovery{GroupVersionForDiscovery{GroupVersion:node.k8s.io/v1beta1,Version:v1beta1,},},PreferredVersion:GroupVersionForDiscovery{GroupVersion:node.k8s.io/
v1beta1,Version:v1beta1,},ServerAddressByClientCIDRs:[]ServerAddressByClientCIDR{},} &APIGroup{Name:metrics.k8s.io,Versions:[]GroupVersionForDiscovery{GroupVersionForDiscovery{GroupVersion:metrics.k8s.io/v1beta1,Version:v1beta1,},},
PreferredVersion:GroupVersionForDiscovery{GroupVersion:metrics.k8s.io/v1beta1,Version:v1beta1,},ServerAddressByClientCIDRs:[]ServerAddressByClientCIDR{},}]



*****************************************************************
GV string [v1]
GV struct [schema.GroupVersion{Group:"", Version:"v1"}]
resources :

bindings
componentstatuses
configmaps
endpoints
events
limitranges
namespaces
namespaces/finalize
namespaces/status
nodes
nodes/proxy
nodes/status
persistentvolumeclaims
persistentvolumeclaims/status
persistentvolumes
persistentvolumes/status
pods
pods/attach
pods/binding
pods/eviction
pods/exec
pods/log
pods/portforward
pods/proxy
pods/status
podtemplates
replicationcontrollers
replicationcontrollers/scale
replicationcontrollers/status
resourcequotas
resourcequotas/status
secrets
serviceaccounts
services
services/proxy
services/status
*****************************************************************
GV string [apiregistration.k8s.io/v1]
GV struct [schema.GroupVersion{Group:"apiregistration.k8s.io", Version:"v1"}]
resources :

apiservices
apiservices/status
*****************************************************************
GV string [apiregistration.k8s.io/v1beta1]
GV struct [schema.GroupVersion{Group:"apiregistration.k8s.io", Version:"v1beta1"}]
resources :

apiservices
apiservices/status

...

dynamicClient和DiscoveryClient结合使用案例

案例需求:

1、根据yaml文件创建对应的deploy

2、查询创建的deploy

3、更新副本数为2

4、删除该deploy

  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
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
package main

import (
   "context"
   "flag"
   "fmt"
   "k8s.io/apimachinery/pkg/api/meta"
   metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
   "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
   "k8s.io/apimachinery/pkg/runtime/schema"
   "k8s.io/apimachinery/pkg/runtime/serializer/yaml"
   "k8s.io/client-go/discovery"
   memory "k8s.io/client-go/discovery/cached"
   "k8s.io/client-go/dynamic"
   "k8s.io/client-go/rest"
   "k8s.io/client-go/restmapper"
   "k8s.io/client-go/tools/clientcmd"
   "k8s.io/client-go/util/retry"
   "log"
   "os"
   "path/filepath"
)

//自动以数据
const metaCRD = `
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: my-nginx
  name: my-nginx
  namespace: default
spec:
  replicas: 1
  selector:
    matchLabels:
      app: my-nginx
  template:
    metadata:
      creationTimestamp: null
      labels:
        app: my-nginx
    spec:
      containers:
      - image: nginx
        name: nginx

`

func homeDir() string{
   if h := os.Getenv("HOME");h != ""{
      return h
   }
   return os.Getenv("USERPROFILE") //windows的家目录
}

func GetK8sConfig()(config *rest.Config,err error){
   //获取k8s rest config
   var kubeconfig *string

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


   flag.Parse()

   // 从本机加载kubeconfig配置文件,因此第一个参数为空字符串
   config, err = clientcmd.BuildConfigFromFlags("",*kubeconfig)
   // kubeconfig 加载失败就直接退出
   if err != nil{
      fmt.Println("load kubeconfig failed!,err:",err)
      panic(err.Error())
   }
   return
}

func GetGVRdyClient(gvk *schema.GroupVersionKind,namespace string)(dr dynamic.ResourceInterface,err error){
   config,err := GetK8sConfig()
   if err != nil{
      panic(err.Error())
   }

   //创建discovery客户端
   discoverClient,err := discovery.NewDiscoveryClientForConfig(config)
   if err != nil{
      panic(err.Error())
   }

   //获取GVK GVR映射,返回的是集群所有的GVK以及GVR映射
   mapperGVRGVK := restmapper.NewDeferredDiscoveryRESTMapper(memory.NewMemCacheClient(discoverClient))



   // 根据资源GVK,获取资源的GVR GVK映射

   resourceMapper,err := mapperGVRGVK.RESTMapping(gvk.GroupKind(),gvk.Version)
   //fmt.Println("resourceMapper  -----",resourceMapper.Scope.Name())
   if err != nil{
      panic(err.Error())
   }

   //创建动态客户端
   dynamicClient ,err := dynamic.NewForConfig(config)
   if err != nil{
      panic(err.Error())
   }

   if resourceMapper.Scope.Name() == meta.RESTScopeNameNamespace{
      // 获取gvr对应的动态客户端
      dr = dynamicClient.Resource(resourceMapper.Resource).Namespace(namespace)
   }else {
      dr = dynamicClient.Resource(resourceMapper.Resource)
   }
   return
}

func main(){
   var (
      err error
      objGET *unstructured.Unstructured
      objCreate *unstructured.Unstructured
      objUpdate *unstructured.Unstructured
      gvk *schema.GroupVersionKind
      dr dynamic.ResourceInterface
   )
   obj := &unstructured.Unstructured{}
   _, gvk, err = yaml.NewDecodingSerializer(unstructured.UnstructuredJSONScheme).Decode([]byte(metaCRD), nil, obj)
   if err != nil {
      panic(fmt.Errorf("failed to get GVK: %v", err))
   }

   dr,err = GetGVRdyClient(gvk,obj.GetNamespace())
   if err != nil {
      panic(fmt.Errorf("failed to get dr: %v", err))
   }
   //创建
   objCreate,err = dr.Create(context.TODO(),obj,metav1.CreateOptions{})
   if err != nil {
      panic(fmt.Errorf("Create resource ERROR: %v", err))
      //panic(err.Error())
   }
   log.Print("Create: : ",objCreate)

   // 查询
   objGET,err = dr.Get(context.TODO(),obj.GetName(),metav1.GetOptions{})
   if err != nil {
      panic(fmt.Errorf("select resource ERROR: %v", err))
   }
   log.Print("GET: ",objGET)

   //更新
   retryErr := retry.RetryOnConflict(retry.DefaultRetry, func() (err error) {
      // 查询resource是否存在
      result, getErr := dr.Get(context.TODO(),obj.GetName(),metav1.GetOptions{})
      if getErr != nil {
         panic(fmt.Errorf("failed to get latest version of : %v", getErr))
      }


      // 提取obj 的 spec 期望值
      spec, found, err := unstructured.NestedMap(obj.Object, "spec")

      if err != nil || !found || spec == nil {
         panic(fmt.Errorf(" not found or error in spec: %v", err))
      }
      //将副本数修改为2个
      spec["replicas"] = int64(2)

      // 更新 存在资源的spec
      if err := unstructured.SetNestedMap(result.Object, spec, "spec", ); err != nil {
         fmt.Println("errrrrr\n")
         panic(err)
      }
      fmt.Println("result-------------",result)
      // 更新资源
      objUpdate, err = dr.Update(context.TODO(),result,metav1.UpdateOptions{})
      log.Print("update : ",objUpdate)
      return err
   })
   if retryErr != nil {
      panic(fmt.Errorf("update failed: %v", retryErr))
   } else {
      log.Print("更新成功")
   }


   //删除
   err = dr.Delete(context.TODO(),obj.GetName(),metav1.DeleteOptions{})
   if err != nil {
      panic(fmt.Errorf("delete resource ERROR : %v", err))
   } else {
      log.Print("删除成功")
   }
}