kube-controller之history

ControllerRevision

ControllerRevision 实现了一个不可变的状态数据快照。客户端负责序列化和反序列化包含其内部状态的对象
一旦成功创建了 ControllerRevision,就不能对其进行更新,但是可以被删除。APIServer会拒绝所有试图修改 Data 字段的请求。
ControllerRevision主要被 DaemonSetStatefulSet 控制器用于更新和回滚。而且这个对象是beta的,在未来可能会发生变化,
客户端不应依赖其稳定性。因此ControllerRevision主要在内部控制器内使用。

源码分析
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
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
// staging/src/k8s.io/api/apps/v1/types.go
type ControllerRevision struct {
metav1.TypeMeta `json:",inline"`
// Standard object's metadata.
metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"`

// Data is the serialized representation of the state.
Data runtime.RawExtension `json:"data,omitempty" protobuf:"bytes,2,opt,name=data"`

// Revision indicates the revision of the state represented by Data.
Revision int64 `json:"revision" protobuf:"varint,3,opt,name=revision"`
}

// pkg/controller/history/controller_history.go
func NewControllerRevision(parent metav1.Object,
parentKind schema.GroupVersionKind,
templateLabels map[string]string,
data runtime.RawExtension,
revision int64,
collisionCount *int32) (*apps.ControllerRevision, error) {
labelMap := make(map[string]string)
for k, v := range templateLabels {
labelMap[k] = v
}
cr := &apps.ControllerRevision{
ObjectMeta: metav1.ObjectMeta{
Labels: labelMap,
// 设置OwnerReferences
OwnerReferences: []metav1.OwnerReference{*metav1.NewControllerRef(parent, parentKind)},
},
Data: data,
Revision: revision,
}
// 返回cr.Data的hash
hash := HashControllerRevision(cr, collisionCount)
// 设置cr.Name
cr.Name = ControllerRevisionName(parent.GetName(), hash)
cr.Labels[ControllerRevisionHashLabel] = hash
return cr, nil
}

// pkg/controller/history/controller_history.go
func ControllerRevisionName(prefix string, hash string) string {
if len(prefix) > 223 {
prefix = prefix[:223]
}

return fmt.Sprintf("%s-%s", prefix, hash)
}


type realHistory struct {
client clientset.Interface
lister appslisters.ControllerRevisionLister
}

func NewHistory(client clientset.Interface, lister appslisters.ControllerRevisionLister) Interface {
return &realHistory{client, lister}
}

// 返回ControllerRevision列表
func (rh *realHistory) ListControllerRevisions(parent metav1.Object, selector labels.Selector) ([]*apps.ControllerRevision, error) {
// List all revisions in the namespace that match the selector
history, err := rh.lister.ControllerRevisions(parent.GetNamespace()).List(selector)
if err != nil {
return nil, err
}
var owned []*apps.ControllerRevision
for i := range history {
ref := metav1.GetControllerOfNoCopy(history[i])
if ref == nil ControllerRevisions|| ref.UID == parent.GetUID() {
owned = append(owned, history[i])
}

}
return owned, err
}

// 创建ControllerRevision
func (rh *realHistory) CreateControllerRevision(parent metav1.Object, revision *apps.ControllerRevision, collisionCount *int32) (*apps.ControllerRevision, error) {
if collisionCount ControllerRevisions== nil {
return nil, fmt.Errorf("collisionCount should not be nil")
}

// Clone the input
clone := revision.DeepCopy()

// Continue to attempt to create the revision updating the name with a new hash on each iteration
for {
hash := HashControllerRevision(revision, collisionCount)
// Update the revisions name
clone.Name = ControllerRevisionName(parent.GetName(), hash)
ns := parent.GetNamespace()
created, err := rh.client.AppsV1().ControllerRevisions(ns).Create(context.TODO(), clone, metav1.CreateOptions{})
if errors.IsAlreadyExists(err) {
exists, err := rh.client.AppsV1().ControllerRevisions(ns).Get(context.TODO(), clone.Name, metav1.GetOptions{})
if err != nil {
return nil, err
}
if bytes.Equal(exists.Data.Raw, clone.Data.Raw) {
return exists, nil
}
*collisionCount++
continue
}
return created, err
}
}

// 更新ControllerRevision,Data字段不能更新
func (rh *realHistory) UpdateControllerRevision(revision *apps.ControllerRevision, newRevision int64) (*apps.ControllerRevision, error) {
clone := revision.DeepCopy()
err := retry.RetryOnConflict(retry.DefaultBackoff, func() error {
if clone.Revision == newRevision {
return nil
}
clone.Revision = newRevision
updated, updateErr := rh.client.AppsV1().ControllerRevisions(clone.Namespace).Update(context.TODO(), clone, metav1.UpdateOptions{})
if updateErr == nil {
return nil
}
if updated != nil {
clone = updated
}
if updated, err := rh.lister.ControllerRevisions(clone.Namespace).Get(clone.Name); err == nil {
// make a copy so we don't mutate the shared cache
clone = updated.DeepCopy()
}
return updateErr
})
return clone, err
}

// 删除ControllerRevision
func (rh *realHistory) DeleteControllerRevision(revision *apps.ControllerRevision) error {
return rh.client.AppsV1().ControllerRevisions(revision.Namespace).Delete(context.TODO(), revision.Name, metav1.DeleteOptions{})
}


// pkg/controller/statefulset/stateful_set_control.go
func newRevision(set *apps.StatefulSet, revision int64, collisionCount *int32) (*apps.ControllerRevision, error) {
patch, err := getPatch(set)
if err != nil {
return nil, err
}
cr, err := history.NewControllerRevision(set,
controllerKind,
set.Spec.Template.Labels,
runtime.RawExtension{Raw: patch},
revision,
collisionCount)
if err != nil {
return nil, err
}
if cr.ObjectMeta.Annotations == nil {
cr.ObjectMeta.Annotations = make(map[string]string)
}
for key, value := range set.Annotations {
cr.ObjectMeta.Annotations[key] = value
}
return cr, nil
}

func (ssc *defaultStatefulSetControl) getStatefulSetRevisions(
set *apps.StatefulSet,
revisions []*apps.ControllerRevision) (*apps.ControllerRevision, *apps.ControllerRevision, int32, error) {
...
updateRevision, err := newRevision(set, nextRevision(revisions), &collisionCount)
...
}
小结

严格来说ControllerRevision history controller并不是一个真正的控制器,只是提供了创建ControlllerRevison和对ControllerRevision操作的一些方法,可以给其它的控制器使用,比如StatefulSet


REF:
1.staging/src/k8s.io/api/apps/v1/types.go
2.pkg/controller/history/controller_history.go
3.pkg/controller/statefulset/stateful_set_control.go