client-go(kuberenetes)的RestClient解析

前言

apiserver的REST客户端可以类比为http.Client,至少它继承了http.Client的功能(更准确的说应该是有一个http.Client的成员变量),如果读者对http.Client不了解,那么建议先简单学习一下,这非常有助于理解本文的内容。

为了区分apiserver的REST客户端和http.Client,本文将用RestClient表示apiserver的REST客户端,而http.Client则直接引用齐全名。因为apiserver是一个相对标准的REST服务,所以访问apiserver的客户端最终都要通过http.Client实现,那么RestClient继承http.Client的功能就很好理解了(笔者注:此处用继承功能而不是继承,是出于概念严谨考虑的,因为继承功能的方法很多,比如采用成员变量亦或是直接集成)。http.Client只是具备http客户端基本功能,Kubernetes对客户端还提出了其他需求,所以就有了RestClient,具体是什么需求,后续章节会给出答案。

本文引用源码为github.com/kubernetes/client-go的release-1.21分支。

Client

Interface

Interface是Kubernetes对REST客户端抽象的接口,从Interface的定义我们可以看出Kubernetes对REST于客户端的需求,源码链接:https://github.com/kubernetes/client-go/blob/release-1.21/rest/client.go#L41

type Interface interface {
    // 获取限速器,关于限速器读者可以暂时不用深入理解,只需要知道它是用来控制客户端向服务端提交请求的QPS即可。
    // 通过这个接口笔者可以得出一个结论:Kubernetes要求RestClient有限速能力,这个是http.Client不具备的。
    // 限速的目的很简单,避免某个客户端恶意/异常操作对apiserver造成压力,进而影响整个系统。
    GetRateLimiter() flowcontrol.RateLimiter
    // 创建指定动词的REST请求(Request),这个有点意思了,说明Kubernetes的REST请求是可以RestClient创建的。
    // 熟悉http.Request的同学都知道,http.Request是不需要依赖http.Client创建的。
    // 通过这个接口笔者可以得出一个结论:Kubernetes要求RestClient有创建Request的能力,这也是http.Client不具备的。
    Verb(verb string) *Request
    // 等同于Verb("POST")
    Post() *Request
    // 等同于Verb("PUT")
    Put() *Request
    // 等同于Verb("PATCH")
    Patch(pt types.PatchType) *Request
    // 等同于Verb("GET")
    Get() *Request
    // 等同于Verb("DELETE")
    Delete() *Request
    // 获取API组和版本,Kubernetes的每个RestClient只能操作某个组/版本的API对象。
    // 这也就可以理解为什么需要RestClient创建Request,因为需要创建的Request操作范围指定在某个API组/版本内。
    // 是不是可以想象得到Clientset的实现?其实就是所有API组/版本的RestClient集合,所以称之为Clientset。
    APIVersion() schema.GroupVersion
}

通过对Interface接口的分析,一句话概括为:“RestClient用于创建操作指定组/版本API对象的Request,同时具有限速能力”。总结的是不是很到位?那么问题来了,只需要创建Request,这跟http.Client一毛钱关系都没有,那为啥前文说可以类比http.Client呢?你想啊,创建Request不是目的,目的是将Request提交给apiserver,那靠啥提交请求呢?Interface只是接口,不会反应具体的实现,它只是需求提出方。要想知道原因,且看下文的解析。

RestClient

RestClient确实有这个类型,而且实现了Interface接口,笔者前言就开始使用RestClient也是为了思路的连贯性,源码链接:https://github.com/kubernetes/client-go/blob/release-1.21/rest/client.go#L81

