From 8936876ceee1f509827679d1bfa35192f0a82c31 Mon Sep 17 00:00:00 2001 From: dustoair <107600816+dustoair@users.noreply.github.com> Date: Wed, 10 Aug 2022 11:27:22 +0800 Subject: [PATCH] client go for k8s --- .gitignore | 3 + ClientSet/ClientSet.go | 117 +++++++++++++++++++++++++++++ ClientSet/ClientSet_test.go | 34 +++++++++ DiscoveryClient/DiscoveryClient.go | 56 ++++++++++++++ DynamicClient/DynamicClient.go | 80 ++++++++++++++++++++ RESTClient/RESTClient.go | 78 +++++++++++++++++++ go.mod | 49 ++++++++++++ readme.md | 11 +++ utils/getConfig.go | 45 +++++++++++ utils/getConfig_test.go | 68 +++++++++++++++++ 10 files changed, 541 insertions(+) create mode 100644 .gitignore create mode 100644 ClientSet/ClientSet.go create mode 100644 ClientSet/ClientSet_test.go create mode 100644 DiscoveryClient/DiscoveryClient.go create mode 100644 DynamicClient/DynamicClient.go create mode 100644 RESTClient/RESTClient.go create mode 100644 go.mod create mode 100644 readme.md create mode 100644 utils/getConfig.go create mode 100644 utils/getConfig_test.go diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d58db1d --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +.idea +go.sum +nogit.* \ No newline at end of file diff --git a/ClientSet/ClientSet.go b/ClientSet/ClientSet.go new file mode 100644 index 0000000..65bb159 --- /dev/null +++ b/ClientSet/ClientSet.go @@ -0,0 +1,117 @@ +package ClientSet + +import ( + "ClientGo/utils" + "context" + "fmt" + v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes" + "log" + "reflect" +) + +/** +@author: sre +@date: 2022/8/10 0010 +@desc: RESTClient虽然可以操作 Kubernetes 的所有资源对象,但是使用起来确实比较复杂,需要配置的参数过于繁琐,因此,为了更优雅的更方便的与 Kubernetes APIServer 进行交互,则需要进一步的封装。 +前面有过介绍,ClientSet 是基于 RESTClient 的封装,同时 ClientSet 是使用预生成的 API 对象与 APIServer 进行交互的,这样做更方便进行二次开发。 +ClientSet 是一组资源对象客户端的集合,例如负责操作 Pods、Services 等资源的 CoreV1Client,负责操作 Deployments、DaemonSets 等资源的 AppsV1Client 等。通过这些资源对象客户端提供的操作方法,即可对 Kubernetes 内置的资源对象进行 Create、Update、Get、List、Delete 等操作。 +**/ + +// GetClientSet ClientSet 是一组资源对象客户端的集合,例如负责操作 Pods、Services 等资源的 CoreV1Client,负责操作 Deployments、DaemonSets 等资源的 AppsV1Client 等。 +// 通过这些资源对象客户端提供的操作方法,即可对 Kubernetes 内置的资源对象进行 Create、Update、Get、List、Delete 等操作。 + +func GetClientSet() (*kubernetes.Clientset, error) { + config, err := utils.GetConfig() + if err != nil { + log.Println("GetConfig 失败: ", err.Error()) + return nil, err + } + + // 实例化 ClientSet + clientset, err := kubernetes.NewForConfig(config) + if err != nil { + log.Println("实例化 ClientSet 失败: ", err.Error()) + return nil, err + } + return clientset, nil + +} + +// ListCms 列出所有的 ConfigMap +func ListCms(namespace string) (*v1.ConfigMapList, error) { + clientSet, err := GetClientSet() + if err != nil { + log.Println("GetClientSet 失败: ", err.Error()) + return nil, err + } + + configMaps, err := clientSet.CoreV1().ConfigMaps(namespace).List(context.TODO(), metav1.ListOptions{}) + if err != nil { + return nil, err + } + return configMaps, err + //for _, cm := range configMaps.Items { + // fmt.Printf("configName: %v, configData: %v \n", cm.Name, cm.Data) + //} + //return nil +} + +func ListNodes() (*v1.NodeList, error) { + clientSet, err := GetClientSet() + if err != nil { + log.Println("GetClientSet 失败: ", err.Error()) + return nil, err + } + + nodeList, err := clientSet.CoreV1().Nodes().List(context.TODO(), metav1.ListOptions{}) + if err != nil { + return nil, err + } + return nodeList, err + //for _, node := range nodeList.Items { + // fmt.Printf("nodeName: %v, status: %v", node.GetName(), node.GetCreationTimestamp()) + //} + //return nil +} + +func ListPods(namespace string) (*v1.PodList, error) { + clientSet, err := GetClientSet() + if err != nil { + log.Println("GetClientSet 失败: ", err.Error()) + return nil, err + } + pods, err := clientSet.CoreV1().Pods(namespace).List(context.TODO(), metav1.ListOptions{}) + if err != nil { + return nil, err + } + return pods, nil + //for _, v := range pods.Items { + // fmt.Printf("namespace: %v podname: %v podstatus: %v \n", v.Namespace, v.Name, v.Status.Phase) + //} +} + +func ListDeployment(namespace string) error { + clientSet, err := GetClientSet() + if err != nil { + log.Println("GetClientSet 失败: ", err.Error()) + return err + } + deployments, err := clientSet.AppsV1().Deployments(namespace).List(context.TODO(), metav1.ListOptions{}) + if err != nil { + return err + } + fmt.Println(reflect.TypeOf(deployments)) + for index, deployment := range deployments.Items { + //fmt.Println(reflect.TypeOf(deployment)) + fmt.Printf("deployment %v: %v \n", index, deployment) + } + + //fmt.Println("deployments: ", deployments) + + //for _, v := range deployments.Items { + // fmt.Printf("deploymentname: %v, available: %v, ready: %v", v.GetName(), v.Status.AvailableReplicas, v.Status.ReadyReplicas) + //} + return nil +} diff --git a/ClientSet/ClientSet_test.go b/ClientSet/ClientSet_test.go new file mode 100644 index 0000000..ca42e0c --- /dev/null +++ b/ClientSet/ClientSet_test.go @@ -0,0 +1,34 @@ +package ClientSet + +import ( + "fmt" + "testing" +) + +/* +* +@author: sre +@date: 2022/8/10 0010 +@desc: todo +* +*/ + +func TestListDeployment(t *testing.T) { + err := ListDeployment("sre") + if err != nil { + fmt.Println(err) + } + +} +func TestListCms(t *testing.T) { + cms, err := ListCms("sre") + if err != nil { + fmt.Println(err) + } + fmt.Println(cms) + +} +func TestGetClientSetRes(t *testing.T) { + + ListPods("sre") +} diff --git a/DiscoveryClient/DiscoveryClient.go b/DiscoveryClient/DiscoveryClient.go new file mode 100644 index 0000000..0d1910c --- /dev/null +++ b/DiscoveryClient/DiscoveryClient.go @@ -0,0 +1,56 @@ +package main + +import ( + "ClientGo/utils" + "fmt" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/client-go/discovery" + "log" +) + +/* +* +@author: sre +@date: 2022/8/10 0010 +@desc: DiscoveryClient 是针对于资源的。用于查看当前 Kubernetes 集群支持那些资源组、资源版本、资源信息。 +* +*/ + +func GetDiscoveryClient() (*discovery.DiscoveryClient, error) { + config, err := utils.GetConfig() + if err != nil { + log.Println("GetConfig 失败: ", err.Error()) + return nil, err + } + + // 实例化 DiscoveryClient + discoveryClient, err := discovery.NewDiscoveryClientForConfig(config) + if err != nil { + log.Println("NewDiscoveryClientForConfig 失败: ", err.Error()) + return nil, err + } + return discoveryClient, nil +} + +func GetDiscoveryClientRes() { + discoveryClient, err := GetDiscoveryClient() + if err != nil { + log.Println("GetDiscoveryClient 失败: ", err.Error()) + return + } + _, apiResources, err := discoveryClient.ServerGroupsAndResources() + if err != nil { + panic(err.Error()) + } + + for _, list := range apiResources { + gv, err := schema.ParseGroupVersion(list.GroupVersion) + if err != nil { + panic(err.Error()) + } + for _, resource := range list.APIResources { + fmt.Printf("name: %v, group: %v, version: %v\n", resource.Name, gv.Group, gv.Version) + } + } + +} diff --git a/DynamicClient/DynamicClient.go b/DynamicClient/DynamicClient.go new file mode 100644 index 0000000..726aef2 --- /dev/null +++ b/DynamicClient/DynamicClient.go @@ -0,0 +1,80 @@ +package main + +import ( + "ClientGo/utils" + "context" + "fmt" + corev1 "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" + "log" +) + +/** +@author: sre +@date: 2022/8/10 0010 +@desc: DynamicClient 是一种动态客户端,通过动态指定资源组、资源版本和资源等信息,来操作任意的 Kubernetes 资源对象的一种客户端。 +即不仅仅是操作 Kubernetes 内置的资源对象,还可以操作 CRD。这也是与 ClientSet 最明显的一个区别。 +使用 ClientSet 的时候,程序会将所用的版本与类型紧密耦合。 +而 DynamicClient 使用嵌套的 map[string]interface{} 结构存储 Kubernetes APIServer 的返回值,使用反射机制,在运行的时候,进行数据绑定,这种方式更加灵活,但是却无法获取强数据类型的检查和验证。 +此外,在介绍 DynamicClient 之前,还需要了解另外两个重要的知识点,Object.runtime 接口和 Unstructured 结构体。 +Object.runtime:Kubernetes 中的所有资源对象,都实现了这个接口,其中包含 DeepCopyObject 和 GetObjectKind 的方法,分别用于对象深拷贝和获取对象的具体资源类型。 +Unstructured:包含 map[string]interface{} 类型字段,在处理无法预知结构的数据时,将数据值存入 interface{} 中,待运行时利用反射判断。该结构体提供了大量的工具方法,便于处理非结构化的数据。 +**/ + +func GetDynamicClient() (dynamic.Interface, error) { + config, err := utils.GetConfig() + if err != nil { + log.Println("GetConfig 失败: ", err.Error()) + return nil, err + } + + // 实例化 DynamicClient + dynamicClient, err := dynamic.NewForConfig(config) + if err != nil { + log.Println("NewForConfig 失败: ", err.Error()) + return nil, err + } + return dynamicClient, nil + +} + +// GetDynamicClientRes from kubeConfigPath +func GetDynamicClientRes(namespace string) { + dynamicClient, err := GetDynamicClient() + if err != nil { + log.Println("GetDynamicClient 失败: ", err.Error()) + return + } + + // 设置要请求的 GVR + gvr := schema.GroupVersionResource{ + Group: "", + Version: "v1", + Resource: "pods", + } + + // 发送请求,并得到返回结果 + unStructData, err := dynamicClient.Resource(gvr).Namespace(namespace).List(context.TODO(), metav1.ListOptions{}) + if err != nil { + panic(err.Error()) + } + + // 使用反射将 unStructData 的数据转成对应的结构体类型,例如这是是转成 v1.PodList 类型 + podList := &corev1.PodList{} + err = runtime.DefaultUnstructuredConverter.FromUnstructured( + unStructData.UnstructuredContent(), + podList, + ) + if err != nil { + panic(err.Error()) + } + + // 输出 Pods 资源信息 + for _, item := range podList.Items { + fmt.Printf("namespace: %v, name: %v\n", item.Namespace, item.Name) + } + +} diff --git a/RESTClient/RESTClient.go b/RESTClient/RESTClient.go new file mode 100644 index 0000000..8489716 --- /dev/null +++ b/RESTClient/RESTClient.go @@ -0,0 +1,78 @@ +package main + +import ( + "ClientGo/utils" + "context" + "fmt" + 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" + "log" +) + +/** +@author: sre +@date: 2022/8/10 0010 +@desc: RESTClient 是所有客户端的父类,这也是为啥前面说,它是最基础的客户端的原因。 +它提供了 RESTful 对应的方法的封装,如:Get()、Put()、Post()、Delete() 等。通过这些封装发方法与 Kubernetes APIServer RESTful API 进行交互。 +因为它是所有客户端的父类,所以它可以操作 Kubernetes 内置的所有资源对象以及 CRD。 +**/ + +// GetRESTClient RESTClient 其实就是底层使用 net/http 库,将调用 Kubernetes APIServer 的 API 请求,进行了封装,对参数和返回结果进行了序列化及反序列化 +func GetRESTClient() (*rest.RESTClient, error) { + config, err := utils.GetConfig() + if err != nil { + log.Println("GetConfig 失败: ", err.Error()) + return nil, err + } + + // 配置 API 路径 + config.APIPath = "api" + + // 配置分组版本 + config.GroupVersion = &corev1.SchemeGroupVersion + + // 配置数据的编解码器 + config.NegotiatedSerializer = scheme.Codecs + + // 实例化 RESTClient + restClient, err := rest.RESTClientFor(config) + if err != nil { + //panic(err.Error()) + log.Println("实例化 RESTClient 失败: ", err.Error()) + return nil, err + } + return restClient, nil + + //它虽然可以操作 Kubernetes 的所有资源对象,但是使用起来确实比较复杂,需要配置的参数过于繁琐,因此,为了更优雅的更方便的与 Kubernetes APIServer 进行交互,则需要进一步的封装。 + +} + +// GetRes from GetRESTClient +func GetRes(namespace string) { + // 定义返回接收值 + restClient, err := GetRESTClient() + if err != nil { + log.Println("获取客户端失败: ", err.Error()) + return + } + // 声明空结构体 + result := &corev1.PodList{} + + err = restClient.Get(). + Namespace(namespace). // 查询的 Namespace + Resource("pods"). // 查询的资源类型 + VersionedParams(&metav1.ListOptions{Limit: 100}, scheme.ParameterCodec). // 参数及序列化工具 + Do(context.TODO()). // 发送请求 + Into(result) // 写入返回值 + if err != nil { + panic(err.Error()) + } + + // 输出返回结果 + for _, res := range result.Items { + fmt.Println(res.GenerateName) + fmt.Printf("namespace: %v, name: %v, status: %v\n", res.Namespace, res.Name, res.Status.Phase) + } +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..7112d85 --- /dev/null +++ b/go.mod @@ -0,0 +1,49 @@ +module ClientGo + +go 1.19 + +require ( + k8s.io/apimachinery v0.24.3 + k8s.io/client-go v0.24.3 +) + +require ( + github.com/PuerkitoBio/purell v1.1.1 // indirect + github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/emicklei/go-restful v2.9.5+incompatible // indirect + github.com/go-logr/logr v1.2.0 // indirect + github.com/go-openapi/jsonpointer v0.19.5 // indirect + github.com/go-openapi/jsonreference v0.19.5 // indirect + github.com/go-openapi/swag v0.19.14 // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/golang/protobuf v1.5.2 // indirect + github.com/google/gnostic v0.5.7-v3refs // indirect + github.com/google/gofuzz v1.1.0 // indirect + github.com/imdario/mergo v0.3.5 // indirect + github.com/josharian/intern v1.0.0 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/mailru/easyjson v0.7.6 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/spf13/pflag v1.0.5 // indirect + golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd // indirect + golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 // indirect + golang.org/x/sys v0.0.0-20220209214540-3681064d5158 // indirect + golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect + golang.org/x/text v0.3.7 // indirect + golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 // indirect + google.golang.org/appengine v1.6.7 // indirect + google.golang.org/protobuf v1.27.1 // indirect + gopkg.in/inf.v0 v0.9.1 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect + gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect + k8s.io/api v0.24.3 // indirect + k8s.io/klog/v2 v2.60.1 // indirect + k8s.io/kube-openapi v0.0.0-20220328201542-3ee0da9b0b42 // indirect + k8s.io/utils v0.0.0-20220210201930-3a6ce19ff2f9 // indirect + sigs.k8s.io/json v0.0.0-20211208200746-9f7c6b3444d2 // indirect + sigs.k8s.io/structured-merge-diff/v4 v4.2.1 // indirect + sigs.k8s.io/yaml v1.2.0 // indirect +) diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..dd642a0 --- /dev/null +++ b/readme.md @@ -0,0 +1,11 @@ +api for kubernetes clientgo + +https://www.huweihuang.com/kubernetes-notes/develop/client-go.html + +https://juejin.cn/post/6962869412785487909 + +https://www.fdevops.com/2022/06/26/31114 + + +[]: # Language: markdown +[]: # Path: readme.md \ No newline at end of file diff --git a/utils/getConfig.go b/utils/getConfig.go new file mode 100644 index 0000000..0c45df4 --- /dev/null +++ b/utils/getConfig.go @@ -0,0 +1,45 @@ +package utils + +import ( + "errors" + "flag" + "k8s.io/client-go/rest" + "k8s.io/client-go/tools/clientcmd" + "log" + "os" + "path/filepath" +) + +/** +@author: sre +@date: 2022/8/10 0010 +@desc: GetConfig from kubeConfigPath +**/ +// GetConfig from kubeConfigPath +func GetConfig(kubeConfigPath ...string) (*rest.Config, error) { + var kubeConfig *string + + if len(kubeConfigPath) > 0 { + kubeConfig = &kubeConfigPath[0] + } else if home := homeDir(); home != "" { + kubeConfig = flag.String("kubeconfig", filepath.Join(home, ".kube", "config"), "(optional) absolute path to the kubeconfig file") + } else { + log.Println("can not find kubeconfig") + return nil, errors.New("can not find kubeconfig") + } + // 加载配置文件,生成 config 对象 + config, err := clientcmd.BuildConfigFromFlags("", *kubeConfig) + if err != nil { + log.Println("加载配置文件失败: ", err.Error()) + return nil, err + } + return config, nil +} + +// homeDir get current user home +func homeDir() string { + if h := os.Getenv("HOME"); h != "" { + return h + } + return os.Getenv("USERPROFILE") // windows +} diff --git a/utils/getConfig_test.go b/utils/getConfig_test.go new file mode 100644 index 0000000..f565704 --- /dev/null +++ b/utils/getConfig_test.go @@ -0,0 +1,68 @@ +package utils + +import ( + "fmt" + "testing" +) + +/** +@author: sre +@date: 2022/8/10 0010 +@desc: todo +**/ + +func TestGetConfigHome(t *testing.T) { + config, err := GetConfig() + if err != nil { + fmt.Println("加载配置文件失败: ", err.Error()) + return + } + fmt.Println(config.APIPath) + fmt.Println(config.Host) + fmt.Println(config.Username) + fmt.Println(config.Password) + fmt.Println(config.BearerToken) + fmt.Println(config.TLSClientConfig.CertFile) + fmt.Println(config.TLSClientConfig.KeyFile) + fmt.Println(config.TLSClientConfig.CAFile) + fmt.Println(config.TLSClientConfig.Insecure) + fmt.Println(config.TLSClientConfig.ServerName) + fmt.Println(config.GroupVersion) + fmt.Println(config.NegotiatedSerializer) + fmt.Println(config.QPS) + fmt.Println(config.Burst) + fmt.Println(config.Timeout) + fmt.Println(config.ContentConfig) + fmt.Println(config.WrapTransport) + fmt.Println(config.RateLimiter) + fmt.Println(config.GroupVersion) + fmt.Println(config.NegotiatedSerializer) +} + +func TestGetConfigDefine(t *testing.T) { + config, err := GetConfig("../nogit.k8s.config") + if err != nil { + fmt.Println("加载配置文件失败: ", err.Error()) + return + } + fmt.Println(config.APIPath) + fmt.Println(config.Host) + fmt.Println(config.Username) + fmt.Println(config.Password) + fmt.Println(config.BearerToken) + fmt.Println(config.TLSClientConfig.CertFile) + fmt.Println(config.TLSClientConfig.KeyFile) + fmt.Println(config.TLSClientConfig.CAFile) + fmt.Println(config.TLSClientConfig.Insecure) + fmt.Println(config.TLSClientConfig.ServerName) + fmt.Println(config.GroupVersion) + fmt.Println(config.NegotiatedSerializer) + fmt.Println(config.QPS) + fmt.Println(config.Burst) + fmt.Println(config.Timeout) + fmt.Println(config.ContentConfig) + fmt.Println(config.WrapTransport) + fmt.Println(config.RateLimiter) + fmt.Println(config.GroupVersion) + fmt.Println(config.NegotiatedSerializer) +}