相先 發表於 2022-4-28 17:16:00

Client-go的四种客户端的简单使用

<h3 id="client-go的四种客户端使用">Client-go的四种客户端使用</h3>
<p>我们知道kubectl是通过命令行交互的方式与Kubernetes API Server进行交互的,Kubernetes还提供了通过编程的方式与Kubernetes API-Server进行通信。</p>
<p>client-go是从 Kubernetes的代码中单独抽离出来的包,并作为官方提 供的Go语言的客户端发挥作用。</p>
<p>client-go简单、易用, Kubernetes系统的其他组件与Kubernetes API Server通信 的方式也基于client-go实现。</p>
<p>client-go在Kubernetes系统上做了大量的 优化,Kubernetes核心组件(如kube-scheduler、kube- controller-manager等)都通过client-go与Kubernetes</p>
<p>API Server进行交互。</p>
<p>Client-go为我们提供了四种客户端。</p>
<ul>
<li>RESTClient:这是最基础的客户端对象,仅对HTTP Request进行了封装,实现RESTFul风格API,这个对象的使用并不方便,因为很多参数都要使用者来设置;</li>
<li>ClientSet:把Resource和Version也封装成方法了,用起来更简单直接,一个资源是一个客户端,多个资源就对应了多个客户端,所以ClientSet就是多个客户端的集合,不过ClientSet只能访问内置资源,访问不了自定义资源;</li>
<li>DynamicClient:可以访问内置资源和自定义资源,拿出的内容是Object类型,按实际情况自己去做强制转换,当然了也会有强转失败的风险;</li>
<li>DiscoveryClient:用于发现kubernetes的API Server支持的Group、Version、Resources等信息。</li>
</ul>
<h4 id="1restclient">1、RESTClient</h4>
<p>我们来使用RESTClinet实现获取kubernetes集群中所有的pod资源。</p>
<pre><code class="language-go">package main

import (
        "context"
        "fmt"
        v1 "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 restClientGetPods() {
        // 获取config,从本机中获取kubeconfig的配置文件。
        config, err := clientcmd.BuildConfigFromFlags("", clientcmd.RecommendedHomeFile)
        if err != nil {
                panic(err)
        }

        // 因为pod的group为空,version为v1
        config.GroupVersion = &amp;v1.SchemeGroupVersion
        // 设置反序列化
        config.NegotiatedSerializer = scheme.Codecs
        // 指定ApiPath,参考/api/v1/namespaces/{namespace}/pods
        config.APIPath = "/api"

        // 根据配置文件,获取rest客户端
        client, err := rest.RESTClientFor(config)
        if err != nil {
                panic(err)
        }

        // 指定接收参数
        result := &amp;v1.PodList{}

        // 指定namespace
        namespace := "kube-system"
        // 通过rest client获取pod的信息
        err = client.Get().
                Namespace(namespace).                                                    // 指定namespace
                Resource("pods").                                                      // 指定要获取的资源对象
                VersionedParams(&amp;metav1.ListOptions{Limit: 100}, scheme.ParameterCodec). // 指定大小限制和序列化工具
                Do(context.TODO()).                                                      // 执行
                Into(result)                                                             // 将结果写入result
        if err != nil {
                panic(err)
        }

        for _, pod := range result.Items {
                fmt.Println(pod.Name)
        }
}
</code></pre>
<p>运行我们的程序,我们就可以获取到"kube-system"命名空间下的所有Pod的列表,但是我们也可以通过RESTClient来增删改查kubernetes其他的内置资源。</p>
<h4 id="2clientset">2、ClientSet</h4>
<p>上面演示了使用RESTClient对kubernetes的内置资源的操作,但是为什么还需要ClientSet呢?</p>
<p>这是因为ClientSet对RESTClient做了一层封装,把每一种资源都封装了对应的客户端。</p>
<p>下面来演示使用ClientSet创建Namespace,在Namespace里创建Deploy,创建Service,使用Service来访问Deploy所管理的Pod。</p>
<pre><code class="language-go">package main

import (
        "context"
        "fmt"
"k8s.io/client-go/rest"
        "k8s.io/client-go/tools/clientcmd"
        v1 "k8s.io/api/apps/v1"
        corev1 "k8s.io/api/core/v1"
        metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
        "k8s.io/client-go/kubernetes"
        "k8s.io/utils/pointer"
)

