1

kubernetes通过GVK唯一标识一个API对象类型,比如HPA:

  • G=autoscaling
  • V=v1/v2beta1/v2beta2/v2...
  • Kind=HorizontalPodAutoscaler

随着kubernetes版本的迭代,产生了不同的Version,这就引发如下的问题:

  • 当使用不同的Version创建的资源对象时,最终存储在etcd的是哪个Version?
  • 当使用不同的Version Get资源对象时,在apiserver中是如何转换的?

一. APIVersion的流转

APIVersion包含以下几种:

  • External Version:

    • apiserver对外暴露的version,比如v1/v2beta1等;
  • Internal Version:

    • 内部version,是所有version字段的超集,通常是latest external version;
  • Storage Version:

    • 保存到etcd的version,通常是external version中stable的vervsion,比如v1/v2等;

ApiServer中各个version之间不能直接转换,通常使用Internal version进行中转,即:

v1-->internal version-->v2beta1

APIVersion的流转过程:

  • 通过kubectl create创建资源对象时,client端使用External Version,即apiserver暴露的version和scheme;
  • 在apiserver中,对于External Version的对象:

    • 首先,被转换为Interval version;
    • 然后,将Internal version转换为storage version;
  • 通过kubectl get查询资源对象时,根据client使用的Version:

    • 首先,将Storage Version对象转换为Internal version;
    • 然后,将Internal version转换为External version,返回给client;

image.png

二. storageVersion

1.选择入口

选择etcd保存Version的入口代码:

  • 通过storageVersioner选择version;
  • storageVersioner是storageProvider的对象内属性;
// vendor/k8s.io/apiserver/pkg/endpoints/installer.go
func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storage, ws *restful.WebService) (*metav1.APIResource, *storageversion.ResourceInfo, error) {
    ...
    storageVersionProvider, isStorageVersionProvider := storage.(rest.StorageVersionProvider)
    ...
    var apiResource metav1.APIResource
    if utilfeature.DefaultFeatureGate.Enabled(features.StorageVersionHash) &&
        isStorageVersionProvider &&
        storageVersionProvider.StorageVersion() != nil {
        versioner := storageVersionProvider.StorageVersion()                        // 通过storageVersioner选择version
        gvk, err := getStorageVersionKind(versioner, storage, a.group.Typer)        // 得到保存的GVK
        if err != nil {
            return nil, nil, err
        }
        apiResource.StorageVersionHash = discovery.StorageVersionHash(gvk.Group, gvk.Version, gvk.Kind)
    }
    ...
}

storageProvider即Store,storageVersioner是其中的一个属性,其属性的构建:

  • 由storageConfig.EncodeVersioner得来;
// vendor/k8s.io/apiserver/pkg/registry/generic/registry/store.go
func (e *Store) CompleteWithOptions(options *generic.StoreOptions) error {
    ...
    if e.Storage.Storage == nil {
        ...
        e.StorageVersioner = opts.StorageConfig.EncodeVersioner
        ...
    }
    ...
}

storageConfig.EncodeVersioner的构建:

  • 由codeConfig构建而来;
  • 而codeConfig.StorageVersion保存了存储到etcd的version;
// vendor/k8s.io/apiserver/pkg/server/storage/storage_factory.go
func (s *DefaultStorageFactory) NewConfig(groupResource schema.GroupResource) (*storagebackend.Config, error) {
    ...
    codecConfig.StorageVersion, err = s.ResourceEncodingConfig.StorageEncodingFor(chosenStorageResource)
    ..
    storageConfig.Codec, storageConfig.EncodeVersioner, err = s.newStorageCodecFn(codecConfig)
    ...
}

最终,根据scheme.PrioritizedVersions,得到该Group下的第0号version,作为存储到etcd的version:

