APIServer walkaround

1,259 阅读17分钟

总览

apiserver是什么?

The Kubernetes API server validates and configures data for the api objects which include pods, services, replicationcontrollers, and others. The API Server services REST operations and provides the frontend to the cluster’s shared state through which all other components interact.

apiserver相当于一个大网管,所有的请求都通过apiserver进行中转。apiserver只负责资源的持久化,而不负责任何资源的CRUD。

有哪些特性?

apiserver,或者说k8s的良好特性有

  • list-watch实现资源同步,通过subscribe和增量更新减少数据量。
  • list的时候通过chunk分页,减缓压力
  • 限流,通过一些简单的工具进行限流,如buffered channel, workqueue
  • 声明式api,只需声明终态
  • 拓展能力,CRD与operator
  • 事件驱动

源码解析

总体流程图

掘金是不支持直接显示draw.io的内容吗?

draw.io-apiserver

入口

在cmd/kube-apiserver/apiserver.go中,command := app.NewAPIServerCommand()之后command.Execute()

app.NewAPIServerCommand()

  • 生成apiserver配置options.NewServerRunOptions()
  • 注册RunE,其中对没有填充的字段自动补全Complete(s),之后校验命令是否合法completedOptions.Validate()

command.Execute()执行Run()

  • 创建server端:CreateServerChain(completeOptions, stopCh)
  • 启动apiserver:server.PrepareRun().Run(stopCh)

server的创建过程

CreateServerChain()的逻辑

  • CreateNodeDialer()创建http连接池与ssh隧道,用于apiserver到cluster的通信
  • CreateKubeAPIServerConfig()createAPIExtensionsConfig()
  • createAPIExtensionsServer()CreateKubeAPIServer()
  • 两个apiserver的prepareRun()
  • createAggregatorConfig()createAggregatorServer()
  • 返回最终的aggregatorServer.GenericAPIServer

NodeDialer

注释中写到

// CreateNodeDialer creates the dialer infrastructure to connect to the nodes.

说的并不清晰。从master-node-communication中得知

  • cluster到master的通信只能通过apiserver
  • apiserver到cluster的通信可以通过三种方法
    • apiserver到kubelet的endpoint,用于logs功能,exec功能,port-forward功能
    • HTTP连接,即使可以用HTTPS也不做任何其他校验,并不安全
    • ssh tunnel,不推荐使用

apiserver到cluster的通信通过NodeDialer完成,它相当于把这些方法做了一层封装,方便使用。

CreateNodeDialer()返回两个参数

  • tunneler.Tunneler,目前只实现了SSHTunneler
  • *http.Transport,一个http连接池

CreateNodeDialer()的流程

  • 判断s.SSHUser是否为空,如果不为空则InitCloudProvider()(这块不分析),之后调用tunneler.New()生ssh tunneler
  • 返回proxyTransport作为连接池,将DialContext设为在初始化ssh tunneler时注册的proxyDialerFn

golang的http连接池

ssh tunneler已经在官方文档中标记为deprecated,所以不看ssh tunneler的内容了,只了解一下golang的http连接池http.Transport。

Transport is an implementation of RoundTripper that supports HTTP, HTTPS, and HTTP proxies (for either HTTP or HTTPS with CONNECT).

Transports should be reused instead of created as needed.

Transports are safe for concurrent use by multiple goroutines.

A Transport is a low-level primitive for making HTTP and HTTPS requests. For high-level functionality, such as cookies and redirects, see Client.

连接池的解析可以参考这篇文章

生成apiserver配置

位于cmd/kube-apiserver/app/server.go中。

kubeAPIServerConfig, insecureServingInfo, serviceResolver, pluginInitializer, admissionPostStartHook, err := CreateKubeAPIServerConfig(completedOptions, nodeTunneler, proxyTransport)
apiExtensionsConfig, err := createAPIExtensionsConfig(*kubeAPIServerConfig.GenericConfig, kubeAPIServerConfig.ExtraConfig.VersionedInformers, pluginInitializer, completedOptions.ServerRunOptions, completedOptions.MasterCount,
		serviceResolver, webhook.NewDefaultAuthenticationInfoResolverWrapper(proxyTransport, kubeAPIServerConfig.GenericConfig.LoopbackClientConfig))