const (
        NAMESPACE= "dev"
        DEPLOYMENT = "dev-deploy"
        SERVICE    = "dev-service"
)

func initConfig() (*rest.Config, error) {
        // config
        // 从本机中获取kubeconfig的配置文件,因此第一个参数是空字符串
        config, err := clientcmd.BuildConfigFromFlags("", clientcmd.RecommendedHomeFile)
        if err != nil {
                return nil, err
        }
        return config, nil
}


// 初始化客户端
func initClient() (*kubernetes.Clientset, error) {
        config, err := initConfig()
        if err != nil {
                panic(err)
        }
        // 实例化clientSet对象
        return kubernetes.NewForConfig(config)
}

func createNamespace(clientSet *kubernetes.Clientset) {
        // 获取namespace的接口
        namespaceClient := clientSet.CoreV1().Namespaces()
        namespace := &amp;corev1.Namespace{
                ObjectMeta: metav1.ObjectMeta{
                        Name: NAMESPACE,
                },
                Status: corev1.NamespaceStatus{},
        }
        // 调用Create方法进行创建
        ns, err := namespaceClient.Create(context.TODO(), namespace, metav1.CreateOptions{})
        if err != nil {
                panic(err)
        }

        fmt.Println(ns.GetName())
}

func createDeploy(clientSet *kubernetes.Clientset) {
        // 获取deploy的接口
        deployClient := clientSet.AppsV1().Deployments(NAMESPACE)
        // 定义要创建的deployment
        deploy := &amp;v1.Deployment{
                ObjectMeta: metav1.ObjectMeta{
                        Name:      DEPLOYMENT,
                        Namespace: NAMESPACE,
                },
                Spec: v1.DeploymentSpec{
                        Replicas: pointer.Int32(2),
                        Selector: &amp;metav1.LabelSelector{
                                MatchLabels: mapstring{
                                        "app": "nginx",
                                },
                        },
                        Template: corev1.PodTemplateSpec{
                                ObjectMeta: metav1.ObjectMeta{
                                        Name: "nginx",
                                        Labels: mapstring{
                                                "app": "nginx",
                                        },
                                },
                                Spec: corev1.PodSpec{
                                        Containers: []corev1.Container{
                                                {
                                                        Name:"nginx",
                                                        Image: "nginx:1.17.1",
                                                        Ports: []corev1.ContainerPort{
                                                                {
                                                                        ContainerPort: 80,
                                                                        Protocol:      corev1.ProtocolTCP,
                                                                },
                                                        },
                                                        ImagePullPolicy: "IfNotPresent",
                                                },
                                        },
                                },
                        },
                },
                Status: v1.DeploymentStatus{},
        }
        // 创建
        result, err := deployClient.Create(context.TODO(), deploy, metav1.CreateOptions{})
        if err != nil {
                panic(err)
        }
        fmt.Println(result.GetName())
}

// 创建svc
func createService(clientSet *kubernetes.Clientset) {
        // 获取serivce的接口
        serviceClient := clientSet.CoreV1().Services(NAMESPACE)
        // 定义service
        service := &amp;corev1.Service{
                ObjectMeta: metav1.ObjectMeta{
                        Name:      SERVICE,
                        Namespace: NAMESPACE,
                },
                Spec: corev1.ServiceSpec{
                        Ports: []corev1.ServicePort{
                                {
                                        Port:   80,
                                        NodePort: 30001,
                                },
                        },
                        Selector: mapstring{
                                "app": "nginx",
                        },
                        Type: corev1.ServiceTypeNodePort,
                },
                Status: corev1.ServiceStatus{},
        }

        result, err := serviceClient.Create(context.TODO(), service, metav1.CreateOptions{})
        if err != nil {
                panic(err)
        }
        fmt.Println(result.GetName())
}

// 测试
func Run() {
        // 创建clientSet的客户端
        clientSet, err := initClient()
        if err != nil {
                panic(err)
        }

        // 通过clientSet创建namespace
        createNamespace(clientSet)

        // 通过clientSet创建deployment
        createDeploy(clientSet)

        // 通过clientSet创建service
        createService(clientSet)
}

