0%

【Kubernetes】ApiServer之初识API

ApiServer作为 Kubernetes 的核心组件,让集群中所有资源可被描述和配置,即包括了pod、ingress、pvc这些基础资源,也包括deployment、rc、hpa等管理对象;ApiServer就像是一个包含一定逻辑的对象数据库代理;提供了RESTful API接口,其他组件或客户端可以通过该接口获取集群中资源对象的配置和状态,以实现各种逻辑处理;ApiServer本身是无状态的,集群所有的数据都会存储在ETCD中,各个组件对于资源对象的List-Watch机制都要通过 ApiServer 的验证授权和准入。

kube-apiserver 是 kubernetes 中与 etcd 直接交互的一个组件,其控制着 kubernetes 中核心资源的变化。它主要提供了以下几个功能:

  • 提供 Kubernetes API,包括认证授权、数据校验以及集群状态变更等,供客户端及其他组件调用
  • 代理集群中的一些附加组件组件,如 Kubernetes UI、metrics-server、npd 等;
  • 允许对于对象状态的操作,比如Pod和Service的状态的改变,实现对象的持久化到Etcd
  • 资源在不同版本之间的转换

API Overview

ApiServer 主要通过对外提供HTTP API 的方式与其他组件进行交互。API首选的序列化方案是JSON,但是也支持Protobuf协议。API主要有下面三种类型:

  • core group:主要在 /api/v1 下;
  • named groups:其 path 为 /apis/$NAME/$VERSION
  • 暴露系统状态的一些 API:如/metrics/healthz 等;

img

GVK/GVR

Terminology

在Kubernetes中,要想定位一个对象,我们需要指定GVK或者GVR。比如这里声明了 apiVersion 是 apps/v1,其实就是隐含了 Group 是 apps,Version 是 v1,Kind 就是定义的 DaemonSet,而 kubectl 接收到这个声明之后,就可以根据这个声明去调用 API Server 对应的 URL 去获取信息,例如这个就是 /api/apps/v1/daemonset

1
2
3
4
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: node-exporter

对应到实际的URI的请求组织形式,如下图所示:

GVR

通过结构体字段可以发现,它们其实就是Group、Version、Kind、Resource的不同组合

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
type GroupVersionKind struct {
Group string `protobuf:"bytes,1,opt,name=group"`
Version string `protobuf:"bytes,2,opt,name=version"`
Kind string `protobuf:"bytes,3,opt,name=kind"`
}

type GroupVersionResource struct {
Group string `protobuf:"bytes,1,opt,name=group"`
Version string `protobuf:"bytes,2,opt,name=version"`
Resource string `protobuf:"bytes,3,opt,name=resource"`
}

type GroupKind struct {
Group string `protobuf:"bytes,1,opt,name=group"`
Kind string `protobuf:"bytes,2,opt,name=kind"`
}

Kind

Kubernetes中的每个对象都有一个字段 Kind 表明其类型,以Pod为例:

1
2
3
4
5
6
7
8
9
10
apiVersion: v1
kind: Pod
metadata:
name: webserver
spec:
containers:
- name: nginx
image: nginx:1.9
ports:
- containerPort: 80

Kubernetes 中 Kind 有三种类型:

  • 系统中持久的实体对象,比如 Pod , Namespace
  • 一系列有某些共同特征的实体列表,比如 PodListsNodeLists
  • 用于某些特定应用的一些非持久的实体,比如 APIGroupAPIResource, Status

Kind就是一个资源对象对应的种类 kind是通过Kind=reflector.TypeOf(&Pod{}).Elem().Name()进行取值,取得的就是Pod这个结构体的名字。

1
2
3
4
5
type Pod struct
type PodList struct

type Node struct
type NodeList struct

Group

API Group 是一些有关系的Kinds的集合,比如所有的批处理对象,Job 或者 ScheduledJob 都在 batch这个API Group。

