client-go源码学习(一):client-go源码结构、Client客户端对象

本文基于Kubernetes v1.22.4版本进行源码学习,对应的client-go版本为v0.22.4

1、client-go源码结构

client-go的代码库已经集成到Kubernetes源码中了,源码结构示例如下:

$ tree vendor/k8s.io/client-go -L 1
vendor/k8s.io/client-go
├── discovery
├── dynamic
├── informers
├── kubernetes
├── listers
├── plugin
├── rest
├── scale
├── tools
├── transport
└── util
源码目录说明
discovery提供DiscoveryClient发现客户端
dynamic提供DynamicClient动态客户端
informers每种Kubernetes资源的Informer实现
kubernetes提供ClientSet客户端
listers为每一个Kubernetes资源提供Lister功能,该功能对Get和List请求提供只读的缓存数据
plugin提供OpenStack、GCP和Azure等云服务商授权插件
rest提供RESTClient客户端,对Kubernetes API Server执行RESTful操作
scale提供ScaleClient客户端,用于扩容或缩容Deployment、ReplicaSet、Replication Controller等资源对象
tools提供常用工具,例如SharedInformer、Reflector、DealtFIFO及Indexers。提供Client查询和缓存机制,以减少向kube-apiserver发起的请求数等
transport提供安全的TCP连接,支持Http Stream,某些操作需要在客户端和容器之间传输二进制流,例如exec、attach等操作。该功能由内部的spdy包提供支持
util提供常用方法,例如WorkQueue工作队列、Certificate证书管理等

2、Client客户端对象

client-go支持4种Client客户端对象与Kubernetes API Server交互的方式,Client交互对象如下图所示:

  • RESTClient:最基础的客户端,对HTTP Request进行了封装,实现了RESTful风格的API。ClientSet、DynamicClient及DiscoveryClient客户端都是基于RESTClient实现的
  • ClientSet:在RESTClient的基础上封装了对Resource和Version的管理方法。每一个Resource可以理解为一个客户端,而ClientSet则是多个客户端的集合,每一个Resource和Version都以函数的方式暴露给开发者。ClientSet只能够处理内置资源,它是通过client-gen代码生成器自动生成的
  • DynamicClient:ClientSet仅能访问Kubernetes自带的资源(即Client集合内的资源),不能直接访问CRD自定义资源。DynamicClient能够处理Kubernetes中的所有资源对象,包括Kubernetes内置资源与CRD自定义资源
  • DiscoveryClient:用于发现kube-apiserver所支持的资源组、资源版本、资源信息(即Group、Versions、Resources)
1)、RESTClient

RESTClient是最基础的客户端,对HTTP Request进行了封装,实现了RESTful风格的API。ClientSet、DynamicClient及DiscoveryClient客户端都是基于RESTClient实现的

1)示例代码

使用RESTClient列出default命名空间下的所有Pod资源对象的相关信息,代码如下:

package main

import (
	"context"
	"fmt"
	corev1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/client-go/kubernetes/scheme"
	"k8s.io/client-go/rest"
	"k8s.io/client-go/tools/clientcmd"
)

func main() {
	// 加载kubeconfig配置信息
	config, err := clientcmd.BuildConfigFromFlags("", clientcmd.RecommendedHomeFile)
	if err != nil {
		panic(err)
	}
	// 设置config.APIPath请求的HTTP路径
	config.APIPath = "/api"
	// 设置config.GroupVersion请求的资源组/资源版本
	config.GroupVersion = &corev1.SchemeGroupVersion
	// 设置config.NegotiatedSerializer数据的编解码器
	config.NegotiatedSerializer = scheme.Codecs

	// 通过kubeconfig配置信息实例化RESTClient对象
	restClient, err := rest.RESTClientFor(config)
	if err != nil {
		panic(err)
	}

	result := &corev1.PodList{}
	// RESTClient对象构建HTTP请求参数
	err = restClient.Get().
		// 设置请求的命名空间
		Namespace("default").
		// 设置请求的资源名称
		Resource("pods").
		// VersionedParams函数将一些查询选项(如limit、TimeoutSeconds等)添加到请求参数中
		VersionedParams(&metav1.ListOptions{Limit: 500}, scheme.ParameterCodec).
		// 通过Do函数执行该请求
		Do(context.TODO()).
		// 将kube-apiserver返回的结果(Result对象)解析到corev1.PodList对象中
		Into(result)
	if err != nil {
		panic(err)
	}

	for _, pod := range result.Items {
		fmt.Printf("namespcae:%v,name:%v,status:%v\n", pod.Namespace, pod.Name, pod.Status.Phase)
	}
}
2)源码解析

