什么是strategy 本次分析将介绍storage的strategy。在创建,更新,删除资源时,我们常常需要有对资源进行预处理的需求。Kubernetes会对系统中的资源都定义一个strategy,用来表明在操作资源时该资源应有的处理方式。在Kubernetes中,主要有RESTCreateStrategy,RESTGracefulDeleteStrategy,RESTUpdateStrategy等。
RESTCreateStrategy RESTCreateStrategy用来标识资源在创建时应有的处理方式,定义在/pkg/api/rest/create.go中:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
type RESTCreateStrategy interface {
runtime.ObjectTyper
api.NameGenerator
NamespaceScoped() bool
PrepareForCreate(ctx api.Context, obj runtime.Object)
Validate(ctx api.Context, obj runtime.Object) field.ErrorList
Canonicalize(obj runtime.Object)
}
RESTUpdateStrategy接口的各方法含义如下:
NamespaceScoped(): 返回该资源是否在namespace scope下;
PrepareForCreate(): 创建前处理资源;
Validate():对资源进行创建前的验证,一般在PrepareForCreate()后执行;
Canonicalize():规范化资源,一般在Validate()执行。
Kubernetes还提供了BeforeCreate()封装PrepareForCreate(), Validate()和Canonicalize():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
func BeforeCreate (strategy RESTCreateStrategy, ctx api.Context, obj runtime.Object) error {
objectMeta, kind, kerr := objectMetaAndKind(strategy, obj)
if kerr != nil {
return kerr
}
if strategy.NamespaceScoped() {
if !api.ValidNamespace(ctx, objectMeta) {
return errors.NewBadRequest("the namespace of the provided object does not match the namespace sent on the request" )
}
} else {
objectMeta.Namespace = api.NamespaceNone
}
objectMeta.DeletionTimestamp = nil
objectMeta.DeletionGracePeriodSeconds = nil
strategy.PrepareForCreate(ctx, obj)
api.FillObjectMetaSystemFields(ctx, objectMeta)
api.GenerateName(strategy, objectMeta)
objectMeta.ClusterName = ""
if errs := strategy.Validate(ctx, obj); len (errs) > 0 {
return errors.NewInvalid(kind.GroupKind(), objectMeta.Name, errs)
}
if errs := validation.ValidateObjectMeta(objectMeta, strategy.NamespaceScoped(), path.ValidatePathSegmentName, field.NewPath("metadata" )); len (errs) > 0 {
return errors.NewInvalid(kind.GroupKind(), objectMeta.Name, errs)
}
strategy.Canonicalize(obj)
return nil
}
BeforeCreate()在/pkg/registry/generic/registry/store.go的Create()中会调用到。
RESTGracefulDeleteStrategy RESTGracefulDeleteStrategy用来标识资源在更新时应有的处理方式,定义在/pkg/api/rest/delete.go中:1
2
3
4
5
6
7
type RESTGracefulDeleteStrategy interface {
CheckGracefulDelete(ctx api.Context, obj runtime.Object, options *api.DeleteOptions) bool
}
RESTGracefulDeleteStrategy接口的各方法含义如下:
CheckGracefulDelete(): 返回该资源是否允许优雅地删除。
Kubernetes还提供了BeforeDelete():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
func BeforeDelete (strategy RESTDeleteStrategy, ctx api.Context, obj runtime.Object, options *api.DeleteOptions) (graceful, gracefulPending bool , err error) {
objectMeta, gvk, kerr := objectMetaAndKind(strategy, obj)
if kerr != nil {
return false , false , kerr
}
if options.Preconditions != nil && options.Preconditions.UID != nil && *options.Preconditions.UID != objectMeta.UID {
return false , false , errors.NewConflict(unversioned.GroupResource{Group: gvk.Group, Resource: gvk.Kind}, objectMeta.Name, fmt.Errorf("the UID in the precondition (%s) does not match the UID in record (%s). The object might have been deleted and then recreated" , *options.Preconditions.UID, objectMeta.UID))
}
gracefulStrategy, ok := strategy.(RESTGracefulDeleteStrategy)
if !ok {
return false , false , nil
}
if objectMeta.DeletionTimestamp != nil {
if objectMeta.DeletionGracePeriodSeconds == nil || *objectMeta.DeletionGracePeriodSeconds == 0 {
return false , false , nil
}
if options.GracePeriodSeconds != nil {
period := int64 (*options.GracePeriodSeconds)
if period >= *objectMeta.DeletionGracePeriodSeconds {
return false , true , nil
}
newDeletionTimestamp := unversioned.NewTime(
objectMeta.DeletionTimestamp.Add(-time.Second * time.Duration(*objectMeta.DeletionGracePeriodSeconds)).
Add(time.Second * time.Duration(*options.GracePeriodSeconds)))
objectMeta.DeletionTimestamp = &newDeletionTimestamp
objectMeta.DeletionGracePeriodSeconds = &period
return true , false , nil
}
options.GracePeriodSeconds = objectMeta.DeletionGracePeriodSeconds
return false , true , nil
}
if !gracefulStrategy.CheckGracefulDelete(ctx, obj, options) {
return false , false , nil
}
now := unversioned.NewTime(unversioned.Now().Add(time.Second * time.Duration(*options.GracePeriodSeconds)))
objectMeta.DeletionTimestamp = &now
objectMeta.DeletionGracePeriodSeconds = options.GracePeriodSeconds
if objectMeta.Generation > 0 {
objectMeta.Generation++
}
return true , false , nil
}
BeforeDelete()在/pkg/registry/generic/registry/store.go的Delete()中会调用到。
RESTUpdateStrategy RESTUpdateStrategy用来标识资源在更新时应有的处理方式,定义在/pkg/api/rest/update.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
type RESTUpdateStrategy interface {
runtime.ObjectTyper
NamespaceScoped() bool
AllowCreateOnUpdate() bool
PrepareForUpdate(ctx api.Context, obj, old runtime.Object)
ValidateUpdate(ctx api.Context, obj, old runtime.Object) field.ErrorList
Canonicalize(obj runtime.Object)
AllowUnconditionalUpdate() bool
}
RESTUpdateStrategy接口的各方法含义如下:
NamespaceScoped(): 返回该资源是否在namespace scope下;
AllowCreateOnUpdate():表明该资源在更新时,如果资源不存在,是否允许创建;
PrepareForUpdate(): 更新前处理资源;
ValidateUpdate():对资源进行更新前的验证,一般在PrepareForUpdate()执行;
Canonicalize():规范化资源,一般在ValidateUpdate()执行;
AllowUnconditionalUpdate():表明在未指定资源版本时,是否允许更新。
Kubernetes还提供了BeforeUpdate()封装PrepareForUpdate(), ValidateUpdate()和Canonicalize():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
func BeforeUpdate (strategy RESTUpdateStrategy, ctx api.Context, obj, old runtime.Object) error {
objectMeta, kind, kerr := objectMetaAndKind(strategy, obj)
if kerr != nil {
return kerr
}
if strategy.NamespaceScoped() {
if !api.ValidNamespace(ctx, objectMeta) {
return errors.NewBadRequest("the namespace of the provided object does not match the namespace sent on the request" )
}
} else {
objectMeta.Namespace = api.NamespaceNone
}
oldMeta, err := api.ObjectMetaFor(old)
if err != nil {
return err
}
objectMeta.Generation = oldMeta.Generation
strategy.PrepareForUpdate(ctx, obj, old)
objectMeta.ClusterName = ""
errs, err := validateCommonFields(obj, old)
if err != nil {
return errors.NewInternalError(err)
}
errs = append (errs, strategy.ValidateUpdate(ctx, obj, old)...)
if len (errs) > 0 {
return errors.NewInvalid(kind.GroupKind(), objectMeta.Name, errs)
}
strategy.Canonicalize(obj)
return nil
}
BeforeUpdate()在/pkg/registry/generic/registry/store.go的Update()中会调用到。
deploymentStrategy 在Kubernetes中,每种资源都有strategy,并实现有之前分析过的各strategy接口中的方法。我们以deploymentStrategy为例说明资源如何实现各strategy接口中的方法。 deploymentStrategy定义在/pkg/registry/extensions/deployment/strategy.go中:1
2
3
4
5
type deploymentStrategy struct {
runtime.ObjectTyper
api.NameGenerator
}
NamespaceScoped()方法实现如下:1
2
3
4
func (deploymentStrategy) NamespaceScoped () bool {
return true
}
NamespaceScoped()直接返回true表明deployment是属于namespace scope的。
PrepareForCreate()方法实现如下:1
2
3
4
5
6
func (deploymentStrategy) PrepareForCreate (ctx api.Context, obj runtime.Object) {
deployment := obj.(*extensions.Deployment)
deployment.Status = extensions.DeploymentStatus{}
deployment.Generation = 1
}
PrepareForCreate()会去除deployment中的status,并设置Generation为1。
Validate()方法实现如下:1
2
3
4
5
func (deploymentStrategy) Validate (ctx api.Context, obj runtime.Object) field .ErrorList {
deployment := obj.(*extensions.Deployment)
return validation.ValidateDeployment(deployment)
}
Validate()调用了validation包中的ValidateDeployment()来确认deployment是否合法。
Canonicalize()方法实现如下:1
2
3
func (deploymentStrategy) Canonicalize (obj runtime.Object) {
}
Canonicalize()方法为空。
AllowCreateOnUpdate()方法实现如下:1
2
3
4
func (deploymentStrategy) AllowCreateOnUpdate () bool {
return false
}
AllowCreateOnUpdate()方法返回false,表明不允许更新时创建。
PrepareForUpdate()方法实现如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
func (deploymentStrategy) PrepareForUpdate (ctx api.Context, obj, old runtime.Object) {
newDeployment := obj.(*extensions.Deployment)
oldDeployment := old.(*extensions.Deployment)
newDeployment.Status = oldDeployment.Status
if !reflect.DeepEqual(newDeployment.Spec, oldDeployment.Spec) ||
!reflect.DeepEqual(newDeployment.Annotations, oldDeployment.Annotations) {
newDeployment.Generation = oldDeployment.Generation + 1
}
if !reflect.DeepEqual(newDeployment.Spec.Selector, oldDeployment.Spec.Selector) {
if newDeployment.Annotations == nil {
newDeployment.Annotations = make (map [string ]string )
}
now := unversioned.Now()
newDeployment.Annotations[util.SelectorUpdateAnnotation] = now.Format(time.RFC3339)
}
}
PrepareForUpdate()会对Generation和Annotations[util.SelectorUpdateAnnotation]进行更新。
ValidateUpdate()方法实现如下:1
2
3
4
func (deploymentStrategy) ValidateUpdate (ctx api.Context, obj, old runtime.Object) field .ErrorList {
return validation.ValidateDeploymentUpdate(obj.(*extensions.Deployment), old.(*extensions.Deployment))
}
ValidateDeploymentUpdate()调用validation包中的ValidateDeploymentUpdate()。
总结 Kubernetes的strategy机制提供了一种对具体资源进行具体处理的能力。资源的特殊操作都应该放在strategy的方法中。