当前位置: 首页 > 编程日记 > 正文

Kubernetes1.5源码分析(二) apiServer之资源注册

源码版本

Kubernetes v1.5.0

简介

k8s里面有各种资源,如Pod、Service、RC、namespaces等资源,用户操作的其实也就是这一大堆资源。但这些资源并不是杂乱无章的,使用了GroupVersion的方式组织在一起。每一种资源都属于一个Group,而资源还有版本之分,如v1、v1beta1等。
k8s目前正在使用的API groups:

  • "core" group:它的REST path是api/v1

  • "extensions" group:它的REST path是/apis/extensions/v1beta1

  • "autoscaling", "abac" ...

服务器的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接口。

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.NegotiatedSerializerParameterCodec runtime.ParameterCodec// 以下4个都是被赋值为Scheme结构Typer     runtime.ObjectTyperCreater   runtime.ObjectCreater// 相互转换任意api版本的对象,需要事先注册转换函数Convertor runtime.ObjectConvertorCopier    runtime.ObjectCopierLinker    runtime.SelfLinker// 用于访问许可控制Admit   admission.InterfaceContext api.RequestContextMapperMinRequestTimeout 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:

type APIGroupInfo struct {// 该Group的元信息GroupMeta apimachinery.GroupMeta// 不同版本的所有的StorageVersionedResourcesStorageMap 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.SchemeScheme *runtime.Scheme// NegotiatedSerializer controls how this group encodes and decodes dataNegotiatedSerializer runtime.NegotiatedSerializer// ParameterCodec performs conversions for query parameters passed to API callsParameterCodec runtime.ParameterCodec// 所有resources信息,key就是resource的path// 比如:key为"replicationcontrollers/scale",GroupVersionKind: autoscaling, v1, ScaleSubresourceGroupVersionKind 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中的版本。

type GroupMeta struct {// 默认版本GroupVersion unversioned.GroupVersion// 该Group中可能会有多个版本,该字段就包含了所有的versionsGroupVersions []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.GroupVersionresourceToKind       map[unversioned.GroupVersionResource]unversioned.GroupVersionKindkindToPluralResource map[unversioned.GroupVersionKind]unversioned.GroupVersionResourcekindToScope          map[unversioned.GroupVersionKind]RESTScopesingularToPlural     map[unversioned.GroupVersionResource]unversioned.GroupVersionResourcepluralToSingular     map[unversioned.GroupVersionResource]unversioned.GroupVersionResourceinterfacesFunc VersionInterfacesFunc// aliasToResource is used for mapping aliases to resourcesaliasToResource map[string][]string
}

APIRegistrationManager:这个结构体主要提供了已经"registered"的概念,将所有已经注册的,已经激活的,第三方的的GroupVersions进行了汇总,还包括了各个GroupVersion的GroupMeta(元数据)。

type APIRegistrationManager struct {// 所以已经registered的GroupVersionsregisteredVersions map[unversioned.GroupVersion]struct{}// 第三方注册的GroupVersions,这些都向apiServer动态注册的thirdPartyGroupVersions []unversioned.GroupVersion// 所有已经enable的GroupVersions,可以通过EnableVersions()将要enable的GroupVersion加入进来。// 只有enable了,才能使用对应的GroupVersionenabledVersions map[unversioned.GroupVersion]struct{}// 所有groups的GroupMetagroupMetaMap map[string]*apimachinery.GroupMeta// 跟环境变量'KUBE_API_VERSIONS'有关envRequestedVersions []unversioned.GroupVersion
}

APIRegistrationManager初始化

该结构的路径: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放入envRequestedVersionsm.envRequestedVersions = append(m.envRequestedVersions, gv)}}// 否则返回一个空的APIRegistrationManagerreturn m, nil
}

瞅了下我们正在使用的环境,没有配置KUBE_API_VERSIONS,即返回了一个空的结构,还提供了好多方法。