要注意的返回值

  • kubeAPIServerConfig: 也就是master config,master apiserver的配置,用于处理k8s原生资源
  • insecure serving info: deprecated
  • service resolver: 输入服务名,输出IP
  • apiExtensionsConfig: CRD apiserver的config

流程

  1. 初始化master apiserver config
    • buildGenericConfig()配置通用参数
    • SplitHostPort()读取etcd的连接配置,尝试连接到etcd。不再展开
    • capability.Initialize()初始化一次apiserver允许提供的特权,是否允许privileged,以及允许哪些特权,目前写死为host network, host PID, host IPC三种。
    • DefaultServiceIPRange()设置k8s服务可用的IP范围,apiserver服务的IP
    • readCAorNil()读取CA文件,返回CA字符串
    • 初始化masterConfig
  2. 初始化apiextension server的config,以mater config为基础,替换一些字段
    • 替换一些etcd的配置
    • GVR资源加入apiextensions.k8s.io/v1beta1

配置通用参数

要注意的返回值

  • genericConfig: apiserver的通用参数
  • versionedInformers: 一个sharedInformerFactory
  • serviceResolver: 内部服务的DNS解析器
  • storageFactory: 存储接口,与etcd有关

流程

  • NewForConfig()生成genericConfig
    • serializer: 序列化、反序列化的工具
    • 注册BuildHandlerChainFuncDefaultBuildHandlerChain
    • HandlerCHainWaitGroup初始化为SafeWaitGroup,safe指在wait group的基础上加一个flag,判断是否处于wait状态,并由一个读写锁保证wait的临界性
    • LegacyAPIGroupPrefixes指定为"/api"
    • 健康检查机制为PingHealthzLogHealthz,其中log healthz每分钟flush一次日志,通过是否flush成功判断是否健康
    • 一些设置,如enable discovery, enable profiling, enable metrics
    • 设置消息体积最大值为3MB
    • 设置是否允许压缩回复消息
    • 设置一个LongRunningFunc,判断一个http连接是否是长连接。只有watch机制或pprof的debug机制才属于长连接
  • 设置MergedResourceConfig决定接收哪些GVR资源,默认的资源列表在pkg/master/master.go的DefaultAPIResourceConfigSource()
  • 将之前Run()中的生成的ServerRunOptions的信息,包括
    • generic server run options
    • insecure serving信息
    • secure serving信息
    • authentication鉴权信息
    • apiEnablement信息,与注册哪些GVR资源有关,和之前的MergedResourceConfig合并
  • 设置一些OpenAPI有关的配置,只和自动产出文档有关,与使用无关
  • 新增LongRunningFunc,watch或proxy机制,以及attch, exec, proxy, log, portforward行为属于长连接
  • 设置version信息,从编译时的-ldflags包含的信息得来
  • 设置storageFactory,这一块等到etcd部分再看
  • 注册了一个versionedInformers,通过本地回环获取资源。注释中解释道:This is required for proper functioning of the PostStartHooks on a GenericAPIServer
  • BuildAuthenticator()生成一些informer,包括secrets, pods, service accounts,提供鉴权时需要的信息,在pkg/kubeapiserver/authenticator/config.go的New()中罗列了鉴权方式:http, x509, bearer token等,在authentication有比较详细的说明
  • BuildAuthorizer(),细节在pkg/kubeapiserver/authorizer/config.go的New()中,文档可以参考authorization
  • buildServiceResolver(),通过endpoint或clusterIP构造DNS,此外针对kubernetes.default.svc这个服务构造了loopback resolver
  • 填充audit option与admission chain

capabilities如何只允许一次初始化

capabilities.Initialize()的注释中提到

Initialize the capability set. This can only be done once per binary, subsequent calls are ignored.

这个特性值得学习,是由sync.Once这个包提供的。

Once is an object that will perform exactly one action.

once结构体包含done与m,分别标志是否执行过,以及一个锁。

type Once struct {
	// done indicates whether the action has been performed.
	// It is first in the struct because it is used in the hot path.
	// The hot path is inlined at every call site.
	// Placing done first allows more compact instructions on some architectures (amd64/x86),
	// and fewer instructions (to calculate offset) on other architectures.
	done uint32
	m    Mutex
}

用法很简单,调用Do()即可,将判断done是否为0,如果为0,调用调用doSlow()函数。do slow首先加锁,之后判断done是否为0,如果为0,调用f,直到f调用结束,done置为1,解锁。

注释中说到了错误的用法是

if atomic.CompareAndSwapUint32(&o.done, 0, 1) {
    f()
}

这样虽然可以保证f只能被执行一次,但无法保证f执行成功。当两次操作同时想执行f时,如果第一次执行直接退出了,则初始化失败,而此时done已经被置为1。而once的设计初衷是保证f执行并且只执行一次,所以用锁与defer atomic.StoreUint32(&o.done, 1)来确保直到f执行成功完成,才将done置为1。

创建可用的apiserver

创建apiserver分为三步,本小节只分析前两步

  • 创建extension apiserver,用于注册CRD
  • 再此基础上创建kube apiserver
  • 聚合为一个aggregator apiserver

extension apiserver的代码可以单独在这里获取

This API server provides the implementation for CustomResourceDefinitions which is included as delegate server inside of kube-apiserver.

aggregator apiserver的说明可以在apiserver-aggregation找到

The aggregation layer enables installing additional Kubernetes-style APIs in your cluster. These can either be pre-built, existing 3rd party solutions, such as service-catalog, or user-created APIs like apiserver-builder, which can get you started.

extension apiserver

位于createAPIExtensionsServer(),直接返回apiextensionsConfig.Complete().New(delegateAPIServer),其中delegateAPIServer为空。

delegate的意思是:extension apiserver一开始啥也不干,直到有CRD注册进来才开始工作。它后续将被注册为kube-apiserver的一部分。

它返回的是一个CRD结构体,包含了一个generic apiServer与一个informer factory。

type CustomResourceDefinitions struct {
	GenericAPIServer *genericapiserver.GenericAPIServer

	// provided for easier embedding
	Informers internalinformers.SharedInformerFactory
}

进入到New()内部,其流程为

  • c.GenericConfig.New()初始化genericServer
  • 初始化storage
  • InstallAPIGroup()
  • 初始化informer与各种controller,注册一些postStartHook,但不真正运行
generic server

位于staging/src/k8s.io/apiserver/pkg/server/config.go的(c completedConfig) New()

	handlerChainBuilder := func(handler http.Handler) http.Handler {
		return c.BuildHandlerChainFunc(handler, c.Config)
	}

这段代码的理解要结合之前提到过的

注册BuildHandlerChainFuncDefaultBuildHandlerChain

在staging/src/k8s.io/apiserver/pkg/server/config.go中的DefaultBuildHandlerChain()中可以理解这个函数的作用:将原来的handler包装一下,生成kubernetes特色的handler。原来的handler是http.Handler。包装的过程步骤比较多

  1. WithAuthorization():在handler的基础上判断授权信息
  2. WithMaxInFlightLimit():进行限流
  3. WithImpersonation():监测请求是否修改用户信息并做相应处理
  4. WithAudit():对请求添加审计信息
  5. WithAuthentication():从request info中获取鉴权信息,对context添加用户信息。如果鉴权失败进行相应的错误处理
  6. WithTimeoutForNonLongRunningRequests():判断请求是否超时
  7. WithCORS():一种CORS的实现,CORS即cross origin resource sharing,可以参考wiki-Cross-origin_resource_sharing
  8. WithWaitGroup():把所有的非长连接的请求加入一个wait group中。这个wait group是之前初始化的c.HandlerChainWaitGroup
  9. WithRequestInfo():将request info加入request的context中
  10. WithPanicRecovery():panic之后打印一些日志

限流

限流首先进行了startOnce.Do(startRecordingUsage)实现,start once前面已经分析过了,而startRecordingUsage()在staging/src/k8s.io/apiserver/pkg/server/filters/maxinflight.go中,用了很形象的比喻:watermark,每隔一段时间记录本时间段内的request数量,通过prometheus的接口暴露给外部。

之后判断nonMutatingLimit与mutatingLimit是否为0,只有全为0的时候才不对handler作任何修改;判断request是不是属于长连接,如果不是,则进入限流流程。即长连接的请求不被限流规则控制。限流对不修改数据的请求与修改数据的请求分别处理,即对读写操作对不同的操作。nonMutatingRequestVerbs规定了get, list, watch操作是nonMutating的,其余均属于mutating。对两种请求,建了两个buffer长度为各自limit的channel。对channel的操作如下

c = isMutating ? mutatingChan : nonMutatingChan // 当然golang没有三目运算,为了简单这么简写一下
select {
case c <- true:
    // 不满
    handler.ServeHTTP(w, r)
    
    defer <-c
default:
    tooManyRequests(r, w)
}

当buffer装得下,则写入一个值,处理该请求,之后读出一个值,否则拒绝该请求。

添加requestInfo

info来自info, err := resolver.NewRequestInfo(req),在staging/src/k8s.io/apiserver/pkg/endpoints/request/requestinfo.go中可以看到详细的说明。它通过解析request的url,填充type RequestInfo struct。具体的逻辑不再展开。

之后通过request.WithRequestInfo(ctx, info)生成新的context,生成的context的形式是&valueCtx{parent, key, val},其中key是request info的key,val是request info。

回到New()函数中

	apiServerHandler := NewAPIServerHandler(name, c.Serializer, handlerChainBuilder, delegationTarget.UnprotectedHandler())

里面用到了gorestful框架。

package for building REST-style Web Services using Go

之后进行了非常多的参数的配置,直到installAPI(s, c.Config),它真正把资源变成rest风格的url添加到前面的apiHandler中。添加的时候有的添加到NonGoRestfulMux,有的添加到GoRestfulContainer,分别看一个例子

routes.Index{}.Install(s.listedPathProvider, s.Handler.NonGoRestfulMux)
routes.Version{Version: c.Version}.Install(s.Handler.GoRestfulContainer)

profiling的install函数

func (d Profiling) Install(c *mux.PathRecorderMux) {
	c.UnlistedHandleFunc("/debug/pprof", redirectTo("/debug/pprof/"))
	c.UnlistedHandlePrefix("/debug/pprof/", http.HandlerFunc(pprof.Index))
	c.UnlistedHandleFunc("/debug/pprof/profile", pprof.Profile)
	c.UnlistedHandleFunc("/debug/pprof/symbol", pprof.Symbol)
	c.UnlistedHandleFunc("/debug/pprof/trace", pprof.Trace)
}

例如:UnlistedHandlerFuncTrace这个handler加入到"/debug/pprof/trace"路径中,而"/debug/pprof"重定向到"/debug/pprof/",将进入pprof的主页。

而version的Install函数则是go-restful风格的:

	versionWS := new(restful.WebService)
	versionWS.Path("/version")
	versionWS.Doc("git code version from which this is built")
	versionWS.Route(
		versionWS.GET("/").To(v.handleVersion).
			Doc("get the code version").
			Operation("getCodeVersion").
			Produces(restful.MIME_JSON).
			Consumes(restful.MIME_JSON).
			Writes(version.Info{}))

	c.Add(versionWS)

要注意这里注册的都是一些通用的,如version, metrics, pprof等,而不涉及具体的kubernetes资源的path。

最后返回该generic server。总结来看,generic server不包含任何具体的kubernetes GVR资源的逻辑,不包含后端的etcd存储的任何逻辑,是一个通用的apiserver的框架。

CRD storage

与CRD storage相关的是以下的几行代码。

		storage := map[string]rest.Storage{}
		// customresourcedefinitions
		customResourceDefintionStorage := customresourcedefinition.NewREST(Scheme, c.GenericConfig.RESTOptionsGetter)
		storage["customresourcedefinitions"] = customResourceDefintionStorage
		storage["customresourcedefinitions/status"] = customresourcedefinition.NewStatusREST(Scheme, customResourceDefintionStorage)

		apiGroupInfo.VersionedResourcesStorageMap["v1beta1"] = storage

Storage is a generic interface for RESTful storage services. Resources which are exported to the RESTful API of apiserver need to implement this interface. It is expected that objects may implement any of the below interfaces.

该实例由NewREST()提供

strategy := NewStrategy(scheme)

初始化了一个strategy,其nameGenerator是SimpleNameGenerator,产生名字的方式是截取前64位加上5位随机字符。

store := &genericregistry.Store{
    // 省略
}

store是一层存储数据结构的抽象,支持get, watch, list与label select操作。其新建操作将返回apiextensions.CustomResourceDefinition,包含typeMeta, ObjectMeta, Spec与status,这些都是我们作为用户非常熟悉的字段。NewListFunc支持资源的获取,路径为"/apis/group/version/namespaces/my-ns/myresource"。PredicateFunc用来匹配label或特定的field,注册为MatchCustomResourceDefinition,在staging/src/k8s.io/apiextensions-apiserver/pkg/registry/customresourcedefinition/strategy.go中可以看到细节,它通过GetAttrs()对object进行筛选。DefaultQualifiedResource是为了防止在请求的context中找不到group resource信息,而自动生成的默认信息。几个strategy函数都是在资源变化时调用的,这里的作用是随机生成name。最后还要通过CompleteWithOptions()配置默认值,其中keyFunc是能看到一个熟悉的函数NamespaceKeyFunc

最后存储的数据结构为

----------------      -----------       -------------
| apigroupInfo | ---> | version | ----> | resources | ----------- 
----------------  |   -----------   |   -------------           |
                  |                 |                           |--> genericregistry.Store
                  |                 |   --------------------    |
                  |                 |-> | resources/status | ---- 
                  |                     --------------------
                  |   ------------
                  --> | version2 |
                      ------------

install api group

综合来看,这部分的功能是将注册的资源,即apiResource,变成restful的路径注册到generic server的APIServerHandler中。对于extensions apiserver来说,url前缀是"/apis"

	// DefaultLegacyAPIPrefix is where the legacy APIs will be located.
	DefaultLegacyAPIPrefix = "/api"

	// APIGroupPrefix is where non-legacy API group will be located.
	APIGroupPrefix = "/apis"

每个apiGroupInfo都会执行s.installAPIResources(APIGroupPrefix, apiGroupInfo, openAPIModels),其中对每个group+version,执行InstallREST(),将url注册到GoRestfulContainer中。在staging/src/k8s.io/apiserver/pkg/server/genericapiserver.go中能看到这部分的细节。

prefix := path.Join(g.Root, g.GroupVersion.Group, g.GroupVersion.Version)

上面这段中,Root就是APIGroupPrefix,所以最后CRD资源的url就是"/apis/extensions/v1beta1/",从使用的角度来说,kubectl get crd将列出所有的crd资源,列出的操作通过discoveryHandler实现,在staging/src/k8s.io/apiserver/pkg/endpoints/installer.go的installer.Install()中列出所有资源的操作通过遍历所有的etcd storage的实现。上面说过这里已经遍历了每个group+version,所以在storage中可以遍历所有的resource。

这一段代码包含了非常多的建立etcd的CRUD接口的内容,为了不与apiserver的框架混淆,etcd相关的部分放在下面的章节中解释

注册postStartHook

回到staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/apiserver.go的New()中,在install api group后,此外,注册了一个crdHandler,由一个type crdStorageMap map[types.UID]*crdInfo存储CRD资源的信息。

之后生成了大量的controller,每个controller的功能都相对简单

  • crdController:用来实现对新注册的资源的获取
  • establishingController:如果InitialNamesAccepted,认为CRD已经established,把condition设为true
  • namingController:用来识别注册的CRD的名称信息是否重复
  • nonStructuralSchemaController:non-structural schema condition controller
  • finalizingController:控制CRD资源的删除行为,前面的crdHandler在这里有部分逻辑

之后把crd-informer-syncedstart-apiextensions-controllersstart-apiextensions-informers三种行为加入到postStartHook中。注意这些controller并没有真正运行。

最后,返回这个专门控制CRD资源的apiextension apiserver。

kube apiserver

本文最核心的部分,位于cmd/kube-apiserver/app/server.go的CreateKubeAPIServer(),进入到kubeAPIServerConfig.Complete().New(delegateAPIServer)内部,重要的过程有

  • c.GenericConfig.New("kube-apiserver", delegationTarget)生成一个generic apiserver
  • InstallLegacyAPI()InstallAPIs()注册restful url
kube generic apiserver

这一部分与上一节的generic server部分调用的函数相同,不同点在于上面的delegationTarget是空的,这里之前生成的extension apiserver。对于delegation target的操作有

  • NewAPIServerHandler中把unprotected handler加入到新的apiserver的chain中
  • 注册postStartHooks
  • 注册PreShutdownHooks
  • 注册health check函数
  • 注册ListProviders

这些操作分为两类,一类是启动前后的预处理与后处理,一类restful url与handler相关。

install api

如果启用了LogsSupport(这个与nodeDialer有关,不重要),则将"/logs"加入到path中,其handler将在"/var/log"目录下寻找用户指定的日志。

如果启用了apiGroup为"",version为v1的资源(这种资源最典型的就是pod),则执行InstallLegacyAPI()

  • NewLegacyRESTStorage()新建了所有"/api/v1"资源的rest storage
  • 新建boostrap controller,"watching the core capabilities of the master",包括system namespace,system service, health check等等。细节可以在pkg/master/controller.go中看到
  • InstallLegacyAPIGroup(),前缀为"/api",注册的是"/api/v1"的path,具体不再展开

除了corev1资源,还有一些其他的资源,如batch, extensions, scheduling等等其他的apiGroups,这些apigroups的具体内容都在pkg/registry/中。

prepare to run

PrepareRun()在staging/src/k8s.io/apiserver/pkg/server/genericapiserver.go中

  • 注册OpenAPI相关的path
  • installHealthz()注册"/healthz"路径

api聚合

与每个单独的apiserver的创建过程类似

  • createAggregatorConfig()
  • createAggregatorServer()

源码在cmd/kube-apiserver/app/aggregator.go中。最终,返回aggregatorServer.GenericAPIServer,完成apiserver的创建。

apiserver与etcd的交互

apiserver并不创建任何具体的资源,唯一的crd controller也只是创建相应的api path。apiserver收到资源请求后将在etcd中进行持久化。

由于extension apiserver与kube apiserver在流程方面基本相同,所以只看kube apiserver的话,与etcd的交互的源码的快速浏览路径为

graph LR
CreateKubeAPIServer-->New
New-->InstallAPIs
InstallAPIs-->InstallAPIGroups

到了InstallAPIGroups之后,分为两条线

installAPIResources()

installAPIResources()的流程中一路跳到

graph LR
installAPIResources-->InstallREST
InstallREST-->installer.Install
installer.Install-->registerResourceHandlers

registerResourceHandlers()中注册了与etcd交互的逻辑。之前rest storage interface的初始化已经提供了creater, lister, getter, watcher的接口。所以对于每种资源,注册一个reqScope,它包含了

  • serializer: 序列化接口
  • creater: 由GVR信息生成一个runtime object的接口
  • defaulter: 填充未填充的字段未默认值的接口
  • GVR信息
  • ...

之后对于每种动作,注册handler。下面以最简单的流程看一下一些动作的具体逻辑。

GET

看到handler = restfulGetResource(getter, exporter, reqScope),它调用handlers.GetResource(),而这个函数调用getResourceHandler()返回一个http handler。其逻辑主要是

func getResourceHandler(scope *RequestScope, getter getterFunc) http.HandlerFunc {
    result, err := getter(ctx, name, req, trace)
    transformResponseObject(ctx, scope, trace, req, w, http.StatusOK, outputMediaType, result)
}

所以重点在于getter函数的实现。在staging/src/k8s.io/apiserver/pkg/endpoints/handlers/get.go中注册了一个匿名函数作为getter函数,其中最终将走到r.Get(ctx, name, &options)。这个调用就和用户的习惯非常类似了。

LIST

最终调用的是staging/src/k8s.io/apiserver/pkg/endpoints/handlers/get.go中的ListResource()方法,最后调用r.List(ctx, &opts)

WATCH

其实watch与list是非常类似的。watch在最开始包含了一次list,list不包含后续的监听资源变化的环节。watch建立了一个长连接,通过staging/src/k8s.io/apiserver/pkg/endpoints/handlers/watch.go中的serveWatch()中的serveHTTP()实现结果的持续输出。

由于list-watch机制是k8s资源同步的核心,所以多花一点篇幅详细的梳理一下watch的机制。watch分为两个场景,一种是apiserver对etcd的watch,一种是apiserver推送事件到watcher。

apiserver与etcd的watch由cacher完成。调用staging/src/k8s.io/apiserver/pkg/registry/generic/registry/storage_factory.go中的StorageWithCacher(),它调用了cacherstorage.NewCacherFromConfig(cacherConfig)生成一个cacher。

cacher的数据结构中比较重要的字段有

  • incoming: 一个event的channel,每个event包含type, object, prevObject, key, resourceVersion等,描述了一个资源实例的变化与当前状态
  • storage: 存储接口
  • watchCache: 资源滑动窗,存储对象最新的变化记录
  • reflector: client-go中的reflector组件,如果忘记了请看informer机制解析
  • watchers: watch该对象的组件的名称

apiserver向etcd的watch将调用etcd相关的util,把事件发送到cacher中。reflector中将注册一系列的回调函数,更新watchCache,把event放到incoming channel中。

cacher将启动一个routine,每当incoming channel有事件时,把事件分发到各个watcher中。

组件与apiserver的watch机制由watcher实现。watch的核心方法是serveWatch(),位于staging/src/k8s.io/apiserver/pkg/endpoints/handlers/watch.go中。组件与apiserver事实上建立了一个长连接,通过订阅-发布的模式实现watch。

NewAPIGroupHandler()

这段逻辑比较简单,只中注册了serializer。

s.Handler.GoRestfulContainer.Add(discovery.NewAPIGroupHandler(s.Serializer, apiGroup).WebService())

apiserver的运行

apiserver的运行的逻辑在staging/src/k8s.io/apiserver/pkg/server/genericapiserver.go的Run()中,大致分为如下步骤

  • 如果启用了审计功能,则执行s.AuditBackend.Run()。审计后端可以参考Auditing
  • 开一个routine,专门用来处理收到结束信号后没来得及处理的请求。见s.HandlerChainWaitGroup.Wait()
  • RunPostStartHooks()启动之前注册的postStartHook
  • 向systemd发送READY=1信号
  • 阻塞住,直到接收到stopCh,执行RunPreShutdownHooks()
  • 等待未完成的请求处理结束:s.HandlerChainWaitGroup.Wait()

apiserver如何处理各种请求

apiserver对请求的处理全部由注册的handler实现,其自身除了crd controller之外不对资源做任何的CRUD。本文后续部分会对不同类型的请求分别举例说明整个apiserver的业务逻辑。

请求non-resource,如healthz信息

我们知道访问"/healthz"如果成功会返回"ok"。下面看一下ok是怎么得来的。

PrepareRun()installHealthz()里面定义了这些处理函数。源码的快速浏览层次为

graph LR
kubeAPIServer.PrepareRun-->installHealthz
installHealthz-->InstallHandler
InstallHandler-->InstallPathHandler
InstallPathHandler-->adaptCheckToHandler

check的类型是HealthzChecker,永远开启的是PingHealthz,此外还有LogHealthz等其他类型。adaptCheckToHandler()中将调用checker的Check()方法,若没有产生错误则fmt.Fprint(w, "ok")。所以用户将得到一个"ok"字符串。

pod的CRUD请求

见上面的章节,组件向apiserver发起请求后,apiserver与etcd进行交互,将事件推送给控制器与调度器处理,最后kubelet实现对pod的实际操作。