各个Group是相互独立的,发展速度也不同,所有每个Group都会有不同的Version,而kubernetes是通过插件的方式来使用各个Group的,可以根据需求决定使用哪个Group。

1
2
3
4
5
Group="core"
Version=v1

Group="apps"
Version=v1beta1

GroupMeta主要包括Group的元信息,里面的成员RESTMapper,与APIGroupVersion一样,其实APIGroupVersion的RESTMapper直接取值于GroupMeta的RESTMapper。一个Group可能包含多个版本,存储在 GroupVersions 中,而 GroupVersion 是默认存储在etcd中的版本。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
type GroupMeta struct {
// GroupVersion represents the preferred version of the group.
// 该group的默认版本
GroupVersion unversioned.GroupVersion

// GroupVersions is Group + all versions in that group.
// 该Group中可能会有多个版本,该字段就包含了所有的versions
GroupVersions []unversioned.GroupVersion

// Codec is the default codec for serializing output that should use
// the preferred version. Use this Codec when writing to
// disk, a data store that is not dynamically versioned, or in tests.
// This codec can decode any object that the schema is aware of.
// 用于编解码
Codec runtime.Codec

// SelfLinker can set or get the SelfLink field of all API types.
// to go through the InterfacesFor method below.
SelfLinker runtime.SelfLinker

// RESTMapper provides the default mapping between REST paths and the objects declared in api.Scheme and all known
// versions.
// RESTMapper提供 REST路径 与 那些在api.Scheme和所有已知版本中声明的对象之间的默认映射。用于类型,对象之间的转换

RESTMapper meta.RESTMapper

// InterfacesFor returns the default Codec and ResourceVersioner for a given version
// string, or an error if the version is not known.
// function provided below once every place that populates this field has been changed.
InterfacesFor func(version unversioned.GroupVersion) (*meta.VersionInterfaces, error)

// InterfacesByVersion stores the per-version interfaces.
InterfacesByVersion map[unversioned.GroupVersion]*meta.VersionInterfaces
}

Version

每个 API Group 下面都能存在有多个 version 版本。为了扩展性,Kubernetes支持多版本的API路径,比如 /api/v1 或者 /apis/extensions/v1beta1/, 不同版本的API意味着不同程度的稳定性和支持度。比如在一个 group 群组中最早有第一个 v1alpha1 版本,后来中间发展到了 v1beta1 版本,最终发展到 v1 的稳定版本。 如果在系统创建了一个 v1beta1 版本的对象,那么它能过被 Group 任一支持的版本( 比如v1 )检索到, 这是由于 API server 能够支持不同版本对象之间的无损耗转换。

type GroupVersion struct

1
2
3
4
type GroupVersion struct {
Group string `protobuf:"bytes,1,opt,name=group"`
Version string `protobuf:"bytes,2,opt,name=version"`
}

GroupVersion中就是两个string类型,GroupVersion,分别对应了api所处的分组和版本,这也是kubernetes实现多版本的基础。

Resource