var (ValidateEnvRequestedVersions  = DefaultAPIRegistrationManager.ValidateEnvRequestedVersionsAllPreferredGroupVersions     = DefaultAPIRegistrationManager.AllPreferredGroupVersionsRESTMapper                    = DefaultAPIRegistrationManager.RESTMapperGroupOrDie                    = DefaultAPIRegistrationManager.GroupOrDieAddThirdPartyAPIGroupVersions = DefaultAPIRegistrationManager.AddThirdPartyAPIGroupVersionsIsThirdPartyAPIGroupVersion   = DefaultAPIRegistrationManager.IsThirdPartyAPIGroupVersionRegisteredGroupVersions       = DefaultAPIRegistrationManager.RegisteredGroupVersionsIsRegisteredVersion           = DefaultAPIRegistrationManager.IsRegisteredVersionIsRegistered                  = DefaultAPIRegistrationManager.IsRegisteredGroup                         = DefaultAPIRegistrationManager.GroupEnabledVersionsForGroup       = DefaultAPIRegistrationManager.EnabledVersionsForGroupEnabledVersions               = DefaultAPIRegistrationManager.EnabledVersionsIsEnabledVersion              = DefaultAPIRegistrationManager.IsEnabledVersionIsAllowedVersion              = DefaultAPIRegistrationManager.IsAllowedVersionEnableVersions                = DefaultAPIRegistrationManager.EnableVersionsRegisterGroup                 = DefaultAPIRegistrationManager.RegisterGroupRegisterVersions              = DefaultAPIRegistrationManager.RegisterVersionsInterfacesFor                 = 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 []stringfor _, 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.Groupif _, found := m.groupMetaMap[groupName]; found {return fmt.Errorf("group %v is already registered", m.groupMetaMap)}m.groupMetaMap[groupName] = &groupMetareturn nil
}

该RegisterGroup接口的入参就是GroupMeta,所以我们得继续查看该结构的初始化了。

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.enabledVersionsif 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,那么继续下一小节吧。。

Scheme初始化

在上一节介绍enableVersions()函数时,第一行便是调用了addVersionsToScheme(externalVersions...),将GroupVersions加到Scheme。我们就来看下该接口:

func addVersionsToScheme(externalVersions ...unversioned.GroupVersion) {// add the internal version to Schemeif err := api.AddToScheme(api.Scheme); err != nil {// Programmer error, detect immediatelypanic(err)}// add the enabled external versions to Schemefor _, 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 immediatelypanic(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 {// 定义空的Schemes := &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 defaultif 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) errorfunc (sb *SchemeBuilder) Register(funcs ...func(*Scheme) error) {for _, f := range funcs {*sb = append(*sb, f)}
}func NewSchemeBuilder(funcs ...func(*Scheme) error) SchemeBuilder {var sb SchemeBuildersb.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中,添加这些对象到Schemescheme.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] = ts.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,所以继续进行介绍。

RESTMapper初始化

该部分的初始化就直接看GroupMeta初始化时调用的接口newRESTMapper():
路径: pkg/api/install/install.go

