Client-go code breakdown (1): Client Object

Posted by Hao Liang's Blog on Friday, August 7, 2020

1. Source code structure

client-go source code structure mind map

2. Client object

RESTClient

restClient encapsulates RESTful-style HTTP requests and is used to interact with apiserver for HTTP request data. The process of obtaining kubernetes resource objects through restClient is: read kubeconfig configuration information –》Encapsulate HTTP request–》Request apiserver –》Deserialize the result of the request into a kubernetes resource object structure.

Sample code:

request := &corev1.PodList{}
err := restClient.Get().
Namespace("default").
Resource("pods").
VersionedParams(&metav1.ListOptions{Limit:500},schema.ParameterCodec).
// Send Http request and communicate with apiserver here
Do().
// Here the returned result is deserialized into a corev1.PodList object
Into(request)

源码分析: k8s.io/client-go/rest/request.go

// Call the Do method to send a request to obtain the resource object to the apiserver
func (r *Request) Do(ctx context.Context) Result {
	var result Result
	// Send http request
	err := r.request(ctx, func(req *http.Request, resp *http.Response) {
		result = r.transformResponse(resp, req)
	})
	if err != nil {
		return Result{err: err}
	}
	return result
}
// Specific implementation of sending http request
func (r *Request) request(ctx context.Context, fn func(*http.Request, *http.Response)) error {
	...
	for {

		url := r.URL().String()
		req, err := http.NewRequest(r.verb, url, r.body)
		...
		// Send request here
		resp, err := client.Do(req)
		...
		// The results returned by apiserver are converted into resource objects.
		fn(req, resp)
		return true
	}
}
// The fn function converts the result into the specific execution logic of the resource object
func (r *Request) transformResponse(resp *http.Response, req *http.Request) Result {
	// The request results are saved in byte slices
	var body []byte
	if resp.Body != nil {
		data, err := ioutil.ReadAll(resp.Body)
		switch err.(type) {
		case nil:
			// Put the returned Response body into a byte slice
			body = data
			...
		}
	}
	...
	// Encapsulated into a Result structure and returned
	return Result{
		body:        body,
		contentType: contentType,
		statusCode:  resp.StatusCode,
		decoder:     decoder,
	}
}
// Convert the Result structure returned by the fn function into a Kubernetes resource structure object
// All kubernetes resource objects implement the runtime.Object interface, so the parameter here is the runtime.Object interface
func (r Result) Into(obj runtime.Object) error {
	...
	// Deserialize the resource object byte slice returned by apiserver into a kubernetes resource object (polymorphic)
	out, _, err := r.decoder.Decode(r.body, nil, obj)
	if err != nil || out == obj {
		return err
	}
	...
	return nil
}

ClientSet

ClientSet encapsulates RESTClient. The code can obtain resource objects more conveniently and concisely, and the code is more readable. For example:

// Get the Pod list in the cluster and find that ClientSet encapsulates resources and versions and is more concise than RESTClient
podClient := clientSet.CoreV1().Pods(apiv1.NamespaceDefault)
list, err := podClient.List(metav1.ListOptions{Limit:500})

Code breakdown: k8s.io/client-go/kubernetes/typed/core/v1/pod.go

// The List method encapsulates RESTClient
func (c *pods) List(ctx context.Context, opts metav1.ListOptions) (result *v1.PodList, err error) {
	var timeout time.Duration
	if opts.TimeoutSeconds != nil {
		timeout = time.Duration(*opts.TimeoutSeconds) * time.Second
	}
	// See the familiar RESTClient code
	result = &v1.PodList{}
	err = c.client.Get().
		Namespace(c.ns).
		Resource("pods").
		VersionedParams(&opts, scheme.ParameterCodec).
		Timeout(timeout).
		Do(ctx).
		Into(result)
	return
}

The source code of ClientSet is in the k8s.io/client-go/kubernetes package. This package only contains the structure implementation of kubernetes’ own resource objects. Therefore, the ClientSet that comes with client-go can only access the resource objects that come with kubernetes.

DynamicClient

DynamicClient also encapsulates RESTClient, but provides a data structure for handling resource objects that cannot be predicted in advance, so it can be used to handle custom resource objects (CRD)

DiscoveryClient

DynamicClient also encapsulates RESTClient, which is responsible for sending requests to the apiserver to query the resource groups and resource version information supported by the apiserver, and caching the results locally to reduce the pressure on the apiserver. (The underlying implementation principle of kubecti api-versions and api-resources commands) **Get all resource groups, resource versions and resource information supported by apiserver: **

discoveryClient, err := discovery.NewDiscoveryClientForConfig(config)
_, APIResourceList, err :=discoveryClient.ServerGroupAndResources()

Code breakdown:

func (d *DiscoveryClient) ServerGroups() (apiGroupList *metav1.APIGroupList, err error) {
	// d.LegacyPrefi is /api, so DiscoveryClient obtains all supported resource version information by calling the /api interface of apiserver
	v := &metav1.APIVersions{}
	err = d.restClient.Get().AbsPath(d.LegacyPrefix).Do(context.TODO()).Into(v)
	apiGroup := metav1.APIGroup{}
	if err == nil && len(v.Versions) != 0 {
		apiGroup = apiVersionsToAPIGroup(v)
	}
	// DiscoveryClient obtains all supported resource group information by calling the /apis interface of apiserver
	apiGroupList = &metav1.APIGroupList{}
	err = d.restClient.Get().AbsPath("/apis").Do(context.TODO()).Into(apiGroupList)
	...
	return apiGroupList, nil
}

3. Thinking

We usually use the client of the client-go package to obtain resource objects in the kubernetes cluster. How do we usually do this? For the resource objects that come with kubernetes, we will use the ClientSet client to obtain them directly. For custom resource objects (CRD), in many cases you will implement your own custom ClientSet client. For example, Openshift implements its own ClientSet: https://github.com/openshift/client-go/blob /master/apps/clientset/versioned/clientset.go , so as to obtain its own unique resource object