? kube-apiserver组件负责将Kubernetes的“资源组、资源版本、资源”以RESTful风格的形式对外暴露并提供服务。 在Kubernetes源码中使用了go-restful框架(github.com/emicklei/go-restful),主要原因在于go-restful框架可定制程度高。
? go-restful框架支持多个Container(容器)。一个Container就相当于一个HTTP Server,不同Container之间监控不同的地址和端口,对外提供不同的HTTP服务。每个Container可以包含多个WebService(Web服务),WebService相当于一组不同服务的分类。每个WebService下又包含了多个Router(路由),Router根据HTTP请求的URL路由到对应的处理函数(即Handler Func) 。
? go-restful核心数据结构有Container、WebService、Route。其核心原理是将Container接收到的HTTP请求分发给匹配的WebService,再由WebService分发给Router中的Handler函数。
? kube-apiserver提供了3种HTTP Server服务,用于将庞大的kube-apiserver组件功能进行解耦,这3种HTTP Server分别是APIExtensionsSerer、KubeAPIServer、AggregatorServer。
? kube-apiserver组件启动代码逻辑如下图所示:
kube-apiserver组件启动后的第一件事情是将Kubernetes所支持的资源注册到Scheme资源注册表中,这样后面启动的逻辑才能够从Scheme资源注册表中拿到资源信息并启动和运行APIExtensionsServer、KubeAPIServer、AggregatorServer这3种服务。资源的注册过程并不是通过函数调用触发的,而是通过Go语言的导入(import)和初始化(init)机制触发的。
kube-apiserver资源注册分为两步:第1步,初始化Scheme资源注册表;第2步,注册Kubernetes所支持的资源。kubeapiserver导入了legacyscheme和controlplane包,在legacyscheme包中进行资源注册表的初始化,controlplane包中注册Kubernetes所支持的资源。
var (
// Scheme is the default instanceof runtime.Scheme to which types in the Kubernetes API are already registered.
Scheme = runtime.NewScheme()
// Codecsprovidesaccesstoencodinganddecodingforthescheme
Codecs = serializer.NewCodecFactory(Scheme)
// ParameterCodechandlesversioningofobjectsthatareconvertedtoqueryparameters.
ParameterCodec = runtime.NewParameterCodec(Scheme)
)
import (
//These imports are the API groups the API server will support.
_ "k8s.io/kubernetes/pkg/apis/admission/install"
_ "k8s.io/kubernetes/pkg/apis/admissionregistration/install"
_ "k8s.io/kubernetes/pkg/apis/apiserverinternal/install"
_ "k8s.io/kubernetes/pkg/apis/apps/install"
_ "k8s.io/kubernetes/pkg/apis/authentication/install"
_ "k8s.io/kubernetes/pkg/apis/authorization/install"
_ "k8s.io/kubernetes/pkg/apis/autoscaling/install"
_ "k8s.io/kubernetes/pkg/apis/batch/install"
_ "k8s.io/kubernetes/pkg/apis/certificates/install"
_ "k8s.io/kubernetes/pkg/apis/coordination/install"
_ "k8s.io/kubernetes/pkg/apis/core/install"
_ "k8s.io/kubernetes/pkg/apis/discovery/install"
_ "k8s.io/kubernetes/pkg/apis/events/install"
_ "k8s.io/kubernetes/pkg/apis/extensions/install"
_ "k8s.io/kubernetes/pkg/apis/flowcontrol/install"
_ "k8s.io/kubernetes/pkg/apis/imagepolicy/install"
_ "k8s.io/kubernetes/pkg/apis/networking/install"
_ "k8s.io/kubernetes/pkg/apis/node/install"
_ "k8s.io/kubernetes/pkg/apis/policy/install"
_ "k8s.io/kubernetes/pkg/apis/rbac/install"
_ "k8s.io/kubernetes/pkg/apis/resource/install"
_ "k8s.io/kubernetes/pkg/apis/scheduling/install"
_ "k8s.io/kubernetes/pkg/apis/storage/install"
)
func init() {
Install(legacyscheme.Scheme)
}
// Install registers the API group and adds types to a scheme
// AddToScheme()中会调用每种资源定义的 addKnownTypes()函数,addKnownTypes()中会调用scheme.AddKnownTypes()完成资源注册
func Install(scheme *runtime.Scheme) {
utilruntime.Must(apps.AddToScheme(scheme))
utilruntime.Must(v1beta1.AddToScheme(scheme))
utilruntime.Must(v1beta2.AddToScheme(scheme))
utilruntime.Must(v1.AddToScheme(scheme))
utilruntime.Must(scheme.SetVersionPriority(v1.SchemeGroupVersion, v1beta2.SchemeGroupVersion, v1beta1.SchemeGroupVersion))
}
APIExtensionsServer 和 AggregatorServer 资源注册过程在包 extensionsapiserver 和 aggregatorapiserver中完成,文件cmd/kube-apiserver/app/server.go如下。
import (
...
extensionsapiserver "k8s.io/apiextensions-apiserver/pkg/apiserver"
...
aggregatorapiserver"k8s.io/kube-aggregator/pkg/apiserver"
...
"k8s.io/kubernetes/pkg/api/legacyscheme"
"k8s.io/kubernetes/pkg/controlplane"
)
? APIServer通用配置是kube-apiserver不同模块实例化所需的配置,在函数BuildGenericConfig()中实现,流程如下。
创建APIExtensionsServer的代码在文件vendor/k8s.io/apiextensionsapiserver/pkg/apiserver/apiserver.go中的New()函数实现。
//New returns a new instance of CustomResourceDefinitions from the given config.
func (c completedConfig) New(delegationTarget genericapiserver.DelegationTarget) (*CustomResourceDefinitions, error) {
// 1.创建 GenericAPIServer,APIExtensionsServer的运行依赖于GenericAPIServer,通过c.GenericConfig.New函数创建名为apiextensions-apiserver的服务。
genericServer, err := c.GenericConfig.New("apiextensions-apiserver",delegationTarget)
if err!= nil {
return nil, err
}
...
// 2.实例化 CustomResourceDefinitions,APIExtensionsServer(API扩展服务)通过CustomResourceDefinitions对象进行管理,
// 实例化该对象后才能注册APIExtensionsServer下的资源。
s := &CustomResourceDefinitions{
GenericAPIServer:genericServer,
}
apiResourceConfig := c.GenericConfig.MergedResourceConfig
// 3.实例化 APIGroupInfo,APIGroupInfo对象用于描述资源组信息, 其中该对象的VersionedResourcesStorageMap字段用于存储资源与资源存储对象的对应关系。
// 表现形式为 map[string]map[string]rest.Storage(即<资源版本>/<资源>/<资源存储对象>)
// 注意:一个资源组对应一个APIGroupInfo对象,每个资源(包括子资源) 对应一个资源存储对象。
apiGroupInfo := genericapiserver.NewDefaultAPIGroupInfo(apiextensions.GroupName, Scheme,metav1.ParameterCodec, Codecs)
storage := map[string]rest.Storage{}
// 如果 apiextensions.k8s.io/v1 资源组/资源版本已启用,将该资源组、 资源版本下的资源与资源存储对象进行映射
// 并存储至 APIGroupInfo 对象的VersionedResourcesStorageMap字段中。
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
}
// 4.InstallAPIGroup注册APIGroup,在函数 InstallREST()中添加路由,过程如下:
// (1) 遍历 APIGroupInfo, 将<资源组>/<资源版本>/<资源名称>映射到 HTTP PATH 请求路径,
// (2) 通过 InstallREST函数将资源存储对象作为资源的 Handlers 方法
// (3) 最后使用 gorestful的ws.Route将定义好的请求路径和 Handlers 方法添加路由到 go-restful中
if err := s.GenericAPIServer.InstallAPIGroup(&apiGroupInfo); err != nil {
return nil, err
}
...
return s, nil
}
APIExtensionsServer路由的设置在InstallREST()函数中调用a.registerResourceHandlers()函数完成,代码调用过程为:InstallAPIGroup()→s.installAPIResources()→InstallREST(),在InstallREST()的最后,通过container.Add函数将WebService添加到go-restful Container中。
func (g*APIGroupVersion) InstallREST(container *restful.Container) ([]apidiscoveryv2beta1.APIResourceDiscovery, []*storageversion.ResourceInfo, error) {
// prefix定义了 HTTP PATH请求路径,其表现形式为<apiPrefix>/<group>/<version>(即/apis/apiextensions.k8s.io/v1)
prefix := path.Join(g.Root, g.GroupVersion.Group, g.GroupVersion.Version)
// 实例化APIInstaller安装器。
installer := &APIInstaller {
group: g,
prefix: prefix,
minRequestTimeout: g.MinRequestTimeout,
}
// 在installer.Install中创建一个gorestful WebService,并通过a.registerResourceHandlers()函数添加资源对应的路由
apiResources, resourceInfos, ws, registrationErrors := installer.Install()
...
// 将WebService添加到 go-restful Container中
container.Add(ws)
aggregatedDiscoveryResources, err := ConvertGroupVersionIntoToDiscovery(apiResources)
if err != nil {
registrationErrors = append(registrationErrors, err)
}
return aggregatedDiscoveryResources, removeNonPersistedResources(resourceInfos), utilerrors.NewAggregate(registrationErrors)
}
registerResourceHandlers() 函数中为资源注册对应的Handlers方法(即资源存储对象Resource Storage) ,完成资源与资源Handlers方法的绑定并为go-restful WebService添加该路由。
func (a *APIInstaller) registerResourceHandlers(pathstring, storagerest.Storage, ws*restful.WebService) (*metav1.APIResource, *storageversion.ResourceInfo, error) {
...
// 通过类型断言的方式确定当前storage支持那些操作,确定 storage 是否实现 creat、list 和 get等函数。
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 {
// 构造无命名空间namespace资源的action
case !namespaceScoped:
...
// 构造有命名空间namespace资源的action
default:
// 填充 resource path
namespaceParamName := "namespaces"
//Handler for standard REST verbs(GET, PUT, POST and DELETE).
namespaceParam := ws.PathParameter("namespace", "object name and auth scope, such as for teams and projects").DataType("string")
namespacedPath := namespaceParamName + "/{namespace}/" + resource
namespaceParams := []*restful.Parameter{namespaceParam}
...
// 根据storage支持的操作添加相关的action到actions切片中
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)
...
}
...
// 根据action添加路由
for _, action := range actions {
...
// 实例化 restful.RouteBuilder
routes:=[]*restful.RouteBuilder{}
...
// 按照acation.Verb添加路由
switch action.Verb {
case "GET": // Get a resource.
// 设置路由对应的处理函数,handler处理函数并不是直接调用 Creater ,Updater等接口定义的方法,
// 而是在外面包了一层代码进行一些额外的处理,例如对象的编解码,admission control 的处理逻辑,针对 watch 这种长链接需要进行协议的处理等,
// 相关的定义在k8s.io/apiserver/pkg/endpoints/handlers包下。
var handler restful.RouteFunction
if isGetterWithOptions {
// restfulGetResourceWithOptions()->GetResourceWithOptions()->r.Get(), GetResourceWithOptions()中会解析url中的选项,构建runtime.Object,然后交给r.Get()处理
handler = restfulGetResourceWithOptions(getterWithOptions,reqScope,isSubresource)
}else{
// restfulGetResource()->GetResource()->r.Get(), GetResource()中将要查询串解码成metav1.GetOptions,然后交给r.Get()处理
handler = restfulGetResource(getter,reqScope)
}
// 设置路由监控函数
if needOverride {
// need change the reported verb
handler = metrics.InstrumentRouteFunc(verbOverrider.OverrideMetricsVerb(action.Verb),group,version,resource,subresource,requestScope,metrics.APIServerComponent,deprecated,removedRelease,handler)
}else{
handler = metrics.InstrumentRouteFunc(action.Verb, group, version, resource, subresource, requestScope, metrics.APIServerComponent, deprecated, removedRelease, handler)
}
// AddWarningsHandler()中会将警告信息添加到req.Request.Context()中
handler = utilwarning.AddWarningsHandler(handler, warnings)
doc := "readthespecified" + kind
if isSubresource {
doc = "read" + subresource + "ofthespecified" + kind
}
// 将 handler注册到 rest容器中
route := ws.GET(action.Path).To(handler).
Doc(doc).
Param(ws.QueryParameter("pretty", "If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget).")).
Operation("read" + namespaced + kind + strings.Title(subresource) + operationSuffix).
Produces(append(storageMeta.ProducesMIMETypes(action.Verb), mediaTypes...)...).
Returns(http.StatusOK, "OK", producedObject).
Writes(producedObject)
if isGetterWithOptions {
if err := AddObjectParams(ws, route, versionedGetOptions); err != nil {
return nil, nil, err
}
}
addParams(route, action.Params)
routes=append(routes, route)
case "LIST": // List all resources of a kind.
...
case "PUT": // Update a resource.
...
case "PATCH": // Partially update a resource.
...
case "POST": // Create a resource.
...
case "DELETE": // Delete a resource.
...
case "WATCH": // Watch a resource.
...
case "WATCHLIST": // Watch all resources of a kind.
...
case "CONNECT":
...
default:
return nil, nil, fmt.Errorf("unrecognized action verb:%s", action.Verb)
}
// 添加所有路由到 WebService中
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)
}
//Note: update GetAuthorizerAttributes() when adding a custom handler.
}
...
}
创建KubeAPIServer的流程与创建APIExtensionsServer的流程类似,其原理都是将<资源组>/<资源版本>/<资源>与资源存储对象进行映射并将其存储至APIGroupInfo对象的VersionedResourcesStorageMap字段中。
未完待续。。。