// RestClient实现了Interface接口,所有的成员变量除了限速器和APIVersion,其他的基本都是为了创建Request而设计的。
type RESTClient struct {
    // 客户端所有调用的根URL,比如https://192.168.1.2:6443,是生成Request的最终URL的必须参数
    base *url.URL
    // 资源路径的一段,连接base就是资源的根路径,比如/apis/apps/v1(对应于apps组v1版本,/apis是绝大部分API的前缀)
    // versionedAPIPath是生成Reqeust的最终URL的必须参数
    versionedAPIPath string

    // 与HTTP内容相关的配置,下面有注释
    content ClientContentConfig

    // 退避管理器的构造函数,首先需要知道什么是退避,说的简单点就是向apiserver提交请求后出错,如果需要重试则需要等待一段时间,这就是退避。
    // 其次需要知道退避管理器的功能是什么?退避管理器有点类似于map[string]int,用来记录不同对象(键)的退避次数,然后根据退避次数计算退避时间。
    // 至于退避管理器的键是什么,后面会有介绍,此处只需要知道向apiserver提交请求可能会失败,失败可能需要重试,重试可能需要计算退避时间。
    // createBackoffMgr就是用来给Request创建自己的退避管理器的。
    createBackoffMgr func() BackoffManager

    // 限速器,首先是为了实现Interface.GetRateLimiter()接口,其次,所有创建的Request共享限速器。
    // 所以rateLimiter用来限制RestClient创建的Request向apiserver提交请求的QPS。至于flowcontrol.RateLimiter的实现感兴趣的读者可以自己了解一下。
    rateLimiter flowcontrol.RateLimiter

    // warningHandler,而不是errorHandler,用来处理一些warning等级的异常,这些异常不会终止程序执行。
    // warningHandler也是Request之间共享使用的。
    warningHandler WarningHandler

    // 这个应该没什么好解释的了,提交HTTP请求必须的对象,如果没有设置将使用http.DefaultClient。
    // 前文提到RestClient继承了http.Client就是通过成员变量实现的。
    Client *http.Client
}

// ClientContentConfig是客户端关于HTTP内容(http.Request.Body和http.Response.Body)的配置。
type ClientContentConfig struct {
    // 指定客户端将接受的内容类型,如果没有设置,将使用ContentType设置Accept请求头。就是RestClient希望apiserver响应内容的类型。
    // 关于Accept请求头参看:https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers/Accept
    AcceptContentTypes string
    // 指定用于与服务端通信的格式,如果未设置AcceptContentTypes,则将用于设置Accept请求头,并设置为给服务端的任何对象的默认内容类型。
    // 如果没有设置,则使用"application/json"。就是告诉服务端(apiserver)http.Request.Body的格式。
    ContentType string
    // API组和版本,直接初始化RestClient时必须提供。
    GroupVersion schema.GroupVersion
    // Negotiator用于获取支持的多种媒体类型的编码器和解码器,关于Negotiator读者可以暂时不用深入理解,只需要知道他可以根据媒体类型获取编解码器就可以了。
    // 至于什么是编解码器,说白了就是用来序列化和反序列化API对象的,可以直接理解为json.Marshal()和json.Unmarsal()。
    // 因为AcceptContentTypes和ContentType两个配置,所以需要根据相应的类型获取编解码器。
    Negotiator runtime.ClientNegotiator
}

看为了RestClient的定义,接下来看看RestClient的构造函数,源码链接:https://github.com/kubernetes/client-go/blob/release-1.21/rest/client.go#L107

// NewRESTClient()是RestClient的构造函数,从构造函数的参数上看,RestClient大部分成员变量都是外部传入的。
func NewRESTClient(baseURL *url.URL, versionedAPIPath string, config ClientContentConfig, rateLimiter flowcontrol.RateLimiter, client *http.Client) (*RESTClient, error) {
    // ContentType如果没有设置,默认使用"application/json",默认的编码器和解码器也可以看做是json.Marshal()和json.Unmarshal()。
    if len(config.ContentType) == 0 {
        config.ContentType = "application/json"
    }

    // 初始化baseURL,这个比较好理解。
    base := *baseURL
    if !strings.HasSuffix(base.Path, "/") {
        base.Path += "/"
    }
    base.RawQuery = ""
    base.Fragment = ""

    // 返回RestClient对象
    return &RESTClient{
        base:             &base,
        versionedAPIPath: versionedAPIPath,
        content:          config,
        // readExpBackoffConfig是构造退避管理器的函数,下面有注释
        createBackoffMgr: readExpBackoffConfig,
        rateLimiter:      rateLimiter,

        Client: client,
    }, nil
}