// staging/src/k8s.io/apiserver/pkg/server/storage/resource_encoding_config.go
func (o *DefaultResourceEncodingConfig) StorageEncodingFor(resource schema.GroupResource) (schema.GroupVersion, error) {
    if !o.scheme.IsGroupRegistered(resource.Group) {
        return schema.GroupVersion{}, fmt.Errorf("group %q is not registered in scheme", resource.Group)
    }
    resourceOverride, resourceExists := o.resources[resource]
    if resourceExists {
        return resourceOverride.ExternalResourceEncoding, nil
    }
    // return the most preferred external version for the group
    return o.scheme.PrioritizedVersionsForGroup(resource.Group)[0], nil
}

2.version构建

上面看到,存储到etcd的version = scheme.PrioritizedVersionsForGroup()返回集群的第0号元素,即第一个元素;

而scheme.PrioritizedVersionsForGroup()返回的是:versionPriority+observedVersions集合;

// staging/src/k8s.io/apimachinery/pkg/runtime/scheme.go
func (s *Scheme) PrioritizedVersionsForGroup(group string) []schema.GroupVersion {
    ret := []schema.GroupVersion{}
    for _, version := range s.versionPriority[group] {
        ret = append(ret, schema.GroupVersion{Group: group, Version: version})
    }
    for _, observedVersion := range s.observedVersions {
        if observedVersion.Group != group {
            continue
        }
        found := false
        for _, existing := range ret {
            if existing == observedVersion {
                found = true
                break
            }
        }
        if !found {
            ret = append(ret, observedVersion)
        }
    }
    return ret
}

重点看一下versionPriority:

  • 由SetVersionPriority()函数赋值;
  • s.versionPriority[group]=[]string{...},其顺序==传入的versions的顺序;
// staging/src/k8s.io/apimachinery/pkg/runtime/scheme.go
func (s *Scheme) SetVersionPriority(versions ...schema.GroupVersion) error {
    groups := sets.String{}
    order := []string{}
    for _, version := range versions {
        ...
        groups.Insert(version.Group)
        order = append(order, version.Version)
    }
    if len(groups) != 1 {
        return fmt.Errorf("must register versions for exactly one group: %v", strings.Join(groups.List(), ", "))
    }
    s.versionPriority[groups.List()[0]] = order
    return nil
}

举个例子,autoscaling这个Group下,如何调用SetVersionPriority()函数:

  • autosclaing中调用SetVersionPriority传入参数=(v1,v2beta1,v2beta2);
  • 由于选择第一个version作为存储到etcd的version,故对于hpa,存储在etcd的version=v1;
// pkg/apis/autoscaling/install/install.go
func init() {
    Install(legacyscheme.Scheme)
}

// Install registers the API group and adds types to a scheme
func Install(scheme *runtime.Scheme) {
    utilruntime.Must(autoscaling.AddToScheme(scheme))
    utilruntime.Must(v2beta2.AddToScheme(scheme))
    utilruntime.Must(v2beta1.AddToScheme(scheme))
    utilruntime.Must(v1.AddToScheme(scheme))
    utilruntime.Must(scheme.SetVersionPriority(v1.SchemeGroupVersion, v2.SchemeGroupVersion, v2beta1.SchemeGroupVersion, v2beta2.SchemeGroupVersion))
}

三. 不同版本之间的转换

对于hpa,在pkg/apis/autoscaling下定义了internal version与各个version之间的转换函数;

对于hpa:

  • 存储在etcd的version=v1;
  • 通过kubectl get hpa.v2beta2.autoscaling的时候:

    • apiserver首先将v1转换为internal version;
    • 然后将internal version转换为v2beta2;

各个version之间的转换函数:

  • pkg/apis/autoscaling/v1/conversion.go
  • pkg/apis/autoscaling/v2/conversion.go
  • pkg/apis/autoscaling/v2beta1/conversion.go
  • pkg/apis/autoscaling/v2beta2/conversion.go

比如v1的objMetricSource属性,被转换为internal version中的objectMetricSource:

