kubernetes client-go源代碼閱讀之動態(tài)客戶端
與client-go靜態(tài)客戶端相對應的自然就是動態(tài)客戶端了,動態(tài)客戶端的價值在于靈活,不用重新生成客戶端代碼就能訪問k8s集群中的所有資源,即使是非內(nèi)置資源。
快速入門
下面是官方的一個例子,完整版可參考: https://github.com/kubernetes/client-go/blob/v0.20.2/examples/dynamic-create-update-delete-deployment/main.go
func?main()?{
?var?kubeconfig?*string
?config,?err?:=?clientcmd.BuildConfigFromFlags("",?*kubeconfig)
?client,?err?:=?dynamic.NewForConfig(config)
?deploymentRes?:=?schema.GroupVersionResource{Group:?"apps",?Version:?"v1",?Resource:?"deployments"}
?deployment?:=?&unstructured.Unstructured{/*具體代碼被省略了*/}
?//?Create?Deployment
?fmt.Println("Creating?deployment...")
?result,?err?:=?client.Resource(deploymentRes).Namespace(namespace).Create(context.TODO(),?deployment,?metav1.CreateOptions{})
?fmt.Printf("Created?deployment?%q.\n",?result.GetName())
?fmt.Println("Updating?deployment...")
?retryErr?:=?retry.RetryOnConflict(retry.DefaultRetry,?func()?error?{
??//?Retrieve?the?latest?version?of?Deployment?before?attempting?update
??//?RetryOnConflict?uses?exponential?backoff?to?avoid?exhausting?the?apiserver
??result,?getErr?:=?client.Resource(deploymentRes).Namespace(namespace).Get(context.TODO(),?"demo-deployment",?metav1.GetOptions{})
??//?update?replicas?to?1
??if?err?:=?unstructured.SetNestedField(result.Object,?int64(1),?"spec",?"replicas");?err?!=?nil?{
???panic(fmt.Errorf("failed?to?set?replica?value:?%v",?err))
??}
??//?extract?spec?containers
??containers,?found,?err?:=?unstructured.NestedSlice(result.Object,?"spec",?"template",?"spec",?"containers")
??if?err?!=?nil?||?!found?||?containers?==?nil?{
???panic(fmt.Errorf("deployment?containers?not?found?or?error?in?spec:?%v",?err))
??}
??//?update?container[0]?image
??if?err?:=?unstructured.SetNestedField(containers[0].(map[string]interface{}),?"nginx:1.13",?"image");?err?!=?nil?{
???panic(err)
??}
??if?err?:=?unstructured.SetNestedField(result.Object,?containers,?"spec",?"template",?"spec",?"containers");?err?!=?nil?{
???panic(err)
??}
??_,?updateErr?:=?client.Resource(deploymentRes).Namespace(namespace).Update(context.TODO(),?result,?metav1.UpdateOptions{})
??return?updateErr
?})
?fmt.Println("Updated?deployment...")
?fmt.Printf("Listing?deployments?in?namespace?%q:\n",?apiv1.NamespaceDefault)
?list,?err?:=?client.Resource(deploymentRes).Namespace(namespace).List(context.TODO(),?metav1.ListOptions{})
?for?_,?d?:=?range?list.Items?{
??replicas,?found,?err?:=?unstructured.NestedInt64(d.Object,?"spec",?"replicas")
??if?err?!=?nil?||?!found?{
???fmt.Printf("Replicas?not?found?for?deployment?%s:?error=%s",?d.GetName(),?err)
???continue
??}
??fmt.Printf("?*?%s?(%d?replicas)\n",?d.GetName(),?replicas)
?}
?fmt.Println("Deleting?deployment...")
?deletePolicy?:=?metav1.DeletePropagationForeground
?deleteOptions?:=?metav1.DeleteOptions{
??PropagationPolicy:?&deletePolicy,
?}
?if?err?:=?client.Resource(deploymentRes).Namespace(namespace).Delete(context.TODO(),?"demo-deployment",?deleteOptions);?err?!=?nil?{
??panic(err)
?}
?fmt.Println("Deleted?deployment.")
}
動態(tài)客戶端的與靜態(tài)客戶端的區(qū)別主要在于是否手動傳GVR。
客戶端構(gòu)造
具體定位到deployment客戶端,可以這么寫
client,?err?:=?dynamic.NewForConfig(config)
deploymentRes?:=?schema.GroupVersionResource{Group:?"apps",?Version:?"v1",?Resource:?"deployments"}
deploymentsClient?:=?client.Resource(deploymentRes).Namespace(namespace)
為了減少文章篇幅,動態(tài)客戶端就只分析Create的代碼了, 其他接口代碼可閱讀client-go/dynamic/simple.go,大致邏輯都是差不多的,不同點主要在于驗證邏輯和最終的構(gòu)造方法。
Create 代碼解析
代碼如下
func?(c?*dynamicResourceClient)?Create(ctx?context.Context,?obj?*unstructured.Unstructured,?opts?metav1.CreateOptions,?subresources?...string)?(*unstructured.Unstructured,?error)?{
????//?1.?
?outBytes,?err?:=?runtime.Encode(unstructured.UnstructuredJSONScheme,?obj)
????
????//?2.
?name?:=?""
?if?len(subresources)?>?0?{
??accessor,?err?:=?meta.Accessor(obj)
??name?=?accessor.GetName()
??if?len(name)?==?0?{
???return?nil,?fmt.Errorf("name?is?required")
??}
?}
?//?3.
?result?:=?c.client.client.
??Post().
??AbsPath(append(c.makeURLSegments(name),?subresources...)...).
??Body(outBytes).
??SpecificallyVersionedParams(&opts,?dynamicParameterCodec,?versionV1).
??Do(ctx)
?//?4.?
?retBytes,?err?:=?result.Raw()
?uncastObj,?err?:=?runtime.Decode(unstructured.UnstructuredJSONScheme,?retBytes)
?return?uncastObj.(*unstructured.Unstructured),?nil
}
代碼主要分為四個部分。
-
將對象序列化成
[]byte對象,用于后續(xù)上傳 - 如果是子資源,就進一步獲取子資源的名字,比如status, scale等
- 構(gòu)造請求,具體就是使用Post方法,構(gòu)造請求路徑,設(shè)置請求體參數(shù), 指定編解碼參數(shù)等,最后請求
- 將結(jié)果解碼并返回
k8s里面的編解碼是個很復雜的過程,這個以后再說,可以單獨寫一篇或者兩篇文章呢。
總結(jié)
在不使用反射的情況下,靜態(tài)客戶端的接口調(diào)用比較機械化,不夠靈活,所以存在動態(tài)客戶端,GVR的指定可以動態(tài)的指定,相較于靜態(tài)客戶端要靈活很多,兩者各有優(yōu)缺點。
評論
圖片
表情