// readExpBackoffConfig()是RestClient构造退避管理器的函数
func readExpBackoffConfig() BackoffManager {
    // 通过环境变量获取退避的初始时间和最大时间,单位为秒。
    backoffBase := os.Getenv(envBackoffBase)
    backoffDuration := os.Getenv(envBackoffDuration)

    // 因为环境变量是字符串型,所以此处需要转为整型
    backoffBaseInt, errBase := strconv.ParseInt(backoffBase, 10, 64)
    backoffDurationInt, errDuration := strconv.ParseInt(backoffDuration, 10, 64)
    // 如果没有配置环境变量或者错误配置,则无需退避
    if errBase != nil || errDuration != nil {
        return &NoBackoff{}
    }
    // URLBackoff是以URL为键计算退避时间,也就是说某个URL访问错误,如果立刻访问相同的URL就需要退避一段时间,退避时间随着错误次数增加以2的指数增加。
    // 此处需要注意的是,URL是baseURL,这个应该好理解,某个主机访问出错,立刻访问这个主机大概率还是可能会出错,即便访问的是不同的API。
    return &URLBackoff{
        Backoff: flowcontrol.NewBackOff(
            time.Duration(backoffBaseInt)*time.Second,
            time.Duration(backoffDurationInt)*time.Second)}
}

看完了RestClient的定义,接下来看看RestClient的构造函数,源码链接:https://github.com/kubernetes/client-go/blob/release-1.21/rest/client.go#L169

// Verb()创建了Request对象,然后设置Request的verb属性。
func (c *RESTClient) Verb(verb string) *Request {
    return NewRequest(c).Verb(verb)
}

// Post()利用Verb()实现,这个非常简单。
func (c *RESTClient) Post() *Request {
    return c.Verb("POST")
}

// Put()利用Verb()实现,这个非常简单。
func (c *RESTClient) Put() *Request {
    return c.Verb("PUT")
}

// Patch()利用Verb()创建了Request,然后将Patch类型设置Content-Type请求头,读者可以看下types.PatchType的枚举定义就更容易理解了。
func (c *RESTClient) Patch(pt types.PatchType) *Request {
    return c.Verb("PATCH").SetHeader("Content-Type", string(pt))
}

// Get()利用Verb()实现,这个非常简单。
func (c *RESTClient) Get() *Request {
    return c.Verb("GET")
}

// Delete()利用Verb()实现,这个非常简单。
func (c *RESTClient) Delete() *Request {
    return c.Verb("DELETE")
}

// 获取API组和版本。
func (c *RESTClient) APIVersion() schema.GroupVersion {
    return c.content.GroupVersion
}

感觉RestClient实现Interface接口非常简单,其实这里面最核心的函数是NewRequest(),利用RestClient构造Request对象,笔者会在解析Request的文章详细说明。

总结

  1. RestClient是操作某个组/版本(比如apps/v1)的API对象的REST客户端;
  2. RestClient的主要功能就是创建Request,甚至可以理解为RequestFactory;
  3. RestClient的限流器是所有Request共享使用的,也就是说RestClient创建的Request虽然可以并发提交,但是都会被限流器统一限制在某个QPS以内;
  4. RestClient的构造函数传入了限流器,不是自己构造的,可以推测多个RestClient(比如apps/v1、core/v1)之间共享同一个限流器;
  5. 退避管理器是以URL(base)为键管理退避时间,同一个Request向不同的host提交产生的错误不会累加,单独计算退避时间;
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值