func deleteAll() {
        emptyDeleteOptions := metav1.DeleteOptions{}
        clientSet, err := initClient()
        if err != nil {
                return
        }
        // 删除svc
        err = clientSet.CoreV1().Services(NAMESPACE).Delete(context.TODO(), SERVICE, emptyDeleteOptions)
        if err != nil {
                return
        }

        // 删除deployment
        err = clientSet.AppsV1().Deployments(NAMESPACE).Delete(context.TODO(), DEPLOYMENT, emptyDeleteOptions)
        if err != nil {
                return
        }

        // 删除namespace
        err = clientSet.CoreV1().Namespaces().Delete(context.TODO(), NAMESPACE, emptyDeleteOptions)
        if err != nil {
                return
        }

        // 删除pod
        err = clientSet.CoreV1().Pods("default").Delete(context.TODO(), "nginx", emptyDeleteOptions)
        if err != nil {
                return
        }
}

</code></pre>
<p>运行Run()后我们进行查看:</p>
<pre><code class="language-shell">$ kubectl get ns | grep dev
dev               Active   57s

$ kubectl get all -n dev
NAME                              READY   STATUS    RESTARTS   AGE
pod/dev-deploy-757ccd788c-jfdsr   1/1   Running   0          2m3s
pod/dev-deploy-757ccd788c-ltp6g   1/1   Running   0          2m3s

NAME                  TYPE       CLUSTER-IP       EXTERNAL-IP   PORT(S)      AGE
service/dev-service   NodePort   10.104.118.223   &lt;none&gt;      80:30001/TCP   2m4s

NAME                         READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/dev-deploy   2/2   2            2         2m4s

NAME                                    DESIRED   CURRENT   READY   AGE
replicaset.apps/dev-deploy-757ccd788c   2         2         2       2m4s

# 访问
$ curl 192.168.216.196:30001
&lt;!DOCTYPE html&gt;
&lt;html&gt;
&lt;head&gt;
&lt;title&gt;Welcome to nginx!&lt;/title&gt;
&lt;style&gt;
    body {
      width: 35em;
      margin: 0 auto;
      font-family: Tahoma, Verdana, Arial, sans-serif;
    }
&lt;/style&gt;
&lt;/head&gt;
&lt;body&gt;
&lt;h1&gt;Welcome to nginx!&lt;/h1&gt;
&lt;p&gt;If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.&lt;/p&gt;

&lt;p&gt;For online documentation and support please refer to
&lt;a href="http://nginx.org/"&gt;nginx.org&lt;/a&gt;.&lt;br/&gt;
Commercial support is available at
&lt;a href="http://nginx.com/"&gt;nginx.com&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Thank you for using nginx.&lt;/em&gt;&lt;/p&gt;
&lt;/body&gt;
&lt;/html&gt;
</code></pre>
<p>最后我们调用deleteAll()做一下清理工作。</p>
<pre><code class="language-shell">$ kubectl get ns | grep dev
dev               Terminating   5m47s

$ kubectl get ns | grep dev