Resource 代表以 JSON 格式通过 HTTP 发送或检索的资源实体。 它既可以使一个单独的resource资源(比如…/namespaces/default,也可以是一组resource 资源(比如…/jobs)。 这里说明一下 Reource 和 Kind 的区别: 其实基本上都是一个概念,只是 Kind 表示一个种类,在实际中它是首字母大写的; Resource 表示资源,在实际中它是全部小写的,并且有单数和复数之分。我们可以把Kind和Resource的关系理解成面向对象编程中类与对象的关系,Kind 其实就是一个类,用于描述对象的;而 Resource 就是具体的 Kind,可以理解成类已经实例化成对象。

Resource就是指定了一个名字和kind的资源对象,不管它有没有namespace。 resource是通过plural, singular := KindToResource(kind)取值,singular是将Kind转换为小写字母,而plural是变为复数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
type APIResource struct {
// name is the name of the resource.
Name string `json:"name" protobuf:"bytes,1,opt,name=name"`
// namespaced indicates if a resource is namespaced or not.
Namespaced bool `json:"namespaced" protobuf:"varint,2,opt,name=namespaced"`
// kind is the kind for the resource (e.g. 'Foo' is the kind for a resource 'foo')
Kind string `json:"kind" protobuf:"bytes,3,opt,name=kind"`
}

reousrce := unversioned.APIResource{
Name: "nodes",
Namespaced: false,
Kind: "Node",
}
reousrce = unversioned.APIResource{
Name: "pods",
Namespaced: true,
Kind: "Pod",
}

资源外部版本与内部版本

资源代码定义

将资源注册到资源注册表

资源首选版本

资源操作方法

资源与命名空间

自定义资源

资源对象描述文件定义

Kubernetes内置资源全图

runtime.Object 类型基石

Unstructured数据

Scheme资源注册表

Scheme资源注册表数据结构

资源注册表注册方法

资源注册表查询方法

Codec编解码器

Codec编解码实例化

jsonSerializer 与 yamlSerializer序列化器

protobufSerializer序列化器

Converter 资源版本转换器

Converter 转换器数据结构

Converter注册转换函数

Converter 资源版本转换原理

API Request Flow

了解了 kube-apiserver 的 API 后,下面会介绍 kube-apiserver 如何处理一个 API 请求,一个请求完整的流程如下图所示:

img

关于 API 的处理代码都在 k8s.io/pkg/api 包中,会处理来自集群内部和集群外部的API请求。

此处以一次 POST 请求示例说明,当请求到达 kube-apiserver 时,kube-apiserver 首先会执行在 http filter chain 中注册的过滤器链。该过滤器对其执行一系列过滤操作,主要有认证、鉴权等检查操作。当 filter chain 处理完成后,请求会通过 route 进入到对应的 handler 中,handler 中的操作主要是与 etcd 的交互。

Filter Chain

一个HTTP Request首先会被 DefaultBuildHandlerChain注册的 filter chain处理,每一个filter会传递各自的info到 ctx.RequestInfo上。

  • WithRequestInfo() as defined in requestinfo.go attaches a RequestInfo to the context
  • WithMaxInFlightLimit() as defined in maxinflight.go limits the number of in-flight requests
  • WithTimeoutForNonLongRunningRequests() as defined in timeout.go times out non-long-running requests like most GET, PUT, POST, DELETE requests in contrast to long-running requests like watches and proxy requests
  • WithPanicRecovery() as defined in wrap.go wraps an handler to recover and log panics
  • WithCORS() as defined in cors.go provides a CORS implementation; CORS stands for Cross-Origin Resource Sharing and is a mechanism that allows JavaScript embedded in a HTML page to make XMLHttpRequests to a domain different from the one the JavaScript originated from.
  • WithAuthentication() as defined in authentication.go tries to authenticate the given request as a user and stores the user info in the provided context. On success, the Authorization HTTP header is removed from the request.
  • WithAudit() as defined in audit.go decorates the handler with audit logging information for all incoming requests The audit log entries contain infos such as source IP of the request, user invoking the operation, and namespace of the request.
  • WithImpersonation() as defined in impersonation.go handles user impersonation, by checking requests that attempt to change the user (similar to sudo).
  • WithAuthorization() as defined in authorization.go passes all authorized requests on to multiplexer which dispatched the request to the right handler, and returns a forbidden error otherwise.
  • WithRequestInfo:
  • MaxInflightLimit
  • TimeoutForNonLongRunningRequests
  • Panic Recovery
  • CORS
  • Authentication
  • Audit
  • Impersonation
  • Authorization
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
func DefaultBuildHandlerChain(apiHandler http.Handler, c *Config) http.Handler {
handler := genericapifilters.WithAuthorization(apiHandler, c.Authorization.Authorizer, c.Serializer)
if c.FlowControl != nil {
handler = genericfilters.WithPriorityAndFairness(handler, c.LongRunningFunc, c.FlowControl)
} else {
handler = genericfilters.WithMaxInFlightLimit(handler, c.MaxRequestsInFlight, c.MaxMutatingRequestsInFlight, c.LongRunningFunc)
}
handler = genericapifilters.WithImpersonation(handler, c.Authorization.Authorizer, c.Serializer)
handler = genericapifilters.WithAudit(handler, c.AuditBackend, c.AuditPolicyChecker, c.LongRunningFunc)
failedHandler := genericapifilters.Unauthorized(c.Serializer)
failedHandler = genericapifilters.WithFailedAuthenticationAudit(failedHandler, c.AuditBackend, c.AuditPolicyChecker)
handler = genericapifilters.WithAuthentication(handler, c.Authentication.Authenticator, failedHandler, c.Authentication.APIAudiences)
handler = genericfilters.WithCORS(handler, c.CorsAllowedOriginList, nil, nil, nil, "true")
handler = genericfilters.WithTimeoutForNonLongRunningRequests(handler, c.LongRunningFunc, c.RequestTimeout)
handler = genericfilters.WithWaitGroup(handler, c.LongRunningFunc, c.HandlerChainWaitGroup)
handler = genericapifilters.WithRequestInfo(handler, c.RequestInfoResolver)
if c.SecureServing != nil && !c.SecureServing.DisableHTTP2 && c.GoawayChance > 0 {
handler = genericfilters.WithProbabilisticGoaway(handler, c.GoawayChance)
}
handler = genericapifilters.WithAuditAnnotations(handler, c.AuditBackend, c.AuditPolicyChecker)
handler = genericapifilters.WithWarningRecorder(handler)
handler = genericapifilters.WithCacheControl(handler)
handler = genericfilters.WithPanicRecovery(handler)
return handler
}

Handler Operation

当 filter chain 处理完成后,请求会通过 route 进入到对应的 handler 中,handler 中的操作主要是与 etcd 的交互,在 handler 中的主要的操作如下所示:

API-server-storage-flow-2

Decoder

在解码时,首先从 HTTP path 中获取期待的 version,然后使用 scheme 以正确的 version 创建一个与之匹配的空对象,并使用 JSON 或 protobuf 解码器进行转换,在转换的第一步中,如果用户省略了某些字段,Decoder 会把其设置为默认值。

Admission

在解码完成后,需要通过验证集群的全局约束来检查是否可以创建或更新对象,并根据集群配置设置默认值。在 k8s.io/kubernetes/plugin/pkg/admission 目录下可以看到 kube-apiserver 可以使用的所有全局约束插件,kube-apiserver 在启动时通过设置 --enable-admission-plugins 参数来开启需要使用的插件,通过 ValidatingAdmissionWebhookMutatingAdmissionWebhook 添加的插件也都会在此处进行工作。

Validation

主要检查 object 中字段的合法性。

在 handler 中执行完以上操作后最后会执行与 etcd 相关的操作,POST 操作会将数据写入到 etcd 中,以上在 handler 中的主要处理流程如下所示:

1
2
v1beta1 ⇒ internal ⇒    ||    ⇒  v1  ⇒ json/yaml ⇒ etcd
admission validation

架构

apiserver diagram

  • Scheme:定义了资源序列化和反序列化的方法,以及资源类型和版本的对应关系
  • Storage:是对资源的完整封装,实现了资源创建、删除、watch等操作
  • APIGroupInfo:是同一个group下所有资源的集合

每个资源对应着两个版本:

  • External Object:这个版本是对外给用户访问创建的接口对象,比如使用yaml或json创建对象时,都要使用External Object
  • Internal Object:这个版本是核心对象,实现了资源的创建和删除,对应了在ETCD中持久化的版本

这两个版本的资源是需要相互转换的,而转换的函数就需要事先初始化到Scheme中, 多个external version版本之间的资源进行相互转换,都是需要通过internal version进行中转。所以在ETCD中存储的资源是带版本的,这也是kubernetes能实现多版本转换的关键。

组件构成

ApiServer 共由 3 个组件构成(Aggregator、KubeAPIServer、APIExtensionServer),这些组件依次通过 Delegation 处理请求:

  • Aggregator:暴露的功能类似于一个七层负载均衡,将来自用户的请求拦截转发给其他服务器,并且负责整个 APIServer 的 Discovery 功能;
  • KubeAPIServer :负责对请求的一些通用处理,认证、鉴权等,以及处理各个内建资源的 REST 服务;
  • APIExtensionServer:主要处理 CustomResourceDefinition(CRD)和 CustomResource(CR)的 REST 请求,也是 Delegation 的最后一环,如果对应 CR 不能被处理的话则会返回 404。

Aggregator 和 APIExtensionsServer 对应两种主要扩展 APIServer 资源的方式,即分别是 AA 和 CRD。

Aggregator

Aggregator 通过 APIServices 对象关联到某个 Service 来进行请求的转发,其关联的 Service 类型进一步决定了请求转发形式。Aggregator 包括一个 GenericAPIServer 和维护自身状态的 Controller。其中 GenericAPIServer 主要处理 apiregistration.k8s.io 组下的 APIService 资源请求。

Aggregator 除了处理资源请求外还包含几个 controller:

  • apiserviceRegistrationController:负责 APIServices 中资源的注册与删除;
  • availableConditionController:维护 APIServices 的可用状态,包括其引用 Service 是否可用等;
  • autoRegistrationController:用于保持 API 中存在的一组特定的 APIServices;
  • crdRegistrationController:负责将 CRD GroupVersions 自动注册到 APIServices 中;
  • openAPIAggregationController:将 APIServices 资源的变化同步至提供的 OpenAPI 文档;

kubernetes 中的一些附加组件,比如 metrics-server 就是通过 Aggregator 的方式进行扩展的,实际环境中可以通过使用 apiserver-builder 工具轻松以 Aggregator 的扩展方式创建自定义资源。

启用 API Aggregation

在 kube-apiserver 中需要增加以下配置来开启 API Aggregation:

1
2
3
4
5
6
7
--proxy-client-cert-file=/etc/kubernetes/certs/proxy.crt
--proxy-client-key-file=/etc/kubernetes/certs/proxy.key
--requestheader-client-ca-file=/etc/kubernetes/certs/proxy-ca.crt
--requestheader-allowed-names=aggregator
--requestheader-extra-headers-prefix=X-Remote-Extra-
--requestheader-group-headers=X-Remote-Group
--requestheader-username-headers=X-Remote-User

KubeAPIServer

KubeAPIServer 主要是提供对 API Resource 的操作请求,为 kubernetes 中众多 API 注册路由信息,暴露 RESTful API 并且对外提供 kubernetes service,使集群中以及集群外的服务都可以通过 RESTful API 操作 kubernetes 中的资源。

APIExtensionServer

APIExtensionServer 作为 Delegation 链的最后一层,是处理所有用户通过 Custom Resource Definition 定义的资源服务器。

其中包含的 controller 以及功能如下所示:

  • openapiController:将 crd 资源的变化同步至提供的 OpenAPI 文档,可通过访问 /openapi/v2 进行查看;
  • crdController:负责将 crd 信息注册到 apiVersions 和 apiResources 中,两者的信息可通过 $ kubectl api-versions$ kubectl api-resources 查看;
  • namingController:检查 crd obj 中是否有命名冲突,可在 crd .status.conditions 中查看;
  • establishingController:检查 crd 是否处于正常状态,可在 crd .status.conditions 中查看;
  • nonStructuralSchemaController:检查 crd obj 结构是否正常,可在 crd .status.conditions 中查看;
  • apiApprovalController:检查 crd 是否遵循 kubernetes API 声明策略,可在 crd .status.conditions 中查看;
  • finalizingController:类似于 finalizes 的功能,与 CRs 的删除有关;