歡迎訪問我的GitHub
https://github.com/zq2599/blog_demos
內(nèi)容:所有原創(chuàng)文章分類匯總及配套源碼,涉及Java哼凯、Docker考榨、Kubernetes吟逝、DevOPS等;
系列文章鏈接
- client-go實(shí)戰(zhàn)之一:準(zhǔn)備工作
- client-go實(shí)戰(zhàn)之二:RESTClient
- client-go實(shí)戰(zhàn)之三:Clientset
- client-go實(shí)戰(zhàn)之四:dynamicClient
- client-go實(shí)戰(zhàn)之五:DiscoveryClient
本篇概覽
- 本文是《client-go實(shí)戰(zhàn)》系列的第四篇,前文咱們學(xué)習(xí)了Clientset客戶端役衡,發(fā)現(xiàn)Clientset在deployment、service這些kubernetes內(nèi)置資源的時(shí)候是很方便的薪棒,每個(gè)資源都有其專屬的方法,配合官方API文檔和數(shù)據(jù)結(jié)構(gòu)定義棵介,開發(fā)起來(lái)比Restclient高效吧史;
- 但如果要處理的不是kubernetes的內(nèi)置資源呢?比如CRD吨述,Clientset的代碼中可沒有用戶自定義的東西,顯然就用不上Clientset了冰啃,此時(shí)本篇的主角dynamicClient就要登場(chǎng)啦阎毅!
相關(guān)知識(shí)儲(chǔ)備
- 在正式學(xué)習(xí)dynamicClient之前扇调,有兩個(gè)重要的知識(shí)點(diǎn)需要了解:<font color="blue">Object.runtime</font>和<font color="blue">Unstructured</font>,對(duì)于整個(gè)kubernetes來(lái)說(shuō)它們都是非常重要的雌团;
Object.runtime
- 聊Object.runtime之前先要明確兩個(gè)概念:資源和資源對(duì)象锦援,關(guān)于資源大家都很熟悉了灵寺,pod略板、deployment這些不都是資源嘛慈缔,個(gè)人的理解是資源更像一個(gè)嚴(yán)格的定義瓤檐,當(dāng)您在kubernetes中創(chuàng)建了一個(gè)deployment之后挠蛉,這個(gè)新建的deployment實(shí)例就是資源對(duì)象了谴古;
- 在kubernetes的代碼世界中掰担,資源對(duì)象對(duì)應(yīng)著具體的數(shù)據(jù)結(jié)構(gòu)带饱,這些數(shù)據(jù)結(jié)構(gòu)都實(shí)現(xiàn)了同一個(gè)接口纠炮,名為<font color="red">Object.runtime</font>,源碼位置是<font color="blue">staging/src/k8s.io/apimachinery/pkg/runtime/interfaces.go</font>穷躁,定義如下:
type Object interface {
GetObjectKind() schema.ObjectKind
DeepCopyObject() Object
}
- DeepCopyObject方法顧名思義猿诸,就是深拷貝梳虽,也就是將內(nèi)存中的對(duì)象克隆出一個(gè)新的對(duì)象窜觉;
- 至于GetObjectKind方法的作用禀挫,相信聰明的您也猜到了:處理Object.runtime類型的變量時(shí)语婴,只要調(diào)用其GetObjectKind方法就知道它的具體身份了(如deployment砰左,service等)菜职;
- 最后再次強(qiáng)調(diào):<font color="blue">資源對(duì)象都是Object.runtime的實(shí)現(xiàn)</font>酬核;
Unstructured
- 在聊Unstructured之前嫡意,先看一個(gè)簡(jiǎn)單的JSON字符串:
{
"id": 101,
"name": "Tom"
}
- 上述JSON的字段名稱和字段值類型都是固定的,因此可以針對(duì)性編寫一個(gè)數(shù)據(jù)結(jié)構(gòu)來(lái)處理它:
type Person struct {
ID int
Name String
}
- 對(duì)于上面的JSON字符串就是結(jié)構(gòu)化數(shù)據(jù)(Structured Data)旧巾,這個(gè)應(yīng)該好理解鲁猩;
- 與結(jié)構(gòu)化數(shù)據(jù)相對(duì)的就是非結(jié)構(gòu)化數(shù)據(jù)了(Unstructured Data),在實(shí)際的kubernetes環(huán)境中隙券,可能會(huì)遇到一些無(wú)法預(yù)知結(jié)構(gòu)的數(shù)據(jù)娱仔,例如前面的JSON字符串中還有第三個(gè)字段薪铜,字段值的具體內(nèi)容和類型在編碼時(shí)并不知曉隔箍,而是在真正運(yùn)行的時(shí)候才知道蜒滩,那么在編碼時(shí)如何處理呢俯艰?相信您會(huì)想到用<font color="blue">interface{}</font>來(lái)表示,實(shí)際上client-go也是這么做的辆飘,來(lái)看Unstructured數(shù)據(jù)結(jié)構(gòu)的源碼芹关,路徑是<font color="blue">staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured/unstructured.go</font>:
type Unstructured struct {
// Object is a JSON compatible map with string, float, int, bool, []interface{}, or
// map[string]interface{}
// children.
Object map[string]interface{}
}
- 顯然,上述數(shù)據(jù)結(jié)構(gòu)定義并不能發(fā)揮什么作用轴总,真正重要的是關(guān)聯(lián)的方法怀樟,如下圖,可見client-go已經(jīng)為Unstructured準(zhǔn)備了豐富的方法投蝉,借助這些方法可以靈活的處理非結(jié)構(gòu)化數(shù)據(jù):
在這里插入圖片描述
重要知識(shí)點(diǎn):Unstructured與資源對(duì)象的相互轉(zhuǎn)換
- 另外還有一個(gè)非常重要的知識(shí)點(diǎn):可以用Unstructured實(shí)例生成資源對(duì)象,也可以用資源對(duì)象生成Unstructured實(shí)例庸娱,這個(gè)神奇的能力是unstructuredConverter的FromUnstructured和ToUnstructured方法分別實(shí)現(xiàn)的,下面的代碼片段展示了如何將Unstructured實(shí)例轉(zhuǎn)為PodList實(shí)例:
// 實(shí)例化一個(gè)PodList數(shù)據(jù)結(jié)構(gòu)斤儿,用于接收從unstructObj轉(zhuǎn)換后的結(jié)果
podList := &apiv1.PodList{}
// unstructObj
err = runtime.DefaultUnstructuredConverter.FromUnstructured(unstructObj.UnstructuredContent(), podList)
- 您可能會(huì)好奇上述FromUnstructured方法究竟是如何實(shí)現(xiàn)轉(zhuǎn)換的,咱們?nèi)タ聪麓朔椒ǖ膬?nèi)部實(shí)現(xiàn)一铅,如下圖所示肮之,其實(shí)也沒啥懸念了局骤,通過(guò)反射可以得到podList的字段信息:
在這里插入圖片描述
- 至此,Unstructured的分析就結(jié)束了嗎暴凑?沒有峦甩,強(qiáng)烈推薦您進(jìn)入上圖紅框2中的fromUnstructured方法去看細(xì)節(jié),這里面是非常精彩的现喳,以podList為例凯傲,這是個(gè)數(shù)據(jù)結(jié)構(gòu),而fromUnstructured只處理原始類型嗦篱,對(duì)于數(shù)據(jù)結(jié)構(gòu)會(huì)調(diào)用structFromUnstructured方法處理冰单,在structFromUnstructured方法中
處理數(shù)據(jù)結(jié)構(gòu)的每個(gè)字段,又會(huì)調(diào)用fromUnstructured诫欠,這是相互迭代的過(guò)程被廓,最終蜓斧,不論podList中有多少數(shù)據(jù)結(jié)構(gòu)的嵌套都會(huì)被處理掉狼荞,篇幅所限就不展開相信分析了丰涉,下圖是一部分關(guān)鍵代碼:
在這里插入圖片描述
- 小結(jié):Unstructured轉(zhuǎn)為資源對(duì)象的套路并不神秘伪煤,無(wú)非是用反射取得資源對(duì)象的字段類型防泵,然后按照字段名去Unstructured的map中取得原始數(shù)據(jù),再用反射設(shè)置到資源對(duì)象的字段中即可;
- 做完了準(zhǔn)備工作惠桃,接下來(lái)該回到本篇文章的主題了:dynamicClient客戶端
關(guān)于dynamicClient
- deployment、pod這些資源续膳,其數(shù)據(jù)結(jié)構(gòu)是明確的固定的,可以精確對(duì)應(yīng)到Clientset中的數(shù)據(jù)結(jié)構(gòu)和方法,但是對(duì)于CRD(用戶自定義資源)熙兔,Clientset客戶端就無(wú)能為力了花沉,此時(shí)需要有一種數(shù)據(jù)結(jié)構(gòu)來(lái)承載資源對(duì)象的數(shù)據(jù),也要有對(duì)應(yīng)的方法來(lái)處理這些數(shù)據(jù)碰声;
- 此刻覆旱,前面提到的Unstructured可以登場(chǎng)了辐马,沒錯(cuò),把Clientset不支持的資源對(duì)象交給Unstructured來(lái)承載诅挑,接下來(lái)看看dynamicClient和Unstructured的關(guān)系:
- 先看數(shù)據(jù)結(jié)構(gòu)定義,和clientset沒啥區(qū)別泛源,只有個(gè)restClient字段:
type dynamicClient struct {
client *rest.RESTClient
}
- 這個(gè)數(shù)據(jù)結(jié)構(gòu)只有一個(gè)關(guān)聯(lián)方法Resource努释,入?yún)镚VR,返回的是另一個(gè)數(shù)據(jù)結(jié)構(gòu)dynamicResourceClient:
func (c *dynamicClient) Resource(resource schema.GroupVersionResource) NamespaceableResourceInterface {
return &dynamicResourceClient{client: c, resource: resource}
}
- 通過(guò)上述代碼可知部逮,dynamicClient的關(guān)鍵是數(shù)據(jù)結(jié)構(gòu)dynamicResourceClient及其關(guān)聯(lián)方法享完,來(lái)看看這個(gè)dynamicResourceClient,如下圖有额,果然般又,dynamicClient所有和資源相關(guān)的操作都是dynamicResourceClient在做(代理模式?)巍佑,選了create方法細(xì)看茴迁,序列化和反序列化都交給unstructured的UnstructuredJSONScheme,與kubernetes的交互交給Restclient:
在這里插入圖片描述
- 小結(jié):
- 與Clientset不同萤衰,dynamicClient為各種類型的資源都提供統(tǒng)一的操作API堕义,資源需要包裝為Unstructured數(shù)據(jù)結(jié)構(gòu);
- 內(nèi)部使用了Restclient與kubernetes交互脆栋;
- 對(duì)dynamicClient的介紹分析就這些吧倦卖,可以開始實(shí)戰(zhàn)了;
需求確認(rèn)
- 本次編碼實(shí)戰(zhàn)的需求很簡(jiǎn)單:查詢指定namespace下的所有pod椿争,然后在控制臺(tái)打印出來(lái)怕膛,要求用dynamicClient實(shí)現(xiàn);
- 您可能會(huì)問:pod是kubernetes的內(nèi)置資源秦踪,更適合Clientset來(lái)操作褐捻,而dynamicClient更適合處理CRD不是么?---您說(shuō)得沒錯(cuò)椅邓,這里用pod是因?yàn)檎垓vCRD太麻煩了柠逞,定義好了還要在kubernetes上發(fā)布,于是干脆用pod來(lái)代替CRD景馁,反正dynamicClient都能處理板壮,咱們通過(guò)實(shí)戰(zhàn)掌握dynamicClient的用法就行了,以后遇到各種資源都能處理之裁僧;
源碼下載
- 本篇實(shí)戰(zhàn)中的源碼可在GitHub下載到个束,地址和鏈接信息如下表所示(https://github.com/zq2599/blog_demos):
名稱 | 鏈接 | 備注 |
---|---|---|
項(xiàng)目主頁(yè) | https://github.com/zq2599/blog_demos | 該項(xiàng)目在GitHub上的主頁(yè) |
git倉(cāng)庫(kù)地址(https) | https://github.com/zq2599/blog_demos.git | 該項(xiàng)目源碼的倉(cāng)庫(kù)地址,https協(xié)議 |
git倉(cāng)庫(kù)地址(ssh) | git@github.com:zq2599/blog_demos.git | 該項(xiàng)目源碼的倉(cāng)庫(kù)地址聊疲,ssh協(xié)議 |
- 這個(gè)git項(xiàng)目中有多個(gè)文件夾茬底,client-go相關(guān)的應(yīng)用在<font color="blue">client-go-tutorials</font>文件夾下,如下圖紅框所示:
在這里插入圖片描述
- client-go-tutorials文件夾下有多個(gè)子文件夾获洲,本篇對(duì)應(yīng)的源碼在<font color="blue">dynamicclientdemo</font>目錄下阱表,如下圖紅框所示:
在這里插入圖片描述
編碼
- 新建文件夾dynamicclientdemo,在里面執(zhí)行以下命令,新建module:
go mod init dynamicclientdemo
- 添加k8s.io/api和k8s.io/client-go這兩個(gè)依賴最爬,注意版本要匹配kubernetes環(huán)境:
go get k8s.io/api@v0.20.0
go get k8s.io/client-go@v0.20.0
- 新建main.go涉馁,內(nèi)容如下,稍后會(huì)說(shuō)一下要注意的重點(diǎn):
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是家目錄爱致,如果能取得家目錄的值烤送,就可以用來(lái)做默認(rèn)值
if home:=homedir.HomeDir(); home != "" {
// 如果輸入了kubeconfig參數(shù),該參數(shù)的值就是kubeconfig文件的絕對(duì)路徑糠悯,
// 如果沒有輸入kubeconfig參數(shù)帮坚,就用默認(rèn)路徑~/.kube/config
kubeconfig = flag.String("kubeconfig", filepath.Join(home, ".kube", "config"), "(optional) absolute path to the kubeconfig file")
} else {
// 如果取不到當(dāng)前用戶的家目錄,就沒辦法設(shè)置kubeconfig的默認(rèn)目錄了互艾,只能從入?yún)⒅腥? kubeconfig = flag.String("kubeconfig", "", "absolute path to the kubeconfig file")
}
flag.Parse()
// 從本機(jī)加載kubeconfig配置文件试和,因此第一個(gè)參數(shù)為空字符串
config, err := clientcmd.BuildConfigFromFlags("", *kubeconfig)
// kubeconfig加載失敗就直接退出了
if err != nil {
panic(err.Error())
}
dynamicClient, err := dynamic.NewForConfig(config)
if err != nil {
panic(err.Error())
}
// dynamicClient的唯一關(guān)聯(lián)方法所需的入?yún)? gvr := schema.GroupVersionResource{Version: "v1", Resource: "pods"}
// 使用dynamicClient的查詢列表方法,查詢指定namespace下的所有pod纫普,
// 注意此方法返回的數(shù)據(jù)結(jié)構(gòu)類型是UnstructuredList
unstructObj, err := dynamicClient.
Resource(gvr).
Namespace("kube-system").
List(context.TODO(), metav1.ListOptions{Limit: 100})
if err != nil {
panic(err.Error())
}
// 實(shí)例化一個(gè)PodList數(shù)據(jù)結(jié)構(gòu)阅悍,用于接收從unstructObj轉(zhuǎn)換后的結(jié)果
podList := &apiv1.PodList{}
// 轉(zhuǎn)換
err = runtime.DefaultUnstructuredConverter.FromUnstructured(unstructObj.UnstructuredContent(), podList)
if err != nil {
panic(err.Error())
}
// 表頭
fmt.Printf("namespace\t status\t\t name\n")
// 每個(gè)pod都打印namespace、status.Phase昨稼、name三個(gè)字段
for _, d := range podList.Items {
fmt.Printf("%v\t %v\t %v\n",
d.Namespace,
d.Status.Phase,
d.Name)
}
}
- 上述代碼中有三處重點(diǎn)需要注意:
- Resource方法指定了本次操作的資源類型节视;
- List方法向kubernetes發(fā)起請(qǐng)求;
- FromUnstructured將Unstructured數(shù)據(jù)結(jié)構(gòu)轉(zhuǎn)成PodList悦昵,其原理前面已經(jīng)分析過(guò)肴茄;
- 執(zhí)行<font color="blue">go run main.go</font>晌畅,如下但指,可以從kubernetes取得數(shù)據(jù),并且轉(zhuǎn)換成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的學(xué)習(xí)和實(shí)戰(zhàn)就完成了棋凳,它是名副其實(shí)的動(dòng)態(tài)客戶端工具,用一套API處理所有資源连躏,除了突破Clientset的內(nèi)置資源限制剩岳,還讓我們的業(yè)務(wù)代碼有了更大的靈活性,希望本文能給您一些參考入热,輔助您寫出與場(chǎng)景更加匹配的代碼拍棕;
你不孤單,欣宸原創(chuàng)一路相伴
歡迎關(guān)注公眾號(hào):程序員欣宸
微信搜索「程序員欣宸」勺良,我是欣宸绰播,期待與您一同暢游Java世界...
https://github.com/zq2599/blog_demos