天天看點

一文帶您了解 Client-Go 的四種用戶端

Client-Go 簡介

Client-Go 是負責與 Kubernetes APIServer 服務進行互動的用戶端庫,利用 Client-Go 與Kubernetes APIServer 進行的互動通路,來對 Kubernetes 中的各類資源對象進行管理操作,包括内置的資源對象及CRD。

Client-Go 不僅被 Kubernetes 項目本身使用,其它圍繞着 Kubernetes 的生态,也被大量的使用,例如:kubectl、ETCD-operator等等。

Client-Go 用戶端

Client-Go 共提供了 4 種與 Kubernetes APIServer 互動的用戶端。分别是 RESTClient、DiscoveryClient、ClientSet、DynamicClient。

  • RESTClient:最基礎的用戶端,主要是對 HTTP 請求進行了封裝,支援 Json 和 Protobuf 格式的資料。
  • DiscoveryClient:發現用戶端,負責發現 APIServer 支援的資源組、資源版本和資源資訊的。
  • ClientSet:負責操作 Kubernetes 内置的資源對象,例如:Pod、Service等。
  • DynamicClient:動态用戶端,可以對任意的 Kubernetes 資源對象進行通用操作,包括 CRD。
一文帶您了解 Client-Go 的四種用戶端

RESTClient

RESTClient 是所有用戶端的父類,這也是為啥前面說,它是最基礎的用戶端的原因。

它提供了 RESTful 對應的方法的封裝,如:Get()、Put()、Post()、Delete() 等。通過這些封裝發方法與 Kubernetes APIServer RESTful API 進行互動。

因為它是所有用戶端的父類,是以它可以操作 Kubernetes 内置的所有資源對象以及 CRD。

運作以下代碼,将得到 default 下的 Pod 部分相關資源資訊。

package main

import (
    "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"
    "k8s.io/client-go/tools/clientcmd"
)

/*
  @Author : lanyulei
  @Desc :
*/

func main() {
    // 加載配置檔案,生成 config 對象
    config, err := clientcmd.BuildConfigFromFlags("", "../../kubeconfig")
    if err != nil {
        panic(err.Error())
    }

    // 配置 API 路徑
    config.APIPath = "api"

    // 配置分組版本
    config.GroupVersion = &corev1.SchemeGroupVersion

    // 配置資料的編解碼器
    config.NegotiatedSerializer = scheme.Codecs

    // 執行個體化 RESTClient
    restClient, err := rest.RESTClientFor(config)
    if err != nil {
        panic(err.Error())
    }

    // 定義傳回接收值
    result := &corev1.PodList{}

    err = restClient.Get().
        Namespace("default"). // 查詢的 Namespace
        Resource("pods"). // 查詢的資源類型
        VersionedParams(&metav1.ListOptions{Limit: 100}, scheme.ParameterCodec). // 參數及序列化工具
        Do(context.TODO()). // 發送請求
        Into(result) // 寫入傳回值
    if err != nil {
        panic(err.Error())
    }

    // 輸出傳回結果
    for _, d := range result.Items {
        fmt.Printf("namespace: %v, name: %v, status: %v\n", d.Namespace, d.Name, d.Status.Phase)
    }
}      

輸出結果

➜  demo1 go run main.go
namespace: default, name: 1231-589c58c8c5-27r94, status: Pending
namespace: default, name: 1231-b6896fd77-7bkgk, status: Pending
namespace: default, name: web-96d5df5c8-rcw6r, status: Running
namespace: default, name: web-96d5df5c8-rdzpr, status: Running      

RESTClient 其實就是底層使用 net/http 庫,将調用 Kubernetes APIServer 的 API 請求,進行了封裝,對參數和傳回結果進行了序列化及反序列化,有興趣的話,可以看下源碼,分析一下請求過程。這裡就不多介紹了。

ClientSet

上面介紹了 RESTClient,它雖然可以操作 Kubernetes 的所有資源對象,但是使用起來确實比較複雜,需要配置的參數過于繁瑣,是以,為了更優雅的更友善的與 Kubernetes APIServer 進行互動,則需要進一步的封裝。