// pkg/apis/autoscaling/v1/conversion.go
func Convert_v1_ObjectMetricSource_To_autoscaling_ObjectMetricSource(in *autoscalingv1.ObjectMetricSource, out *autoscaling.ObjectMetricSource, s conversion.Scope) error {
    var metricType autoscaling.MetricTargetType
    if in.AverageValue == nil {
        metricType = autoscaling.ValueMetricType
    } else {
        metricType = autoscaling.AverageValueMetricType
    }
    out.Target = autoscaling.MetricTarget{
        Type:         metricType,
        Value:        &in.TargetValue,
        AverageValue: in.AverageValue,
    }
    out.DescribedObject = autoscaling.CrossVersionObjectReference{
        Kind:       in.Target.Kind,
        Name:       in.Target.Name,
        APIVersion: in.Target.APIVersion,
    }
    out.Metric = autoscaling.MetricIdentifier{
        Name:     in.MetricName,
        Selector: in.Selector,
    }
    return nil
}

1.注册conversion函数

对于hpa v2beta2,注册了一堆的conversion函数

// pkg/apis/autoscaling/v2beta2/zz_generated.conversion.go
func init() {
    localSchemeBuilder.Register(RegisterConversions)
}

// RegisterConversions adds conversion functions to the given scheme.
// Public to allow building arbitrary schemes.
func RegisterConversions(s *runtime.Scheme) error {
    if err := s.AddGeneratedConversionFunc((*v2beta2.ContainerResourceMetricSource)(nil), (*autoscaling.ContainerResourceMetricSource)(nil), func(a, b interface{}, scope conversion.Scope) error {
        return Convert_v2beta2_ContainerResourceMetricSource_To_autoscaling_ContainerResourceMetricSource(a.(*v2beta2.ContainerResourceMetricSource), b.(*autoscaling.ContainerResourceMetricSource), scope)
    }); err != nil {
        return err
    }
    ...
    // v1的ObjectMetricSource ----> v2beta2的ObjectMetricSource
    if err := s.AddGeneratedConversionFunc((*autoscaling.ObjectMetricSource)(nil), (*v2beta2.ObjectMetricSource)(nil), func(a, b interface{}, scope conversion.Scope) error {
        return Convert_autoscaling_ObjectMetricSource_To_v2beta2_ObjectMetricSource(a.(*autoscaling.ObjectMetricSource), b.(*v2beta2.ObjectMetricSource), scope)
    }); err != nil {
        return err
    }
    ...
}

2. 由convertor对象进行转换

// staging/src/k8s.io/apimachinery/pkg/runtime/scheme.go
func NewScheme() *Scheme {
    s := &Scheme{
        gvkToType:                 map[schema.GroupVersionKind]reflect.Type{},
        typeToGVK:                 map[reflect.Type][]schema.GroupVersionKind{},
        unversionedTypes:          map[reflect.Type]schema.GroupVersionKind{},
        unversionedKinds:          map[string]reflect.Type{},
        fieldLabelConversionFuncs: map[schema.GroupVersionKind]FieldLabelConversionFunc{},
        defaulterFuncs:            map[reflect.Type]func(interface{}){},
        versionPriority:           map[string][]string{},
        schemeName:                naming.GetNameFromCallsite(internalPackages...),
    }
    s.converter = conversion.NewConverter(s.nameFunc)

    // Enable couple default conversions by default.
    utilruntime.Must(RegisterEmbeddedConversions(s))
    utilruntime.Must(RegisterStringConversions(s))
    return s
}

执行转换:

// staging/src/k8s.io/apimachinery/pkg/runtime/scheme.go
func (s *Scheme) ConvertToVersion(in Object, target GroupVersioner) (Object, error) {
    return s.convertToVersion(true, in, target)
}

func (s *Scheme) convertToVersion(copy bool, in Object, target GroupVersioner) (Object, error) {
    ...
    meta := s.generateConvertMeta(in)
    meta.Context = target
    if err := s.converter.Convert(in, out, meta); err != nil {
        return nil, err
    }
    setTargetKind(out, gvk)
    return out, nil
}

参考:

  1. https://segmentfault.com/a/1190000042657668

a朋
63 声望38 粉丝