func newRESTMapper(externalVersions []unversioned.GroupVersion) meta.RESTMapper {// 这些是API最顶层的对象,可以理解为没有namespace的对象// 根据有无namespace,对象分为两类:RESTScopeNamespace和RESTScopeRootrootScoped := sets.NewString("Node","Namespace","PersistentVolume","ComponentStatus",)// 需要忽略Scheme中如下的kindsignoredKinds := 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// 然后进行Addfor _, gv := range defaultGroupVersions {for kind, oType := range scheme.KnownTypes(gv) {gvk := gv.WithKind(kind)// 过滤掉不属于"k8s.io/kubernetes/pkg/api"路径下的api,和ignoredKindsif !strings.Contains(oType.PkgPath(), importPathPrefix) || ignoredKinds.Has(kind) {continue}// 判断该kind是否有namespace属性scope := meta.RESTScopeNamespaceif 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] = pluralm.pluralToSingular[plural] = singular// 根据单复数的resource找到对应的kindm.resourceToKind[singular] = kindm.resourceToKind[plural] = kind// 根据kind找到对应的单复数resourcem.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。

API资源注册为restful api

之前所有的初始化都是为了这步做铺垫,上面还有一个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,是的话再进行installif 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 APIif 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) {// 初始化创建一个APIGroupVersionapiGroupInfo := 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是该资源的pathif autoscalingGroupVersion := (unversioned.GroupVersion{Group: "autoscaling", Version: "v1"}); registered.IsEnabledVersion(autoscalingGroupVersion) {apiGroupInfo.SubresourceGroupVersionKind["replicationcontrollers/scale"] = autoscalingGroupVersion.WithKind("Scale")}var podDisruptionClient policyclient.PodDisruptionBudgetsGetterif policyGroupVersion := (unversioned.GroupVersion{Group: "policy", Version: "v1beta1"}); registered.IsEnabledVersion(policyGroupVersion) {apiGroupInfo.SubresourceGroupVersionKind["pods/eviction"] = policyGroupVersion.WithKind("Eviction")var err errorpodDisruptionClient, err = policyclient.NewForConfig(c.LoopbackClientConfig)if err != nil {return LegacyRESTStorage{}, genericapiserver.APIGroupInfo{}, err}}// 初始化一个LegacyRESTStorage对象// 下面会进行各个接口的初始化,会有Node注册,IP申请,NodePort申请等等restStorage := LegacyRESTStorage{}// 创建各类StoragepodTemplateStorage := 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.RangeRegistryserviceClusterIPRange := c.ServiceIPRangeif 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 storageFactoryetcd := etcdallocator.NewEtcd(mem, "/ranges/serviceips", api.Resource("serviceipallocations"), serviceStorageConfig)serviceClusterIPRegistry = etcdreturn etcd})restStorage.ServiceClusterIPAllocator = serviceClusterIPRegistryvar serviceNodePortRegistry rangeallocation.RangeRegistryServiceNodePortAllocator := 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 storageFactoryetcd := etcdallocator.NewEtcd(mem, "/ranges/servicenodeports", api.Resource("servicenodeportallocations"), serviceStorageConfig)serviceNodePortRegistry = etcdreturn etcd})restStorage.ServiceNodePortAllocator = serviceNodePortRegistrycontrollerStorage := 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赋值给v1apiGroupInfo.VersionedResourcesStorageMap["v1"] = restStorageMapreturn 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 APIif 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下的所有GroupVersonsfor _, groupVersion := range apiGroupInfo.GroupMeta.GroupVersions {// 创建APIGroupVersionapiGroupVersion, 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的Containerif 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,并赋值给storagefor k, v := range apiGroupInfo.VersionedResourcesStorageMap[groupVersion.Version] {storage[strings.ToLower(k)] = v}// 创建APIGroupVersionversion, err := s.newAPIGroupVersion(apiGroupInfo, groupVersion)// 设置Prefix, 核心组的话是"/api"version.Root = apiPrefixversion.Storage = storagereturn 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.ResourceListerif 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 = 0for path := range a.group.Storage {paths[i] = pathi++}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...

相关文章:

opencv3 视频稳像

OpneCV3.x中提供了专门应用于视频稳像技术的模块&#xff0c;该模块包含一系列用于全局运动图像估计的函数和类。结构体videostab::RansacParams实现了RANSAC算法&#xff0c;这个算法用来实现连续帧间的运动估计。videostab::MotionEstimatorBase是基类中所有全局运动估计方法…

perf+火焰图 = 性能分析利器

perf 1. perf安装 sudo apt install linux-tools-common检查是否安装好 perf如果出现 You may need to install the following packages for this specific kernel:推荐安装可以按照提示将推荐安装包全部安装好 sudo apt-get install linux-tools-对应版本-generic linux-c…

3- MySQL数据类型

MySQL表字段类型 MySQL数据表的表示一个二维表&#xff0c;由一个或多个数据列构成。 每个数据列都有它的特定类型&#xff0c;该类型决定了MySQL如何看待该列数据&#xff0c;并且约束列存放相应类型的数据。 MySQL中的列表有三种&#xff1a;数值类&#xff0c;字符串类和日期…

AddressSanitizer+cmake

1. AddressSanitizercmake(Linux) 编译指令&#xff1a; CXXFLAGS通常需要加上 -fsanitizeaddress -fno-omit-frame-pointer #打印函数调用路径 -fsanitize-recoveraddress #AddressSanitizer遇到错误时能够继续-fsanitizeaddress-fno-omit-frame-pointer-fsanitize-rec…

vibe前景提取改进算法

// improveVibeAlgorithm.h #ifndef IMPROVED_VIBE_ALGORITHM_H #define IMPROVED_VIBE_ALGORITHM_H#include <opencv2/opencv.hpp> using namespace std;#define WINSIZE 5 // Vibe改进算法, Barnich, Olivier & Droogenbroeck, Marc. (2009). // ViBE: A powerfu…

npm-debug.log文件出现原因

项目主目录下总是会出现这个文件&#xff0c;而且不止一个&#xff0c;原因是npm i 的时候&#xff0c;如果报错&#xff0c;就会增加一个此文件来显示报错信息&#xff0c;npm install的时候则不会出现。转载于:https://www.cnblogs.com/liuna/p/6558006.html

AutoFac Ioc依赖注入容器

本文原著&#xff1a;牛毅 原文路径 http://niuyi.github.io/blog/2012/04/06/autofac-by-unit-test/ 理解IOC容器请看下图&#xff1a; 没有使用IOC容器的情况下: 使用IOC容器的情况下&#xff1a; 去掉IOC容器的情况后&#xff1a; IOC容器又像一个插座&#xff0c;将电输送…

Linux安装App记录

Ubuntu18.04安装微信

windows socket编程入门示例3

// Lock.h #ifndef _Lock_H #define _Lock_H #include <windows.h>class CriticalSection { private:CRITICAL_SECTION g_cs; //临界区 public:CriticalSection();~CriticalSection();void Lock();void UnLock(); }; #endif// Lock.cpp #include "Lock.h"…

游戏开发:js实现简单的板球游戏

js实现简单的板球游戏大家好&#xff0c;本次我们来使用js来实现一个简单的板球游戏。截图如下&#xff1a;首先&#xff0c;设计页面代码&#xff0c;页面代码很简单&#xff0c;因为整个几乎是使用js编写的&#xff0c;页面几乎没有代码&#xff0c;如下&#xff1a;<!DOC…

SLAM精度测评——EVO进阶

1. 基本概念 1.1 Umeyama算法 ATE&#xff1a; evo_ape tum state_groundtruth_estimate0/data.tum orb2/CameraTrajectory.txt -r trans_part -va --plot --plot_mode xy --save_results /home/sun/evo/v1_01_easy/orb2/ate.zip RPE&#xff1a; evo_rpe tum state_groun…

python读取图片并且显示

使用python-opencv读取图片&#xff0c;利用opencv或matplotlib显示图片。 # -*- coding: utf-8 -*-import numpy as np from matplotlib import pyplot as plt #import urllib import cv2def load_image1(file):# Load an color image in grayscaleimg cv2.imread(file,0)cv…

shiro认证

shiro权限认证&#xff1a; 具体的认证流程是这样的&#xff1a; 一般流程&#xff1a; 通过.ini的文件来初始化工厂&#xff0c;.ini的文件的好处是可以创建多个组&#xff0c;而.properties的文件只能创建一组。 系统默认有shiro.ini的文件&#xff0c;但是一般我们是自定义数…

小猿圈Linux基础面试题,看看你能答对几道?

最近身边的很多朋友都在学习linux&#xff0c;从最开始的安装软件都需要百度一天的他们&#xff0c;现在已经成长为了&#xff0c;不需要百度就可以把自己弄懵圈的了&#xff0c;接下来的几天小猿圈linux老师会为大家准备一些实用的linux技巧分析给大家&#xff0c;希望对你有所…

ORB-SLAM2 论文翻译

https://ug98gs7tbw.feishu.cn/docs/doccnKKOWAjkKv7AzAiEvbnM3Tf

mxnet教程1

import mxnet as mx #%matplotlib inline import os import subprocess import numpy as np import matplotlib.pyplot as plt import tarfileimport warnings warnings.filterwarnings("ignore", categoryDeprecationWarning)# 从内存中读取数据 def test1():data …

番外:Spring MVC环境搭建和Mybatis配置避坑篇

2019独角兽企业重金招聘Python工程师标准>>> web.xml引入对spring mvc的支持&#xff1b; spring-mvc配置spring-mvc&#xff1b; spring-mybatis配置mybatis支持&#xff0c;并指名mapper文件的位置&#xff1b; mybaits-config配置mybatis&#xff1b; jdbc.prope…

50个云终端只需一台服务器是怎么一回事

看到这个标题也许有人会说50个云终端只需要一台服务器这应该是不可能的吧&#xff0c;即使是真的那这个服务器的配置和价格应该也要非常高的吧。但是如果有人和你说50个云终端只需要一台中等配置和价格的服务器就可以的呢。而且这50个用户桌面都可以正常的使用不会出现卡顿等现…

SLAM学习,小白入门到殿堂级大牛资料整理

总结一下我接触过的SLAM算法吧,主要集中在visual slam: 特征法: ORB SLAM https://github.com/raulmur/ORB_SLAM2优势: 在静态环境下定位准确,稳定, 单目和双目版本都可以达到实时(高于10frames/s)。代码可读性强,易扩展, 网上也有实现和imu融合的版本。 劣势:建的地图…

python解析json

“data.json”文件内容如下&#xff1a; {"id":"1220562","alt":"http:\/\/book.douban.com\/book\/1220562","rating":{"max":10, "average":"7.0", "numRaters":282, "min…

8.改进应用程序

2019独角兽企业重金招聘Python工程师标准>>> ##8.1数字格式设置&#xff08;storyboard&#xff09; ##8.1.2可选类型拆封 import Cocoa extension Double{var dollars : String{let formatter : NumberFormatter NumberFormatter()//NSNumberFormatter弃用var re…

小猿圈Linux学习-Linux种搜索的命令

做Linux工程师的每天都不能少的工作就是搜索文件&#xff0c;这是他们的日常活动&#xff0c;很繁琐很枯燥&#xff0c;所以我们就需要知道一些搜索的命令&#xff0c;这些命令更高效更快捷&#xff0c;今天小猿圈就给大家分享4个可以搜索的Linux命令。。 方法 1&#xff1a;使…

SURF与SIFT比较分析

opencv3.2 SURF实现特征点匹配 opencv3.2中SurfFeatureDetector、SurfDescriptorExtractor、BruteForceMatcher这三个的使用方法已经和原先2.4版本前不一样了。 使用方法示例如下&#xff1a; Ptr<SURF> detector SURF::create(minHessian);detector->detect(img_1…

python文件和目录

# -*- coding: utf-8 -*-import osdef printFile(rootDir):allFiles os.listdir(rootDir) #列出文件夹下所有文件和目录for i in range(0, len(allFiles)):# print(rootDir allFiles[i])path os.path.join(rootDir, allFiles[i])if not os.path.isfile(path):print(path, &q…

你不怕他离职吗?

图片来自“wikiart” 这是我同事在晚上11点多跟我聊微信时问起的一个问题&#xff0c;我觉得这个问题还是挺有代表性的&#xff0c;所以我还是决定就这个问题展开聊聊我对这句话的看法。 我同事之所以这么说&#xff0c;是因为他的组员&#xff0c;也就是问题中的那个他&#x…

tensorflow 1

import tensorflow as tf import numpy as np import matplotlib.pylab as pltdef tfDemo1():#create datax_data np.random.rand(100).astype(np.float32)y_data x_data * 0.1 0.3#create tensorflow structureWeightstf.Variable(tf.random_uniform([1],-1.0,1.0)) #一维&…

SLAM资源整理

资源整理 浙大3D Group VINS orbslam2 orbslam2 video study https://www.bilibili.com/video/BV1bK4y197kB/?spm_id_fromautoNext orbslam2 csdn https://blog.csdn.net/ncepu_chen/category_9874746.html https://www.zhihu.com/column/c_1114864226103037952 https:…

Golang微服务开发实践

github: github.com/yun-mu/Micr… 微服务概念学习&#xff1a;可参考 Nginx 的微服务文章 微服务最佳实践&#xff1a;可参考 微服务最佳实践 demo 简介 服务&#xff1a; consignment-service&#xff08;货运服务&#xff09;user-service&#xff08;用户服务&#xff09;l…

Linux ssh/scp/docker学习

文章目录Linux ssh/scp/docker使用学习1. ssh 登录2. scp传输文件3. docker4. git checkout 替换指定分支的单个文件Linux ssh/scp/docker使用学习 1. ssh 登录 sudo ssh fireflyip (登录账号密码) scp -r company/data_depthfill/ firefly192.168.105.6:/tmp/ ​​​​ 2…

tensorflow mnist 1

import tensorflow as tf from tensorflow.examples.tutorials.mnist import input_data import keras.backend.tensorflow_backend as KTFdef add_layer(inputs,in_size,out_size,activation_functionNone):#Weights是一个矩阵&#xff0c;[行&#xff0c;列]为[in_size,out_s…