前面有過介紹,ClientSet 是基于 RESTClient 的封裝,同時 ClientSet 是使用預生成的 API 對象與 APIServer 進行互動的,這樣做更友善進行二次開發。

ClientSet 是一組資源對象用戶端的集合,例如負責操作 Pods、Services 等資源的 CoreV1Client,負責操作 Deployments、DaemonSets 等資源的 AppsV1Client 等。通過這些資源對象用戶端提供的操作方法,即可對 Kubernetes 内置的資源對象進行 Create、Update、Get、List、Delete 等操作。

運作以下代碼,将得到 default 下的 Pod 部分相關資源資訊。

package main

import (
    "context"
    "fmt"
    metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    "k8s.io/client-go/kubernetes"
    "k8s.io/client-go/tools/clientcmd"
)

/*
  @Author : lanyulei
  @Desc :
*/

func main() {
    // 加載配置檔案,生成 config 對象
    config, err := clientcmd.BuildConfigFromFlags("", "../../kubeconfig")
    if err != nil {
        panic(err.Error())
    }

    // 執行個體化 ClientSet
    clientset, err := kubernetes.NewForConfig(config)
    if err != nil {
        panic(err.Error())
    }

    // 查詢 default 下的 pods 部門資源資訊
    pods, err := clientset.
        CoreV1().                                  // 執行個體化資源用戶端,這裡辨別執行個體化 CoreV1Client
        Pods("default").                           // 選擇 namespace,為空則表示所有 Namespace
        List(context.TODO(), metav1.ListOptions{}) // 查詢 pods 清單
    if err != nil {
        panic(err.Error())
    }

    // 輸出 Pods 資源資訊
    for _, item := range pods.Items {
        fmt.Printf("namespace: %v, name: %v\n", item.Namespace, item.Name)
    }
}      

輸出結果

➜  demo1 go run main.go 
namespace: default, name: 1231-589c58c8c5-27r94
namespace: default, name: 1231-b6896fd77-7bkgk
namespace: default, name: web-96d5df5c8-rcw6r
namespace: default, name: web-96d5df5c8-rdzpr      

DynamicClient

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{} 中,待運作時利用反射判斷。該結構體提供了大量的工具方法,便于處理非結構化的資料。

運作以下代碼,将得到 default 下的 Pod 部分相關資源資訊。

package main

import (
    "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"
    "k8s.io/client-go/tools/clientcmd"
)

/*
  @Author : lanyulei
  @Desc :
*/

func main() {
    // 加載配置檔案,生成 config 對象
    config, err := clientcmd.BuildConfigFromFlags("", "../../kubeconfig")
    if err != nil {
        panic(err.Error())
    }

    // 執行個體化 DynamicClient
    dynamicClient, err := dynamic.NewForConfig(config)
    if err != nil {
        panic(err.Error())
    }

    // 設定要請求的 GVR
    gvr := schema.GroupVersionResource{
        Group:    "",
        Version:  "v1",
        Resource: "pods",
    }

    // 發送請求,并得到傳回結果
    unStructData, err := dynamicClient.Resource(gvr).Namespace("default").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)
    }
}      

輸出結果

➜  demo1 go run main.go
namespace: default, name: 1231-589c58c8c5-27r94
namespace: default, name: 1231-b6896fd77-7bkgk
namespace: default, name: web-96d5df5c8-rcw6r
namespace: default, name: web-96d5df5c8-rdzpr      

DiscoveryClient

package main

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

/*
  @Author : lanyulei
  @Desc :
*/

func main() {
    // 加載配置檔案,生成 config 對象
    config, err := clientcmd.BuildConfigFromFlags("", "../../kubeconfig")
    if err != nil {
        panic(err.Error())
    }

    // 執行個體化 DiscoveryClient
    discoveryClient, err := discovery.NewDiscoveryClientForConfig(config)
    if err != nil {
        panic(err.Error())
    }

    _, 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)
        }
    }
}      
➜  demo1 go run main.go
name: bindings, group: , version: v1
name: componentstatuses, group: , version: v1
name: configmaps, group: , version: v1
name: endpoints, group: , version: v1
name: events, group: , version: v1
...      

繼續閱讀