</code></pre>
<h4 id="3dynamicclient">3、DynamicClient</h4>
<p>如果我们只是操作kubernetes的内置资源,前面介绍的两种客户端已经足够了,而且ClientSet把每一种资源都封装了对应的客户端。但是对于CRD资源,前面两种客户端就无能为力了。这时候就该我们的DynamicClient出场了。</p>
<p>在使用DynamicClient之前,我们必须先了解下<code>Object.runtime</code>和<code>Unstructured</code>分别是什么东西。</p>
<ul>
<li>Object.runtime</li>
</ul>
<p>在kubernetes的代码中,资源对象对应着具体的数据结构,这些数据结构都实现了同一个接口,名为Object.runtime,源码位置是k8s.io/apimachinery/pkg/runtime/interfaces.go,定义如下:</p>
<pre><code class="language-go">// Object interface must be supported by all API types registered with Scheme. Since objects in a scheme are
// expected to be serialized to the wire, the interface an Object must provide to the Scheme allows
// serializers to set the kind, version, and group the object is represented as. An Object may choose
// to return a no-op ObjectKindAccessor in cases where it is not expected to be serialized.
type Object interface {
        GetObjectKind() schema.ObjectKind
        DeepCopyObject() Object
}
</code></pre>
<p>DeepCopyObject():将内存中的对象克隆出一个新的对象;</p>
<p>GetObjectKind():处理Object.runtime类型的变量时,只要调用其GetObjectKind方法就知道它的具体身份了(GVK)。</p>
<ul>
<li>Unstructured</li>
</ul>
<p>与结构化数据相对的就是非结构化数据了。先看看什么是结构化数据:</p>
<pre><code class="language-json">{
"id": 11,
"name": "libi"
}
</code></pre>
<p>对于上面的json字串,key和value的类型是固定的,因此可以编写对应的数据结构来表示:</p>
<pre><code class="language-go">type Person struct{
Id int `json:id`
Name string `json:name`
}
</code></pre>
<p>在实际的kubernetes环境中,可能会遇到一些无法预知结构的数据,例如前面的JSON字符串中还有第三个字段,字段值的具体内容和类型在编码时并不确定,而是在真正运行的时候才知道,那么在编码时如何处理呢?在go中我们可以使用空接口interface{}来表示,实际上client-go也是这么做的。k8s.io/apimachinery/pkg/apis/meta/v1/unstructured/unstructured.go:</p>
<pre><code class="language-go">type Unstructured struct {
        // Object is a JSON compatible map with string, float, int, bool, []interface{}, or
        // mapinterface{}
        // children.
        Object mapinterface{}
}
</code></pre>
<p>下面我们进入实战环节,这里直接搬取了官方demo。</p>
<p>首先是定义了一个deployment资源实例,进行创建后,更新Pod的数量,接着又获取了一下containers以及更新第一个容器的镜像。接下来获取deploy资源列表并,最后清理deployment。</p>
<pre><code class="language-go">package main

import (
        "bufio"
        "context"
        "fmt"
"k8s.io/client-go/rest"
        "k8s.io/client-go/tools/clientcmd"
        metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
        "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
        "k8s.io/apimachinery/pkg/runtime/schema"
        "k8s.io/client-go/dynamic"
        "k8s.io/client-go/util/retry"
        "os"
)

func initConfig() (*rest.Config, error) {
        // config
        // 从本机中获取kubeconfig的配置文件,因此第一个参数是空字符串
        config, err := clientcmd.BuildConfigFromFlags("", clientcmd.RecommendedHomeFile)
        if err != nil {
                return nil, err
        }
        return config, nil
}


