# 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官方文档](https://github.com/kubernetes/client-go) ## 知识点 ### package包的功能说明 - kubernetes: 访问kubernetes API的一系列的clientset - discovery: 通过Kubernetes API进行服务发现 - dynamic: 对任意Kubernetes对象执行通用操作的动态client - transport:启动连接和鉴权auth - tools/cache: controllers控制器 #### 安装 ``` #下载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 ``` #安装用于格式打印的库 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 ``` 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个) ``` 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信息 ``` 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 ``` 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("删除成功") } } ```