接着 Kubernetes:kube-apiserver 之启动流程(一) 加以介绍。

1.2.2 创建 APIExtensions Server

创建完通用 APIServer 后继续创建 APIExtensions Server

func (c completedConfig) New(delegationTarget genericapiserver.DelegationTarget) (*CustomResourceDefinitions, error) {
genericServer, err := c.GenericConfig.New("apiextensions-apiserver", delegationTarget) s := &CustomResourceDefinitions{
GenericAPIServer: genericServer,
} // 存储建立 REST API 到资源实体的信息
apiGroupInfo := genericapiserver.NewDefaultAPIGroupInfo(apiextensions.GroupName, Scheme, metav1.ParameterCodec, Codecs) // 资源实体
storage := map[string]rest.Storage{} // customresourcedefinitions
if resource := "customresourcedefinitions"; apiResourceConfig.ResourceEnabled(v1.SchemeGroupVersion.WithResource(resource)) {
// 创建资源实体
customResourceDefinitionStorage, err := customresourcedefinition.NewREST(Scheme, c.GenericConfig.RESTOptionsGetter)
if err != nil {
return nil, err
}
storage[resource] = customResourceDefinitionStorage
storage[resource+"/status"] = customresourcedefinition.NewStatusREST(Scheme, customResourceDefinitionStorage)
}
if len(storage) > 0 {
apiGroupInfo.VersionedResourcesStorageMap[v1.SchemeGroupVersion.Version] = storage
} if err := s.GenericAPIServer.InstallAPIGroup(&apiGroupInfo); err != nil {
return nil, err
}

APIGroupInfo 对象用于描述资源组信息,storage 存储资源到资源实体的对应关系。

资源实体,通过 NewREST() 函数创建。

# kubernetes/vendor/k8s.io/apiextensions-apiserver/pkg/registry/customresourcedefinition/etcd.go
package customresourcedefinition // NewREST returns a RESTStorage object that will work against API services.
func NewREST(scheme *runtime.Scheme, optsGetter generic.RESTOptionsGetter) (*REST, error) {
strategy := NewStrategy(scheme) store := &genericregistry.Store{
NewFunc: func() runtime.Object { return &apiextensions.CustomResourceDefinition{} },
NewListFunc: func() runtime.Object { return &apiextensions.CustomResourceDefinitionList{} },
PredicateFunc: MatchCustomResourceDefinition,
DefaultQualifiedResource: apiextensions.Resource("customresourcedefinitions"),
SingularQualifiedResource: apiextensions.Resource("customresourcedefinition"), CreateStrategy: strategy,
UpdateStrategy: strategy,
DeleteStrategy: strategy,
ResetFieldsStrategy: strategy, // TODO: define table converter that exposes more than name/creation timestamp
TableConvertor: rest.NewDefaultTableConvertor(apiextensions.Resource("customresourcedefinitions")),
}
options := &generic.StoreOptions{RESTOptions: optsGetter, AttrFunc: GetAttrs}
if err := store.CompleteWithOptions(options); err != nil {
return nil, err
}
return &REST{store}, nil
}

可以看到,资源实体是在资源包 customresourcedefinitionetcd.go 中创建的,创建的资源实体负责和 etcd 交互。

(关于 etcd 交互的部分先不讲,后续会专门介绍。)

创建完资源实体后,通过 apiGroupInfo.VersionedResourcesStorageMap[v1.SchemeGroupVersion.Version] = storage 将资源实体存储到 apiGroupInfo

继续调用 InstallAPIGroup(apiGroupInfo *APIGroupInfo) 安装 REST API

# kubernetes/vendor/k8s.io/apiserver/pkg/server/genericapiserver.go
func (s *GenericAPIServer) InstallAPIGroup(apiGroupInfo *APIGroupInfo) error {
return s.InstallAPIGroups(apiGroupInfo)
} func (s *GenericAPIServer) InstallAPIGroups(apiGroupInfos ...*APIGroupInfo) error {
for _, apiGroupInfo := range apiGroupInfos {
// 调用 installAPIResources
if err := s.installAPIResources(APIGroupPrefix, apiGroupInfo, openAPIModels); err != nil {
return fmt.Errorf("unable to install api resources: %v", err)
}
}
s.DiscoveryGroupManager.AddGroup(apiGroup)
s.Handler.GoRestfulContainer.Add(discovery.NewAPIGroupHandler(s.Serializer, apiGroup).WebService())
} // installAPIResources is a private method for installing the REST storage backing each api groupversionresource
func (s *GenericAPIServer) installAPIResources(apiPrefix string, apiGroupInfo *APIGroupInfo, typeConverter managedfields.TypeConverter) error {
for _, groupVersion := range apiGroupInfo.PrioritizedVersions {
apiGroupVersion, err := s.getAPIGroupVersion(apiGroupInfo, groupVersion, apiPrefix)
if err != nil {
return err
} // 调用 InstallREST
discoveryAPIResources, r, err := apiGroupVersion.InstallREST(s.Handler.GoRestfulContainer)
}
} # kubernetes/vendor/k8s.io/apiserver/pkg/endpoints/groupversion.go
func (g *APIGroupVersion) InstallREST(container *restful.Container) ([]apidiscoveryv2beta1.APIResourceDiscovery, []*storageversion.ResourceInfo, error) {
prefix := path.Join(g.Root, g.GroupVersion.Group, g.GroupVersion.Version)
installer := &APIInstaller{
group: g,
prefix: prefix,
minRequestTimeout: g.MinRequestTimeout,
} // 调用 Install
apiResources, resourceInfos, ws, registrationErrors := installer.Install()
container.Add(ws) return aggregatedDiscoveryResources, removeNonPersistedResources(resourceInfos), utilerrors.NewAggregate(registrationErrors)
} # kubernetes/vendor/k8s.io/apiserver/pkg/endpoints/installer.go
// Install handlers for API resources.
func (a *APIInstaller) Install() ([]metav1.APIResource, []*storageversion.ResourceInfo, *restful.WebService, []error) {
for _, path := range paths {
// 注册资源 Handler
apiResource, resourceInfo, err := a.registerResourceHandlers(path, a.group.Storage[path], ws)
if err != nil {
errors = append(errors, fmt.Errorf("error in registering resource: %s, %v", path, err))
}
if apiResource != nil {
apiResources = append(apiResources, *apiResource)
}
if resourceInfo != nil {
resourceInfos = append(resourceInfos, resourceInfo)
}
}
return apiResources, resourceInfos, ws, errors
}

如上例所示,注册资源 REST API 的调用链很长,通过逐层调用,走到 registerResourceHandlers 注册资源 handler

registerResourceHandlers 函数非常的长,主要抓一点:注册 RESTful API 的资源 handler 需要什么?回答好这个问题基本上就能抓住 registerResourceHandlers 的精髓了。

注册资源 handler 需要知道资源的 API path 和资源实体(指和 etcd 交互的资源实例)的对应关系,接着需要知道哪些 action 可以访问 API path

围绕这两块看资源 handler 的注册过程。

func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storage, ws *restful.WebService) (*metav1.APIResource, *storageversion.ResourceInfo, error) {
// what verbs are supported by the storage, used to know what verbs we support per path
creater, isCreater := storage.(rest.Creater)
namedCreater, isNamedCreater := storage.(rest.NamedCreater)
lister, isLister := storage.(rest.Lister)
getter, isGetter := storage.(rest.Getter)
getterWithOptions, isGetterWithOptions := storage.(rest.GetterWithOptions)
gracefulDeleter, isGracefulDeleter := storage.(rest.GracefulDeleter)
collectionDeleter, isCollectionDeleter := storage.(rest.CollectionDeleter)
updater, isUpdater := storage.(rest.Updater)
patcher, isPatcher := storage.(rest.Patcher)
watcher, isWatcher := storage.(rest.Watcher)
connecter, isConnecter := storage.(rest.Connecter)
storageMeta, isMetadata := storage.(rest.StorageMetadata)
storageVersionProvider, isStorageVersionProvider := storage.(rest.StorageVersionProvider)
gvAcceptor, _ := storage.(rest.GroupVersionAcceptor) // Get the list of actions for the given scope.
switch {
case !namespaceScoped:
...
default:
// Handler for standard REST verbs (GET, PUT, POST and DELETE).
actions := []action{} actions = appendIf(actions, action{"LIST", resourcePath, resourceParams, namer, false}, isLister)
actions = appendIf(actions, action{"POST", resourcePath, resourceParams, namer, false}, isCreater)
actions = appendIf(actions, action{"DELETECOLLECTION", resourcePath, resourceParams, namer, false}, isCollectionDeleter)
// DEPRECATED in 1.11
actions = appendIf(actions, action{"WATCHLIST", "watch/" + resourcePath, resourceParams, namer, false}, allowWatchList) actions = appendIf(actions, action{"GET", itemPath, nameParams, namer, false}, isGetter)
if getSubpath {
actions = appendIf(actions, action{"GET", itemPath + "/{path:*}", proxyParams, namer, false}, isGetter)
}
actions = appendIf(actions, action{"PUT", itemPath, nameParams, namer, false}, isUpdater)
actions = appendIf(actions, action{"PATCH", itemPath, nameParams, namer, false}, isPatcher)
actions = appendIf(actions, action{"DELETE", itemPath, nameParams, namer, false}, isGracefulDeleter)
// DEPRECATED in 1.11
actions = appendIf(actions, action{"WATCH", "watch/" + itemPath, nameParams, namer, false}, isWatcher)
actions = appendIf(actions, action{"CONNECT", itemPath, nameParams, namer, false}, isConnecter)
actions = appendIf(actions, action{"CONNECT", itemPath + "/{path:*}", proxyParams, namer, false}, isConnecter && connectSubpath)
} for _, action := range actions {
switch action.Verb {
case "GET": // Get a resource.
var handler restful.RouteFunction
if isGetterWithOptions {
handler = restfulGetResourceWithOptions(getterWithOptions, reqScope, isSubresource)
} else {
handler = restfulGetResource(getter, reqScope)
} route := ws.GET(action.Path).To(handler).
Doc(doc).
Param(ws.QueryParameter("pretty", "If 'true', then the output is pretty printed.")).
Operation("read"+namespaced+kind+strings.Title(subresource)+operationSuffix).
Produces(append(storageMeta.ProducesMIMETypes(action.Verb), mediaTypes...)...).
Returns(http.StatusOK, "OK", producedObject).
Writes(producedObject) routes = append(routes, route)
} for _, route := range routes {
route.Metadata(ROUTE_META_GVK, metav1.GroupVersionKind{
Group: reqScope.Kind.Group,
Version: reqScope.Kind.Version,
Kind: reqScope.Kind.Kind,
})
route.Metadata(ROUTE_META_ACTION, strings.ToLower(action.Verb))
ws.Route(route)
}
}
}

可以看到,通过资源实体 storage 支持的接口类型可以反射出资源支持的方法。接着将支持的方法加入 actionsactions 存储的是 action.Verb 和支持的资源 API path 信息。

拿到 actions 后,通过匹配 actions.Verb 建立 action.Verb -> action.Path -> handler 的路由。其中,创建 handler 需要用到 storage,因为 handler 是通过 storageetcd 交互的。

通过 go-restful 库建立上述路由,接着 ws.Route(route)route 加入到 restful.WebService 中。

回头看 InstallREST

func (g *APIGroupVersion) InstallREST(container *restful.Container) ([]apidiscoveryv2beta1.APIResourceDiscovery, []*storageversion.ResourceInfo, error) {

	apiResources, resourceInfos, ws, registrationErrors := installer.Install()
container.Add(ws) return aggregatedDiscoveryResources, removeNonPersistedResources(resourceInfos), utilerrors.NewAggregate(registrationErrors)
} func (s *GenericAPIServer) installAPIResources(apiPrefix string, apiGroupInfo *APIGroupInfo, typeConverter managedfields.TypeConverter) error {
discoveryAPIResources, r, err := apiGroupVersion.InstallREST(s.Handler.GoRestfulContainer)
}

通过 container.Add(ws)ws 添加到 go-restfulcontainer 中,该 container 即为 APIExtensions ServerHandler 所提供的 GoRestfulContainer

这里介绍了 APIExtensions ServerREST API 创建过程。对于 KubeAPIServerAggregatorServer 的创建过程与之类似,不过多介绍。

REST API 创建好以后,下一步就到如何运行 kube-apiserver了。

1.3 运行 kube-apiserver

kube-apiserver 作为提供 RESTful API 的组件,其运行主要是监听端口和启动服务。理清了这点,就能在复杂的运行代码中找出头绪。

调用 APIAggregator.PrepareRunpreparedAPIAggregator.Run 运行 kube-apiserver

func Run(opts options.CompletedOptions, stopCh <-chan struct{}) error {
prepared, err := server.PrepareRun()
if err != nil {
return err
} return prepared.Run(stopCh)
}

启动过程在 PrepareRun 中。

func (s *APIAggregator) PrepareRun() (preparedAPIAggregator, error) {
prepared := s.GenericAPIServer.PrepareRun()
return preparedAPIAggregator{APIAggregator: s, runnable: prepared}, nil
}

运行 prepared.Run(stopCh) 实际调用的是 preparedGenericAPIServer.Run 方法。

func (s preparedGenericAPIServer) Run(stopCh <-chan struct{}) error {
// 调用 preparedGenericAPIServer.NonBlockingRun
stoppedCh, listenerStoppedCh, err := s.NonBlockingRun(stopHttpServerCh, shutdownTimeout)
if err != nil {
return err
}
} func (s preparedGenericAPIServer) NonBlockingRun(stopCh <-chan struct{}, shutdownTimeout time.Duration) (<-chan struct{}, <-chan struct{}, error) {
if s.SecureServingInfo != nil && s.Handler != nil {
var err error
// 调用 SecureServingInfo.Serve
stoppedCh, listenerStoppedCh, err = s.SecureServingInfo.Serve(s.Handler, shutdownTimeout, internalStopCh)
if err != nil {
close(internalStopCh)
return nil, nil, err
}
}
} func (s *SecureServingInfo) Serve(handler http.Handler, shutdownTimeout time.Duration, stopCh <-chan struct{}) (<-chan struct{}, <-chan struct{}, error) {
// 组 http.Server
secureServer := &http.Server{
Addr: s.Listener.Addr().String(),
Handler: handler,
MaxHeaderBytes: 1 << 20,
TLSConfig: tlsConfig, IdleTimeout: 90 * time.Second, // matches http.DefaultTransport keep-alive timeout
ReadHeaderTimeout: 32 * time.Second, // just shy of requestTimeoutUpperBound
} return RunServer(secureServer, s.Listener, shutdownTimeout, stopCh)
} func RunServer(
server *http.Server,
ln net.Listener,
shutDownTimeout time.Duration,
stopCh <-chan struct{},
) (<-chan struct{}, <-chan struct{}, error) {
go func() {
// 调用 http 的 Server.Serve 提供 `RESTful API` 服务
err := server.Serve(listener) msg := fmt.Sprintf("Stopped listening on %s", ln.Addr().String())
}
}

可以看到,最终调用 http 包的 Server.Serve 提供 RESTful API 服务。

至此,已介绍完 kube-apiserver 从启动到运行的核心逻辑。下一篇,将重点介绍 kube-apiserver 是怎么和 etcd 进行交互的。


Kubernetes:kube-apiserver 之启动流程(二)的更多相关文章

  1. 《转》深入理解Activity启动流程(二)–Activity启动相关类的类图

    本文原创作者:Cloud Chou. 出处:本文链接 本系列博客将详细阐述Activity的启动流程,这些博客基于Cm 10.1源码研究. 在介绍Activity的详细启动流程之前,先为大家介绍Act ...

  2. 深入理解Activity启动流程(二)–Activity启动相关类的类图

    本文原创作者:Cloud Chou. 欢迎转载,请注明出处和本文链接 本系列博客将详细阐述Activity的启动流程,这些博客基于Cm 10.1源码研究. 在介绍Activity的详细启动流程之前,先 ...

  3. CC3200模块的内存地址划分和bootloader,启动流程(二)

    1. 首先启动内部ROM固化的BOOT,然后这个ROM启动需要使用内存空间0X2000 0000 --- 0X2000 4000共16K的空间.一级BOOT的作用是串口升级和驱动库. 2. 然后是二级 ...

  4. 《转》深入理解Activity启动流程(四)–Activity Task的调度算法

    本文原创作者:Cloud Chou. 出处:本文链接 本系列博客将详细阐述Activity的启动流程,这些博客基于Cm 10.1源码研究. 深入理解Activity启动流程(一)--Activity启 ...

  5. 《转》深入理解Activity启动流程(三)–Activity启动的详细流程2

    本文原创作者:Cloud Chou. 出处:本文链接 本系列博客将详细阐述Activity的启动流程,这些博客基于Cm 10.1源码研究. 深入理解Activity启动流程(一)--Activity启 ...

  6. 《转》深入理解Activity启动流程(三)–Activity启动的详细流程1

    本文原创作者:Cloud Chou. 出处:本文链接 本系列博客将详细阐述Activity的启动流程,这些博客基于Cm 10.1源码研究. 深入理解Activity启动流程(一)--Activity启 ...

  7. 《转》深入理解Activity启动流程(一)–Activity启动的概要流程

    本文原创作者:Cloud Chou. 原文地址:http://www.cloudchou.com/android/post-788.html Android中启动某个Activity,将先启动Acti ...

  8. 深入理解Activity启动流程(四)–Activity Task的调度算法

    本系列博客将详细阐述Activity的启动流程,这些博客基于Cm 10.1源码研究. 深入理解Activity启动流程(一)--Activity启动的概要流程 深入理解Activity启动流程(二)- ...

  9. 深入理解Activity启动流程(三)–Activity启动的详细流程2

    本文原创作者:Cloud Chou. 欢迎转载,请注明出处和本文链接 本系列博客将详细阐述Activity的启动流程,这些博客基于Cm 10.1源码研究. 深入理解Activity启动流程(一)--A ...

  10. 深入理解Activity启动流程(一)–Activity启动的概要流程

    概述 Android中启动某个Activity,将先启动Activity所在的应用.应用启动时会启动一个以应用包名为进程名的进程,该进程有一个主线程,叫ActivityThread,也叫做UI线程. ...

随机推荐

  1. linux top中 VSS,RSS,PSS,USS 4个字段的解读

    参考文章:linux中top命令 VSS,RSS,PSS,USS 四个内存字段的解读

  2. Blazor前后端框架Known-V1.2.8

    V1.2.8 Known是基于C#和Blazor开发的前后端分离快速开发框架,开箱即用,跨平台,一处代码,多处运行. Gitee: https://gitee.com/known/Known Gith ...

  3. Pandas: 将dataframe转换为dict

    背景 将Dataframe的每一列数据转换成字典并保存.也就是字段名变为key, 数值变为value. 方案 以下是效果图 参考链接 https://blog.csdn.net/hanyunkaka/ ...

  4. 通过Proxy和Reflect实现vue的响应式原理

    vue3通过Proxy+Reflect实现响应式,vue2通过defineProperty来实现 Proxy Proxy是什么 Proxy是ES6中增加的类,表示代理. 如果我们想要监听对象的操作过程 ...

  5. nginx配置gzip压缩

    前言 为提高用户获取响应数据的速度,Nginx服务器可以将响应数据进行gzip压缩,在减小响应数据的大小后再发送给用户端浏览器. 要想启用gzip压缩,需要浏览器支持gzip压缩功能,目前大多数浏览器 ...

  6. 【Python】@property用法简述

    参考自:Python的@property是干嘛的?作者:Python测试开发 如果我们设置类的属性私有化,那么可以使用@property 使属性可以被外部访问并修改. 在使用 @property 之前 ...

  7. C# 中关于 T 泛型【C# 基础】

    〇.前言 C# 里面的泛型不仅可以使用泛型函数.泛型接口,也可以使用泛型类.泛型委托等等.在使用泛型的时候,它们会自行检测你传入参数的类型,因此它可以为我们省去大量的时间,不用一个个编写方法的重载.与 ...

  8. Programming abstractions in C阅读笔记:p84-p87

    <Programming Abstractions In C>学习第43天,p84-p87总结. 一.技术总结 1.record record也称为structure(结构体),是一种数据 ...

  9. HTTP请求时哪些请求头是默认携带的?

    提起HTTP的请求头和响应头总是一头雾水,因为总是看上去一大堆,好多还不知道是什么意思. 今天我们先研究请求头,我就想,如果我们能弄清楚,在我们什么都不做的情况下,一个最简单的HTTP请求会携带哪些请 ...

  10. 用户空间协议栈设计和netmap综合指南

    本文分享自华为云社区<用户空间协议栈设计和netmap综合指南,将网络效率提升到新高度>,作者:Lion Long . 协议概念 1.1.七层网络模型和五层网络模型 应用层: 最接近用户的 ...