func dynamicClient() {
        config, err := initConfig()
        if err != nil {
                panic(err)
        }

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

        deploymentRes := schema.GroupVersionResource{
                Group:    "apps",
                Version:"v1",
                Resource: "deployments",
        }

        deployment := &amp;unstructured.Unstructured{
                Object: mapinterface{}{
                        "apiVersion": "apps/v1",
                        "kind":       "Deployment",
                        "metadata": mapinterface{}{
                                "name": "demo-deployment",
                        },
                        "spec": mapinterface{}{
                                "replicas": 2,
                                "selector": mapinterface{}{
                                        "matchLabels": mapinterface{}{
                                                "app": "demo",
                                        },
                                },
                                "template": mapinterface{}{
                                        "metadata": mapinterface{}{
                                                "labels": mapinterface{}{
                                                        "app": "demo",
                                                },
                                        },
                                        "spec": mapinterface{}{
                                                "containers": []mapinterface{}{
                                                        {
                                                                "name":"nginx",
                                                                "image": "nginx:1.17.1",
                                                                "ports": []mapinterface{}{
                                                                        {
                                                                                "name":          "http",
                                                                                "protocol":      "TCP",
                                                                                "containerPort": 80,
                                                                        },
                                                                },
                                                        },
                                                },
                                        },
                                },
                        },
                },
        }

        // 创建Deployment
        fmt.Println("Create deployment......")
        result, err := client.Resource(deploymentRes).Namespace("default").Create(context.TODO(), deployment, metav1.CreateOptions{})
        if err != nil {
                panic(err)
        }
        fmt.Printf("Create deployment %q.\n", result.GetName())

        // 更新
        prompt()
        retryErr := retry.RetryOnConflict(retry.DefaultRetry, func() error {
                result, getErr := client.Resource(deploymentRes).Namespace("default").
                        Get(context.TODO(), "demo-deployment", metav1.GetOptions{})
                if err != nil {
                        panic(fmt.Errorf("failed to get latest version of Deployment:%v", getErr))
                }

                // update replicas to 1
                if err := unstructured.SetNestedField(result.Object, int64(1), "spec", "replicas"); err != nil {
                        panic(fmt.Errorf("failed to set replica value:%v", err))
                }

                // extract spec containers
                containers, found, err := unstructured.NestedSlice(result.Object, "spec", "template", "spec", "containers")
                if err != nil || !found || containers == nil {
                        panic(fmt.Errorf("deployment containers not found or err in sepc:%v", err))
                }

                // update container image
                if err := unstructured.SetNestedField(containers.(mapinterface{}), "nginx:1.17.9", "image"); err != nil {
                        panic(err)
                }

                _, updateErr := client.Resource(deploymentRes).Namespace("default").Update(context.TODO(), result, metav1.UpdateOptions{})
                return updateErr
        })
        if retryErr != nil {
                panic(fmt.Errorf("update failed:%v", retryErr))
        }
        fmt.Println("Update deployment......")

        // list Deployments
        prompt()
        fmt.Printf("Listing deployment in namesapce %q:\n", metav1.NamespaceDefault)
        list, err := client.Resource(deploymentRes).Namespace("default").List(context.TODO(), metav1.ListOptions{})
        if err != nil {
                panic(err)
        }

        for _, d := range list.Items {
                replicas, found, err := unstructured.NestedInt64(d.Object, "spec", "replicas")
                if err != nil || !found {
                        fmt.Printf("Replicas not found for deployment %s: error=%s", d.GetName(), err)
                        continue
                }
                fmt.Printf("* %s(%d replicas)\n", d.GetName(), replicas)
        }

        // Delete Deployment
        prompt()
        fmt.Println("Deleting deployment......")
        deletePolicy := metav1.DeletePropagationBackground
        deleteOptions := metav1.DeleteOptions{
                PropagationPolicy: &amp;deletePolicy,
        }
        if err := client.Resource(deploymentRes).Namespace("default").Delete(context.TODO(), "demo-deployment", deleteOptions); err != nil {
                panic(err)
        }

}

func prompt() {
        fmt.Printf("-&gt; Press Return key to continue.")
        scanner := bufio.NewScanner(os.Stdin)
        for scanner.Scan() {
                break
        }

        if err := scanner.Err(); err != nil {
                panic(err)
        }
        fmt.Println()
}
</code></pre>
<p>上面的例子都有一个共同的特点,都是需要我们去组装数据,那么现在有这样一个需求,我不想去组装数据,我已经有了对应的yaml了,我要读取里面的内容,将其转换为kubernetesr认识的资源:</p>
<pre><code class="language-go">package main

import (
        "context"
        "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/serializer/yaml"
        "k8s.io/client-go/discovery"
        memory "k8s.io/client-go/discovery/cached"
        "k8s.io/client-go/dynamic"
        "k8s.io/client-go/restmapper"
        "k8s.io/client-go/tools/clientcmd"
        "k8s.io/client-go/util/retry"
)

const deploymentYAML = `
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
namespace: default
spec:
selector:
    matchLabels:
      app: nginx
template:
    metadata:
      labels:
      app: nginx
    spec:
      containers:
      - name: nginx
      image: nginx:latest
`