RESTClient中的Request结构体代码如下,当调用Get()Namespace()Resource()VersionedParams()等函数时,实际是对Request的属性进行赋值:

// vendor/k8s.io/client-go/rest/request.go
type Request struct {
	c *RESTClient

	warningHandler WarningHandler

	rateLimiter flowcontrol.RateLimiter
	backoff     BackoffManager
	timeout     time.Duration

	// generic components accessible via method setters
	verb       string
	pathPrefix string
	subpath    string
	params     url.Values
	headers    http.Header

	// structural elements of the request that are part of the Kubernetes API conventions
	namespace    string
	namespaceSet bool
	resource     string
	resourceName string
	subresource  string

	// output
	err   error
	body  io.Reader
	retry WithRetry
}

RESTClient发送请求的过程对Go标准库net/http进行了封装,在Do函数中真正执行请求,相关源码如下:

// vendor/k8s.io/client-go/rest/request.go
func (r *Request) Do(ctx context.Context) Result {
	var result Result
    // 调用request方法执行请求
	err := r.request(ctx, func(req *http.Request, resp *http.Response) {
        // 将response转换为Result对象
		result = r.transformResponse(resp, req)
	})
	if err != nil {
		return Result{err: err}
	}
	return result
}

func (r *Request) request(ctx context.Context, fn func(*http.Request, *http.Response)) error {
	//Metrics for total request latency
	start := time.Now()
	defer func() {
		metrics.RequestLatency.Observe(ctx, r.verb, r.finalURLTemplate(), time.Since(start))
	}()

	if r.err != nil {
		klog.V(4).Infof("Error in request: %v", r.err)
		return r.err
	}

	if err := r.requestPreflightCheck(); err != nil {
		return err
	}

	client := r.c.Client
	if client == nil {
		client = http.DefaultClient
	}

	// Throttle the first try before setting up the timeout configured on the
	// client. We don't want a throttled client to return timeouts to callers
	// before it makes a single request.
	if err := r.tryThrottle(ctx); err != nil {
		return err
	}

	if r.timeout > 0 {
		var cancel context.CancelFunc
		ctx, cancel = context.WithTimeout(ctx, r.timeout)
		defer cancel()
	}

	// Right now we make about ten retry attempts if we get a Retry-After response.
	var retryAfter *RetryAfter
	for {
        // 1)初始化request对象
		req, err := r.newHTTPRequest(ctx)
		if err != nil {
			return err
		}

		r.backoff.Sleep(r.backoff.CalculateBackoff(r.URL()))
		if retryAfter != nil {
			// We are retrying the request that we already send to apiserver
			// at least once before.
			// This request should also be throttled with the client-internal rate limiter.
			if err := r.tryThrottleWithInfo(ctx, retryAfter.Reason); err != nil {
				return err
			}
			retryAfter = nil
		}
    	// 2)发送请求
		resp, err := client.Do(req)
		updateURLMetrics(ctx, r, resp, err)
		if err != nil {
			r.backoff.UpdateBackoff(r.URL(), err, 0)
		} else {
			r.backoff.UpdateBackoff(r.URL(), err, resp.StatusCode)
		}

		done := func() bool {
			defer readAndCloseResponseBody(resp)

			// if the the server returns an error in err, the response will be nil.
			f := func(req *http.Request, resp *http.Response) {
				if resp == nil {
					return
				}
        		// 3)fn函数(即transformResponse)将response转换为Result对象
				fn(req, resp)
			}

			var retry bool
			retryAfter, retry = r.retry.NextRetry(req, resp, err, func(req *http.Request, err error) bool {
				// "Connection reset by peer" or "apiserver is shutting down" are usually a transient errors.
				// Thus in case of "GET" operations, we simply retry it.
				// We are not automatically retrying "write" operations, as they are not idempotent.
				if r.verb != "GET" {
					return false
				}
				// For connection errors and apiserver shutdown errors retry.
				if net.IsConnectionReset(err) || net.IsProbableEOF(err) {
					return true
				}
				return false
			})
			if retry {
				err := r.retry.BeforeNextRetry(ctx, r.backoff, retryAfter, req.URL.String(), r.body)
				if err == nil {
					return false
				}
				klog.V(4).Infof("Could not retry request - %v", err)
			}
			f(req, resp)
			return true
		}()
		if done {
			return err
		}
	}
}

