kubernetes client-go源代码阅读之动态客户端

邓胖

共 4673字,需浏览 10分钟

 · 2023-07-15

与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
}

代码主要分为四个部分。

  1. 将对象序列化成[]byte对象,用于后续上传
  2. 如果是子资源,就进一步获取子资源的名字,比如status, scale等
  3. 构造请求,具体就是使用Post方法,构造请求路径,设置请求体参数, 指定编解码参数等,最后请求
  4. 将结果解码并返回

k8s里面的编解码是个很复杂的过程,这个以后再说,可以单独写一篇或者两篇文章呢。

总结

在不使用反射的情况下,静态客户端的接口调用比较机械化,不够灵活,所以存在动态客户端,GVR的指定可以动态的指定,相较于静态客户端要灵活很多,两者各有优缺点。

浏览 19
点赞
评论
收藏
分享

手机扫一扫分享

举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

举报