func dynamicClient_demo() {
        // config
        // 从本机中获取kubeconfig的配置文件,因此第一个参数是空字符串
        config, err := clientcmd.BuildConfigFromFlags("", clientcmd.RecommendedHomeFile)
        if err != nil {
                panic(err)
        }

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

        obj := &amp;unstructured.Unstructured{}
        _, gvk, err := yaml.NewDecodingSerializer(unstructured.UnstructuredJSONScheme).Decode([]byte(deploymentYAML), nil, obj)
        if err != nil {
                panic(err)
        }

        // 通过discoveryClient获取GVK GVR 映射
        resourceMapper, err := restmapper.NewDeferredDiscoveryRESTMapper(memory.NewMemCacheClient(discoveryClient)).RESTMapping(gvk.GroupKind(), gvk.Version)
        if err != nil {
                return
        }

        fmt.Println(resourceMapper.Scope.Name())

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

        var dClient dynamic.ResourceInterface
        if resourceMapper.Scope.Name() == meta.RESTScopeNameNamespace {
                dClient = dynamicClient.Resource(resourceMapper.Resource).Namespace(obj.GetNamespace())
        } else {
                dClient = dynamicClient.Resource(resourceMapper.Resource)
        }

        // 创建
        result, err := dClient.Create(context.TODO(), obj, metav1.CreateOptions{})
        if err != nil {
                panic(err)
        }

        fmt.Println(result.GetName())

        // 更新
        if retryErr := retry.RetryOnConflict(retry.DefaultRetry, func() error {
                // 先获取
                getResult, getErr := dClient.Get(context.TODO(), obj.GetName(), metav1.GetOptions{})
                if getErr != nil {
                        return err
                }

                // 更新replicas to 2
                err = unstructured.SetNestedField(getResult.Object, int64(2), "spec", "replicas")
                if err != nil {
                        return err
                }

                // 更新镜像
                containers, found, err := unstructured.NestedSlice(getResult.Object, "spec", "template", "spec", "containers")
                if err != nil || !found {
                        return err
                }
                if err = unstructured.SetNestedField(containers.(mapinterface{}), "nginx:1.17.9", "image"); err != nil {
                        return err
                }

                if err = unstructured.SetNestedField(getResult.Object, containers, "spec", "template", "spec", "containers"); err != nil {
                        return err
                }

                _, updateErr := dClient.Update(context.TODO(), getResult, metav1.UpdateOptions{})
                return updateErr
        }); retryErr != nil {
                panic(retryErr)
        }

        // 删除
        err = dClient.Delete(context.TODO(), obj.GetName(), metav1.DeleteOptions{})
        if err != nil {
                panic(err)
        }
}
</code></pre>
<blockquote>
<p>Unstructured与资源对象的相互转换</p>
<p>用Unstructured实例生成资源对象,也可以用资源对象生成Unstructured实例,这个能力是unstructuredConverter的FromUnstructured和ToUnstructured方法分别实现的。</p>
</blockquote>
<h4 id="4discoveryclient">4、DiscoveryClient</h4>
<p>DiscoveryClient主要是用于发现kubernetes的API Server支持的Group、Version、Resources等信息。</p>
<pre><code class="language-go">package test

import (
        "fmt"
        "k8s.io/client-go/discovery"
"k8s.io/client-go/rest"
        "k8s.io/client-go/tools/clientcmd"
)

func initConfig() (*rest.Config, error) {
        // config
        // 从本机中获取kubeconfig的配置文件,因此第一个参数是空字符串
        config, err := clientcmd.BuildConfigFromFlags("", clientcmd.RecommendedHomeFile)
        if err != nil {
                return nil, err
        }
        return config, nil
}

func discoveryClient() {
        config, err := initConfig()
        if err != nil {
                panic(config)
        }

        // 新建discoveryClient实例
        client, err := discovery.NewDiscoveryClientForConfig(config)
        if err != nil {
                panic(err)
        }

        // 获取apiGroups 和 apiResources,返回是两个切片
        _, resources, err := client.ServerGroupsAndResources()
        if err != nil {
                panic(err)
        }

        // 先循环遍历看一下有哪些groups,这里直接打印名字
        //for _, group := range apiGroups {
        //        fmt.Println(group.Name)
        //}

        fmt.Println()
        // 查看resources
        for _, resource := range resources {
                // resource里面包含了groupVersion,是个字符串
                // groupVersionStr := resource.GroupVersion
                // ParseGroupVersion将gv字符串转换为struct
                //gv, err := schema.ParseGroupVersion(groupVersionStr)
                //if err != nil {
                //        panic(err)
                //}
                // fmt.Println(gv)
                for _, r := range resource.APIResources {
                        fmt.Println(r.Name)
                }
        }
}
</code></pre><br><br>
来源:https://www.cnblogs.com/huiyichanmian/p/16203690.html
頁: [1]
查看完整版本: Client-go的四种客户端的简单使用