核心函数request逻辑如下:

  1. 代码1)处初始化request对象,其中会调用r.URL().String()生成请求的RESTful URL,实际是使用RESTClient中Request对象的属性进行拼接,在RESTClient示例代码中生成请求的URL为http://127.0.0.1:8001/api/v1/namespaces/default/pods?limit=500
  2. 代码2)处通过Go标准库net/http向kube-apiserver发送请求,请求得到的结果放到response对象中
  3. 代码3)处fn函数(即transformResponse)将response转换为Result对象
2)、ClientSet

ClientSet在RESTClient的基础上封装了对Resource和Version的管理方法。每一个Resource可以理解为一个客户端,而ClientSet则是多个客户端的集合,每一个Resource和Version都以函数的方式暴露给开发者。多ClientSet多资源集合如下图所示:

ClientSet仅能访问Kubernetes自身内置的资源(即客户端集合内的资源),不能直接访问CRD自定义资源。如果需要ClientSet访问CRD自定义资源,可以通过client-gen代码生成器重新生成ClientSet,在ClientSet集合中自动生成与CRD操作相关的接口

1)示例代码

使用ClientSet列出default命名空间下的所有Pod资源对象的相关信息,代码如下:

package main

import (
	"context"
	"fmt"
	corev1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/client-go/kubernetes"
	"k8s.io/client-go/tools/clientcmd"
)

func main() {
	// 加载kubeconfig配置信息
	config, err := clientcmd.BuildConfigFromFlags("", clientcmd.RecommendedHomeFile)
	if err != nil {
		panic(err)
	}

	// 通过kubeconfig配置信息实例化Clientset对象,该对象用于管理所有Resource的客户端
	clientset, err := kubernetes.NewForConfig(config)
	if err != nil {
		panic(err)
	}
	// clientset.CoreV1().Pods表示请求core资源组的v1资源版本下的Pod资源对象
	// Pods函数是一个资源接口对象,用于Pod资源对象的管理
	podClient := clientset.CoreV1().Pods(corev1.NamespaceDefault)

	// podClient.List函数通过RESTClient获得Pod列表
	result, err := podClient.List(context.TODO(), metav1.ListOptions{Limit: 500})
	if err != nil {
		panic(err)
	}
	for _, pod := range result.Items {
		fmt.Printf("namespcae:%v,name:%v,status:%v\n", pod.Namespace, pod.Name, pod.Status.Phase)
	}
}
2)源码解析

Clientset中包含了一组Client,每个Group Version都有一个Client,Clientset结构体定义如下:

// vendor/k8s.io/client-go/kubernetes/clientset.go
type Clientset struct {
	*discovery.DiscoveryClient
	admissionregistrationV1      *admissionregistrationv1.AdmissionregistrationV1Client
	admissionregistrationV1beta1 *admissionregistrationv1beta1.AdmissionregistrationV1beta1Client
	internalV1alpha1             *internalv1alpha1.InternalV1alpha1Client
	appsV1                       *appsv1.AppsV1Client
	appsV1beta1                  *appsv1beta1.AppsV1beta1Client
	appsV1beta2                  *appsv1beta2.AppsV1beta2Client
	authenticationV1             *authenticationv1.AuthenticationV1Client
	authenticationV1beta1        *authenticationv1beta1.AuthenticationV1beta1Client
	authorizationV1              *authorizationv1.AuthorizationV1Client
	authorizationV1beta1         *authorizationv1beta1.AuthorizationV1beta1Client
	autoscalingV1                *autoscalingv1.AutoscalingV1Client
	autoscalingV2beta1           *autoscalingv2beta1.AutoscalingV2beta1Client
	autoscalingV2beta2           *autoscalingv2beta2.AutoscalingV2beta2Client
	batchV1                      *batchv1.BatchV1Client
	batchV1beta1                 *batchv1beta1.BatchV1beta1Client
	certificatesV1               *certificatesv1.CertificatesV1Client
	certificatesV1beta1          *certificatesv1beta1.CertificatesV1beta1Client
	coordinationV1beta1          *coordinationv1beta1.CoordinationV1beta1Client
	coordinationV1               *coordinationv1.CoordinationV1Client
	coreV1                       *corev1.CoreV1Client
	discoveryV1                  *discoveryv1.DiscoveryV1Client
	discoveryV1beta1             *discoveryv1beta1.DiscoveryV1beta1Client
	eventsV1                     *eventsv1.EventsV1Client
	eventsV1beta1                *eventsv1beta1.EventsV1beta1Client
	extensionsV1beta1            *extensionsv1beta1.ExtensionsV1beta1Client
	flowcontrolV1alpha1          *flowcontrolv1alpha1.FlowcontrolV1alpha1Client
	flowcontrolV1beta1           *flowcontrolv1beta1.FlowcontrolV1beta1Client
	networkingV1                 *networkingv1.NetworkingV1Client
	networkingV1beta1            *networkingv1beta1.NetworkingV1beta1Client
	nodeV1                       *nodev1.NodeV1Client
	nodeV1alpha1                 *nodev1alpha1.NodeV1alpha1Client
	nodeV1beta1                  *nodev1beta1.NodeV1beta1Client
	policyV1                     *policyv1.PolicyV1Client
	policyV1beta1                *policyv1beta1.PolicyV1beta1Client
	rbacV1                       *rbacv1.RbacV1Client
	rbacV1beta1                  *rbacv1beta1.RbacV1beta1Client
	rbacV1alpha1                 *rbacv1alpha1.RbacV1alpha1Client
	schedulingV1alpha1           *schedulingv1alpha1.SchedulingV1alpha1Client
	schedulingV1beta1            *schedulingv1beta1.SchedulingV1beta1Client
	schedulingV1                 *schedulingv1.SchedulingV1Client
	storageV1beta1               *storagev1beta1.StorageV1beta1Client
	storageV1                    *storagev1.StorageV1Client
	storageV1alpha1              *storagev1alpha1.StorageV1alpha1Client
}

