client-go實戰之四:dynamicClient


歡迎訪問我的GitHub

https://github.com/zq2599/blog_demos

內容:所有原創文章分類匯總及配套源碼,涉及Java、Docker、Kubernetes、DevOPS等;

系列文章鏈接

  1. client-go實戰之一:准備工作
  2. client-go實戰之二:RESTClient
  3. client-go實戰之三:Clientset
  4. client-go實戰之四:dynamicClient
  5. client-go實戰之五:DiscoveryClient

本篇概覽

  • 本文是《client-go實戰》系列的第四篇,前文咱們學習了Clientset客戶端,發現Clientset在deployment、service這些kubernetes內置資源的時候是很方便的,每個資源都有其專屬的方法,配合官方API文檔和數據結構定義,開發起來比Restclient高效;
  • 但如果要處理的不是kubernetes的內置資源呢?比如CRD,Clientset的代碼中可沒有用戶自定義的東西,顯然就用不上Clientset了,此時本篇的主角dynamicClient就要登場啦!

相關知識儲備

  • 在正式學習dynamicClient之前,有兩個重要的知識點需要了解:Object.runtimeUnstructured,對於整個kubernetes來說它們都是非常重要的;

Object.runtime

  • 聊Object.runtime之前先要明確兩個概念:資源和資源對象,關於資源大家都很熟悉了,pod、deployment這些不都是資源嘛,個人的理解是資源更像一個嚴格的定義,當您在kubernetes中創建了一個deployment之后,這個新建的deployment實例就是資源對象了;
  • 在kubernetes的代碼世界中,資源對象對應着具體的數據結構,這些數據結構都實現了同一個接口,名為Object.runtime,源碼位置是staging/src/k8s.io/apimachinery/pkg/runtime/interfaces.go,定義如下:
type Object interface {
	GetObjectKind() schema.ObjectKind
	DeepCopyObject() Object
}
  • DeepCopyObject方法顧名思義,就是深拷貝,也就是將內存中的對象克隆出一個新的對象;
  • 至於GetObjectKind方法的作用,相信聰明的您也猜到了:處理Object.runtime類型的變量時,只要調用其GetObjectKind方法就知道它的具體身份了(如deployment,service等);
  • 最后再次強調:資源對象都是Object.runtime的實現

Unstructured

  • 在聊Unstructured之前,先看一個簡單的JSON字符串:
{
	"id": 101,
	"name": "Tom"
}
  • 上述JSON的字段名稱和字段值類型都是固定的,因此可以針對性編寫一個數據結構來處理它:
type Person struct {
	ID int
	Name String
}
  • 對於上面的JSON字符串就是結構化數據(Structured Data),這個應該好理解;
  • 與結構化數據相對的就是非結構化數據了(Unstructured Data),在實際的kubernetes環境中,可能會遇到一些無法預知結構的數據,例如前面的JSON字符串中還有第三個字段,字段值的具體內容和類型在編碼時並不知曉,而是在真正運行的時候才知道,那么在編碼時如何處理呢?相信您會想到用interface{}來表示,實際上client-go也是這么做的,來看Unstructured數據結構的源碼,路徑是staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured/unstructured.go
type Unstructured struct {
	// Object is a JSON compatible map with string, float, int, bool, []interface{}, or
	// map[string]interface{}
	// children.
	Object map[string]interface{}
}
  • 顯然,上述數據結構定義並不能發揮什么作用,真正重要的是關聯的方法,如下圖,可見client-go已經為Unstructured准備了豐富的方法,借助這些方法可以靈活的處理非結構化數據:

在這里插入圖片描述

重要知識點:Unstructured與資源對象的相互轉換

  • 另外還有一個非常重要的知識點:可以用Unstructured實例生成資源對象,也可以用資源對象生成Unstructured實例,這個神奇的能力是unstructuredConverter的FromUnstructured和ToUnstructured方法分別實現的,下面的代碼片段展示了如何將Unstructured實例轉為PodList實例:
// 實例化一個PodList數據結構,用於接收從unstructObj轉換后的結果
podList := &apiv1.PodList{}

// unstructObj
err = runtime.DefaultUnstructuredConverter.FromUnstructured(unstructObj.UnstructuredContent(), podList)
  • 您可能會好奇上述FromUnstructured方法究竟是如何實現轉換的,咱們去看下此方法的內部實現,如下圖所示,其實也沒啥懸念了,通過反射可以得到podList的字段信息:

在這里插入圖片描述

  • 至此,Unstructured的分析就結束了嗎?沒有,強烈推薦您進入上圖紅框2中的fromUnstructured方法去看細節,這里面是非常精彩的,以podList為例,這是個數據結構,而fromUnstructured只處理原始類型,對於數據結構會調用structFromUnstructured方法處理,在structFromUnstructured方法中
    處理數據結構的每個字段,又會調用fromUnstructured,這是相互迭代的過程,最終,不論podList中有多少數據結構的嵌套都會被處理掉,篇幅所限就不展開相信分析了,下圖是一部分關鍵代碼:

在這里插入圖片描述

  • 小結:Unstructured轉為資源對象的套路並不神秘,無非是用反射取得資源對象的字段類型,然后按照字段名去Unstructured的map中取得原始數據,再用反射設置到資源對象的字段中即可;
  • 做完了准備工作,接下來該回到本篇文章的主題了:dynamicClient客戶端

關於dynamicClient

  • deployment、pod這些資源,其數據結構是明確的固定的,可以精確對應到Clientset中的數據結構和方法,但是對於CRD(用戶自定義資源),Clientset客戶端就無能為力了,此時需要有一種數據結構來承載資源對象的數據,也要有對應的方法來處理這些數據;
  • 此刻,前面提到的Unstructured可以登場了,沒錯,把Clientset不支持的資源對象交給Unstructured來承載,接下來看看dynamicClient和Unstructured的關系:
  • 先看數據結構定義,和clientset沒啥區別,只有個restClient字段:
type dynamicClient struct {
	client *rest.RESTClient
}
  • 這個數據結構只有一個關聯方法Resource,入參為GVR,返回的是另一個數據結構dynamicResourceClient:
func (c *dynamicClient) Resource(resource schema.GroupVersionResource) NamespaceableResourceInterface {
	return &dynamicResourceClient{client: c, resource: resource}
}
  • 通過上述代碼可知,dynamicClient的關鍵是數據結構dynamicResourceClient及其關聯方法,來看看這個dynamicResourceClient,如下圖,果然,dynamicClient所有和資源相關的操作都是dynamicResourceClient在做(代理模式?),選了create方法細看,序列化和反序列化都交給unstructured的UnstructuredJSONScheme,與kubernetes的交互交給Restclient:

在這里插入圖片描述

  • 小結:
  1. 與Clientset不同,dynamicClient為各種類型的資源都提供統一的操作API,資源需要包裝為Unstructured數據結構;
  2. 內部使用了Restclient與kubernetes交互;
  • 對dynamicClient的介紹分析就這些吧,可以開始實戰了;

需求確認

  • 本次編碼實戰的需求很簡單:查詢指定namespace下的所有pod,然后在控制台打印出來,要求用dynamicClient實現;
  • 您可能會問:pod是kubernetes的內置資源,更適合Clientset來操作,而dynamicClient更適合處理CRD不是么?---您說得沒錯,這里用pod是因為折騰CRD太麻煩了,定義好了還要在kubernetes上發布,於是干脆用pod來代替CRD,反正dynamicClient都能處理,咱們通過實戰掌握dynamicClient的用法就行了,以后遇到各種資源都能處理之;

源碼下載

名稱 鏈接 備注
項目主頁 https://github.com/zq2599/blog_demos 該項目在GitHub上的主頁
git倉庫地址(https) https://github.com/zq2599/blog_demos.git 該項目源碼的倉庫地址,https協議
git倉庫地址(ssh) git@github.com:zq2599/blog_demos.git 該項目源碼的倉庫地址,ssh協議
  • 這個git項目中有多個文件夾,client-go相關的應用在client-go-tutorials文件夾下,如下圖紅框所示:

在這里插入圖片描述

  • client-go-tutorials文件夾下有多個子文件夾,本篇對應的源碼在dynamicclientdemo目錄下,如下圖紅框所示:

在這里插入圖片描述

編碼

  • 新建文件夾dynamicclientdemo,在里面執行以下命令,新建module:
go mod init dynamicclientdemo
  • 添加k8s.io/api和k8s.io/client-go這兩個依賴,注意版本要匹配kubernetes環境:
go get k8s.io/api@v0.20.0
go get k8s.io/client-go@v0.20.0
  • 新建main.go,內容如下,稍后會說一下要注意的重點:
package main

import (
	"context"
	"flag"
	"fmt"
	apiv1 "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"
	"k8s.io/client-go/util/homedir"
	"path/filepath"
)

func main() {

	var kubeconfig *string

	// home是家目錄,如果能取得家目錄的值,就可以用來做默認值
	if home:=homedir.HomeDir(); home != "" {
		// 如果輸入了kubeconfig參數,該參數的值就是kubeconfig文件的絕對路徑,
		// 如果沒有輸入kubeconfig參數,就用默認路徑~/.kube/config
		kubeconfig = flag.String("kubeconfig", filepath.Join(home, ".kube", "config"), "(optional) absolute path to the kubeconfig file")
	} else {
		// 如果取不到當前用戶的家目錄,就沒辦法設置kubeconfig的默認目錄了,只能從入參中取
		kubeconfig = flag.String("kubeconfig", "", "absolute path to the kubeconfig file")
	}

	flag.Parse()

	// 從本機加載kubeconfig配置文件,因此第一個參數為空字符串
	config, err := clientcmd.BuildConfigFromFlags("", *kubeconfig)

	// kubeconfig加載失敗就直接退出了
	if err != nil {
		panic(err.Error())
	}

	dynamicClient, err := dynamic.NewForConfig(config)

	if err != nil {
		panic(err.Error())
	}

	// dynamicClient的唯一關聯方法所需的入參
	gvr := schema.GroupVersionResource{Version: "v1", Resource: "pods"}

	// 使用dynamicClient的查詢列表方法,查詢指定namespace下的所有pod,
	// 注意此方法返回的數據結構類型是UnstructuredList
	unstructObj, err := dynamicClient.
		Resource(gvr).
		Namespace("kube-system").
		List(context.TODO(), metav1.ListOptions{Limit: 100})

	if err != nil {
		panic(err.Error())
	}

	// 實例化一個PodList數據結構,用於接收從unstructObj轉換后的結果
	podList := &apiv1.PodList{}

	// 轉換
	err = runtime.DefaultUnstructuredConverter.FromUnstructured(unstructObj.UnstructuredContent(), podList)

	if err != nil {
		panic(err.Error())
	}

	// 表頭
	fmt.Printf("namespace\t status\t\t name\n")

	// 每個pod都打印namespace、status.Phase、name三個字段
	for _, d := range podList.Items {
		fmt.Printf("%v\t %v\t %v\n",
			d.Namespace,
			d.Status.Phase,
			d.Name)
	}
}
  • 上述代碼中有三處重點需要注意:
  1. Resource方法指定了本次操作的資源類型;
  2. List方法向kubernetes發起請求;
  3. FromUnstructured將Unstructured數據結構轉成PodList,其原理前面已經分析過;
  • 執行go run main.go,如下,可以從kubernetes取得數據,並且轉換成PodList也正常:
zhaoqin@zhaoqindeMBP-2 dynamicclientdemo % go run main.go
namespace        status          name
kube-system      Running         coredns-7f89b7bc75-5pdwc
kube-system      Running         coredns-7f89b7bc75-nvbvm
kube-system      Running         etcd-hedy
kube-system      Running         kube-apiserver-hedy
kube-system      Running         kube-controller-manager-hedy
kube-system      Running         kube-flannel-ds-v84vc
kube-system      Running         kube-proxy-hlppx
kube-system      Running         kube-scheduler-hedy
  • 至此,dynamicClient的學習和實戰就完成了,它是名副其實的動態客戶端工具,用一套API處理所有資源,除了突破Clientset的內置資源限制,還讓我們的業務代碼有了更大的靈活性,希望本文能給您一些參考,輔助您寫出與場景更加匹配的代碼;

你不孤單,欣宸原創一路相伴

  1. Java系列
  2. Spring系列
  3. Docker系列
  4. kubernetes系列
  5. 數據庫+中間件系列
  6. DevOps系列

歡迎關注公眾號:程序員欣宸

微信搜索「程序員欣宸」,我是欣宸,期待與您一同暢游Java世界...
https://github.com/zq2599/blog_demos


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM