kubernetes client-go源代码阅读之动态客户端
与client-go静态客户端相对应的自然就是动态客户端了,动态客户端的价值在于灵活,不用重新生成客户端代码就能访问k8s集群中的所有资源,即使是非内置资源。
快速入门
下面是官方的一个例子,完整版可参考: 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.")
}
动态客户端的与静态客户端的区别主要在于是否手动传GVR。
客户端构造
具体定位到deployment客户端,可以这么写
client, err := dynamic.NewForConfig(config)
deploymentRes := schema.GroupVersionResource{Group: "apps", Version: "v1", Resource: "deployments"}
deploymentsClient := client.Resource(deploymentRes).Namespace(namespace)
为了减少文章篇幅,动态客户端就只分析Create的代码了, 其他接口代码可阅读client-go/dynamic/simple.go
,大致逻辑都是差不多的,不同点主要在于验证逻辑和最终的构造方法。
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
对象,用于后续上传 - 如果是子资源,就进一步获取子资源的名字,比如status, scale等
- 构造请求,具体就是使用Post方法,构造请求路径,设置请求体参数, 指定编解码参数等,最后请求
- 将结果解码并返回
k8s里面的编解码是个很复杂的过程,这个以后再说,可以单独写一篇或者两篇文章呢。
总结
在不使用反射的情况下,静态客户端的接口调用比较机械化,不够灵活,所以存在动态客户端,GVR的指定可以动态的指定,相较于静态客户端要灵活很多,两者各有优缺点。
评论