本次分析将介绍如何在kubernetes v1.5.2中通过代码添加一种资源。我们假设要添加的资源为”Application”,只有一个”replicas”属性,即如下所示:
1 2 3 4 5 6 7 8 9 10 11
| apiVersion: extensions/v1beta1 kind: Application metadata: creationTimestamp: 2017-12-06T11:37:22Z generation: 1 name: ubuntu namespace: default resourceVersion: "448526" selfLink: /apis/extensions/v1beta1/namespaces/default/applications/ubuntu uid: d2fb278c-da79-11e7-adb3-0800274a4ec3 replicas: 20
|
因为这个demo是参考Deployment写的,所以,我们把Application也放在了extensions/v1beta1中。自己另起一个group添加资源待以后弄清楚Kubernetes的自动生成代码机制再实验,这部分可以参考帮助文件adding-an-APIGroup.md。
添加Application的定义
首先,我们要在/pkg/apis/extensions/types.go中添加Application及ApplicationList的定义:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| type Application struct { unversioned.TypeMeta `json:",inline"` api.ObjectMeta `json:"metadata,omitempty"` Replicas int32 `json:"replicas,omitempty"` } type ApplicationList struct { unversioned.TypeMeta `json:",inline"` unversioned.ListMeta `json:"metadata,omitempty"` Items []Application `json:"items"` }
|
其中,// +genclient=true
估计是和代码自动生成机制有关,以后研究。
同样,在/pkg/extensions/v1beta1/types.go中添加Application及ApplicationList的定义:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| type Application struct { unversioned.TypeMeta `json:",inline"` v1.ObjectMeta `json:"metadata,omitempty"` Replicas int32 `json:"replicas,omitempty"` } type ApplicationList struct { unversioned.TypeMeta `json:",inline"` unversioned.ListMeta `json:"metadata,omitempty"` Items []Application `json:"items"` }
|
添加完成之后,执行hack/update-all.sh -v
,执行该脚本需要下载gcr.io/google_containers/kube-cross:v1.7.4-0。然后就等,需要很长时间完成,我这边执行到特定阶段时会报错,但此时zz_generated.deepcopy.go等文件已有Application的相关转换代码,ClientSet中也已经有了Application的定义,所以主目标已经达到。至于为什么出错,出错带来的影响,还有待研究完代码自动生成机制后才能给出结论。
报错信息如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| ...... make[1]: Leaving directory '/home/fankang/kubernetes/kubernetes-1.5.2' +++ [1210 14:31:32] Building the toolchain targets: k8s.io/kubernetes/hack/cmd/teststale k8s.io/kubernetes/vendor/github.com/jteeuwen/go-bindata/go-bindata +++ [1210 14:31:32] Generating bindata: test/e2e/framework/gobindata_util.go +++ [1210 14:31:33] Building go targets for linux/amd64: cmd/gendocs cmd/genkubedocs cmd/genman cmd/genyaml federation/cmd/genfeddocs make: Leaving directory '/home/fankang/kubernetes/kubernetes-1.5.2' Generated docs have been placed in the repository tree. Running hack/update-munge-docs.sh. fatal: Not a git repository (or any of the parent directories): .git !!! Error in /home/fankang/kubernetes/kubernetes-1.5.2/hack/update-munge-docs.sh:24 'git_upstream=$(kube::util::git_upstream_remote_name)' exited with status 1 Call stack: 1: /home/fankang/kubernetes/kubernetes-1.5.2/hack/update-munge-docs.sh:24 main(...) Exiting with status 1 !!! Error in hack/../hack/update-generated-docs.sh:57 '"${KUBE_ROOT}/hack/update-munge-docs.sh"' exited with status 1 Call stack: 1: hack/../hack/update-generated-docs.sh:57 main(...) Exiting with status 1 Updating generated-docs FAILED
|
现在修改/pkg/apis/extensions/register.go,添加Application和ApplicationList的注册:
1 2 3 4 5 6 7 8
| func addKnownTypes(scheme *runtime.Scheme) error { scheme.AddKnownTypes(SchemeGroupVersion, &Application{}, &ApplicationList{}, &Deployment{}, &DeploymentList{}, ......
|
同样,修改/pkg/apis/extensions/v1beta1/register.go,添加Application和ApplicationList的注册:
1 2 3 4 5 6 7
| func addKnownTypes(scheme *runtime.Scheme) error { scheme.AddKnownTypes(SchemeGroupVersion, &Application{}, &ApplicationList{}, &Deployment{}, &DeploymentList{}, ......
|
至此,已经完成apiserver侧的资源添加,但此时,资源对应的storage还未定义,所以资源还处在不可用状态。
添加Storage
首先在/pkg/registry/extensions目录下添加application目录。
在application目录下添加doc.go:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| Copyright 2015 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package application
|
然后添加etcd目录,并在etcd目录下,即/pkg/registry/extensions/application/etcd下添加etcd.go:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97
| Copyright 2015 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package etcd import ( "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/apis/extensions" "k8s.io/kubernetes/pkg/registry/cachesize" "k8s.io/kubernetes/pkg/registry/extensions/application" "k8s.io/kubernetes/pkg/registry/generic" "k8s.io/kubernetes/pkg/registry/generic/registry" "k8s.io/kubernetes/pkg/runtime" "k8s.io/kubernetes/pkg/storage" ) type ApplicationStorage struct { Application *REST } func NewStorage(opts generic.RESTOptions) ApplicationStorage { applicationRest := NewREST(opts) return ApplicationStorage{ Application: applicationRest, } } type REST struct { *registry.Store } func NewREST(opts generic.RESTOptions) (*REST) { prefix := "/" + opts.ResourcePrefix newListFunc := func() runtime.Object { return &extensions.ApplicationList{} } storageInterface, dFunc := opts.Decorator( opts.StorageConfig, cachesize.GetWatchCacheSizeByResource(cachesize.Applications), &extensions.Application{}, prefix, application.Strategy, newListFunc, storage.NoTriggerPublisher, ) store := ®istry.Store{ NewFunc: func() runtime.Object { return &extensions.Application{} }, NewListFunc: newListFunc, KeyRootFunc: func(ctx api.Context) string { return registry.NamespaceKeyRootFunc(ctx, prefix) }, KeyFunc: func(ctx api.Context, name string) (string, error) { return registry.NamespaceKeyFunc(ctx, prefix, name) }, ObjectNameFunc: func(obj runtime.Object) (string, error) { return obj.(*extensions.Application).Name, nil }, PredicateFunc: application.MatchApplication, QualifiedResource: extensions.Resource("applications"), EnableGarbageCollection: opts.EnableGarbageCollection, DeleteCollectionWorkers: opts.DeleteCollectionWorkers, CreateStrategy: application.Strategy, UpdateStrategy: application.Strategy, DeleteStrategy: application.Strategy, Storage: storageInterface, DestroyFunc: dFunc, } return &REST{store} }
|
现在我们可以看到,在NewREST()函数中,我们还缺cachesize.Applications及application.Strategy还未定义。
所以修改/pkg/registry/cachesize/cachesize.go,添加Application的定义:
1 2 3 4 5
| const ( Applications Resource = "applications" CertificateSigningRequests Resource = "certificatesigningrequests" ClusterRoles Resource = "clusterroles" ......
|
再来定义application.Strategy,在/pkg/registry/extensions/application/目录下添加strategy.go:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104
| Copyright 2015 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package application import ( "fmt" "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/apis/extensions" "k8s.io/kubernetes/pkg/apis/extensions/validation" "k8s.io/kubernetes/pkg/fields" "k8s.io/kubernetes/pkg/labels" "k8s.io/kubernetes/pkg/registry/generic" "k8s.io/kubernetes/pkg/runtime" apistorage "k8s.io/kubernetes/pkg/storage" "k8s.io/kubernetes/pkg/util/validation/field" ) type applicationStrategy struct { runtime.ObjectTyper api.NameGenerator } var Strategy = applicationStrategy{api.Scheme, api.SimpleNameGenerator} func (applicationStrategy) NamespaceScoped() bool { return true } func (applicationStrategy) PrepareForCreate(ctx api.Context, obj runtime.Object) { application := obj.(*extensions.Application) application.Generation = 1 } func (applicationStrategy) Validate(ctx api.Context, obj runtime.Object) field.ErrorList { application := obj.(*extensions.Application) return validation.ValidateApplication(application) } func (applicationStrategy) Canonicalize(obj runtime.Object) { } func (applicationStrategy) AllowCreateOnUpdate() bool { return false } func (applicationStrategy) PrepareForUpdate(ctx api.Context, obj, old runtime.Object) { } func (applicationStrategy) ValidateUpdate(ctx api.Context, obj, old runtime.Object) field.ErrorList { return validation.ValidateApplicationUpdate(obj.(*extensions.Application), old.(*extensions.Application)) } func (applicationStrategy) AllowUnconditionalUpdate() bool { return true } func ApplicationToSelectableFields(application *extensions.Application) fields.Set { return generic.ObjectMetaFieldsSet(&application.ObjectMeta, true) } func MatchApplication(label labels.Selector, field fields.Selector) apistorage.SelectionPredicate { return apistorage.SelectionPredicate{ Label: label, Field: field, GetAttrs: func(obj runtime.Object) (labels.Set, fields.Set, error) { application, ok := obj.(*extensions.Application) if !ok { return nil, nil, fmt.Errorf("given object is not a application.") } return labels.Set(application.ObjectMeta.Labels), ApplicationToSelectableFields(application), nil }, } }
|
strategy.go中的这些方法或函数必须定义,至于要实现的功能,还要看具体的需求。当然,由于参考了Deployment的实现,applicationStrategy的方法Validate()调用了validate.ValidateApplication(),所以我们要去定义ValidateApplication(),定义在/pkg/apis/extensions/validation/validation.go中:
1 2 3 4 5 6 7
| func ValidateApplication(obj *extensions.Application) field.ErrorList { return apivalidation.ValidateObjectMeta(&obj.ObjectMeta, true, ValidateDeploymentName, field.NewPath("metadata")) } func ValidateApplicationUpdate(update, old *extensions.Application) field.ErrorList { return apivalidation.ValidateObjectMeta(&update.ObjectMeta, true, ValidateDeploymentName, field.NewPath("metadata")) }
|
至此我们有了Application storage,现在可以在RESTStorageProvider的v1beta1Storage()中添加该storage了,在/pkg/registry/extensions/rest/storage_extensions.go中:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| import ( ...... applicationetcd "k8s.io/kubernetes/pkg/registry/extensions/application/etcd" ...... ) func (p RESTStorageProvider) v1beta1Storage(apiResourceConfigSource genericapiserver.APIResourceConfigSource, restOptionsGetter genericapiserver.RESTOptionsGetter) map[string]rest.Storage { version := extensionsapiv1beta1.SchemeGroupVersion storage := map[string]rest.Storage{} ...... if apiResourceConfigSource.ResourceEnabled(version.WithResource("applications")) { applicationStorage := applicationetcd.NewStorage(restOptionsGetter(extensions.Resource("applications"))) storage["applications"] = applicationStorage.Application } ......
|
这里可以看到,apiResourceConfigSource.ResourceEnabled(version.WithResource(“applications”))的结果是false,因为我们还没有在apiResourceConfigSource中注册Application。
修改/pkg/master/master.go的DefaultAPIResourceConfigSource():
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| func DefaultAPIResourceConfigSource() *genericapiserver.ResourceConfig { ...... ret.EnableResources( extensionsapiv1beta1.SchemeGroupVersion.WithResource("applications"), extensionsapiv1beta1.SchemeGroupVersion.WithResource("daemonsets"), extensionsapiv1beta1.SchemeGroupVersion.WithResource("deployments"), extensionsapiv1beta1.SchemeGroupVersion.WithResource("horizontalpodautoscalers"), extensionsapiv1beta1.SchemeGroupVersion.WithResource("ingresses"), extensionsapiv1beta1.SchemeGroupVersion.WithResource("jobs"), extensionsapiv1beta1.SchemeGroupVersion.WithResource("networkpolicies"), extensionsapiv1beta1.SchemeGroupVersion.WithResource("replicasets"), extensionsapiv1beta1.SchemeGroupVersion.WithResource("thirdpartyresources"), ) return ret }
|
添加HumanPrinter
之前分析过,kubectl get pod
这类命令调用的是HumanPrinter。现在我们就需要在HumanPrinter中添加Application的打印函数。
修改/pkg/kubectl/resource_printer.go中HumanReadablePrinter的addDefaultHandlers():
1 2 3 4 5
| func (h *HumanReadablePrinter) addDefaultHandlers() { ...... h.Handler(applicationColumns, printApplication) h.Handler(applicationColumns, printApplicationList) ......
|
可以看到,我们现在缺applicationColumns,printApplication和printApplicationList。
先来定义applicationColumns,在同文件下的var xxxColumns列表定义中添加applicationColumns:
1 2 3 4
| withNamespacePrefixColumns = []string{"NAMESPACE"} deploymentColumns = []string{"NAME", "DESIRED", "CURRENT", "UP-TO-DATE", "AVAILABLE", "AGE"} applicationColumns = []string{"NAME", "DESIRED", "AGE"} configMapColumns = []string{"NAME", "DATA", "AGE"}
|
再来定义printApplication和printApplicationList,在相同文件下,添加:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| func printApplication(application *extensions.Application, w io.Writer, options PrintOptions) error { name := formatResourceName(options.Kind, application.Name, options.WithKind) if options.WithNamespace { if _, err := fmt.Fprintf(w, "%s\t", application.Namespace); err != nil { return err } } desiredReplicas := application.Replicas age := translateTimestamp(application.CreationTimestamp) if _, err := fmt.Fprintf(w, "%s\t%d\t%s", name, desiredReplicas, age); err != nil { return err } if _, err := fmt.Fprint(w, AppendLabels(application.Labels, options.ColumnLabels)); err != nil { return err } _, err := fmt.Fprint(w, AppendAllLabels(options.ShowLabels, application.Labels)) return err } func printApplicationList(list *extensions.ApplicationList, w io.Writer, options PrintOptions) error { for _, item := range list.Items { if err := printApplication(&item, w, options); err != nil { return err } } return nil }
|
现在,我们可以执行make编译了。
验证
可以使用下面文件创建Application:
1 2 3 4 5 6 7 8 9
| { "apiVersion": "extensions/v1beta1", "kind": "Application", "metadata": { "name": "ubuntu1", "namespace": "default" }, "replicas": 20 }
|
可以使用kubectl get application
获取application列表:
1 2 3 4
| root@fankang:/home/fankang/kubernetes/kubernetes-1.5.2# ./_output/bin/kubectl get application NAME DESIRED AGE ubuntu 20 4d ubuntu1 20 3d
|
也可以查看具体的application:
1 2 3 4 5 6 7 8 9 10 11 12
| root@fankang:/home/fankang/kubernetes/kubernetes-1.5.2# ./_output/bin/kubectl get application ubuntu -o yaml apiVersion: extensions/v1beta1 kind: Application metadata: creationTimestamp: 2017-12-06T11:37:22Z generation: 1 name: ubuntu namespace: default resourceVersion: "448526" selfLink: /apis/extensions/v1beta1/namespaces/default/applications/ubuntu uid: d2fb278c-da79-11e7-adb3-0800274a4ec3 replicas: 20
|
资源添加完成。