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;
二. 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
}
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。