clientset.CoreV1().Pods函数表示请求core资源组的v1资源版本下的Pod资源对象,其内部设置了APIPath请求的HTTP路径,GroupVersion请求的资源组、资源版本,NegotiatedSerializer数据的编解码器

其中,Pods函数是一个资源接口对象,用于Pod资源对象的管理,例如,对Pod资源执行Create、Update、Delete、Get、List、Watch、Patch等操作,这些操作实际上是对RESTClient进行了封装,可以设置选项(如Limit、TimeoutSeconds等)。podClient.List函数通过RESTClient获得Pod列表,代码如下:

// vendor/k8s.io/client-go/kubernetes/typed/core/v1/pod.go
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
	}
	result = &v1.PodList{}
	err = c.client.Get().
		Namespace(c.ns).
		Resource("pods").
		VersionedParams(&opts, scheme.ParameterCodec).
		Timeout(timeout).
		Do(ctx).
		Into(result)
	return
}
3)、DynamicClient

DynamicClient是一种动态客户端,它可以对任意Kubernetes资源进行RESTful操作,包括CRD自定义资源。DynamicClient与ClientSet操作类似,同样封装了RESTClient,同样提供了Create、Update、Delete、Get、List、Watch、Patch等方法

DynamicClient与ClientSet最大的不同之处是,ClientSet仅能访问Kubernetes自带的资源(即Client集合内的资源),不能直接访问CRD自定义资源。ClientSet需要预先实现每种Resource和Version的操作,其内部的数据都是结构化数据(即已知数据结构)。而DynamicClient内部实现了Unstructured,用于处理非结构化数据结构(即无法提前预知数据结构),这也是DynamicClient能够处理CRD自定义资源的关键

DynamicClient的处理过程将Resource(例如PodList)转换成Unstructured结构类型,Kubernetes的所有Resource都可以转换成该结构类型。处理完成后,再将Unstructured转换成PodList。整个过程类似于Go的interface{}断言转换过程。另外,Unstructured结构类型是通过map[string]interface{}转换的

1)示例代码

使用DynamicClient列出default命名空间下的所有Pod资源对象的相关信息,代码如下:

package main

import (
	"context"
	"fmt"
	corev1 "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"
)

