歡迎訪問我的GitHub
https://github.com/zq2599/blog_demos
內容:所有原創文章分類匯總及配套原始碼,涉及Java、Docker、Kubernetes、DevOPS等;
系列文章鏈接
- client-go實戰之一:準備作業
- client-go實戰之二:RESTClient
- client-go實戰之三:Clientset
- client-go實戰之四:dynamicClient
- client-go實戰之五:DiscoveryClient
本篇概覽
- 本文是《client-go實戰》系列的第四篇,前文咱們學習了Clientset客戶端,發現Clientset在deployment、service這些kubernetes內置資源的時候是很方便的,每個資源都有其專屬的方法,配合官方API檔案和資料結構定義,開發起來比Restclient高效;
- 但如果要處理的不是kubernetes的內置資源呢?比如CRD,Clientset的代碼中可沒有用戶自定義的東西,顯然就用不上Clientset了,此時本篇的主角dynamicClient就要登場啦!
相關知識儲備
- 在正式學習dynamicClient之前,有兩個重要的知識點需要了解:Object.runtime和Unstructured,對于整個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:

- 小結:
- 與Clientset不同,dynamicClient為各種型別的資源都提供統一的操作API,資源需要包裝為Unstructured資料結構;
- 內部使用了Restclient與kubernetes互動;
- 對dynamicClient的介紹分析就這些吧,可以開始實戰了;
需求確認
- 本次編碼實戰的需求很簡單:查詢指定namespace下的所有pod,然后在控制臺列印出來,要求用dynamicClient實作;
- 您可能會問:pod是kubernetes的內置資源,更適合Clientset來操作,而dynamicClient更適合處理CRD不是么?---您說得沒錯,這里用pod是因為折騰CRD太麻煩了,定義好了還要在kubernetes上發布,于是干脆用pod來代替CRD,反正dynamicClient都能處理,咱們通過實戰掌握dynamicClient的用法就行了,以后遇到各種資源都能處理之;
原始碼下載
- 本篇實戰中的原始碼可在GitHub下載到,地址和鏈接資訊如下表所示(https://github.com/zq2599/blog_demos):
| 名稱 | 鏈接 | 備注 |
|---|---|---|
| 專案主頁 | https://github.com/zq2599/blog_demos | 該專案在GitHub上的主頁 |
| git倉庫地址(https) | https://github.com/zq2599/blog_demos.git | 該專案原始碼的倉庫地址,https協議 |
| git倉庫地址(ssh) | [email protected]: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/[email protected]
go get k8s.io/[email protected]
- 新建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)
}
}
- 上述代碼中有三處重點需要注意:
- Resource方法指定了本次操作的資源型別;
- List方法向kubernetes發起請求;
- 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的內置資源限制,還讓我們的業務代碼有了更大的靈活性,希望本文能給您一些參考,輔助您寫出與場景更加匹配的代碼;
你不孤單,欣宸原創一路相伴
- Java系列
- Spring系列
- Docker系列
- kubernetes系列
- 資料庫+中間件系列
- DevOps系列
歡迎關注公眾號:程式員欣宸
微信搜索「程式員欣宸」,我是欣宸,期待與您一同暢游Java世界...
https://github.com/zq2599/blog_demos
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/298786.html
標籤:其他
