rest包源码分析
rest包
主要是提供对外(就是自定义程序所引用的)相关的基础类/接口,client、config、exec、request等具体实现和构造的基础
client.go
- 接口.go
// 接口用于与 Kubernetes REST api 进行交互的操作集。 type Interface interface { // 接口请求速率控制器 GetRateLimiter() flowcontrol.RateLimiter // 设置何种操作,通用性 Verb(verb string) *Request // 设置post操作 Post() *Request // 设置put操作 Put() *Request // 设置patch操作 Patch(pt types.PatchType) *Request // 设置get操作 Get() *Request // 设置delete操作 Delete() *Request // 获取client的gv(要操作对象的gv) APIVersion() schema.GroupVersion }
- 函数
// readExpBackoffConfig 创建URLBackoff(用来控制request请求和apiserver交互如果出现异常的处理)对象。默认情况下,如果没有可用信息,则为 NoBackoff。 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{} } return &URLBackoff{ Backoff: flowcontrol.NewBackOff( time.Duration(backoffBaseInt)*time.Second, time.Duration(backoffDurationInt)*time.Second)} } // NewRESTClient 创建一个新的 RESTClient。此客户端在指定的路径上执行通用 REST 功能,例如 Get、Put、Post 和 Delete。 func NewRESTClient(baseURL *url.URL, versionedAPIPath string, config ClientContentConfig, rateLimiter flowcontrol.RateLimiter, client *http.Client) (*RESTClient, error) { // 如果contentType没有设置,则设置默认为"application/json" if len(config.ContentType) == 0 { config.ContentType = "application/json" } base := *baseURL // 判断是否以/结尾,否则追加 if !strings.HasSuffix(base.Path, "/") { base.Path += "/" } // 设置queryParams为空 base.RawQuery = "" // 设置base url对应的分段(用于定位页面位置,在url的#字符后)为空 base.Fragment = "" return &RESTClient{ base: &base, versionedAPIPath: versionedAPIPath, content: config, createBackoffMgr: readExpBackoffConfig, rateLimiter: rateLimiter, Client: client, }, nil }
- 结构体
// ClientContentConfig 控制 RESTClient 如何(主要是序列化)与服务器通信。 type ClientContentConfig struct { // AcceptContentTypes 指定客户端可以接受的类型。如果未设置,ContentType 将用于定义 Accept 标头 AcceptContentTypes string // ContentType 指定用于与服务器通信的内容格式。 // 如果未设置 AcceptContentTypes,则此值将设置为对服务器发出的请求的 Accept 标头,并设置为发送到服务器的任何对象的默认内容类型。 // 注意 :如果未设置,则使用“application/json”。 ContentType string // GroupVersion 是要与之交互的 API 版本。 // 注意:创建restclient时,不设置则调用config.UnversionedRESTClientFor,若设置则调用config.RESTClientFor,因为在与apiserver交互操作时, // 比如list(c.client.Get().VersionedParams(&opts, scheme.ParameterCodec)),用作 VersionedParams 的默认组版本(用来转化为默认版本对应的obj) // 也可以在VersionedParams第三个参数指定,则该参数可以为空 GroupVersion schema.GroupVersion // 用于获取多种支持的媒体类型的编码器和解码器。 Negotiator runtime.ClientNegotiator } // RESTClient 在一组资源路径上强加了通用的 Kubernetes API 约定。 // baseURL 应该指向一个 HTTP 或 HTTPS 路径,它是一个或多个资源的父路径。服务器应该返回一个可解码的 API 资源对象,或一个 api.Status 对象,其中包含有关失败原因的信息。 type RESTClient struct { // base 是客户端所有调用的根 URL base *url.URL // versionedAPIPath 是将base URL 连接到资源根的路径(在base url后面追加了group version) versionedAPIPath string // 描述了 RESTClient 如何编码请求body和解码响应。 content ClientContentConfig // 创建传递给请求的 BackoffManager。(用来控制request请求和apiserver交互如果出现异常的处理,简单说就是如果出现异常,就会sleep指数的时间间隔,然后向下执行) createBackoffMgr func() BackoffManager // rateLimiter 在此客户端创建的所有请求之间共享。也就是所有与apiserver交互的request的速率限制 rateLimiter flowcontrol.RateLimiter // warningHandler 在此客户端创建的所有请求之间共享。主要是用来处理warn信息。如果未设置,则使用 defaultWarningHandler。 warningHandler WarningHandler // 与apiserver交互的基础。如果未设置,则会设置http.DefaultClient。 Client *http.Client } // GetRateLimiter 返回给定客户端的速率限制器 func (c *RESTClient) GetRateLimiter() flowcontrol.RateLimiter { if c == nil { return nil } return c.rateLimiter } // 设置request的请求动作(create、get、put等) func (c *RESTClient) Verb(verb string) *Request { // NewRequest用来构造一个request对象,下面单独分析 return NewRequest(c).Verb(verb) } // 设置post请求 func (c *RESTClient) Post() *Request { return c.Verb("POST") }
config.go
- 变量
const ( // 默认的qps(每秒查询率)配置 DefaultQPS float32 = 5.0 // 默认请求速率限制器的bucket最大个数 DefaultBurst int = 10 ) // 表示restconfig中没有配置host和port产生的异常信息 var ErrNotInCluster = errors.New("unable to load in-cluster configuration, KUBERNETES_SERVICE_HOST and KUBERNETES_SERVICE_PORT must be defined")
- 函数
// RESTClientFor 创建一个RESTClient,它满足客户端 Config对象上的请求属性。请注意,RESTClient 可能需要在初始化客户端时可选的字段。 // 通过此方法创建的 RESTClient 是通用的 - 它期望在遵循Kubernetes 约定的 API 上进行操作,但可能不是 Kubernetes API。 func RESTClientFor(config *Config) (*RESTClient, error) { // 这里会限制 GroupVersion(其实是ContentTypeConfig中的,这里是用来序列化请求参数为指定的gv)不能为空 if config.GroupVersion == nil { return nil, fmt.Errorf("GroupVersion is required when initializing a RESTClient") } // 限制NegotiatedSerializer(其实是ContentTypeConfig中的,这里是用来序列化请求参数)不能为空 if config.NegotiatedSerializer == nil { return nil, fmt.Errorf("NegotiatedSerializer is required when initializing a RESTClient") } // 用来获取base (比如https://localhost:9600)地址和版本化的uri(比如 /api/${group}/${verison}),本文在url_utils.go中会详细分析 baseURL, versionedAPIPath, err := defaultServerUrlFor(config) if err != nil { return nil, err } // 用来获取传输层(没错,就是七层网络协议中的传输层)表示对象,本文在transport.go中会详细分析 transport, err := TransportFor(config) if err != nil { return nil, err } // 用来接收创建的rest client var httpClient *http.Client // 判断transport是否是(TransportFor中会判断config的tlsConfig/Dial/Proxy如果都为空 那么就返回DefaultTransport)默认的Transport // 注意:这里只在不是默认的Transport才会给httpClient赋值,那么后续在request请求时该值为空会怎么处理呢? // 会判断httpclient是否为空,如果为空创建一个空的http.client,并在调用transport()时会判断http.client的Transport是否为空,为空创建一个默认的Transport if transport != http.DefaultTransport { // 不是默认的,则会创建一个包装了transport的http.Client对象 httpClient = &http.Client{Transport: transport} // 如果config设置了Timeout 则覆盖httpClient的 if config.Timeout > 0 { httpClient.Timeout = config.Timeout } } // 获取请求速率限制器 rateLimiter := config.RateLimiter // 如果为空 if rateLimiter == nil { // 如果config的qps没有设置,则使用默认值 qps := config.QPS if config.QPS == 0.0 { qps = DefaultQPS } // 如果config的Burst没有设置,则使用默认值 burst := config.Burst if config.Burst == 0 { burst = DefaultBurst } if qps > 0 { // 创建一个基于令牌桶的速率限制器 rateLimiter = flowcontrol.NewTokenBucketRateLimiter(qps, burst) } } // 获取config中gv的指针指向的数据 var gv schema.GroupVersion if config.GroupVersion != nil { gv = *config.GroupVersion } // ClientContentConfig是分析client.go中的结构体 clientContent := ClientContentConfig{ AcceptContentTypes: config.AcceptContentTypes, ContentType: config.ContentType, GroupVersion: gv, // runtime.NewClientNegotiator其实只是包装了NegotiatedSerializer和gv,这里会把gv用作是最终需要转化的group version Negotiator: runtime.NewClientNegotiator(config.NegotiatedSerializer, gv), } // 在本文的client.go部分已经分析 restClient, err := NewRESTClient(baseURL, versionedAPIPath, clientContent, rateLimiter, httpClient) if err == nil && config.WarningHandler != nil { restClient.warningHandler = config.WarningHandler } return restClient, err } // 和上面RESTClientFor很相似,只是不限定gv为空而已(用于unversion操作使用),其他都是一样 func UnversionedRESTClientFor(config *Config) (*RESTClient, error) // SetKubernetesDefaults 在提供的客户端配置上设置默认值以访问Kubernetes API ,目前值设置了UserAgent,默认值是所在服务器的环境信息。 func SetKubernetesDefaults(config *Config) error { if len(config.UserAgent) == 0 { config.UserAgent = DefaultKubernetesUserAgent() } return nil } // DefaultKubernetesUserAgent 返回一个从静态全局变量构建的 User-Agent 字符串。 func DefaultKubernetesUserAgent() string { return buildUserAgent( adjustCommand(os.Args[0]), adjustVersion(version.Get().GitVersion), gruntime.GOOS, gruntime.GOARCH, adjustCommit(version.Get().GitCommit)) } // adjustCommit 返回提交的 git 哈希的足够有效数字。 func adjustCommit(c string) string { if len(c) == 0 { return "unknown" } if len(c) > 7 { return c[:7] } return c } // adjustVersion 从表单中的版本中去除“alpha”、“beta”等 类似,major.minor.patch-[alpha|beta|etc]。 func adjustVersion(v string) string { if len(v) == 0 { return "unknown" } seg := strings.SplitN(v, "-", 2) return seg[0] } // adjustCommand 返回用于用户代理的特定操作系统的最后一个组件的命令路径。 func adjustCommand(p string) string { // Unlikely, but better than returning "". if len(p) == 0 { return "unknown" } return filepath.Base(p) } // buildUserAgent 从给定的 args 构建一个 User-Agent 字符串。 func buildUserAgent(command, version, os, arch, commit string) string { return fmt.Sprintf( "%s/%s (%s/%s) kubernetes/%s", command, version, os, arch, commit) } // InClusterConfig 返回一个配置对象,该对象使用kubernetes的服务帐户提供给 pods。 // 它适用于运行在kubernetes pod 内的客户端(也就是如果程序是在k8s部署运行,那么我们可以直接调用该方法获取一个Config. // 在程序在k8s中创建时,k8s会在器container中加入一些环境(常用的KUBERNETES_SERVICE_HOST和KUBERNETES_SERVICE_PORT),供内部使用)。 // 如果从不在 kubernetes 环境中运行的进程调用,它将返回 ErrNotInCluster。 func InClusterConfig() (*Config, error) { // pod内部存放token和ca证书的绝对路径 const ( tokenFile = "/var/run/secrets/kubernetes.io/serviceaccount/token" rootCAFile = "/var/run/secrets/kubernetes.io/serviceaccount/ca.crt" ) // pod内的环境变量KUBERNETES_SERVICE_HOST(k8s apiserver service的host)和KUBERNETES_SERVICE_PORT(k8s apiserver service的port) host, port := os.Getenv("KUBERNETES_SERVICE_HOST"), os.Getenv("KUBERNETES_SERVICE_PORT") // 如果host和port任意一个为空,那么证明该pod不在k8s 集群内 if len(host) == 0 || len(port) == 0 { return nil, ErrNotInCluster } // 读取tokenFile(该pod和k8s交互的token)对应的内容 token, err := ioutil.ReadFile(tokenFile) if err != nil { return nil, err } // tls配置 tlsClientConfig := TLSClientConfig{} // 读取rootCAFile对应的pem内容 if _, err := certutil.NewPool(rootCAFile); err != nil { klog.Errorf("Expected to load root CA config from %s, but got err: %v", rootCAFile, err) } else { tlsClientConfig.CAFile = rootCAFile } // 构造rest config对象 return &Config{ Host: "https://" + net.JoinHostPort(host, port), TLSClientConfig: tlsClientConfig, BearerToken: string(token), BearerTokenFile: tokenFile, }, nil } // IsConfigTransportTLS 判断client和服务器的连接是否是tls模式(也就是https模式,是否配置了CA相关数据)。 // 注意: 此方法是在获取rest config过程中调用,用于判断是否通过网络发送凭据进行身份验证并持久化到原始config path对应的文件。 func IsConfigTransportTLS(config Config) bool { baseURL, _, err := defaultServerUrlFor(&config) if err != nil { return false } return baseURL.Scheme == "https" } // dataFromSliceOrFile 从切片(如果非空)或文件中返回数据, 或者如果读取文件发生错误,则返回错误 func dataFromSliceOrFile(data []byte, file string) ([]byte, error) { if len(data) > 0 { return data, nil } if len(file) > 0 { fileData, err := ioutil.ReadFile(file) if err != nil { return []byte{}, err } return fileData, nil } return nil, nil } // 覆盖config对应的UserAgent func AddUserAgent(config *Config, userAgent string) *Config { // 格式是${默认值}/${userAgent参数} fullUserAgent := DefaultKubernetesUserAgent() + "/" + userAgent config.UserAgent = fullUserAgent return config } // CopyConfig 返回给定配置的副本(指针地址) func CopyConfig(config *Config) *Config { c := &Config{ Host: config.Host, APIPath: config.APIPath, ContentConfig: config.ContentConfig, Username: config.Username, Password: config.Password, BearerToken: config.BearerToken, BearerTokenFile: config.BearerTokenFile, Impersonate: ImpersonationConfig{ Groups: config.Impersonate.Groups, Extra: config.Impersonate.Extra, UserName: config.Impersonate.UserName, }, AuthProvider: config.AuthProvider, AuthConfigPersister: config.AuthConfigPersister, ExecProvider: config.ExecProvider, TLSClientConfig: TLSClientConfig{ Insecure: config.TLSClientConfig.Insecure, ServerName: config.TLSClientConfig.ServerName, CertFile: config.TLSClientConfig.CertFile, KeyFile: config.TLSClientConfig.KeyFile, CAFile: config.TLSClientConfig.CAFile, CertData: config.TLSClientConfig.CertData, KeyData: config.TLSClientConfig.KeyData, CAData: config.TLSClientConfig.CAData, NextProtos: config.TLSClientConfig.NextProtos, }, UserAgent: config.UserAgent, DisableCompression: config.DisableCompression, Transport: config.Transport, WrapTransport: config.WrapTransport, QPS: config.QPS, Burst: config.Burst, RateLimiter: config.RateLimiter, WarningHandler: config.WarningHandler, Timeout: config.Timeout, Dial: config.Dial, Proxy: config.Proxy, } if config.ExecProvider != nil && config.ExecProvider.Config != nil { c.ExecProvider.Config = config.ExecProvider.Config.DeepCopyObject() } return c }
- 结构体
// Config 在初始化时传递给 Kubernetes 客户端的通用属性。 type Config struct { // 主机必须是主机字符串(主机:端口或指向 apiserver基础的 URL)。 // 如果不为空必须附加到用于访问 apiserver 的所有请求 URI 的前缀。 Host string // APIPath 是指向 API 根的子路径。 APIPath string // 控制 RESTClient 如何(主要是序列化)与服务器通信 ContentConfig // 服务器需要基本身份验证 Username string Password string `datapolicy:"password"` // 服务器需要的认证。 BearerToken string `datapolicy:"token"` // 包含 BearerToken 的文件的路径。如果设置,则定期读取内容。 最后成功读取的值优先于 BearerToken。 BearerTokenFile string // Impersonate 是 RESTClient 将用于模拟的配置。 Impersonate ImpersonationConfig //服务器需要插件(目前大概有四种:azure、gcp、oidc、openstack)指定的身份验证,获取认证信息 AuthProvider *clientcmdapi.AuthProviderConfig // 用于持久化AuthProvider 的Config属性(如果用户通过插件认证成功后会产生一些认证信息)。 AuthConfigPersister AuthProviderConfigPersister // 基于 Exec(命令式,可以是所在系统支持的可执行文件或者命令) 的身份验证提供程序,获取认证信息 ExecProvider *clientcmdapi.ExecConfig // TLSClientConfig 包含启用传输层安全的设置(证书相关数据) TLSClientConfig // UserAgent 是一个可选字段,用于指定此请求的调用者。 UserAgent string // DisableCompression 是否绕过自动 GZip 压缩请求(是否对request进行GZip). DisableCompression bool // 传输可用于自定义 HTTP 行为。 // 不能使用 TLS 客户端证书选项指定此属性。使用 WrapTransport提供额外的每服务器中间件行为(可以在RoundTripper执行之前的操作)。 Transport http.RoundTripper // 在底层传输初始化(从 TLSClientConfig 创建的传输、传输或 http.DefaultTransport)之后,将为自定义 HTTP 行为调用 WrapTransport。 // ps:未来的版本会将这个字段更改为一个数组。使用 config.Wrap() 而不是直接设置这个值。 WrapTransport transport.WrapperFunc // QPS 表示从这个客户端到 master (apiserver的master服务)的最大 QPS。如果为零,则创建的 RESTClient 将使用 DefaultQPS: 5 // 创建client的速率限制器时使用,限制每秒的处理个数 QPS float32 // 创建client的速率限制器时使用,限制bluket的最大数目 // 如果为零,则创建的 RESTClient 将使用 DefaultBurst: 10. Burst int // 速率限制器,用于限制从该客户端到 master 的连接。如果存在覆盖 QPS/Burst RateLimiter flowcontrol.RateLimiter // WarningHandler 处理服务器响应中的警告。如果未设置,则使用默认警告处理程序。有关详细信息,请参阅 SetDefaultWarningHandler() 的文档。 WarningHandler WarningHandler // 在放弃服务器请求之前等待的最长时间。零值意味着没有超时。 Timeout time.Duration // Dial 指定用于创建未加密 TCP 连接的拨号功能。创建http conn Dial func(ctx context.Context, network, address string) (net.Conn, error) // 代理是用于此传输发出的所有请求的代理功能。如果 Proxy 为零,则使用 http.ProxyFromEnvironment。如果代理 返回一个 nil *URL,则不使用代理。 // 注意:socks5 代理目前不支持 spdy 流端点。 Proxy func(*http.Request) (*url.URL, error) // 强制使用特定版本(如果已注册) // Version string } // GoString 实现 fmt.GoStringer 并清理 Config 的敏感字段以防止通过日志意外泄漏。 func (c *Config) GoString() string { return c.String() } // String 实现 fmt.Stringer 并清理 Config 的敏感字段以防止通过日志意外泄漏。 func (c *Config) String() string { if c == nil { return "<nil>" } // sanitizedConfig是指针变量对应的值,防止改变原始值 cc := sanitizedConfig(CopyConfig(c)) // 将非空凭据字段显式标记为REDACTED(已编辑)。 if cc.Password != "" { cc.Password = "--- REDACTED ---" } if cc.BearerToken != "" { cc.BearerToken = "--- REDACTED ---" } if cc.AuthConfigPersister != nil { cc.AuthConfigPersister = sanitizedAuthConfigPersister{cc.AuthConfigPersister} } if cc.ExecProvider != nil && cc.ExecProvider.Config != nil { cc.ExecProvider.Config = sanitizedObject{Object: cc.ExecProvider.Config} } return fmt.Sprintf("%#v", cc) } // ImpersonationConfig 所有可用的模拟选项 type ImpersonationConfig struct { // UserName 是模拟每个请求的用户名。 UserName string // 组是模拟每个请求的组。 Groups []string // Extra 是一个自由格式字段,可用于将某些身份验证信息链接到授权信息。 Extra map[string][]string } // TLSClientConfig 包含启用传输层安全的设置 type TLSClientConfig struct { // 是否在不验证 TLS 证书的情况下访问服务器。仅供测试。 Insecure bool // ServerName 被传递到 SNI (用于解析具有多个域名)的服务器,并在客户端中用于检查服务器证书。如果 ServerName 为空,则使用hostname代替。 ServerName string //服务器需要 TLS 客户端认证证书的路径 CertFile string // 一般是key的文件名称 KeyFile string // 服务器的可信根证书的文件名称 CAFile string // CertData 保存 PEM 编码的字节(通常从客户端证书文件中读取)。CertData 优先于 CertFile CertData []byte // KeyData 保存 PEM 编码的字节(通常从客户端证书密钥文件中读取)。KeyData 优先于 KeyFile KeyData []byte `datapolicy:"security-key"` // CAData 保存 PEM 编码的字节(通常从根证书包中读取)。CAData 优先于 CAFile CAData []byte // NextProtos 是支持的应用程序级协议列表,按优先顺序排列。 // 用于填充 tls.Config.NextProtos。要向服务器表明 http/1.1 优先于 http/2,请设置为 ["http/1.1", "h2"](尽管服务器可以随意忽略该偏好)。 // 要仅使用 http/1.1,请设置为 ["http/1.1"]。 NextProtos []string } // GoString 实现 fmt.GoStringer 并清理TLSClientConfig 的敏感字段,以防止通过日志意外泄漏。 func (c TLSClientConfig) GoString() string { return c.String() } // String 实现 fmt.Stringer 并清理TLSClientConfig 的敏感字段,以防止通过日志意外泄漏。 func (c TLSClientConfig) String() string { // sanitizedTLSClientConfig本身还是TLSClientConfig,不过是内部使用,不对外暴漏 cc := sanitizedTLSClientConfig{ Insecure: c.Insecure, ServerName: c.ServerName, CertFile: c.CertFile, KeyFile: c.KeyFile, CAFile: c.CAFile, CertData: c.CertData, KeyData: c.KeyData, CAData: c.CAData, NextProtos: c.NextProtos, } // 将非空凭据字段显式标记为已编辑。 if len(cc.CertData) != 0 { cc.CertData = []byte("--- TRUNCATED ---") } if len(cc.KeyData) != 0 { cc.KeyData = []byte("--- REDACTED ---") } return fmt.Sprintf("%#v", cc) } // 一般我们构造ClientContentConfig,就是基于ContentConfig来创建的 type ContentConfig struct { // AcceptContentTypes 指定客户端将接受的类型并且是可选的。 // 如果未设置,ContentType 将用于定义 Accept 标头 AcceptContentTypes string // ContentType 指定用于与服务器通信的有线格式。 // 此值将设置为对服务器发出的请求的 Accept 标头,并作为发送到服务器的任何对象的默认内容类型。如果未设置,使用“application/json”。 ContentType string // GroupVersion 是要与之对话的 API 版本。直接初始化 RESTClient 时必须提供。初始化客户端时,将设置为默认代码版本。 GroupVersion *schema.GroupVersion // NegotiatedSerializer 用于获取多种支持的媒体类型的编码器和解码器。 NegotiatedSerializer runtime.NegotiatedSerializer }
exec.go
此文件包含与 exec 凭据插件相关的配置逻辑。
- 函数
// ConfigToExecCluster 由Config创建一个clientauthenticationapi.Cluster。 func ConfigToExecCluster(config *Config) (*clientauthenticationapi.Cluster, error) { // 本文config.go文件的函数,获取caData caData, err := dataFromSliceOrFile(config.CAData, config.CAFile) if err != nil { return nil, fmt.Errorf("failed to load CA bundle for execProvider: %v", err) } var proxyURL string // 判断是否使用代理 if config.Proxy != nil { req, err := http.NewRequest("", config.Host, nil) if err != nil { return nil, fmt.Errorf("failed to create proxy URL request for execProvider: %w", err) } // 获取代理地址 url, err := config.Proxy(req) if err != nil { return nil, fmt.Errorf("failed to get proxy URL for execProvider: %w", err) } if url != nil { proxyURL = url.String() } } return &clientauthentication.Cluster{ Server: config.Host, TLSServerName: config.ServerName, InsecureSkipTLSVerify: config.Insecure, CertificateAuthorityData: caData, ProxyURL: proxyURL, Config: config.ExecProvider.Config, }, nil } // ExecClusterToConfig 使用提供的clientauthenticationapi.Cluster创建一个config。返回的配置将是匿名的(即,它不会设置任何与身份验证相关的字段)。 func ExecClusterToConfig(cluster *clientauthentication.Cluster) (*Config, error) { var proxy func(*http.Request) (*url.URL, error) if cluster.ProxyURL != "" { proxyURL, err := url.Parse(cluster.ProxyURL) if err != nil { return nil, fmt.Errorf("cannot parse proxy URL: %w", err) } proxy = http.ProxyURL(proxyURL) } return &Config{ Host: cluster.Server, TLSClientConfig: TLSClientConfig{ Insecure: cluster.InsecureSkipTLSVerify, ServerName: cluster.TLSServerName, CAData: cluster.CertificateAuthorityData, }, Proxy: proxy, }, nil }
plugin.go
- 变量
// 保存所有注册的身份验证提供程序插件。 var pluginsLock sync.Mutex var plugins = make(map[string]Factory)
- 接口
// 认证供应商接口(内部有azure/gcp/aidc/openstack,也可以自定义) type AuthProvider interface { // WrapTransport 允许插件构建一个新的RoundTripper,新的RoundTripper包含一些参数和参数RoundTripper,用于在执行参数RoundTripper时修改其authorization headers(或其他信息)到请求。 WrapTransport(http.RoundTripper) http.RoundTripper // 登录允许插件初始化其配置。它不需要直接的用户交互。 Login() error } // AuthProviderConfigPersister 保留插件从remote认证后获取的信息。 type AuthProviderConfigPersister interface { Persist(map[string]string) error }
- 结构体
// Factory 生成一个 AuthProvider 插件。clusterAddress 是当前集群的地址。config 是这个插件的初始配置。persister保存插件更新的配置。 type Factory func(clusterAddress string, config map[string]string, persister AuthProviderConfigPersister) (AuthProvider, error)
- 函数
// 注册认证插件 func RegisterAuthProviderPlugin(name string, plugin Factory) error { pluginsLock.Lock() defer pluginsLock.Unlock() // 遍历全局plugins 如果存在名称为name的插件,则返回error if _, found := plugins[name]; found { return fmt.Errorf("auth Provider Plugin %q was registered twice", name) } klog.V(4).Infof("Registered Auth Provider Plugin %q", name) // 添加到map plugins[name] = plugin return nil } // 根据AuthProviderConfig的name属性从plugins中获取factory,然后生成对应的AuthProvider func GetAuthProvider(clusterAddress string, apc *clientcmdapi.AuthProviderConfig, persister AuthProviderConfigPersister) (AuthProvider, error) { pluginsLock.Lock() defer pluginsLock.Unlock() // name属性从plugins中获取factory p, ok := plugins[apc.Name] if !ok { return nil, fmt.Errorf("no Auth Provider found for name %q", apc.Name) } // 如果persister为空,表示不需要对认证后的信息报错到原始config对应的文件中 if persister == nil { // noopPersister表示什么都不做 persister = &noopPersister{} } return p(clusterAddress, apc.Config, persister) }
request.go
- 变量
var ( // longThrottleLatency 定义日志记录请求的阈值。所有被限制(通过提供的 rateLimiter)超过 longThrottleLatency 的请求都将被记录。 longThrottleLatency = 50 * time.Millisecond // extraLongThrottleLatency 定义了日志级别 2 记录请求的阈值。 extraLongThrottleLatency = 1 * time.Second ) // 定义全局节流日志控制器 var globalThrottledLogger = &throttledLogger{ clock: utilclock.RealClock{}, settings: []*throttleSettings{ { logLevel: 2, minLogInterval: 1 * time.Second, }, { logLevel: 0, minLogInterval: 10 * time.Second, }, }, }
- 接口
// HTTPClient 是用于执行request的接口。 type HTTPClient interface { Do(req *http.Request) (*http.Response, error) } // ResponseWrapper 是一个用于获取响应的接口。响应可以作为原始数据(整个输出放入内存)或作为流访问。 type ResponseWrapper interface { DoRaw(context.Context) ([]byte, error) Stream(context.Context) (io.ReadCloser, error) }
- 结构体
// RequestConstructionError 在构造请求时出现错误,则返回该error。 type RequestConstructionError struct { Err error } // 返回该error的文本描述。 func (r *RequestConstructionError) Error() string { return fmt.Sprintf("request construction error: '%v'", r.Err) } // Request 允许以链式方式构建对服务器的请求。任何错误都会存储到您的调用结束,因此您只需检查一次。 type Request struct { c *RESTClient // 通常是rest client结构体,用来和apiserver交互 warningHandler WarningHandler // rest client和apiserver交互后,用来处理产生的warn rateLimiter flowcontrol.RateLimiter // 用来限制和apiserver交互的速率 backoff BackoffManager // rest client和apiserver交互后,如果产生了error,用来处理下此(因为request再请求apiserver调用的是Request,内部是for循环)需要sleep多长时间再请求apiserver timeout time.Duration // request请求apiserver的请求超时时长 // 可通过方法来设置如何访问的通用组件。顾名思义,不做具体介绍 verb string pathPrefix string subpath string params url.Values headers http.Header // Kubernetes API 约定的一部分(用于产生请求api url) namespace string namespaceSet bool resource string resourceName string subresource string err error body io.Reader retry WithRetry } // Verb 设置此请求将使用的动词。 func (r *Request) Verb(verb string) *Request { r.verb = verb return r } // 前缀将段(可以理解为url中组成的uri)添加到请求路径的相对开头。用于将命名空间、资源或名称追加到pathPrefix。 // 注意:使用AbsPath 将清除任何先前设置的 Prefix 段 func (r *Request) Prefix(segments ...string) *Request { if r.err != nil { return r } r.pathPrefix = path.Join(r.pathPrefix, path.Join(segments...)) return r } // 后缀将段附加到subpath的末尾。subpath用于放置在pathPrefix(设置过Prefix之后)之后。 func (r *Request) Suffix(segments ...string) *Request { if r.err != nil { return r } r.subpath = path.Join(r.subpath, path.Join(segments...)) return r } // Resource 设置要访问的资源 ([ns/<namespace>/]<resource>/<name>) func (r *Request) Resource(resource string) *Request { if r.err != nil { return r } if len(r.resource) != 0 { r.err = fmt.Errorf("resource already set to %q, cannot change to %q", r.resource, resource) return r } if msgs := IsValidPathSegmentName(resource); len(msgs) != 0 { r.err = fmt.Errorf("invalid resource %q: %v", resource, msgs) return r } r.resource = resource return r } // BackOff 将请求的退避管理器设置为指定的一个或者如果提供了 nil,则设置为默认的NoBackoff func (r *Request) BackOff(manager BackoffManager) *Request { if manager == nil { r.backoff = &NoBackoff{} return r } r.backoff = manager return r } // WarningHandler 设置此客户端在遇到警告标头时使用的处理程序。 // 如果设置为 nil,此客户端将使用默认警告处理程序(请参阅 SetDefaultWarningHandler)。 func (r *Request) WarningHandler(handler WarningHandler) *Request { r.warningHandler = handler return r } // Throttle 接收速率限制器并设置或替换现有的请求限制器 func (r *Request) Throttle(limiter flowcontrol.RateLimiter) *Request { r.rateLimiter = limiter return r } // SubResource 设置子资源路径,该路径可以是资源名称之后但后缀之前的多个段。 func (r *Request) SubResource(subresources ...string) *Request { if r.err != nil { return r } // 构建subresource subresource := path.Join(subresources...) // 如果已存在,则r.err赋值错误,并返回r if len(r.subresource) != 0 { r.err = fmt.Errorf("subresource already set to %q, cannot change to %q", r.subresource, subresource) return r } // 验证参数subresources的每一项是否是安全地编码路径 for _, s := range subresources { if msgs := IsValidPathSegmentName(s); len(msgs) != 0 { r.err = fmt.Errorf("invalid subresource %q: %v", s, msgs) return r } } r.subresource = subresource return r } // Name 设置要访问的资源的名称 ([ns/<namespace>/]<resource>/<name>) func (r *Request) Name(resourceName string) *Request { if r.err != nil { return r } if len(resourceName) == 0 { r.err = fmt.Errorf("resource name may not be empty") return r } if len(r.resourceName) != 0 { r.err = fmt.Errorf("resource name already set to %q, cannot change to %q", r.resourceName, resourceName) return r } if msgs := IsValidPathSegmentName(resourceName); len(msgs) != 0 { r.err = fmt.Errorf("invalid resource name %q: %v", resourceName, msgs) return r } r.resourceName = resourceName return r } // 命名空间将命名空间范围应用于请求 ([ns/<namespace>/]<resource>/<name>) func (r *Request) Namespace(namespace string) *Request { if r.err != nil { return r } if r.namespaceSet { r.err = fmt.Errorf("namespace already set to %q, cannot change to %q", r.namespace, namespace) return r } if msgs := IsValidPathSegmentName(namespace); len(msgs) != 0 { r.err = fmt.Errorf("invalid namespace %q: %v", namespace, msgs) return r } r.namespaceSet = true r.namespace = namespace return r } // NamespaceIfScoped 用于在 scoped 为 true 时设置命名空间 func (r *Request) NamespaceIfScoped(namespace string, scoped bool) *Request { if scoped { return r.Namespace(namespace) } return r } // AbsPath 使用提供的段覆盖现有路径。当传递单个段时,保留尾部斜杠。 func (r *Request) AbsPath(segments ...string) *Request { if r.err != nil { return r } r.pathPrefix = path.Join(r.c.base.Path, path.Join(segments...)) if len(segments) == 1 && (len(r.c.base.Path) > 1 || len(segments[0]) > 1) && strings.HasSuffix(segments[0], "/") { // 为遗留行为保留任何尾部斜杠 r.pathPrefix += "/" } return r } // RequestURI 使用提供的服务器相关URI 的值覆盖现有路径和参数。 func (r *Request) RequestURI(uri string) *Request { if r.err != nil { return r } locator, err := url.Parse(uri) if err != nil { r.err = err return r } r.pathPrefix = locator.Path if len(locator.Query()) > 0 { if r.params == nil { r.params = make(url.Values) } for k, v := range locator.Query() { r.params[k] = v } } return r } // Param 使用给定的字符串值创建一个查询参数。 func (r *Request) Param(paramName, s string) *Request { if r.err != nil { return r } return r.setParam(paramName, s) } // VersionedParams 使用隐式(r.c.content中的gv) RESTClient API 版本和默认参数编解码器将提供的对象序列化为 map[string][]string,然后将这些作为参数添加到请求中。 // 使用它来提供客户端库的版本化查询参数。VersionedParams 不会写入设置了 omitempty 且为空的查询参数。 func (r *Request) VersionedParams(obj runtime.Object, codec runtime.ParameterCodec) *Request { return r.SpecificallyVersionedParams(obj, codec, r.c.content.GroupVersion) } func (r *Request) SpecificallyVersionedParams(obj runtime.Object, codec runtime.ParameterCodec, version schema.GroupVersion) *Request { if r.err != nil { return r } // 序列化obj为对应gv的obj,并将obj转化为map(会舍弃掉obj中设置了 omitempty 且为空的属性) params, err := codec.EncodeParameters(obj, version) if err != nil { r.err = err return r } for k, v := range params { if r.params == nil { r.params = make(url.Values) } r.params[k] = append(r.params[k], v...) } return r } // 设置请求参数 url中?后面的键值对 func (r *Request) setParam(paramName, value string) *Request { if r.params == nil { r.params = make(url.Values) } r.params[paramName] = append(r.params[paramName], value) return r } // 设置请求的header func (r *Request) SetHeader(key string, values ...string) *Request { if r.headers == nil { r.headers = http.Header{} } r.headers.Del(key) for _, value := range values { r.headers.Add(key, value) } return r } // 超时是请求使用给定的持续时间作为请求的总超时时间。此外,如果设置将值作为 URL 中的“超时”参数传递。 func (r *Request) Timeout(d time.Duration) *Request { if r.err != nil { return r } r.timeout = d return r } // MaxRetries 使请求使用给定的整数作为重试上限在响应中收到“Retry-After”标头和 429 状态代码。 // 默认值为 10 func (r *Request) MaxRetries(maxRetries int) *Request { r.retry.SetMaxRetries(maxRetries) return r } // Body 设置请求的body。根据参数obj的类型,有以下几种情况(最终都是转化为io.Reader): // 如果 obj 是字符串,请尝试读取该名称的文件,并构造io.Reader。 // 如果 obj 是 []byte,则直接构造io.Reader。 // 如果obj是io.Reader,直接使用. // 如果 obj 是 runtime.Object,根据content中的Negotiator编码obj,并设置 Content-Type 标头(为什么其他情况不用设置,只有这种情况需要,因为这是根据不同的contenttype选择一个的,其他的不设置默认是application/json)。 // 如果 obj 是一个 runtime.Object 并且 nil,则什么都不做。 // 其他,设置错误。 func (r *Request) Body(obj interface{}) *Request { if r.err != nil { return r } switch t := obj.(type) { case string: data, err := ioutil.ReadFile(t) if err != nil { r.err = err return r } glogBody("Request Body", data) r.body = bytes.NewReader(data) case []byte: glogBody("Request Body", t) r.body = bytes.NewReader(t) case io.Reader: r.body = t case runtime.Object: // 调用者可能会传递类型化的接口指针,因此我们必须使用反射检查 nil if reflect.ValueOf(t).IsNil() { return r } encoder, err := r.c.content.Negotiator.Encoder(r.c.content.ContentType, nil) if err != nil { r.err = err return r } data, err := runtime.Encode(encoder, t) if err != nil { r.err = err return r } glogBody("Request Body", data) r.body = bytes.NewReader(data) r.SetHeader("Content-Type", r.c.content.ContentType) default: r.err = fmt.Errorf("unknown type used for body: %+v", obj) } return r } // URL 返回当前的工作 URL。 func (r *Request) URL() *url.URL { // 设置url.URL中path的初始值为pathPrefix(前面分析过如何产生pathPrefix) p := r.pathPrefix // 判断是否设置namespace(注意这里需要namespaceSet为true且namespace不能为空),设置namespace,格式为/namespaces/${r.namespace} if r.namespaceSet && len(r.namespace) > 0 { p = path.Join(p, "namespaces", r.namespace) } // 判断resource是否为空(注意这里是resource 而不是resource name) if len(r.resource) != 0 { p = path.Join(p, strings.ToLower(r.resource)) } // 加入修剪尾部斜杠,因此如果没有任何更改,请保留 r.pathPrefix 的尾部斜杠以实现向后兼容性 if len(r.resourceName) != 0 || len(r.subpath) != 0 || len(r.subresource) != 0 { p = path.Join(p, r.resourceName, r.subresource, r.subpath) } // 初始化空的URL finalURL := &url.URL{} // 如果r中的rest clinet中已经有了base url,则覆盖finalURL if r.c.base != nil { *finalURL = *r.c.base } // 设置finalURL的Path为p finalURL.Path = p // 设置query params query := url.Values{} for key, values := range r.params { for _, value := range values { query.Add(key, value) } } // 超时在这里专门处理。 if r.timeout != 0 { query.Set("timeout", r.timeout.String()) } // query params转化为字节数组并赋值给finalURL RawQuery属性 finalURL.RawQuery = query.Encode() return finalURL } // finalURLTemplate 与 URL() 类似,但会使所有特定参数值以{xxx}表示 - 而不是值,比如将使用“{name}”和“{namespace}”,并且所有查询参数将被重置(params的value是{value})。这会创建 url 的副本,以免更改底层对象。 func (r Request) finalURLTemplate() url.URL // 执行速率限制器(看是否需要限制等待并修改内部参数)且Throttle节流(就是根据最新输出日志的时间和当前日志比较,看是否大于一个特定时间)输出日志 func (r *Request) tryThrottle(ctx context.Context) error { return r.tryThrottleWithInfo(ctx, "") } func (r *Request) tryThrottleWithInfo(ctx context.Context, retryInfo string) error { if r.rateLimiter == nil { return nil } now := time.Now() err := r.rateLimiter.Wait(ctx) latency := time.Since(now) var message string switch { case len(retryInfo) > 0: message = fmt.Sprintf("Waited for %v, %s - request: %s:%s", latency, retryInfo, r.verb, r.URL().String()) default: message = fmt.Sprintf("Waited for %v due to client-side throttling, not priority and fairness, request: %s:%s", latency, r.verb, r.URL().String()) } if latency > longThrottleLatency { klog.V(3).Info(message) } if latency > extraLongThrottleLatency { globalThrottledLogger.Infof("%s", message) } // 字面上这里会加入普罗米修斯的指标(但其实啥也没做,因为啥都没有实现) metrics.RateLimiterLatency.Observe(ctx, r.verb, r.finalURLTemplate(), latency) return err } // 生成一个http.Request对象并配置必要属性,用来和apiserver交互 func (r *Request) newHTTPRequest(ctx context.Context) (*http.Request, error) { // 获取request 的url url := r.URL().String() // 生成http.Request对象 req, err := http.NewRequest(r.verb, url, r.body) if err != nil { return nil, err } // 设置上下文环境 req = req.WithContext(ctx) // 设置请求header req.Header = r.headers return req, nil } // transformUnstructuredResponseError 处理来自apiserver 响应的非结构化(缺少了metadata信息的StatusError)形式的错误。 // 期望将任何来自 K8S API 无法识别响应转换为明确服务器错误的响应。 // 在实践中,HTTP 代理和客户端库为服务器返回的响应引入了一定程度的不确定性,这通常会导致意外响应. // 几种情况: // // 1. 假设服务器向你发送了一些理智的东西 - JSON + 明确定义的错误对象 + 正确的代码 // - 这是我们比较期望的 - 当你得到这个输出时,使用服务器发送的内容 // 2. 防止接收到的 JSON 中的空字段/主体,并尝试从中剔除足够的信息以生成原始失败的合理信息。 // - 确保使用不同的错误类型或标志以允许客户端区分此错误和上面的错误1 // 3. 通过移动到更通用的客户端错误来处理真正的断开连接失败/完全格式错误的数据 // 4. 区分各种连接失败,如 SSL 证书、超时、代理错误、意外初始连接、发布的内容类型中存在不匹配的正文内容 // - 给这些单独的不同错误类型并尽可能多地捕获原始消息 func (r *Request) transformUnstructuredResponseError(resp *http.Response, req *http.Request, body []byte) error { // 如果body为空且resp.Body不为空,说明读取resp.Body没有出现错误但是没有读到数据且resp.Body不为空(什么情况下,会出现?) if body == nil && resp.Body != nil { // 读取所有resp body中的数据 if data, err := ioutil.ReadAll(&io.LimitedReader{R: resp.Body, N: maxUnstructuredResponseTextBytes}); err == nil { body = data } } // 获取多长时间后再次重试 retryAfter, _ := retryAfterSeconds(resp) // 构造一个UnstructuredResponseError 其实是一个StatusError(包含请求响应的statuscode reason gvr等信息) return r.newUnstructuredResponseError(body, isTextResponse(resp), resp.StatusCode, req.Method, retryAfter) } // newUnstructuredResponseError 为提供的参数实例化适当的通用错误(StatusError)。它还记录身体。 func (r *Request) newUnstructuredResponseError(body []byte, isTextResponse bool, statusCode int, method string, retryAfter int) error { // 限制最后的返回Error的message的长度,最大为maxUnstructuredResponseTextBytes(2048) if len(body) > maxUnstructuredResponseTextBytes { body = body[:maxUnstructuredResponseTextBytes] } // 返回Error的message message := "unknown" if isTextResponse { message = strings.TrimSpace(string(body)) } // Error的gr,表明是在操作gr时出现的错误 var groupResource schema.GroupResource if len(r.resource) > 0 { groupResource.Group = r.c.content.GroupVersion.Group groupResource.Resource = r.resource } return errors.NewGenericServerResponse( statusCode, method, groupResource, r.resourceName, message, retryAfter, true, ) } // 在Request 中查找常见的编程错误。 // 第一个是监测当namespaceSet 为true且使用空字符串作为命名空间时,如果verb = POST,那么产生error。 // 第二个错误是,如果 namespaceSet 为真,且命名空间不为空,当verb是 GET、PUT 或 DELETE 且命名资源(resourceName != ""),那么产生error。 func (r *Request) requestPreflightCheck() error { if !r.namespaceSet { return nil } if len(r.namespace) > 0 { return nil } switch r.verb { case "POST": return fmt.Errorf("an empty namespace may not be set during creation") case "GET", "PUT", "DELETE": if len(r.resourceName) > 0 { return fmt.Errorf("an empty namespace may not be set when a resource name is provided") } } return nil } // 节流设置-用于日志的节流(就是根据最新输出日志的时间lastLogTime和当前日志比较,看是否大于一个特定时间minLogInterval) type throttleSettings struct {、 // 日志级别 logLevel klog.Level // 下次日志输出的最小时间间隔 minLogInterval time.Duration // 最新的节流日期 lastLogTime time.Time lock sync.RWMutex } // 节流日志管理器 type throttledLogger struct { clock utilclock.PassiveClock // 多个节流设置(只要满足设置要求就输出日志) settings []*throttleSettings } // 尝试输出日志(名字取得好 尝试。。。,因为并不一定输出,需要满足条件),返回日志等级和是否输出 func (b *throttledLogger) attemptToLog() (klog.Level, bool) { for _, setting := range b.settings { if bool(klog.V(setting.logLevel).Enabled()) { // Return early without write locking if possible. if func() bool { setting.lock.RLock() defer setting.lock.RUnlock() return b.clock.Since(setting.lastLogTime) >= setting.minLogInterval }() { setting.lock.Lock() defer setting.lock.Unlock() if b.clock.Since(setting.lastLogTime) >= setting.minLogInterval { setting.lastLogTime = b.clock.Now() return setting.logLevel, true } } return -1, false } } return -1, false } // Infof 判读是否满足节流条件,满足则设置level并输出日志。 func (b *throttledLogger) Infof(message string, args ...interface{}) { if logLevel, ok := b.attemptToLog(); ok { klog.V(logLevel).Infof(message, args...) } } // Result 包含调用 Request.Do() 的结果。 type Result struct { body []byte // apiserver响应的原始数据 warnings []net.WarningHeader // apiserver响应的warn头 contentType string // 内容类型,标识如何序列化body数据 err error // apiserver的响应err statusCode int // apiserver的响应code decoder runtime.Decoder // 用来解码body为k8s对象 } // Raw 返回原始结果。 func (r Result) Raw() ([]byte, error) { return r.body, r.err } // Get 将结果作为对象返回,这意味着它通过了解码器. // func (r Result) Get() (runtime.Object, error) { // 如果r.err不为空 if r.err != nil { // 如果r中的body转化为 Status 类型并且 .Status != StatusSuccess,则从该Status对象中返回err信息。 return nil, r.Error() } // 如果r中的解码器为空,返回err if r.decoder == nil { return nil, fmt.Errorf("serializer for %s doesn't exist", r.contentType) } // 解码 out, _, err := r.decoder.Decode(r.body, nil, nil) if err != nil { return nil, err } // 判断解码后的对象类型 switch t := out.(type) { case *metav1.Status: // 除了 StatusSuccess 之外的任何状态都被视为错误。 if t.Status != metav1.StatusSuccess { return nil, errors.FromObject(t) } } return out, nil } // Into 将结果存储到 obj 中。如果 obj 为空,则将其忽略。 // 如果返回的对象是 Status 类型并且具有 .Status != StatusSuccess,则 Status 中的附加信息将用于丰富错误。 func (r Result) Into(obj runtime.Object) error { if r.err != nil { // 如果r中的body转化为 Status 类型并且 .Status != StatusSuccess,则从该Status对象中返回err信息。 return r.Error() } // 如果r中的解码器为空,返回err if r.decoder == nil { return fmt.Errorf("serializer for %s doesn't exist", r.contentType) } // 如果r.body为空,则返回err if len(r.body) == 0 { return fmt.Errorf("0-length response with status code: %d and content type: %s", r.statusCode, r.contentType) } // 这里指定decode的obj out, _, err := r.decoder.Decode(r.body, nil, obj) // err不为空或者指定的obj和decode的out相同(此时err为空),则return err if err != nil || out == obj { return err } // 如果返回不同的对象(out != obj) switch t := out.(type) { case *metav1.Status: // 除了 StatusSuccess 之外的任何状态都被视为错误。 if t.Status != metav1.StatusSuccess { return errors.FromObject(t) } } return nil } // Error 返回执行请求响应的错误,如果没有发生错误,则返回 nil. // 如果返回的对象是 Status 类型并且具有 Status != StatusSuccess,则 Status 中的附加信息将用于丰富错误。 // 请参阅 Request.Do() 了解可能遇到的错误。 func (r Result) Error() error { // 如果我们收到一个意外的服务器错误,并且我们有一个主体和解码器,我们可以尝试提取一个状态对象。 if r.err == nil || !errors.IsUnexpectedServerError(r.err) || len(r.body) == 0 || r.decoder == nil { return r.err } // 尝试将主体转换为状态对象,默认version为“v1” out, _, err := r.decoder.Decode(r.body, &schema.GroupVersionKind{Version: "v1"}, nil) if err != nil { klog.V(5).Infof("body was not decodable (unable to check for Status): %v", err) return r.err } // 获取解码后的类型 switch t := out.(type) { case *metav1.Status: // 因为我们默认这种类型,我们*必须*检查 StatusFailure if t.Status == metav1.StatusFailure { return errors.FromObject(t) } } return r.err }
- 函数
// NewRequest 创建一个新的请求对象,用于访问服务器上的 runtime.Objects。 func NewRequest(c *RESTClient) *Request { // 构建backoff var backoff BackoffManager if c.createBackoffMgr != nil { backoff = c.createBackoffMgr() } // 如果backoff为空,则会赋值noBackoff(不做任何操作) if backoff == nil { backoff = noBackoff } // 请求apiserver的url前缀(c.base.Path(域名或者ip:端口)),包含了group verison var pathPrefix string if c.base != nil { pathPrefix = path.Join("/", c.base.Path, c.versionedAPIPath) } else { pathPrefix = path.Join("/", c.versionedAPIPath) } // 设置request超时时长 var timeout time.Duration if c.Client != nil { timeout = c.Client.Timeout } r := &Request{ c: c, rateLimiter: c.rateLimiter, backoff: backoff, timeout: timeout, pathPrefix: pathPrefix, retry: &withRetry{maxRetries: 10}, warningHandler: c.warningHandler, } // 注意这里的switch的执行顺序,先判断AcceptContentTypes,如果为空,则是ContentType。用来设置Header的Accept switch { case len(c.content.AcceptContentTypes) > 0: r.SetHeader("Accept", c.content.AcceptContentTypes) case len(c.content.ContentType) > 0: r.SetHeader("Accept", c.content.ContentType+", */*") } return r } // NewRequestWithClient 创建一个带有嵌入式 RESTClient 的请求。注意:用于测试场景。 func NewRequestWithClient(base *url.URL, versionedAPIPath string, content ClientContentConfig, client *http.Client) *Request { return NewRequest(&RESTClient{ base: base, versionedAPIPath: versionedAPIPath, content: content, Client: client, }) } // updateURLMetrics 是一个用于推送指标的函数。它还处理不完整/无效请求数据的极端情况。 func updateURLMetrics(ctx context.Context, req *Request, resp *http.Response, err error) { url := "none" // 如果http.client的base.Host不为空,覆盖url if req.c.base != nil { url = req.c.base.Host } // 错误可以是任意字符串。未绑定标签,因此我们将它们报告为 `<error>`。 if err != nil { metrics.RequestResult.Increment(ctx, "<error>", req.verb, url) } else { // 故障代码指标 metrics.RequestResult.Increment(ctx, strconv.Itoa(resp.StatusCode), req.verb, url) } } // 如果响应显示为文本 mediatype,则 isTextResponse 返回 true。 func isTextResponse(resp *http.Response) bool { contentType := resp.Header.Get("Content-Type") if len(contentType) == 0 { return true } media, _, err := mime.ParseMediaType(contentType) if err != nil { return false } return strings.HasPrefix(media, "text/") } // retryAfterSeconds 返回 Retry-After 标头和 true 的值,如果标头丢失或不是有效数字,则返回 0 和 false。 func retryAfterSeconds(resp *http.Response) (int, bool) { if h := resp.Header.Get("Retry-After"); len(h) > 0 { if i, err := strconv.Atoi(h); err == nil { return i, true } } return 0, false }
- Request特别重要的几个方法
// Watch 尝试开始watch请求的位置(r.base.Path)。返回 watch.Interface(包装了一个decoder,其实是一个tcp的长链接) 或错误。 func (r *Request) Watch(ctx context.Context) (watch.Interface, error) { // 我们特别不想对watch进行速率限制,所以我们不在这里使用 r.rateLimiter。也就是说速率限制器不限制watch操作 if r.err != nil { return nil, r.err } client := r.c.Client if client == nil { client = http.DefaultClient } // 用来判断是否是可以进行重试链接的错误 isErrRetryableFunc := func(request *http.Request, err error) bool { // watch 流机制处理了很多常见的部分数据错误(这些关闭的连接可以在很多情况下重试)。 if net.IsProbableEOF(err) || net.IsTimeout(err) { return true } return false } // 定义下次重试请求的相关参数,在本文的with_retry.go详细分析 var retryAfter *RetryAfter // 获取requet的url url := r.URL().String() // for循环,什么时候结束 // for { // 生成一个http.Request对象并配置必要属性,用来和apiserver交互 req, err := r.newHTTPRequest(ctx) if err != nil { return nil, err } // backoff处理对应url的退避时长(从backoff的map中获取,如果成功,则会清除对应key:url,否则指数增加),并sleep r.backoff.Sleep(r.backoff.CalculateBackoff(r.URL())) // 如果retryAfter不为空 if retryAfter != nil { // 我们正在重试我们之前已经发送到 apiserver的请求(到这里说明不只一次发送request)。 // 此请求还应使用客户端内部速率限制器进行限制。并可能输出重试日志 if err := r.tryThrottleWithInfo(ctx, retryAfter.Reason); err != nil { return nil, err } // 清空 retryAfter = nil } // http.client.Do 执行request请求,并返回请求响应 resp, err := client.Do(req) // 更新url对应的metrics指标 updateURLMetrics(ctx, r, resp, err) // 如果http.client.base不为空 if r.c.base != nil { // 本文的urlbackoff.go会做分析,根据statuscode用来更新backoff if err != nil { r.backoff.UpdateBackoff(r.c.base, err, 0) } else { r.backoff.UpdateBackoff(r.c.base, err, resp.StatusCode) } } // 如果err为空且statuscode为200 if err == nil && resp.StatusCode == http.StatusOK { // 返回watch.interface return r.newStreamWatcher(resp) } // done, transformErr := func() (bool, error) { defer readAndCloseResponseBody(resp) // 接收是否需要重试 var retry bool // 在本文with_retry.go做分析,用来获取下一个retry对象,并赋值retry retryAfter, retry = r.retry.NextRetry(req, resp, err, isErrRetryableFunc) if retry { // 在本文with_retry.go做分析,用来重置resp.Body的offset和whence为0,并且如果backoff不为空,则会sleep retryAfter.Wait err := r.retry.BeforeNextRetry(ctx, r.backoff, retryAfter, url, r.body) if err == nil { return false, nil } klog.V(4).Infof("Could not retry request - %v", err) } // 如果响应为nil,则表示存在连接异常 if resp == nil { return true, nil } // 转换resp为,详细分析见transformResponse if result := r.transformResponse(resp, req); result.err != nil { return true, result.err } return true, fmt.Errorf("for request %s, got status: %v", url, resp.StatusCode) }() // done = true表示什么意思? 表示已经执行完成 if done { // 这里如果能满足条件(表示是可再次重试的err),表示retry重试超多了最大重试次数 if isErrRetryableFunc(req, err) { // 返回一个空的watch 接口(这里为什么要返回一个空的,而不是返回一个err?) return watch.NewEmptyWatch(), nil } // 如果从apiserver响应的结果中没有错误,则会用transformResponse转化后得到err代替 if err == nil { err = transformErr } return nil, err } } } // 构造streamwatcher,一个stream式的watch接口(有什么特殊之处呢?他会产生一对基于tcp长连接 reader和writer的stream conn) func (r *Request) newStreamWatcher(resp *http.Response) (watch.Interface, error) { // 从resp的header中获取Content-Type值 contentType := resp.Header.Get("Content-Type") // 解析contentType(有人会问,这有什么解析的?其实学问大了。比如application/json;as=xxx,application/json就是mediaType,as=xxx会被解析为key:as value:xxx) mediaType, params, err := mime.ParseMediaType(contentType) if err != nil { klog.V(4).Infof("Unexpected content type from the server: %q: %v", contentType, err) } // 根据config->rest client->content中配置的Negotiator 获取对应的对象的解码器、stream序列化器和framer(用于获取reader(读取resp.body)和writer(req.body)的工厂模式) objectDecoder, streamingSerializer, framer, err := r.c.content.Negotiator.StreamDecoder(mediaType, params) if err != nil { return nil, err } // 处理watch请求响应的header中是的Warning信息,本文章的warnings.go会做分析 handleWarnings(resp.Header, r.warningHandler) // framer构造一个读取resp.Body的reader frameReader := framer.NewFrameReader(resp.Body) // 在分析kubernetes/apimechinery中会做详细分析,用来构造一个decoder解码从frameReader中读取的byte[] watchEventDecoder := streaming.NewDecoder(frameReader, streamingSerializer) // 构造StreamWatcher,用来receive apiserver的event和obj return watch.NewStreamWatcher( restclientwatch.NewDecoder(watchEventDecoder, objectDecoder), errors.NewClientErrorReporter(http.StatusInternalServerError, r.verb, "ClientWatchDecoding"), ), nil } // transformResponse 转化resp.body中byte[] 为结构化的API object func (r *Request) transformResponse(resp *http.Response, req *http.Request) Result { // 接收body数据 var body []byte if resp.Body != nil { // 读取body中的数据 data, err := ioutil.ReadAll(resp.Body) // 判断err那种类型 switch err.(type) { case nil: // 为空 表示200 接收数据 body = data case http2.StreamError: // 表示有请求错误(为什么是http2.StreamError,说明transport中执行的http.client是使用的http2依赖) // 试图捕捉服务器在发送响应正文时可能关闭连接的场景。这可能是由于网络连接缓慢导致的服务器超时造成的。 klog.V(2).Infof("Stream error %#v when reading response body, may be caused by closed connection.", err) // 构建err str,并返回result streamErr := fmt.Errorf("stream error when reading response body, may be caused by closed connection. Please retry. Original error: %w", err) return Result{ err: streamErr, } default: // 这种情况一般是不会发生的(即不是程序层面的问题) klog.Errorf("Unexpected error when reading response body: %v", err) unexpectedErr := fmt.Errorf("unexpected error when reading response body. Please retry. Original error: %w", err) return Result{ err: unexpectedErr, } } } glogBody("Response Body", body) // 验证content type是否准确 var decoder runtime.Decoder // 获取resp header中的Content-Type contentType := resp.Header.Get("Content-Type") // 如果contentType为空,则设置config->rest client->content的ContentType if len(contentType) == 0 { contentType = r.c.content.ContentType } // 如果contentType存在 if len(contentType) > 0 { var err error // 解析contentType mediaType, params, err := mime.ParseMediaType(contentType) if err != nil { return Result{err: errors.NewInternalError(err)} } // 从获取rest client ->content->Negotiator(根据config构建的)中获取对应的解码器 decoder, err = r.c.content.Negotiator.Decoder(mediaType, params) if err != nil { // 如果我们无法协商解码器,则将其视为非结构化错误 switch { case resp.StatusCode == http.StatusSwitchingProtocols:// 表示一个协议切换的响应 // 无操作 case resp.StatusCode < http.StatusOK || resp.StatusCode > http.StatusPartialContent: //非正常响应 // 转化 return Result{err: r.transformUnstructuredResponseError(resp, req, body)} } return Result{ body: body, contentType: contentType, statusCode: resp.StatusCode, warnings: handleWarnings(resp.Header, r.warningHandler), } } } switch { case resp.StatusCode == http.StatusSwitchingProtocols: // 表示一个协议切换的响应 // 无操作 case resp.StatusCode < http.StatusOK || resp.StatusCode > http.StatusPartialContent: //非正常响应 // 如果调用方没有返回结构化(StatusError)错误,则从 Result 对象中计算非结构化(没有metadata数据的StatusError)错误。 retryAfter, _ := retryAfterSeconds(resp) err := r.newUnstructuredResponseError(body, isTextResponse(resp), resp.StatusCode, req.Method, retryAfter) return Result{ body: body, contentType: contentType, statusCode: resp.StatusCode, decoder: decoder, err: err, warnings: handleWarnings(resp.Header, r.warningHandler), } } return Result{ body: body, contentType: contentType, statusCode: resp.StatusCode, decoder: decoder, warnings: handleWarnings(resp.Header, r.warningHandler), } } // 和Watch类似,只是返回不同的对象,内部处理逻辑相似 // Stream 格式化并执行请求,并提供响应的流。 // 返回 io.ReadCloser 可用于响应流或 任何非 2xx http 状态代码导致的错误. 如果我们得到非 2xx 代码,我们会尝试将主体转换为包装了 APIStatus 的对象。 // 如果可以,我们将其作为错误返回。否则,我们会创建一个非机构化(没有metadata信息)错误,列出 http 状态和响应内容。 func (r *Request) Stream(ctx context.Context) (io.ReadCloser, error) // 和Watch类似,只是返回不同的对象,内部处理逻辑相似 // 请求连接到apiserver并在收到apiserver的响应时调用提供的函数fn. 它处理重试行为(retry)和请求的预先验证(requestPreflightCheck). 它最多会调用一次fn func (r *Request) request(ctx context.Context, fn func(*http.Request, *http.Response)) error // 格式化并执行请求。返回一个 Result 对象以便于处理响应 。 // // result中err属性的错误类型: // * 如果服务器以状态响应:*errors.StatusError 或 *errors.UnexpectedObjectError // * http.Client.Do 错误直接返回。 func (r *Request) Do(ctx context.Context) Result { var result Result 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 } // DoRaw 执行请求但不处理响应正文(resp.body)。 func (r *Request) DoRaw(ctx context.Context) ([]byte, error) { var result Result err := r.request(ctx, func(req *http.Request, resp *http.Response) { result.body, result.err = ioutil.ReadAll(resp.Body) glogBody("Response Body", result.body) if resp.StatusCode < http.StatusOK || resp.StatusCode > http.StatusPartialContent { result.err = r.transformUnstructuredResponseError(resp, req, result.body) } }) if err != nil { return nil, err } return result.body, result.err }
transport.go
- 方法
// TransportConfig 将rest client配置转换为适当的transport配置。 func (c *Config) TransportConfig() (*transport.Config, error) { // 从rest config中读取数据构建transport config conf := &transport.Config{ UserAgent: c.UserAgent, Transport: c.Transport, WrapTransport: c.WrapTransport, DisableCompression: c.DisableCompression, TLS: transport.TLSConfig{ Insecure: c.Insecure, ServerName: c.ServerName, CAFile: c.CAFile, CAData: c.CAData, CertFile: c.CertFile, CertData: c.CertData, KeyFile: c.KeyFile, KeyData: c.KeyData, NextProtos: c.NextProtos, }, Username: c.Username, Password: c.Password, BearerToken: c.BearerToken, BearerTokenFile: c.BearerTokenFile, Impersonate: transport.ImpersonationConfig{ UserName: c.Impersonate.UserName, Groups: c.Impersonate.Groups, Extra: c.Impersonate.Extra, }, Dial: c.Dial, Proxy: c.Proxy, } // 这里不能同时配置ExecProvider和AuthProvider,这两个是互斥的,都是获取auth token信息的 if c.ExecProvider != nil && c.AuthProvider != nil { return nil, errors.New("execProvider and authProvider cannot be used in combination") } // 配置 ExecProvider相关信息 if c.ExecProvider != nil { // 用于接收client auth关于cluster信息 var cluster *clientauthentication.Cluster // ProvideClusterInfo = true,表示需要提供cluster信息 if c.ExecProvider.ProvideClusterInfo { var err error // 详情见exec.go的ConfigToExecCluster函数,从rest config获取一个cluster信息 cluster, err = ConfigToExecCluster(c) if err != nil { return nil, err } } // 详情见exec.go的GetAuthenticator函数,从缓存中获取*Authenticator(plugin/exec),如果存在则返回,否则会根据参数构建一个并put近globalcache provider, err := exec.GetAuthenticator(c.ExecProvider, cluster) if err != nil { return nil, err } // 使用exec Authenticator更新(主要涉及证书方式和wrapTransport及dial拨号)TransportConfig if err := provider.UpdateTransportConfig(conf); err != nil { return nil, err } } // 如果AuthProvider不为空 则获取配置文件中关于AuthProvider的信息 if c.AuthProvider != nil { // 详情见plugin.go, 从config(其实是AuthInfos下的AuthProvider)中获取第三方认证插件的认证信息 provider, err := GetAuthProvider(c.Host, c.AuthProvider, c.AuthConfigPersister) if err != nil { return nil, err } // 其实就是返回一个函数(按顺序执行参数函数来修饰base RoundTripper) conf.Wrap(provider.WrapTransport) } return conf, nil } // 构建WrapTransport(其实是一个函数),用来 func (c *Config) Wrap(fn transport.WrapperFunc) { c.WrapTransport = transport.Wrappers(c.WrapTransport, fn) }
- 函数
// TLSConfigFor 返回一个 tls.Config,它将提供由提供的rest Config 定义的传输级别安全性。如果没有要求传输级别的安全性,将返回 nil。 func TLSConfigFor(config *Config) (*tls.Config, error) { cfg, err := config.TransportConfig() if err != nil { return nil, err } return transport.TLSConfigFor(cfg) }
url_utils.go
- 函数
// DefaultServerURL 将主机、主机:端口或 URL 字符串转换为默认的基本服务器 API 路径,以在给定 API 版本中使用客户端,遵循Kubernetes API 的标准约定。 func DefaultServerURL(host, apiPath string, groupVersion schema.GroupVersion, defaultTLS bool) (*url.URL, string, error) { // 限制host不能为空 if host == "" { return nil, "", fmt.Errorf("host must be a URL or a host:port pair") } // 复制host,防止更改原内容 base := host // 解析host格式 hostURL, err := url.Parse(base) // 比如: http://localhost:2000 这里hostURL.Scheme是http:// hostURL.Host是localhost:2000 if err != nil || hostURL.Scheme == "" || hostURL.Host == "" { scheme := "http://" if defaultTLS { scheme = "https://" } hostURL, err = url.Parse(scheme + base) if err != nil { return nil, "", err } // 如果到这里满足条件,表明host中包含了处域名或者ip:port以外的路径 if hostURL.Path != "" && hostURL.Path != "/" { return nil, "", fmt.Errorf("host must be a URL or a host:port pair: %q", base) } } // hostURL.Path 是可选的;非空路径被视为要应用于用于访问主机的所有 URI 的前缀。 // 这在apiserver 前面有一个代理时很有用,它已经重新定位了 apiserver 端点,将所有请求转发到, 例如,/a/b/c 到 apiserver。在这种情况下,路径应该是 /a/b/c。 // //如果在没有前端代理的情况下运行(即更改 apiserver 的位置),则hostURL.Path 应为空。 // // versionedAPIPath,一个相对于 baseURL.Path 的路径,指向一个版本化的 API 库 versionedAPIPath := DefaultVersionedAPIPath(apiPath, groupVersion) return hostURL, versionedAPIPath, nil } // DefaultVersionedAPIPath 构造给定组版本的默认路径,假设给定API 路径,遵循 Kubernetes API 的标准约定。类似于:/api/${group}/${version} func DefaultVersionedAPIPath(apiPath string, groupVersion schema.GroupVersion) string { versionedAPIPath := path.Join("/", apiPath) // 如果group不为空,则是/api/${group}/${version},否则是/api/${version} if len(groupVersion.Group) > 0 { versionedAPIPath = path.Join(versionedAPIPath, groupVersion.Group, groupVersion.Version) } else { versionedAPIPath = path.Join(versionedAPIPath, groupVersion.Version) } return versionedAPIPath } // defaultServerUrlFor 用于 IsConfigTransportTLS 和 RESTClientFor 两个方法。要求在调用之前设置host和gv。 func defaultServerUrlFor(config *Config) (*url.URL, string, error) { // 其实判断是否需要https协议,只需要判断是否有server公钥即可 // 通过判断config文件是否包含证书认证机构数据(可以通过ca颁发证书) hasCA := len(config.CAFile) != 0 || len(config.CAData) != 0 // 判断证书数据是否存在(证书数据中包含了公钥信息) hasCert := len(config.CertFile) != 0 || len(config.CertData) != 0 defaultTLS := hasCA || hasCert || config.Insecure host := config.Host if host == "" { host = "localhost" } if config.GroupVersion != nil { return DefaultServerURL(host, config.APIPath, *config.GroupVersion, defaultTLS) } return DefaultServerURL(host, config.APIPath, schema.GroupVersion{}, defaultTLS) }
urlbackoff.go
- 接口
type BackoffManager interface { // 用于更新backoff UpdateBackoff(actualUrl *url.URL, err error, responseCode int) // 计算下次backoff的间隔时间 CalculateBackoff(actualUrl *url.URL) time.Duration // sleep Sleep(d time.Duration) }
- 结构体
// URLBackoff 结构在 Backoff 之上实现语义,我们需要 URL 特定的指数 backoff。 type URLBackoff struct { // 使用flowcontrol.Backoff(在本文util包中会分析)作为底层实现。 Backoff *flowcontrol.Backoff } // 禁用使backoff不可用,即将defaultDuration和maxDuration设置为零。 func (b *URLBackoff) Disable() { klog.V(4).Infof("Disabling backoff strategy") b.Backoff = flowcontrol.NewBackOff(0*time.Second, 0*time.Second) } // baseUrlKey 返回 url 将被映射到的键。目前只是针对域名或者ip:port做backoff // 例如, 127.0.0.1:8080/api/v2/abcde -> 127.0.0.1:8080. func (b *URLBackoff) baseUrlKey(rawurl *url.URL) string { // 解析参数url host, err := url.Parse(rawurl.String()) if err != nil { klog.V(4).Infof("Error extracting url: %v", rawurl) panic("bad url!") } // 返回的是解析后的Host(域名或者ip:port) return host.Host } // UpdateBackoff 更新backoff元数据 func (b *URLBackoff) UpdateBackoff(actualUrl *url.URL, err error, responseCode int) { // 如果状态码大于499或者状态码在serverIsOverloadedSet(一个map,初始化了一个key:429(代表当你需要限制客户端请求某个服务的数量,也就是限制请求速度的code) value:interface{})中 if responseCode > maxResponseCode || serverIsOverloadedSet.Has(responseCode) { // util包中会详细分析,主要是以 maxDuration 为上限,将backoff移动到下一个标记(简单来说就是跟新backoff中对应url的backoffentry中的lastUpdate和backoff) b.Backoff.Next(b.baseUrlKey(actualUrl), b.Backoff.Clock.Now()) return } else if responseCode >= 300 || err != nil {// 响应code如果是大于等于300且小于等于499 或者 err不为空 // 这里只打印日志,也就是backoff不做任何变更,表示在计算下次backoff时,还是上次的间隔时长 klog.V(4).Infof("Client is returning errors: code %v, error %v", responseCode, err) } // 如果我们走到这一步,这个 URL 就不再需要退避了。重置--底层就是从map中移除对应url b.Backoff.Reset(b.baseUrlKey(actualUrl)) } // CalculateBackoff 获取一个 url 对应的backoff时长 func (b *URLBackoff) CalculateBackoff(actualUrl *url.URL) time.Duration { return b.Backoff.Get(b.baseUrlKey(actualUrl)) } // 睡眠多长时间,在需要backoff时,才会触发 func (b *URLBackoff) Sleep(d time.Duration) { b.Backoff.Clock.Sleep(d) }
warnings.go
- 变量
var ( // 初始化一个处理waring header的logger defaultWarningHandler WarningHandler = WarningLogger{} // 锁 defaultWarningHandlerLock sync.RWMutex )
- 接口
// WarningHandler 是用于处理Waring header的接口 type WarningHandler interface { // 处理Waring header时,使用警告代码、代理和文本作为参数调用 HandleWarningHeader。 HandleWarningHeader(code int, agent string, text string) }
- 函数
// SetDefaultWarningHandler 设置遇到警告标头时客户端使用的默认处理程序。 // 默认情况下,会记录警告。提供了几个内置实现: // - NoWarnings 不处理警告。 // - WarningLogger 记录警告。 // - NewWarningWriter() 向提供的writer输出警告. func SetDefaultWarningHandler(l WarningHandler) { defaultWarningHandlerLock.Lock() defer defaultWarningHandlerLock.Unlock() defaultWarningHandler = l } func getDefaultWarningHandler() WarningHandler { defaultWarningHandlerLock.RLock() defer defaultWarningHandlerLock.RUnlock() l := defaultWarningHandler return l } // NewWarningWriter 返回一个 WarningHandler 的实现(warningWriter),该实现将响应代码为299的警告输出到指定的writer. func NewWarningWriter(out io.Writer, opts WarningWriterOptions) *warningWriter { h := &warningWriter{out: out, opts: opts} if opts.Deduplicate { h.written = map[string]struct{}{} } return h } // 指定WarningHandler和response header,来处理header的waring信息 func handleWarnings(headers http.Header, handler WarningHandler) []net.WarningHeader { // 如果handler为空,则获取默认(logger输出)的 if handler == nil { handler = getDefaultWarningHandler() } // 获取header中Warning字段值 warnings, _ := net.ParseWarningHeaders(headers["Warning"]) // 遍历数组处理 for _, warning := range warnings { handler.HandleWarningHeader(warning.Code, warning.Agent, warning.Text) } return warnings }
- 结构体
// NoWarnings 表示不处理警告。 type NoWarnings struct{} func (NoWarnings) HandleWarningHeader(code int, agent string, message string) {} // WarningLogger 是 WarningHandler 的一个实现,它记录响应代码为299的警告 type WarningLogger struct{} func (WarningLogger) HandleWarningHeader(code int, agent string, message string) { if code != 299 || len(message) == 0 { return } klog.Warning(message) } // 向提供的writer输出警告. type warningWriter struct { // out 是输出警告的writer out io.Writer // opts 包含控制警告输出的选项 opts WarningWriterOptions // writeLock 并发控制写入和writtenCount writtenLock sync.Mutex writtenCount int // 写入次数 written map[string]struct{} // 记录已经写入writer的message } // WarningWriterOptions 控制使用 NewWarningWriter() 构造的 WarningHandler 的行为 type WarningWriterOptions struct { // false表示警告消息只应写入一次。如果在处理许多警告的长时间运行的进程中将此设置为 true 可能会导致内存使用增加。 Deduplicate bool // 颜色表示警告输出可以包括 ANSI 颜色代码 Color bool } // HandleWarningHeader 将响应代码为299的警告打印到配置的writer。 func (w *warningWriter) HandleWarningHeader(code int, agent string, message string) { // 如果code不为299或者message为空,那么return if code != 299 || len(message) == 0 { return } w.writtenLock.Lock() defer w.writtenLock.Unlock() // 如果Deduplicate为true,表示希望记录warning message到writen中(相当于缓存,会增加内存消耗,没有特殊用途建议设置为false) if w.opts.Deduplicate { // 如果已经存在,则return if _, alreadyWritten := w.written[message]; alreadyWritten { return } // 添加到written(map)中 w.written[message] = struct{}{} } // 写入次数加一 w.writtenCount++ // 如果设置了颜色 if w.opts.Color { // 按照设置的字体颜色输出到writer fmt.Fprintf(w.out, "%sWarning:%s %s\n", yellowColor, resetColor, message) } else { // 黑体输出 fmt.Fprintf(w.out, "Warning: %s\n", message) } } func (w *warningWriter) WarningCount() int { w.writtenLock.Lock() defer w.writtenLock.Unlock() return w.writtenCount }
with_retry.go
- 函数类型
// IsRetryableErrorFunc 允许客户端提供自己的函数,来确定服务器的错误是否可重试。 // // request:发送到服务器的原始请求 err:服务器将此错误发送给我们 // // 如果错误可重试并且请求可以重试,则该函数返回 true,否则返回 false。 // 我们有四种通信模式(request的几个常见方法) - 'Stream'、'Watch'、'Do' 和 'DoRaw',这个函数允许我们自定义每种模式的可重试性。 type IsRetryableErrorFunc func(request *http.Request, err error) bool func (r IsRetryableErrorFunc) IsErrorRetryable(request *http.Request, err error) bool { return r(request, err) } // 表示一个从不重试的函数变量 var neverRetryError = IsRetryableErrorFunc(func(_ *http.Request, _ error) bool { return false })
- 接口
// WithRetry 允许客户端重试请求达到一定次数。 请注意:如果没有额外的锁定或协调,多个 goroutines 并发使用 WithRetry 是不安全的。 type WithRetry interface { // SetMaxRetries 使请求使用指定的整数作为上限,在接收到响应代码为429和响应中的“Retry-After”标头时重试。maxRetries=0 表示防止进行任何重试并立即返回。 SetMaxRetries(maxRetries int) // NextRetry 适当地推进重试计数器,如果应该重试请求则返回true,否则返回false,下面是返回为false的情况: // - 我们已经达到了最大重试阈值。 // - 该错误不属于可重试类别。 // - 服务器尚未向我们发送 429 或 5xx 状态代码,并且'Retry-After' 响应标头未设置值。 // // 如果 retry 设置为 true,则 retryAfter 将包含 关于下一次重试的信息。 // // request:发送到服务器的原始请求 // resp:服务器发送的响应,如果err为nil则应设置 // err:服务器将此错误发送给我们,如果设置了 err,则 resp 设置为nil。 // f: 客户端提供的 IsRetryableErrorFunc 函数,用于确定服务器发送的 err 是否可重试。 NextRetry(req *http.Request, resp *http.Response, err error, f IsRetryableErrorFunc) (*RetryAfter, bool) // BeforeNextRetry 负责执行需要在发起下一次重试之前完成的操作: // - 如果请求上下文已经被取消,则无需重试,函数将返回 ctx.Err()。 // - 我们需要在启动下一次重试之前寻找请求正文的开头,如果失败,该函数应该返回错误。 // - 我们应该等待服务器要求我们在“Retry-After”响应标头中等待的秒数。 // // 如果 BeforeNextRetry 返回错误,客户端应中止重试, 否则启动下一次重试是非安全的。 BeforeNextRetry(ctx context.Context, backoff BackoffManager, retryAfter *RetryAfter, url string, body io.Reader) error }
- 结构体
// RetryAfter 保存与下一次重试相关的信息。 type RetryAfter struct { // 等待下一次重试开始之前的持续时间。 这是以秒为单位的“Retry-After”响应标头的值。 Wait time.Duration // 我们收到了一个可重试的错误或来自服务器的“Retry-After”响应标头中的重试次数。 Attempt int // 原因描述了我们重试请求的原因 Reason string } // 实现了WithRetry接口的结构体 type withRetry struct { // 最大重试次数 maxRetries int // 当前重试次数 attempts int } func (r *withRetry) SetMaxRetries(maxRetries int) { if maxRetries < 0 { maxRetries = 0 } r.maxRetries = maxRetries } func (r *withRetry) NextRetry(req *http.Request, resp *http.Response, err error, f IsRetryableErrorFunc) (*RetryAfter, bool) { // 不是正常的请求操作,判定不可重试 if req == nil || (resp == nil && err == nil) { return nil, false } // r中当前重试次数加一 r.attempts++ // 构造retryAfter retryAfter := &RetryAfter{Attempt: r.attempts} // 判断如果当前重试次数大于最大重试次数,返回 不可重试 if r.attempts > r.maxRetries { return retryAfter, false } // 表示是否可重试 var errIsRetryable bool // 如果f不为空且err不为空且f执行后为true,表示此err为可重试err if f != nil && err != nil && f.IsErrorRetryable(req, err) { // 设置errIsRetryable = true errIsRetryable = true // 我们有一个可重试的错误,为此我们将创建一个人工“Retry-After”响应。 resp = retryAfterResponse() } if err != nil && !errIsRetryable { return retryAfter, false } // 这里有两种情况: // a: 我们有一个可重试的错误,我们已经有了一个人工的“Retry-After”响应。 // b: 我们有来自服务器的响应,我们需要检查它是否可以重试 seconds, wait := checkWait(resp) if !wait { return retryAfter, false } // 计算下次重试的时间间隔 retryAfter.Wait = time.Duration(seconds) * time.Second // 构建重试原因 retryAfter.Reason = getRetryReason(r.attempts, seconds, resp, err) return retryAfter, true } func (r *withRetry) BeforeNextRetry(ctx context.Context, backoff BackoffManager, retryAfter *RetryAfter, url string, body io.Reader) error { if ctx.Err() != nil { return ctx.Err() } // 重置body体 if seeker, ok := body.(io.Seeker); ok && body != nil { if _, err := seeker.Seek(0, 0); err != nil { return fmt.Errorf("can't Seek() back to beginning of body for %T", r) } } klog.V(4).Infof("Got a Retry-After %s response for attempt %d to %v", retryAfter.Wait, retryAfter.Attempt, url) // 如果backoff不为空 则也要sleep retryAfter.Wait时长 if backoff != nil { backoff.Sleep(retryAfter.Wait) } return nil }
- 函数
// 如果response指示我们在重试之前等待,则 checkWait 返回 true 以及秒数。 func checkWait(resp *http.Response) (int, bool) { switch r := resp.StatusCode; { // 任何 500 错误代码和 429 都可以触发等待 case r == http.StatusTooManyRequests, r >= 500: default: return 0, false } i, ok := retryAfterSeconds(resp) return i, ok } func getRetryReason(retries, seconds int, resp *http.Response, err error) string { // 响应标头中的关联优先级和公平性设置 FlowSchema 的 UID。 const responseHeaderMatchedFlowSchemaUID = "X-Kubernetes-PF-FlowSchema-UID" message := fmt.Sprintf("retries: %d, retry-after: %ds", retries, seconds) switch { case resp.StatusCode == http.StatusTooManyRequests:// resp statuscode = 429 // 它是从优先级和公平性的服务器端节流 flowSchemaUID := resp.Header.Get(responseHeaderMatchedFlowSchemaUID) return fmt.Sprintf("%s - retry-reason: due to server-side throttling, FlowSchema UID: %q", message, flowSchemaUID) case err != nil: // 这是一个可重试的错误 return fmt.Sprintf("%s - retry-reason: due to retryable error, error: %v", message, err) default: return fmt.Sprintf("%s - retry-reason: %d", message, resp.StatusCode) } } func readAndCloseResponseBody(resp *http.Response) { if resp == nil { return } // // 在我们重新连接之前,确保响应正文被完全读取并关闭,以便我们重用相同的 TCP连接。 const maxBodySlurpSize = 2 << 10 defer resp.Body.Close() if resp.ContentLength <= maxBodySlurpSize { io.Copy(ioutil.Discard, &io.LimitedReader{R: resp.Body, N: maxBodySlurpSize}) } } // 创建重试的response(设置Header中的Retry-After) func retryAfterResponse() *http.Response { return &http.Response{ StatusCode: http.StatusInternalServerError, Header: http.Header{"Retry-After": []string{"1"}}, } }
watch包
- decoder.go
- 结构体
// 实现 watch.Decoder 接口, 其内容先由一系列使用给定流解码器编码的 watchEvent 对象组成,然后event中的obj随后将被嵌入的解码器解码。 type Decoder struct { decoder streaming.Decoder embeddedDecoder runtime.Decoder } // 解码阻塞直到它可以读取reader中的下一个对象。如果读取器已关闭或对象无法解码,返回错误。 func (d *Decoder) Decode() (watch.EventType, runtime.Object, error) { // 流解码器获取的WatchEvent var got metav1.WatchEvent // tcp长连接的流解码器阻塞解码event对象 res, _, err := d.decoder.Decode(nil, &got) if err != nil { return "", nil, err } if res != &got { return "", nil, fmt.Errorf("unable to decode to metav1.Event") } // 判断获取的event类型 switch got.Type { // 如果是watch类型,则会进行下面的解码为event中的obj case string(watch.Added), string(watch.Modified), string(watch.Deleted), string(watch.Error), string(watch.Bookmark): default: return "", nil, fmt.Errorf("got invalid watch event type: %v", got.Type) } // obj解码器解码event中的Object obj, err := runtime.Decode(d.embeddedDecoder, got.Object.Raw) if err != nil { return "", nil, fmt.Errorf("unable to decode watch event: %v", err) } return watch.EventType(got.Type), obj, nil } // Close 关闭底层 r(request->conn). func (d *Decoder) Close() { d.decoder.Close() }
- 函数
// NewDecoder 为给定的 streaming.Decoder和runtime.Decoder创建一个解码器。 func NewDecoder(decoder streaming.Decoder, embeddedDecoder runtime.Decoder) *Decoder { return &Decoder{ decoder: decoder, embeddedDecoder: embeddedDecoder, } }
- 结构体
- encoder.go
- 结构体
// 编码器将 watch.Events 序列化为 io.Writer。内部对象使用嵌入式编码器进行编码,而外部事件使用流编码器进行序列化。 // 注意:此类型仅用于测试 type Encoder struct { encoder streaming.Encoder embeddedEncoder runtime.Encoder } // 编码将事件写入编写器。如果编写器已关闭或无法对对象进行编码,返回错误 。 func (e *Encoder) Encode(event *watch.Event) error { data, err := runtime.Encode(e.embeddedEncoder, event.Object) if err != nil { return err } // FIXME: get rid of json.RawMessage. return e.encoder.Encode(&metav1.WatchEvent{ Type: string(event.Type), Object: runtime.RawExtension{Raw: json.RawMessage(data)}, }) }
- 函数
func NewEncoder(encoder streaming.Encoder, embeddedEncoder runtime.Encoder) *Encoder { return &Encoder{ encoder: encoder, embeddedEncoder: embeddedEncoder, } }
- 结构体