func main() {
	// 加载kubeconfig配置信息
	config, err := clientcmd.BuildConfigFromFlags("", clientcmd.RecommendedHomeFile)
	if err != nil {
		panic(err)
	}

	// 通过kubeconfig配置信息实例化dynamicClient对象,该对象用于管理Kubernetes的所有Resource的客户端
	dynamicClient, err := dynamic.NewForConfig(config)
	if err != nil {
		panic(err)
	}

	gvr := schema.GroupVersionResource{Version: "v1", Resource: "pods"}
	unstructObj, err := dynamicClient.
		// 设置请求的资源组、资源版本、资源名称
		Resource(gvr).
		// 设置请求的命名空间
		Namespace(corev1.NamespaceDefault).
		// 获取Pod列表,得到的Pod列表为unstructured.UnstructuredList指针类型
		List(context.TODO(), metav1.ListOptions{Limit: 500})
	if err != nil {
		panic(err)
	}

	podList := &corev1.PodList{}
	// 将unstructured.UnstructuredList转换成PodList类型
	err = runtime.DefaultUnstructuredConverter.FromUnstructured(
		unstructObj.UnstructuredContent(), podList)
	if err != nil {
		panic(err)
	}

	for _, pod := range podList.Items {
		fmt.Printf("namespcae:%v,name:%v,status:%v\n", pod.Namespace, pod.Name, pod.Status.Phase)
	}
}
2)源码解析

DynamicClient对RESTClient进行了封装,List函数代码如下:

// vendor/k8s.io/client-go/dynamic/simple.go
func (c *dynamicResourceClient) List(ctx context.Context, opts metav1.ListOptions) (*unstructured.UnstructuredList, error) {
	result := c.client.client.Get().AbsPath(c.makeURLSegments("")...).SpecificallyVersionedParams(&opts, dynamicParameterCodec, versionV1).Do(ctx)
	if err := result.Error(); err != nil {
		return nil, err
	}
	retBytes, err := result.Raw()
	if err != nil {
		return nil, err
	}
	uncastObj, err := runtime.Decode(unstructured.UnstructuredJSONScheme, retBytes)
	if err != nil {
		return nil, err
	}
	if list, ok := uncastObj.(*unstructured.UnstructuredList); ok {
		return list, nil
	}

	list, err := uncastObj.(*unstructured.Unstructured).ToList()
	if err != nil {
		return nil, err
	}
	return list, nil
}

c.client.client.Get().AbsPath(c.makeURLSegments("")...).SpecificallyVersionedParams(&opts, dynamicParameterCodec, versionV1)使用RESTClient来构建Request对象,对请求的RESTful URL进行拼接,最终调用RESTClient的Do函数真正执行请求

4)、DiscoveryClient

DiscoveryClient是发现客户端,用于发现Kubernetes API Server所支持的资源组、资源版本、资源信息,还可以将这些信息存储到本地,用于本地缓存,以减轻对Kubernetes API Server访问的压力,缓存信息默认存储于~/.kube/cache

kubectl的api-versions和api-resources命令输出也是通过DiscoveryClient实现的。另外,DiscoveryClient同样在RESTClient的基础上进行了封装

1)示例代码
package main

import (
	"fmt"
	"k8s.io/apimachinery/pkg/runtime/schema"
	"k8s.io/client-go/discovery"
	"k8s.io/client-go/tools/clientcmd"
)

func main() {
	// 加载kubeconfig配置信息
	config, err := clientcmd.BuildConfigFromFlags("", clientcmd.RecommendedHomeFile)
	if err != nil {
		panic(err)
	}

	// 通过kubeconfig配置信息实例化DiscoveryClient对象,该对象用于发现Kubernetes API Server所支持的资源组、资源版本、资源信息的客户端
	discoveryClient, err := discovery.NewDiscoveryClientForConfig(config)
	if err != nil {
		panic(err)
	}

	// 获取Kubernetes API Server所支持的资源组、资源版本、资源信息
	_, apiResourceList, err := discoveryClient.ServerGroupsAndResources()
	if err != nil {
		panic(err)
	}

	for _, list := range apiResourceList {
		gv, err := schema.ParseGroupVersion(list.GroupVersion)
		if err != nil {
			panic(err)
		}
		fmt.Printf("group:%v,version:%v\nresources:\n", gv.Group, gv.Version)
		for _, resource := range list.APIResources {
			fmt.Printf("%v\n", resource.Name)
		}
		fmt.Println()
	}
}
2)源码解析
// vendor/k8s.io/client-go/discovery/discovery_client.go
func ServerGroupsAndResources(d DiscoveryInterface) ([]*metav1.APIGroup, []*metav1.APIResourceList, error) {
  	// 1)获取Kubernetes API Server所支持的GroupVersion
	sgs, err := d.ServerGroups()
	if sgs == nil {
		return nil, nil, err
	}
	resultGroups := []*metav1.APIGroup{}
	for i := range sgs.Groups {
		resultGroups = append(resultGroups, &sgs.Groups[i])
	}

  	// 2)获取Kubernetes API Server所支持的GroupVersionResources
	groupVersionResources, failedGroups := fetchGroupVersionResources(d, sgs)

	// order results by group/version discovery order
	result := []*metav1.APIResourceList{}
	for _, apiGroup := range sgs.Groups {
		for _, version := range apiGroup.Versions {
			gv := schema.GroupVersion{Group: apiGroup.Name, Version: version.Version}
			if resources, ok := groupVersionResources[gv]; ok {
				result = append(result, resources)
			}
		}
	}

	if len(failedGroups) == 0 {
		return resultGroups, result, nil
	}

	return resultGroups, result, &ErrGroupDiscoveryFailed{Groups: failedGroups}
}

