kubernetes API 資源對象的版本控制
一個 API 資源對象的 Schema 的唯一標識由 apiVersion 和 kind 組成,其中 apiVersion 又分爲 Group 和 Version。例如:
Group Version Kind apps v1 Deployment
kubernetes 的 apiVersion 有很多,可以通過 kubectl api-versions 命令查看當前支持的 API 版本:
# kubectl api-versions admissionregistration.k8s.io/v1beta1 apiextensions.k8s.io/v1beta1 apiregistration.k8s.io/v1 apiregistration.k8s.io/v1beta1 apps/v1 apps/v1beta1 apps/v1beta2 authentication.k8s.io/v1 authentication.k8s.io/v1beta1 authorization.k8s.io/v1 authorization.k8s.io/v1beta1 autoscaling/v1 autoscaling/v2beta1 autoscaling/v2beta2 batch/v1 batch/v1beta1 certificates.k8s.io/v1beta1 coordination.k8s.io/v1 coordination.k8s.io/v1beta1 crd.projectcalico.org/v1 events.k8s.io/v1beta1 extensions/v1beta1 networking.k8s.io/v1 networking.k8s.io/v1beta1 node.k8s.io/v1beta1 policy/v1beta1 rbac.authorization.k8s.io/v1 rbac.authorization.k8s.io/v1beta1 scheduling.k8s.io/v1 scheduling.k8s.io/v1beta1 storage.k8s.io/v1 storage.k8s.io/v1beta1 v1
通過 kubectl api-resources 還可以查看當前支持的 API 資源對象:
# kubectl api-resources | grep deploy deployments deploy apps true Deployment deployments deploy extensions true Deployment
一個 kind 可能出現在不同的 apiVersion 中,例如 Deployment 就出現在瞭如下多個 apiVersion 中:
- k8s.io/api/apps/v1.Deployment
- k8s.io/api/apps/v1beta1.Deployment
- k8s.io/api/apps/v1beta2.Deployment
- k8s.io/api/extensions/v1beta1.Deployment
對於不同的 apiVersion,在 kube-apiserver 中,其對應的 HTTP Handler 是什麼樣子的呢?
在 kube-apiserver 的啓動過程中,服務路由的安裝是通過如下調用實現的:
func (c completedConfig) New(delegationTarget genericapiserver.DelegationTarget) (*Master, error) { ...... restStorageProviders := []RESTStorageProvider{ auditregistrationrest.RESTStorageProvider{}, authenticationrest.RESTStorageProvider{Authenticator: c.GenericConfig.Authentication.Authenticator, APIAudiences: c.GenericConfig.Authentication.APIAudiences}, authorizationrest.RESTStorageProvider{Authorizer: c.GenericConfig.Authorization.Authorizer, RuleResolver: c.GenericConfig.RuleResolver}, autoscalingrest.RESTStorageProvider{}, batchrest.RESTStorageProvider{}, certificatesrest.RESTStorageProvider{}, coordinationrest.RESTStorageProvider{}, extensionsrest.RESTStorageProvider{}, networkingrest.RESTStorageProvider{}, noderest.RESTStorageProvider{}, policyrest.RESTStorageProvider{}, rbacrest.RESTStorageProvider{Authorizer: c.GenericConfig.Authorization.Authorizer}, schedulingrest.RESTStorageProvider{}, settingsrest.RESTStorageProvider{}, storagerest.RESTStorageProvider{}, // keep apps after extensions so legacy clients resolve the extensions versions of shared resource names. // See https://github.com/kubernetes/kubernetes/issues/42392 appsrest.RESTStorageProvider{}, admissionregistrationrest.RESTStorageProvider{}, eventsrest.RESTStorageProvider{TTL: c.ExtraConfig.EventTTL}, } m.InstallAPIs(c.ExtraConfig.APIResourceConfigSource, c.GenericConfig.RESTOptionsGetter, restStorageProviders...) ...... }
以 appsrest.RESTStorageProvider{} 爲例,從它的 NewRESTStorage 方法可以看出,它管理了 apps 這個 Group 下支持的 Version:
func (p RESTStorageProvider) NewRESTStorage(apiResourceConfigSource serverstorage.APIResourceConfigSource, restOptionsGetter generic.RESTOptionsGetter) (genericapiserver.APIGroupInfo, bool) { apiGroupInfo := genericapiserver.NewDefaultAPIGroupInfo(apps.GroupName, legacyscheme.Scheme, legacyscheme.ParameterCodec, legacyscheme.Codecs) // If you add a version here, be sure to add an entry in `k8s.io/kubernetes/cmd/kube-apiserver/app/aggregator.go with specific priorities. // TODO refactor the plumbing to provide the information in the APIGroupInfo if apiResourceConfigSource.VersionEnabled(appsapiv1beta1.SchemeGroupVersion) { apiGroupInfo.VersionedResourcesStorageMap[appsapiv1beta1.SchemeGroupVersion.Version] = p.v1beta1Storage(apiResourceConfigSource, restOptionsGetter) } if apiResourceConfigSource.VersionEnabled(appsapiv1beta2.SchemeGroupVersion) { apiGroupInfo.VersionedResourcesStorageMap[appsapiv1beta2.SchemeGroupVersion.Version] = p.v1beta2Storage(apiResourceConfigSource, restOptionsGetter) } if apiResourceConfigSource.VersionEnabled(appsapiv1.SchemeGroupVersion) { apiGroupInfo.VersionedResourcesStorageMap[appsapiv1.SchemeGroupVersion.Version] = p.v1Storage(apiResourceConfigSource, restOptionsGetter) } return apiGroupInfo, true }
而每個 Version 下面管理着支持的 Kind:
func (p RESTStorageProvider) v1Storage(apiResourceConfigSource serverstorage.APIResourceConfigSource, restOptionsGetter generic.RESTOptionsGetter) map[string]rest.Storage { storage := map[string]rest.Storage{} // deployments deploymentStorage := deploymentstore.NewStorage(restOptionsGetter) storage["deployments"] = deploymentStorage.Deployment storage["deployments/status"] = deploymentStorage.Status storage["deployments/scale"] = deploymentStorage.Scale ...... return storage }
對比不同 Version,發現對於同一個 Kind,使用的都是相同的 Storage 實現,例如 Deployment 的 Storage 實現就是 pkg/registry/apps/deployment/storage.DeploymentStorage。爲何不同 apiVersion 的資源對象可以使用相同的 Storage 實現呢?
通過跟蹤請求 PATCH /apis/apps/v1/namespaces/default/deployments/nginx,發現在處理過程中對資源對象版本進行了轉換:
patcher.patchResource ... schemaReferenceObj, err := p.unsafeConvertor.ConvertToVersion(p.restPatcher.New(), p.kind.GroupVersion()) // *v1.Deployment *apps.Deployment apps/v1 ... smpPatcher.applyPatchToCurrentObject ... versionedObjToUpdate, err := p.creater.New(p.kind) // runtime.Scheme // *v1.Deployment if err != nil { return nil, err } if err := strategicPatchObject(p.defaulter, currentVersionedObject, p.patchBytes, versionedObjToUpdate, p.schemaReferenceObj); err != nil { return nil, err } newObj, err := p.unsafeConvertor.ConvertToVersion(versionedObjToUpdate, p.hubGroupVersion) // *apps.Deployment *v1.Deployment apps/__internal
也就是 apps/v1/Deployment 會在處理的過程中被轉換爲內部對象 apps/Deployment。那麼這個轉換又是如何完成的呢?
通過調查 p.unsafeConvertor.ConvertToVersion,其實際上調用的是:
// 該方法會將 in 轉換爲 target 指定的類型。 k8s.io/apimachinery/pkg/runtime.Scheme.ConvertToVersion(in Object, target GroupVersioner) k8s.io/apimachinery/pkg/conversion.Converter.Convert(src, dest interface{}, flags FieldMatchingFlags, meta *Meta) // 該方法會被註冊到 conversion.Converter 中 pkg/apis/apps/v1.Convert_v1_Deployment_To_apps_Deployment(in *appsv1.Deployment, out *apps.Deployment, s conversion.Scope)
所以完成 apps/v1/Deployment 到 apps/Deployment 的轉換就是由 pkg/apis/apps/v1.Convert_v1_Deployment_To_apps_Deployment 實現的,對應的 apps/Deployment 到 apps/v1/Deployment 的轉換是由 pkg/apis/apps/v1.Convert_apps_Deployment_To_v1_Deployment 實現,用於從 Etcd 中讀取資源對象後,解碼出來的是 apps/Deployment,然後根據當前請求的 apiVersion 進行轉換。