歡迎訪問我的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) | 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)
}
}
- 上述代碼中有三處重點需要注意:
- 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世界...
https://github.com/zq2599/blog_demos