ServerGroups函数实现如下:

// vendor/k8s.io/client-go/discovery/discovery_client.go
func (d *DiscoveryClient) ServerGroups() (apiGroupList *metav1.APIGroupList, err error) {
  	// Get the groupVersions exposed at /api
	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)
	}
	if err != nil && !errors.IsNotFound(err) && !errors.IsForbidden(err) {
		return nil, err
	}

  	// Get the groupVersions exposed at /apis
	apiGroupList = &metav1.APIGroupList{}
	err = d.restClient.Get().AbsPath("/apis").Do(context.TODO()).Into(apiGroupList)
	if err != nil && !errors.IsNotFound(err) && !errors.IsForbidden(err) {
		return nil, err
	}
	// to be compatible with a v1.0 server, if it's a 403 or 404, ignore and return whatever we got from /api
	if err != nil && (errors.IsNotFound(err) || errors.IsForbidden(err)) {
		apiGroupList = &metav1.APIGroupList{}
	}

	// prepend the group retrieved from /api to the list if not empty
	if len(v.Versions) != 0 {
		apiGroupList.Groups = append([]metav1.APIGroup{apiGroup}, apiGroupList.Groups...)
	}
	return apiGroupList, nil
}

通过RESTClient分别请求/api和/apis接口获取Kubernetes API Server所支持的资源组及对应的版本

Kubernetes中支持两类资源组,分别是拥有组名的资源组和没有组名的资源组

  • 拥有组名的资源组:其表现形式为<group>/<version>/<resource>,例如apps/v1/deployments
  • 没有组名的资源组:被称为Core Groups(核心资源组),其表现形式为/<version>/<resource>,例如/v1/pods

两类资源组表现形式不同,形成的HTTP PATH路径也不同。拥有组名的资源组的HTTP PATH以/apis为前缀,其表现形式为/apis/<group>/<version>/<resource>,例如http://127.0.0.1:8001/apis/app/v1/deployments。没有组名的资源组的HTTP PATH以/api为前缀,其表现形式为/api/<version>/<resource>,例如http://127.0.0.1:8001/api/v1/pods

fetchGroupVersionResources函数中遍历所有的GroupVersion获取其对应的资源信息,实现如下:

// vendor/k8s.io/client-go/discovery/discovery_client.go
func fetchGroupVersionResources(d DiscoveryInterface, apiGroups *metav1.APIGroupList) (map[schema.GroupVersion]*metav1.APIResourceList, map[schema.GroupVersion]error) {
	groupVersionResources := make(map[schema.GroupVersion]*metav1.APIResourceList)
	failedGroups := make(map[schema.GroupVersion]error)

	wg := &sync.WaitGroup{}
	resultLock := &sync.Mutex{}
	for _, apiGroup := range apiGroups.Groups {
		for _, version := range apiGroup.Versions {
			groupVersion := schema.GroupVersion{Group: apiGroup.Name, Version: version.Version}
			wg.Add(1)
			go func() {
				defer wg.Done()
				defer utilruntime.HandleCrash()

        		// 获取GroupVersion对应的资源信息
				apiResourceList, err := d.ServerResourcesForGroupVersion(groupVersion.String())

				// lock to record results
				resultLock.Lock()
				defer resultLock.Unlock()

				if err != nil {
					// TODO: maybe restrict this to NotFound errors
					failedGroups[groupVersion] = err
				}
				if apiResourceList != nil {
					// even in case of error, some fallback might have been returned
					groupVersionResources[groupVersion] = apiResourceList
				}
			}()
		}
	}
	wg.Wait()

	return groupVersionResources, failedGroups
}

参考:

《Kubernetes源码剖析》

2022年最新k8s编程operator篇

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

邋遢的流浪剑客

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值