Kubernetes v1.5.0node
k8s里面有各类资源,如Pod、Service、RC、namespaces等资源,用户操做的其实也就是这一大堆资源。但这些资源并非杂乱无章的,使用了GroupVersion的方式组织在一块儿。每一种资源都属于一个Group,而资源还有版本之分,如v一、v1beta1等。
k8s目前正在使用的API groups:git
"core" group:它的REST path是api/v1github
"extensions" group:它的REST path是/apis/extensions/v1beta1bootstrap
"autoscaling", "abac" ...api
服务器的URL的格式:/prefix/group/version/... (例如:/apis/extensions/v1beta1)服务器
APIGroupVersion:对API资源的组织,里面包含了Storage、GroupVersion、Mapper、Serializer、Convertor等成员。Storage是etcd的接口,这是一个map类型,每一种资源都会与etcd创建一个链接;GroupVersion表示该APIGroupVersion属于哪一个Group、哪一个version;Serializer用于序列化,反序列化;Convertor提供各个不一样版本进行转化的接口;Mapper实现了RESTMapper接口。restful
type APIGroupVersion struct { // key存在对象的url,value是一个rest.Storage,用于对接etcd存储 Storage map[string]rest.Storage // 该group的prefix,例如核心组的Root是'/api' Root string // 包含相似'api/v1'这样的string,用于标识这个实例 GroupVersion unversioned.GroupVersion // OptionsExternalVersion controls the Kubernetes APIVersion used for common objects in the apiserver // schema like api.Status, api.DeleteOptions, and api.ListOptions. Other implementors may // define a version "v1beta1" but want to use the Kubernetes "v1" internal objects. If // empty, defaults to GroupVersion. OptionsExternalVersion *unversioned.GroupVersion // 关键性成员 Mapper meta.RESTMapper // 对象序列化和反序列化器 Serializer runtime.NegotiatedSerializer ParameterCodec runtime.ParameterCodec // 如下4个都是被赋值为Scheme结构 Typer runtime.ObjectTyper Creater runtime.ObjectCreater // 相互转换任意api版本的对象,须要事先注册转换函数 Convertor runtime.ObjectConvertor Copier runtime.ObjectCopier Linker runtime.SelfLinker // 用于访问许可控制 Admit admission.Interface Context api.RequestContextMapper MinRequestTimeout time.Duration // SubresourceGroupVersionKind contains the GroupVersionKind overrides for each subresource that is // accessible from this API group version. The GroupVersionKind is that of the external version of // the subresource. The key of this map should be the path of the subresource. The keys here should // match the keys in the Storage map above for subresources. SubresourceGroupVersionKind map[string]unversioned.GroupVersionKind // ResourceLister is an interface that knows how to list resources // for this API Group. ResourceLister APIResourceLister }
APIGroupVersion的建立接口是pkg/genericapiserver/genericapiserver.go中的newAPIGroupVersion()接口,在接口在建立APIGroupVersion还用到了好几个别的结构:APIGroupInfo、Scheme、GroupMeta。下面一个一个介绍:
APIGroupInfo:app
type APIGroupInfo struct { // 该Group的元信息 GroupMeta apimachinery.GroupMeta // 不一样版本的全部的Storage VersionedResourcesStorageMap map[string]map[string]rest.Storage // OptionsExternalVersion controls the APIVersion used for common objects in the // schema like api.Status, api.DeleteOptions, and api.ListOptions. Other implementors may // define a version "v1beta1" but want to use the Kubernetes "v1" internal objects. // If nil, defaults to groupMeta.GroupVersion. // TODO: Remove this when https://github.com/kubernetes/kubernetes/issues/19018 is fixed. OptionsExternalVersion *unversioned.GroupVersion // core group的话,对应的就是api.Scheme Scheme *runtime.Scheme // NegotiatedSerializer controls how this group encodes and decodes data NegotiatedSerializer runtime.NegotiatedSerializer // ParameterCodec performs conversions for query parameters passed to API calls ParameterCodec runtime.ParameterCodec // 全部resources信息,key就是resource的path // 好比:key为"replicationcontrollers/scale",GroupVersionKind: autoscaling, v1, Scale SubresourceGroupVersionKind map[string]unversioned.GroupVersionKind }
Scheme: 用于API资源之间的序列化、反序列化、版本转换。Scheme里面还有好几个map,前面的结构体存储的都是unversioned.GroupVersionKind、unversioned.GroupVersion这些东西,这些东西本质上只是表示资源的字符串标识,Scheme存储了对应着标志的具体的API资源的结构体,即relect.Type框架
type Scheme struct { // versionMap allows one to figure out the go type of an object with // the given version and name. gvkToType map[unversioned.GroupVersionKind]reflect.Type // typeToGroupVersion allows one to find metadata for a given go object. // The reflect.Type we index by should *not* be a pointer. typeToGVK map[reflect.Type][]unversioned.GroupVersionKind // unversionedTypes are transformed without conversion in ConvertToVersion. unversionedTypes map[reflect.Type]unversioned.GroupVersionKind // unversionedKinds are the names of kinds that can be created in the context of any group // or version // TODO: resolve the status of unversioned types. unversionedKinds map[string]reflect.Type // Map from version and resource to the corresponding func to convert // resource field labels in that version to internal version. fieldLabelConversionFuncs map[string]map[string]FieldLabelConversionFunc // defaulterFuncs is an array of interfaces to be called with an object to provide defaulting // the provided object must be a pointer. defaulterFuncs map[reflect.Type]func(interface{}) // converter stores all registered conversion functions. It also has // default coverting behavior. converter *conversion.Converter // cloner stores all registered copy functions. It also has default // deep copy behavior. cloner *conversion.Cloner }
GroupMeta: 主要包括Group的元信息,里面的成员RESTMapper,与APIGroupVersion同样,其实APIGroupVersion的RESTMapper直接取之于GroupMeta的RESTMapper.一个Group可能包含多个版本,存储在GroupVersion中,而GroupVersion是默认存储在etcd中的版本。ide
type GroupMeta struct { // 默认版本 GroupVersion unversioned.GroupVersion // 该Group中可能会有多个版本,该字段就包含了全部的versions GroupVersions []unversioned.GroupVersion // 用于编解码 Codec runtime.Codec // SelfLinker can set or get the SelfLink field of all API types. // TODO: when versioning changes, make this part of each API definition. // TODO(lavalamp): Combine SelfLinker & ResourceVersioner interfaces, force all uses // to go through the InterfacesFor method below. SelfLinker runtime.SelfLinker // 用于类型,对象之间的转换 RESTMapper meta.RESTMapper // InterfacesFor returns the default Codec and ResourceVersioner for a given version // string, or an error if the version is not known. // TODO: make this stop being a func pointer and always use the default // 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 }
RESTMapper: 用于管理全部对象的信息。外部要获取的话,直接经过version,group获取到RESTMapper,而后经过kind类型能够获取到对应的信息。该RESTMapper实际上是实现了一个DefaultRESTMapper结构。
type DefaultRESTMapper struct { defaultGroupVersions []unversioned.GroupVersion resourceToKind map[unversioned.GroupVersionResource]unversioned.GroupVersionKind kindToPluralResource map[unversioned.GroupVersionKind]unversioned.GroupVersionResource kindToScope map[unversioned.GroupVersionKind]RESTScope singularToPlural map[unversioned.GroupVersionResource]unversioned.GroupVersionResource pluralToSingular map[unversioned.GroupVersionResource]unversioned.GroupVersionResource interfacesFunc VersionInterfacesFunc // aliasToResource is used for mapping aliases to resources aliasToResource map[string][]string }
APIRegistrationManager:这个结构体主要提供了已经"registered"的概念,将全部已经注册的,已经激活的,第三方的的GroupVersions进行了汇总,还包括了各个GroupVersion的GroupMeta(元数据)。
type APIRegistrationManager struct { // 因此已经registered的GroupVersions registeredVersions map[unversioned.GroupVersion]struct{} // 第三方注册的GroupVersions,这些都向apiServer动态注册的 thirdPartyGroupVersions []unversioned.GroupVersion // 全部已经enable的GroupVersions,能够经过EnableVersions()将要enable的GroupVersion加入进来。 // 只有enable了,才能使用对应的GroupVersion enabledVersions map[unversioned.GroupVersion]struct{} // 全部groups的GroupMeta groupMetaMap map[string]*apimachinery.GroupMeta // 跟环境变量'KUBE_API_VERSIONS'有关 envRequestedVersions []unversioned.GroupVersion }
该结构的路径:pkg/apimachinery/registered/registered.go
在该文件中咱们能看到初始化了一个DefaultAPIRegistrationManager对象:
var ( DefaultAPIRegistrationManager = NewOrDie(os.Getenv("KUBE_API_VERSIONS")) )
进入NewOrDie()接口看下:
func NewOrDie(kubeAPIVersions string) *APIRegistrationManager { m, err := NewAPIRegistrationManager(kubeAPIVersions) if err != nil { glog.Fatalf("Could not construct version manager: %v (KUBE_API_VERSIONS=%q)", err, kubeAPIVersions) } return m } func NewAPIRegistrationManager(kubeAPIVersions string) (*APIRegistrationManager, error) { m := &APIRegistrationManager{ registeredVersions: map[unversioned.GroupVersion]struct{}{}, thirdPartyGroupVersions: []unversioned.GroupVersion{}, enabledVersions: map[unversioned.GroupVersion]struct{}{}, groupMetaMap: map[string]*apimachinery.GroupMeta{}, envRequestedVersions: []unversioned.GroupVersion{}, } // 若是环境变量KUBE_API_VERSIONS进行了设置的话,进行遍历 if len(kubeAPIVersions) != 0 { // 经过逗号进行分隔 for _, version := range strings.Split(kubeAPIVersions, ",") { // 解析version并转换成GroupVersion格式 // 通常这里的version是group/version格式,好比'/api/v1' gv, err := unversioned.ParseGroupVersion(version) if err != nil { return nil, fmt.Errorf("invalid api version: %s in KUBE_API_VERSIONS: %s.", version, kubeAPIVersions) } // 而后将该gv放入envRequestedVersions m.envRequestedVersions = append(m.envRequestedVersions, gv) } } // 不然返回一个空的APIRegistrationManager return m, nil }
瞅了下咱们正在使用的环境,没有配置KUBE_API_VERSIONS,即返回了一个空的结构,还提供了好多方法。
var ( ValidateEnvRequestedVersions = DefaultAPIRegistrationManager.ValidateEnvRequestedVersions AllPreferredGroupVersions = DefaultAPIRegistrationManager.AllPreferredGroupVersions RESTMapper = DefaultAPIRegistrationManager.RESTMapper GroupOrDie = DefaultAPIRegistrationManager.GroupOrDie AddThirdPartyAPIGroupVersions = DefaultAPIRegistrationManager.AddThirdPartyAPIGroupVersions IsThirdPartyAPIGroupVersion = DefaultAPIRegistrationManager.IsThirdPartyAPIGroupVersion RegisteredGroupVersions = DefaultAPIRegistrationManager.RegisteredGroupVersions IsRegisteredVersion = DefaultAPIRegistrationManager.IsRegisteredVersion IsRegistered = DefaultAPIRegistrationManager.IsRegistered Group = DefaultAPIRegistrationManager.Group EnabledVersionsForGroup = DefaultAPIRegistrationManager.EnabledVersionsForGroup EnabledVersions = DefaultAPIRegistrationManager.EnabledVersions IsEnabledVersion = DefaultAPIRegistrationManager.IsEnabledVersion IsAllowedVersion = DefaultAPIRegistrationManager.IsAllowedVersion EnableVersions = DefaultAPIRegistrationManager.EnableVersions RegisterGroup = DefaultAPIRegistrationManager.RegisterGroup RegisterVersions = DefaultAPIRegistrationManager.RegisterVersions InterfacesFor = DefaultAPIRegistrationManager.InterfacesFor )
在分析apiServer的启动流程的时候,你会发现初始化ServerRunOptions对象时,用到了好多上面的变量,好比:
路径:pkg/genericapiserver/options/server_run_options.go
func NewServerRunOptions() *ServerRunOptions { return &ServerRunOptions{ AdmissionControl: "AlwaysAdmit", 。。。 // 这里就使用了AllPreferredGroupVersions接口 DefaultStorageVersions: registered.AllPreferredGroupVersions(), 。。。 StorageVersions: registered.AllPreferredGroupVersions(), } }
上面就使用到了registered.AllPreferredGroupVersions()接口,顺便看下接口具体实现:
func (m *APIRegistrationManager) AllPreferredGroupVersions() string { // 若是没有注册groupMeta的话,这里就==0 // 不过不可能没有注册,至于在哪里进行注册就得看下后面介绍的GroupMeta初始化了 if len(m.groupMetaMap) == 0 { return "" } var defaults []string for _, groupMeta := range m.groupMetaMap { defaults = append(defaults, groupMeta.GroupVersion.String()) } sort.Strings(defaults) return strings.Join(defaults, ",") }
该接口比较简单,就是从m.groupMetaMap中取出全部的groupMeta,而后经过逗号拼接成"group1/version1,group2/version2,..."的字符串。
这里能够想一下,既然有list,那总得有groupMeta啊。而咱们看APIRegistrationManager的初始化,若是没有设置KUBE_API_VERSIONS环境变量的话,根本就没有groupMeta。
既然不可能没有groupMeta,那确定得从别的地方进行register & enable。咱们能够从APIRegistrationManager提供的RegisterGroup方法入手:
func (m *APIRegistrationManager) RegisterGroup(groupMeta apimachinery.GroupMeta) error { groupName := groupMeta.GroupVersion.Group if _, found := m.groupMetaMap[groupName]; found { return fmt.Errorf("group %v is already registered", m.groupMetaMap) } m.groupMetaMap[groupName] = &groupMeta return nil }
该RegisterGroup接口的入参就是GroupMeta,因此咱们得继续查看该结构的初始化了。
k8s现阶段,API一共分为13个Group:Core、apps、authentication、authorization、autoscaling、batch、certificates、componentconfig、extensions、imagepolicy、policy、rbac、storage。其中Core的Group Name为空,它包含的API是最核心的API,如Pod、Service等,它的代码位于pkg/api下面,其它12个Group代码位于pkg/apis。每一个目录下都有一个install目录,里面有一个install.go文件,接着经过init()负责初始化。这些程序都是经过下列文件进行import:
路径: pkg/master/import_known_versions.go
package master // These imports are the API groups the API server will support. import ( "fmt" _ "k8s.io/kubernetes/pkg/api/install" "k8s.io/kubernetes/pkg/apimachinery/registered" _ "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/componentconfig/install" _ "k8s.io/kubernetes/pkg/apis/extensions/install" _ "k8s.io/kubernetes/pkg/apis/imagepolicy/install" _ "k8s.io/kubernetes/pkg/apis/policy/install" _ "k8s.io/kubernetes/pkg/apis/rbac/install" _ "k8s.io/kubernetes/pkg/apis/storage/install" )
一共import了13个group。其中"k8s.io/kubernetes/pkg/api/install"就是Core Group,咱们就以它为例,查看下对应的install.go文件。
路径: pkg/api/install/install.go
var availableVersions = []unversioned.GroupVersion{v1.SchemeGroupVersion} func init() { // 进行Versions注册,其实就是存入APIRegistrationManager.registeredVersions中 registered.RegisterVersions(availableVersions) externalVersions := []unversioned.GroupVersion{} for _, v := range availableVersions { // 判断下是否已经注册,并追加成一个切片 if registered.IsAllowedVersion(v) { externalVersions = append(externalVersions, v) } } if len(externalVersions) == 0 { glog.V(4).Infof("No version is registered for group %v", api.GroupName) return } // 再进行enable,其实就是存入APIRegistrationManager.enabledVersions if err := registered.EnableVersions(externalVersions...); err != nil { glog.V(4).Infof("%v", err) return } // 该接口比较关键,进行单独介绍 if err := enableVersions(externalVersions); err != nil { glog.V(4).Infof("%v", err) return } }
首先定义了一个切片availableVersions,里面只有一个元素v1.SchemeGroupVersion:
const GroupName = "" var SchemeGroupVersion = unversioned.GroupVersion{Group: GroupName, Version: "v1"}
根据该元素的定义,能够看出availableVersions就定义了一个GroupName为空,Version是'v1'的GroupVersion。接着把该GroupVersion放入APIRegistrationManager的registeredVersions和enabledVersions中。
registered的几个接口实现比较简单不进行介绍了,可是执行的enableVersions()是重头戏,咱们继续深刻:
func enableVersions(externalVersions []unversioned.GroupVersion) error { // 字面意思:将全部的Versions添加到Scheme // 又牵扯到Scheme,后面会介绍Scheme的初始化 // 越深刻看牵扯出的概念越多,该接口也很重要,须要耐心层层挖掘 addVersionsToScheme(externalVersions...) // 将一个GroupVersion做为默认的,即'/api/v1' preferredExternalVersion := externalVersions[0] // 就是这里! 进行了GroupMeta的初始化。这就是咱们这小节要看的关键 groupMeta := apimachinery.GroupMeta{ GroupVersion: preferredExternalVersion, GroupVersions: externalVersions, // RESTMapper也是关键所在,下面也会单作一节进行介绍 RESTMapper: newRESTMapper(externalVersions), SelfLinker: runtime.SelfLinker(accessor), InterfacesFor: interfacesFor, } // 前面都是register和enable了versions,这里才是进行了Group的register // 该接口其实就是以第一个GroupVersion的groupName为key,groupMeta为value // 对APIRegistrationManager的groupMetaMap,进行了赋值 if err := registered.RegisterGroup(groupMeta); err != nil { return err } return nil }
到这步,咱们再结合以前APIRegistrationManager的初始化,就能知道groupMetaMap中应该有了好几组groupMeta。那在ServerRunOptions对象初始化中调用的registered.AllPreferredGroupVersions()接口,能返回好几个DefaultStorageVersions,至少确定有'/api/v1'。至于别的groupMeta,须要再看下别的install.go,大同小异就不展开一个一个讲了。
groupMeta的初始化虽然结束了,可是这里又引出一个关键Scheme,那么继续下一小节吧。。
在上一节介绍enableVersions()函数时,第一行即是调用了addVersionsToScheme(externalVersions...),将GroupVersions加到Scheme。咱们就来看下该接口:
func addVersionsToScheme(externalVersions ...unversioned.GroupVersion) { // add the internal version to Scheme if err := api.AddToScheme(api.Scheme); err != nil { // Programmer error, detect immediately panic(err) } // add the enabled external versions to Scheme for _, v := range externalVersions { if !registered.IsEnabledVersion(v) { glog.Errorf("Version %s is not enabled, so it will not be added to the Scheme.", v) continue } switch v { case v1.SchemeGroupVersion: if err := v1.AddToScheme(api.Scheme); err != nil { // Programmer error, detect immediately panic(err) } } } }
接口中咱们能够看到AddToScheme(api.Scheme)都是将GroupVersion加入到api.Scheme。咱们先将上面的接口解析放放,先看下api.Scheme是如何初始化的:
路径:pkg/api/register.go
var Scheme = runtime.NewScheme()
定义了Scheme,再看NewScheme():
路径:pkg/runtime/scheme.go
func NewScheme() *Scheme { // 定义空的Scheme s := &Scheme{ gvkToType: map[unversioned.GroupVersionKind]reflect.Type{}, typeToGVK: map[reflect.Type][]unversioned.GroupVersionKind{}, unversionedTypes: map[reflect.Type]unversioned.GroupVersionKind{}, unversionedKinds: map[string]reflect.Type{}, cloner: conversion.NewCloner(), fieldLabelConversionFuncs: map[string]map[string]FieldLabelConversionFunc{}, defaulterFuncs: map[reflect.Type]func(interface{}){}, } // 建立converter,用于不一样版本对象转换 s.converter = conversion.NewConverter(s.nameFunc) // 增长一些转换函数 s.AddConversionFuncs(DefaultEmbeddedConversions()...) // Enable map[string][]string conversions by default if err := s.AddConversionFuncs(DefaultStringConversions...); err != nil { panic(err) } if err := s.RegisterInputDefaults(&map[string][]string{}, JSONKeyMapper, conversion.AllowDifferentFieldTypeNames|conversion.IgnoreMissingFields); err != nil { panic(err) } if err := s.RegisterInputDefaults(&url.Values{}, JSONKeyMapper, conversion.AllowDifferentFieldTypeNames|conversion.IgnoreMissingFields); err != nil { panic(err) } return s }
上面就建立了一个空的Scheme。
知道哪里建立Scheme后,咱们继续回到上面的addVersionsToScheme()函数。
其实主要就是看两个接口: api.AddToScheme()和v1.AddToScheme()。
先看第一个:
var ( SchemeBuilder = runtime.NewSchemeBuilder(addKnownTypes, addDefaultingFuncs) AddToScheme = SchemeBuilder.AddToScheme )
经过runtime.NewSchemeBuilder()接口传入两个函数,而后建立了SchemeBuilder:
type SchemeBuilder []func(*Scheme) error func (sb *SchemeBuilder) Register(funcs ...func(*Scheme) error) { for _, f := range funcs { *sb = append(*sb, f) } } func NewSchemeBuilder(funcs ...func(*Scheme) error) SchemeBuilder { var sb SchemeBuilder sb.Register(funcs...) return sb }
根据上面的定义和函数能够看出,SchemeBuilder就是一个接口切片,包含了addKnownTypes, addDefaultingFuncs两个接口。
SchemeBuilder定义好了以后,继续看AddToScheme:
func (sb *SchemeBuilder) AddToScheme(s *Scheme) error { for _, f := range *sb { if err := f(s); err != nil { return err } } return nil }
该函数就是调用了addKnownTypes, addDefaultingFuncs两个接口,咱们一个一个看:
func addKnownTypes(scheme *runtime.Scheme) error { if err := scheme.AddIgnoredConversionType(&unversioned.TypeMeta{}, &unversioned.TypeMeta{}); err != nil { return err } // 把下列对象加入到Scheme中 // 该SchemeGroupVersion的GroupName为空,Version是"__internal" // 因此该接口实际上是把k8s内置的version添加到Scheme,并且每一个group都有该步 scheme.AddKnownTypes(SchemeGroupVersion, &Pod{}, &PodList{}, &PodStatusResult{}, &PodTemplate{}, &PodTemplateList{}, &ReplicationControllerList{}, &ReplicationController{}, &ServiceList{}, &Service{}, &ServiceProxyOptions{}, &NodeList{}, &Node{}, &NodeProxyOptions{}, &Endpoints{}, &EndpointsList{}, &Binding{}, &Event{}, &EventList{}, &List{}, &LimitRange{}, &LimitRangeList{}, &ResourceQuota{}, &ResourceQuotaList{}, &Namespace{}, &NamespaceList{}, &ServiceAccount{}, &ServiceAccountList{}, &Secret{}, &SecretList{}, &PersistentVolume{}, &PersistentVolumeList{}, &PersistentVolumeClaim{}, &PersistentVolumeClaimList{}, &DeleteOptions{}, &ListOptions{}, &PodAttachOptions{}, &PodLogOptions{}, &PodExecOptions{}, &PodProxyOptions{}, &ComponentStatus{}, &ComponentStatusList{}, &SerializedReference{}, &RangeAllocation{}, &ConfigMap{}, &ConfigMapList{}, ) // 在GroupName为空,Version为"v1"的groupVersion中,添加这些对象到Scheme scheme.AddUnversionedTypes(Unversioned, &unversioned.ExportOptions{}, &unversioned.Status{}, &unversioned.APIVersions{}, &unversioned.APIGroupList{}, &unversioned.APIGroup{}, &unversioned.APIResourceList{}, ) return nil }
查看AddKnownTypes()接口:
func (s *Scheme) AddKnownTypes(gv unversioned.GroupVersion, types ...Object) { if len(gv.Version) == 0 { panic(fmt.Sprintf("version is required on all types: %s %v", gv, types[0])) } for _, obj := range types { t := reflect.TypeOf(obj) if t.Kind() != reflect.Ptr { panic("All types must be pointers to structs.") } t = t.Elem() if t.Kind() != reflect.Struct { panic("All types must be pointers to structs.") } gvk := gv.WithKind(t.Name()) s.gvkToType[gvk] = t s.typeToGVK[t] = append(s.typeToGVK[t], gvk) } }
该接口主要操做了s.gvkToType和s.typeToGVK,用于转换的目的。
综上得出,是将internal version添加到Scheme中。
为何会有一个internal version呢? 其实每个Group都有一个internal version。而apiserver操做的也都是internal version.
举个例子:假若有一个建立Pod的请求来了,apiserver首先会将请求给反序列化,用户发过来的Pod请求每每是有版本的,好比v1,所以会反序列化为一个v1.Pod。apiserver会当即将这个v1.Pod利用convertor转换成internal.Pod,而后进行一些操做,最后要把它存到etcd里面去,etcd里面的Pod信息是有版本的,所以会先发生一次转换,将其转换为v1.Pod,而后序列化存入etcd。
这样看上去好像画蛇添足?其实这就是k8s对api多版本的支持,这样用户能够以一个v1beta1建立一个Pod,而后存入etcd的是一个相对稳定的版本,好比v1版本。
internal version添加完成后,继续回到最开始的addVersionsToScheme()函数,还要继续执行v1.AddToScheme(api.Scheme)函数.其实就是把v1版本的api添加到Scheme中,和添加internal版本同样。
咱们看看v1.AddToScheme。
路径:pkg/api/v1/register.go
var ( SchemeBuilder = runtime.NewSchemeBuilder(addKnownTypes, addDefaultingFuncs, addConversionFuncs, addFastPathConversionFuncs) AddToScheme = SchemeBuilder.AddToScheme )
这里能够看到v1相比较internal版本,还多了好几个函数addConversionFuncs, addFastPathConversionFuncs。
这些函数在执行AddToScheme()时其实都会要遍历执行,能够深刻看下。其实就是向Scheme添加了转换函数,好比将v1.Pod转换为internal.Pod,将internal.Pod转换为v1.Pod。若是同时有v1,v2,v3会如何进行转换?其实也仍是先统一转换成internal,而后再转换为相应的版本(v1,v2,v3).因此internal至关于转换的桥梁,更好的支持了不一样版本的api。
到这里Scheme的初始化基本结束了。 上面讲GroupMeta初始化时还引出了关键性的RESTMapper,因此继续进行介绍。
该部分的初始化就直接看GroupMeta初始化时调用的接口newRESTMapper():
路径: pkg/api/install/install.go
func newRESTMapper(externalVersions []unversioned.GroupVersion) meta.RESTMapper { // 这些是API最顶层的对象,能够理解为没有namespace的对象 // 根据有无namespace,对象分为两类:RESTScopeNamespace和RESTScopeRoot rootScoped := sets.NewString( "Node", "Namespace", "PersistentVolume", "ComponentStatus", ) // 须要忽略Scheme中以下的kinds ignoredKinds := sets.NewString( "ListOptions", "DeleteOptions", "Status", "PodLogOptions", "PodExecOptions", "PodAttachOptions", "PodProxyOptions", "NodeProxyOptions", "ServiceProxyOptions", "ThirdPartyResource", "ThirdPartyResourceData", "ThirdPartyResourceList") mapper := api.NewDefaultRESTMapper(externalVersions, interfacesFor, importPrefix, ignoredKinds, rootScoped) return mapper }
其实全部的api资源能够分为两类:一类是有namespace,另外一类是没有namespace。好比该接口中的Node、Namespace、PersistentVolume、ComponentStatus不属于任何namespace。ignoredKinds是下面接口须要用到的参数,表示遍历Scheme时忽略这些kinds。
而后调用api.NewDefaultRESTMapper(),importPrefix参数为:"k8s.io/kubernetes/pkg/api",
interfacesFor是一个接口。
路径:pkg/api/mapper.go
func NewDefaultRESTMapper(defaultGroupVersions []unversioned.GroupVersion, interfacesFunc meta.VersionInterfacesFunc, importPathPrefix string, ignoredKinds, rootScoped sets.String) *meta.DefaultRESTMapper { // 加入Scheme,并继续调用下面的接口 return NewDefaultRESTMapperFromScheme(defaultGroupVersions, interfacesFunc, importPathPrefix, ignoredKinds, rootScoped, Scheme) } func NewDefaultRESTMapperFromScheme(defaultGroupVersions []unversioned.GroupVersion, interfacesFunc meta.VersionInterfacesFunc, importPathPrefix string, ignoredKinds, rootScoped sets.String, scheme *runtime.Scheme) *meta.DefaultRESTMapper { // 初始化了一个DefaultRESTMapper对象 mapper := meta.NewDefaultRESTMapper(defaultGroupVersions, interfacesFunc) // 根据输入的defaultGroupVersions,好比"/api/v1",从Scheme中遍历全部的kinds // 而后进行Add for _, gv := range defaultGroupVersions { for kind, oType := range scheme.KnownTypes(gv) { gvk := gv.WithKind(kind) // 过滤掉不属于"k8s.io/kubernetes/pkg/api"路径下的api,和ignoredKinds if !strings.Contains(oType.PkgPath(), importPathPrefix) || ignoredKinds.Has(kind) { continue } // 判断该kind是否有namespace属性 scope := meta.RESTScopeNamespace if rootScoped.Has(kind) { scope = meta.RESTScopeRoot } // 而后将该gvk加入到对应的组中 mapper.Add(gvk, scope) } } return mapper }
再看看该接口,先建立了一个空的DefaultRESTMapper,而后根据"/api/v1"的groupVersion,遍历Scheme中全部的kinds,接着再调用mapper.Add(gvk, scope)去填充这个mapper,最后返回该mapper。
看下mapper.Add()的实现:
func (m *DefaultRESTMapper) Add(kind unversioned.GroupVersionKind, scope RESTScope) { // resource还分为单数和复数 plural, singular := KindToResource(kind) // 单数,复数相互转换 m.singularToPlural[singular] = plural m.pluralToSingular[plural] = singular // 根据单复数的resource找到对应的kind m.resourceToKind[singular] = kind m.resourceToKind[plural] = kind // 根据kind找到对应的单复数resource m.kindToPluralResource[kind] = plural // kind到scope的转换 m.kindToScope[kind] = scope }
RESTMapper其实包含的是一种转换关系,resource到kind,kind到resource,kind到scope的转换。resource还分单数和复数。
kind和resource有什么区别呢?两者都是字符串,kind是经过Kind=reflector.TypeOf(&Pod{}).Elem().Name()进行取值,去的就是Pod这个结构体的名字。resource是经过plural, singular := KindToResource(kind)取值。singular是将Kind转换为小写字母,而plural是变为复数。
示例:以Pod为例,Kind是{Group:"", Version: "v1", Kind: "Pod"},那么singular是{Group:"", Version: "v1", Kind: "pod"},plural则是{Group:"", Version:"v1", Resource:"pods"}。
resource要区分单复数,是为了获取Pods信息。好比能够kubectl get pod,也能够kubectl get pods.
到这里RESTMapper也基本初始化完了,综合上面全部的初始化能够看到,其实主要用internal version和external versions填充Scheme,用external versions去填充GroupMeta以及其成员RESTMapper。
GroupMeta有啥做用呢?主要用于初始化APIGroupVersion。
以前全部的初始化都是为了这步作铺垫,上面还有一个APIGroupInfo和APIGroupVersion都没有进行介绍,这一节都会出现。
当API资源初始化完成之后,须要将这些API资源注册为restful api,用来接收用户的请求。
kube-apiServer使用了go-restful这套框架,里面主要包括三种对象:
Container: 一个Container包含多个WebService
WebService: 一个WebService包含多条route
Route: 一条route包含一个method(GET、POST、DELETE等),一条具体的path(URL)以及一个响应的handler function。
API注册的入口函数有两个: m.InstallAPIs 和 m.InstallLegacyAPI。
文件路径:pkg/master/master.go
这两个函数分别用于注册"/api"和"/apis"的API,这里先拿InstallLegacyAPI进行介绍。
这些接口都是在config.Complete().New()函数中被调用:
restOptionsFactory := restOptionsFactory{ deleteCollectionWorkers: c.DeleteCollectionWorkers, enableGarbageCollection: c.GenericConfig.EnableGarbageCollection, storageFactory: c.StorageFactory, } // 判断是否使能了用于Watch的Cache // 有无cache赋值的是不一样的接口实现 // restOptionsFactory.storageDecorator:是一个各个资源的REST interface(CRUD)装饰者 // 后面调用NewStorage()时会用到该接口,并输出对应的CRUD接口及销毁接口。 // 能够参考pkg/registry/core/pod/etcd/etcd.go中的NewStorage() // 其实这里有无cache的接口差别就在于:有cache的话,就提供操做cache的接口;无cache的话,就提供直接操做etcd的接口 if c.EnableWatchCache { restOptionsFactory.storageDecorator = registry.StorageWithCacher } else { restOptionsFactory.storageDecorator = generic.UndecoratedStorage } // 判断/api/v1的group是否已经注册并enable,是的话再进行install if c.GenericConfig.APIResourceConfigSource.AnyResourcesForVersionEnabled(apiv1.SchemeGroupVersion) { // 该对象主要提供了一个NewLegacyRESTStorage()的接口 legacyRESTStorageProvider := corerest.LegacyRESTStorageProvider{ StorageFactory: c.StorageFactory, ProxyTransport: c.ProxyTransport, KubeletClientConfig: c.KubeletClientConfig, EventTTL: c.EventTTL, ServiceIPRange: c.ServiceIPRange, ServiceNodePortRange: c.ServiceNodePortRange, LoopbackClientConfig: c.GenericConfig.LoopbackClientConfig, } // 进行"/api/v1"的API安装 m.InstallLegacyAPI(c.Config, restOptionsFactory.NewFor, legacyRESTStorageProvider) }
继续查看m.InstallLegacyAPI():
func (m *Master) InstallLegacyAPI(c *Config, restOptionsGetter genericapiserver.RESTOptionsGetter, legacyRESTStorageProvider corerest.LegacyRESTStorageProvider) { // 该对象前面介绍过了,比较关键,须要深刻查看 // 返回了RESTStorage和apiGroupInfo,都是重量级的成员 // 这些初始化也就在这个接口中 legacyRESTStorage, apiGroupInfo, err := legacyRESTStorageProvider.NewLegacyRESTStorage(restOptionsGetter) if err != nil { glog.Fatalf("Error building core storage: %v", err) } // 判断是否enable了controller,默认是true,这里跟主题关系不大,暂不深刻 if c.EnableCoreControllers { serviceClient := coreclient.NewForConfigOrDie(c.GenericConfig.LoopbackClientConfig) bootstrapController := c.NewBootstrapController(legacyRESTStorage, serviceClient) if err := m.GenericAPIServer.AddPostStartHook("bootstrap-controller", bootstrapController.PostStartHook); err != nil { glog.Fatalf("Error registering PostStartHook %q: %v", "bootstrap-controller", err) } } // install core Group's API if err := m.GenericAPIServer.InstallLegacyAPIGroup(genericapiserver.DefaultLegacyAPIPrefix, &apiGroupInfo); err != nil { glog.Fatalf("Error in registering group versions: %v", err) } }
先看下建立APIGroupVersion和RESTStorage对象的接口NewLegacyRESTStorage().
路径:pkg/registry/core/rest/storage_core.go
func (c LegacyRESTStorageProvider) NewLegacyRESTStorage(restOptionsGetter genericapiserver.RESTOptionsGetter) (LegacyRESTStorage, genericapiserver.APIGroupInfo, error) { // 初始化建立一个APIGroupVersion apiGroupInfo := genericapiserver.APIGroupInfo{ // 该GroupMeta是从APIRegistrationManager初始化后的结构体获取 GroupMeta: *registered.GroupOrDie(api.GroupName), VersionedResourcesStorageMap: map[string]map[string]rest.Storage{}, // 这个api.Scheme以前已经介绍过其初始化了 Scheme: api.Scheme, ParameterCodec: api.ParameterCodec, NegotiatedSerializer: api.Codecs, SubresourceGroupVersionKind: map[string]unversioned.GroupVersionKind{}, } // 判断下autoscaling是否已经注册并使能,是的话加入到apiGroupInfo.SubresourceGroupVersionKind // key是该资源的path if autoscalingGroupVersion := (unversioned.GroupVersion{Group: "autoscaling", Version: "v1"}); registered.IsEnabledVersion(autoscalingGroupVersion) { apiGroupInfo.SubresourceGroupVersionKind["replicationcontrollers/scale"] = autoscalingGroupVersion.WithKind("Scale") } var podDisruptionClient policyclient.PodDisruptionBudgetsGetter if policyGroupVersion := (unversioned.GroupVersion{Group: "policy", Version: "v1beta1"}); registered.IsEnabledVersion(policyGroupVersion) { apiGroupInfo.SubresourceGroupVersionKind["pods/eviction"] = policyGroupVersion.WithKind("Eviction") var err error podDisruptionClient, err = policyclient.NewForConfig(c.LoopbackClientConfig) if err != nil { return LegacyRESTStorage{}, genericapiserver.APIGroupInfo{}, err } } // 初始化一个LegacyRESTStorage对象 // 下面会进行各个接口的初始化,会有Node注册,IP申请,NodePort申请等等 restStorage := LegacyRESTStorage{} // 建立各种Storage podTemplateStorage := podtemplateetcd.NewREST(restOptionsGetter(api.Resource("podTemplates"))) eventStorage := eventetcd.NewREST(restOptionsGetter(api.Resource("events")), uint64(c.EventTTL.Seconds())) limitRangeStorage := limitrangeetcd.NewREST(restOptionsGetter(api.Resource("limitRanges"))) resourceQuotaStorage, resourceQuotaStatusStorage := resourcequotaetcd.NewREST(restOptionsGetter(api.Resource("resourceQuotas"))) secretStorage := secretetcd.NewREST(restOptionsGetter(api.Resource("secrets"))) serviceAccountStorage := serviceaccountetcd.NewREST(restOptionsGetter(api.Resource("serviceAccounts"))) persistentVolumeStorage, persistentVolumeStatusStorage := pvetcd.NewREST(restOptionsGetter(api.Resource("persistentVolumes"))) persistentVolumeClaimStorage, persistentVolumeClaimStatusStorage := pvcetcd.NewREST(restOptionsGetter(api.Resource("persistentVolumeClaims"))) configMapStorage := configmapetcd.NewREST(restOptionsGetter(api.Resource("configMaps"))) namespaceStorage, namespaceStatusStorage, namespaceFinalizeStorage := namespaceetcd.NewREST(restOptionsGetter(api.Resource("namespaces"))) restStorage.NamespaceRegistry = namespace.NewRegistry(namespaceStorage) endpointsStorage := endpointsetcd.NewREST(restOptionsGetter(api.Resource("endpoints"))) restStorage.EndpointRegistry = endpoint.NewRegistry(endpointsStorage) nodeStorage, err := nodeetcd.NewStorage(restOptionsGetter(api.Resource("nodes")), c.KubeletClientConfig, c.ProxyTransport) if err != nil { return LegacyRESTStorage{}, genericapiserver.APIGroupInfo{}, err } restStorage.NodeRegistry = node.NewRegistry(nodeStorage.Node) // 建立PodStorage // api.Resource("pods")是合成了一个GroupResource的结构 podStorage := podetcd.NewStorage( restOptionsGetter(api.Resource("pods")), nodeStorage.KubeletConnectionInfo, c.ProxyTransport, podDisruptionClient, ) serviceRESTStorage, serviceStatusStorage := serviceetcd.NewREST(restOptionsGetter(api.Resource("services"))) restStorage.ServiceRegistry = service.NewRegistry(serviceRESTStorage) var serviceClusterIPRegistry rangeallocation.RangeRegistry serviceClusterIPRange := c.ServiceIPRange if serviceClusterIPRange.IP == nil { return LegacyRESTStorage{}, genericapiserver.APIGroupInfo{}, fmt.Errorf("service clusterIPRange is missing") } serviceStorageConfig, err := c.StorageFactory.NewConfig(api.Resource("services")) if err != nil { return LegacyRESTStorage{}, genericapiserver.APIGroupInfo{}, err } ServiceClusterIPAllocator := ipallocator.NewAllocatorCIDRRange(&serviceClusterIPRange, func(max int, rangeSpec string) allocator.Interface { mem := allocator.NewAllocationMap(max, rangeSpec) // TODO etcdallocator package to return a storage interface via the storageFactory etcd := etcdallocator.NewEtcd(mem, "/ranges/serviceips", api.Resource("serviceipallocations"), serviceStorageConfig) serviceClusterIPRegistry = etcd return etcd }) restStorage.ServiceClusterIPAllocator = serviceClusterIPRegistry var serviceNodePortRegistry rangeallocation.RangeRegistry ServiceNodePortAllocator := portallocator.NewPortAllocatorCustom(c.ServiceNodePortRange, func(max int, rangeSpec string) allocator.Interface { mem := allocator.NewAllocationMap(max, rangeSpec) // TODO etcdallocator package to return a storage interface via the storageFactory etcd := etcdallocator.NewEtcd(mem, "/ranges/servicenodeports", api.Resource("servicenodeportallocations"), serviceStorageConfig) serviceNodePortRegistry = etcd return etcd }) restStorage.ServiceNodePortAllocator = serviceNodePortRegistry controllerStorage := controlleretcd.NewStorage(restOptionsGetter(api.Resource("replicationControllers"))) serviceRest := service.NewStorage(restStorage.ServiceRegistry, restStorage.EndpointRegistry, ServiceClusterIPAllocator, ServiceNodePortAllocator, c.ProxyTransport) // 初始化了一个restStorage的map,而后赋值给APIGroupInfo.VersionedResourcesStorageMap["v1"] restStorageMap := map[string]rest.Storage{ "pods": podStorage.Pod, "pods/attach": podStorage.Attach, "pods/status": podStorage.Status, "pods/log": podStorage.Log, "pods/exec": podStorage.Exec, "pods/portforward": podStorage.PortForward, "pods/proxy": podStorage.Proxy, "pods/binding": podStorage.Binding, "bindings": podStorage.Binding, "podTemplates": podTemplateStorage, "replicationControllers": controllerStorage.Controller, "replicationControllers/status": controllerStorage.Status, "services": serviceRest.Service, "services/proxy": serviceRest.Proxy, "services/status": serviceStatusStorage, "endpoints": endpointsStorage, "nodes": nodeStorage.Node, "nodes/status": nodeStorage.Status, "nodes/proxy": nodeStorage.Proxy, "events": eventStorage, "limitRanges": limitRangeStorage, "resourceQuotas": resourceQuotaStorage, "resourceQuotas/status": resourceQuotaStatusStorage, "namespaces": namespaceStorage, "namespaces/status": namespaceStatusStorage, "namespaces/finalize": namespaceFinalizeStorage, "secrets": secretStorage, "serviceAccounts": serviceAccountStorage, "persistentVolumes": persistentVolumeStorage, "persistentVolumes/status": persistentVolumeStatusStorage, "persistentVolumeClaims": persistentVolumeClaimStorage, "persistentVolumeClaims/status": persistentVolumeClaimStatusStorage, "configMaps": configMapStorage, "componentStatuses": componentstatus.NewStorage(componentStatusStorage{c.StorageFactory}.serversToValidate), } if registered.IsEnabledVersion(unversioned.GroupVersion{Group: "autoscaling", Version: "v1"}) { restStorageMap["replicationControllers/scale"] = controllerStorage.Scale } if registered.IsEnabledVersion(unversioned.GroupVersion{Group: "policy", Version: "v1beta1"}) { restStorageMap["pods/eviction"] = podStorage.Eviction } // 将上面的restStorageMap赋值给v1 apiGroupInfo.VersionedResourcesStorageMap["v1"] = restStorageMap return restStorage, apiGroupInfo, nil }
看完这个接口后,咱们继续回到前面,看下m.GenericAPIServer.InstallLegacyAPIGroup()接口:
路径:pkg/genericapiserver/genericapiserver.go
func (s *GenericAPIServer) InstallLegacyAPIGroup(apiPrefix string, apiGroupInfo *APIGroupInfo) error { // 判断前缀参数是否正确 if !s.legacyAPIGroupPrefixes.Has(apiPrefix) { return fmt.Errorf("%q is not in the allowed legacy API prefixes: %v", apiPrefix, s.legacyAPIGroupPrefixes.List()) } // 关键接口,真正install API if err := s.installAPIResources(apiPrefix, apiGroupInfo); err != nil { return err } // 获取了该Group下全部的version信息 // 应该用于发现当前的全部版本信息 apiVersions := []string{} for _, groupVersion := range apiGroupInfo.GroupMeta.GroupVersions { apiVersions = append(apiVersions, groupVersion.Version) } // Install the version handler. // Add a handler at /<apiPrefix> to enumerate the supported api versions. apiserver.AddApiWebService(s.Serializer, s.HandlerContainer.Container, apiPrefix, func(req *restful.Request) *unversioned.APIVersions { clientIP := utilnet.GetClientIP(req.Request) apiVersionsForDiscovery := unversioned.APIVersions{ ServerAddressByClientCIDRs: s.discoveryAddresses.ServerAddressByClientCIDRs(clientIP), Versions: apiVersions, } return &apiVersionsForDiscovery }) return nil }
那咱们继续进入关键接口s.installAPIResources(apiPrefix, apiGroupInfo):
func (s *GenericAPIServer) installAPIResources(apiPrefix string, apiGroupInfo *APIGroupInfo) error { // 遍历该Group下的全部GroupVersons for _, groupVersion := range apiGroupInfo.GroupMeta.GroupVersions { // 建立APIGroupVersion apiGroupVersion, err := s.getAPIGroupVersion(apiGroupInfo, groupVersion, apiPrefix) if err != nil { return err } if apiGroupInfo.OptionsExternalVersion != nil { apiGroupVersion.OptionsExternalVersion = apiGroupInfo.OptionsExternalVersion } // 根据以前建立的APIGroupVersion,而后安装restful API // 该s.HandlerContainer.Container就是go-restful的Container if err := apiGroupVersion.InstallREST(s.HandlerContainer.Container); err != nil { return fmt.Errorf("Unable to setup API %v: %v", apiGroupInfo, err) } } return nil } func (s *GenericAPIServer) getAPIGroupVersion(apiGroupInfo *APIGroupInfo, groupVersion unversioned.GroupVersion, apiPrefix string) (*apiserver.APIGroupVersion, error) { storage := make(map[string]rest.Storage) // 若是是核心组的话,Version为"v1",该VersionedResourcesStorageMap的初始化要看 // 以前的NewLegacyRESTStorage()接口,在该接口中进行的初始化 // 遍历全部的ResourcesStorage,并赋值给storage for k, v := range apiGroupInfo.VersionedResourcesStorageMap[groupVersion.Version] { storage[strings.ToLower(k)] = v } // 建立APIGroupVersion version, err := s.newAPIGroupVersion(apiGroupInfo, groupVersion) // 设置Prefix, 核心组的话是"/api" version.Root = apiPrefix version.Storage = storage return version, err }
到这里从API资源到restful API,就已经注册完成了。
至于apiGroupVersion.InstallREST()接口,咱们这里先简单介绍,后面会另起一篇文章结合go-restful进行介绍。
InstallREST()接口路径:pkg/apiserver/apiserver.go
func (g *APIGroupVersion) InstallREST(container *restful.Container) error { installer := g.newInstaller() ws := installer.NewWebService() apiResources, registrationErrors := installer.Install(ws) lister := g.ResourceLister if lister == nil { lister = staticLister{apiResources} } AddSupportedResourcesWebService(g.Serializer, ws, g.GroupVersion, lister) container.Add(ws) return utilerrors.NewAggregate(registrationErrors) } func (a *APIInstaller) Install(ws *restful.WebService) (apiResources []unversioned.APIResource, errors []error) { errors = make([]error, 0) proxyHandler := (&ProxyHandler{ prefix: a.prefix + "/proxy/", storage: a.group.Storage, serializer: a.group.Serializer, mapper: a.group.Context, }) // Register the paths in a deterministic (sorted) order to get a deterministic swagger spec. paths := make([]string, len(a.group.Storage)) var i int = 0 for path := range a.group.Storage { paths[i] = path i++ } sort.Strings(paths) for _, path := range paths { // 该接口是关键,最终将一个rest.Storage对象转换成实际的restful api,好比getter、lister等处理函数,并将实际的URL关联起来 apiResource, err := a.registerResourceHandlers(path, a.group.Storage[path], ws, proxyHandler) if err != nil { errors = append(errors, fmt.Errorf("error in registering resource: %s, %v", path, err)) } if apiResource != nil { apiResources = append(apiResources, *apiResource) } } return apiResources, errors }
在这个注册的过程当中,InstallREST最终调用了registerResourceHandlers()接口,该接口最终会把一个rest.Storage对象转换成实际的getter、lister等处理函数,并和实际的URL关联起来。
runtime-config: 用于enable/disable extensions group。默认的状况下DaemonSets、Deployments、HorizontalPodAutoscalers、Ingress、Jobs和ReplicaSets是使能的,还有v1下的默认都是使能的。另外的功能就能够经过该配置进行设置. 例如:disable deployments: --runtime-config=extensions/v1beta1/deployments=false.
1.api-group.md: https://github.com/kubernetes...