@@ -23,9 +23,11 @@ import (
23
23
v1 "k8s.io/api/core/v1"
24
24
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
25
25
utilerrors "k8s.io/apimachinery/pkg/util/errors"
26
+ utilfeature "k8s.io/apiserver/pkg/util/feature"
26
27
"k8s.io/client-go/tools/record"
27
28
"k8s.io/klog/v2"
28
29
"k8s.io/kubernetes/pkg/controller/history"
30
+ "k8s.io/kubernetes/pkg/features"
29
31
)
30
32
31
33
// StatefulSetControl implements the control logic for updating StatefulSets and their children Pods. It is implemented
@@ -36,7 +38,7 @@ type StatefulSetControlInterface interface {
36
38
// If an implementation returns a non-nil error, the invocation will be retried using a rate-limited strategy.
37
39
// Implementors should sink any errors that they do not wish to trigger a retry, and they may feel free to
38
40
// exit exceptionally at any point provided they wish the update to be re-run at a later point in time.
39
- UpdateStatefulSet (set * apps.StatefulSet , pods []* v1.Pod ) error
41
+ UpdateStatefulSet (set * apps.StatefulSet , pods []* v1.Pod ) ( * apps. StatefulSetStatus , error )
40
42
// ListRevisions returns a array of the ControllerRevisions that represent the revisions of set. If the returned
41
43
// error is nil, the returns slice of ControllerRevisions is valid.
42
44
ListRevisions (set * apps.StatefulSet ) ([]* apps.ControllerRevision , error )
@@ -71,60 +73,57 @@ type defaultStatefulSetControl struct {
71
73
// strategy allows these constraints to be relaxed - pods will be created and deleted eagerly and
72
74
// in no particular order. Clients using the burst strategy should be careful to ensure they
73
75
// understand the consistency implications of having unpredictable numbers of pods available.
74
- func (ssc * defaultStatefulSetControl ) UpdateStatefulSet (set * apps.StatefulSet , pods []* v1.Pod ) error {
75
-
76
+ func (ssc * defaultStatefulSetControl ) UpdateStatefulSet (set * apps.StatefulSet , pods []* v1.Pod ) (* apps.StatefulSetStatus , error ) {
76
77
// list all revisions and sort them
77
78
revisions , err := ssc .ListRevisions (set )
78
79
if err != nil {
79
- return err
80
+ return nil , err
80
81
}
81
82
history .SortControllerRevisions (revisions )
82
83
83
- currentRevision , updateRevision , err := ssc .performUpdate (set , pods , revisions )
84
+ currentRevision , updateRevision , status , err := ssc .performUpdate (set , pods , revisions )
84
85
if err != nil {
85
- return utilerrors .NewAggregate ([]error {err , ssc .truncateHistory (set , pods , revisions , currentRevision , updateRevision )})
86
+ return nil , utilerrors .NewAggregate ([]error {err , ssc .truncateHistory (set , pods , revisions , currentRevision , updateRevision )})
86
87
}
87
88
88
89
// maintain the set's revision history limit
89
- return ssc .truncateHistory (set , pods , revisions , currentRevision , updateRevision )
90
+ return status , ssc .truncateHistory (set , pods , revisions , currentRevision , updateRevision )
90
91
}
91
92
92
93
func (ssc * defaultStatefulSetControl ) performUpdate (
93
- set * apps.StatefulSet , pods []* v1.Pod , revisions []* apps.ControllerRevision ) (* apps.ControllerRevision , * apps.ControllerRevision , error ) {
94
-
94
+ set * apps.StatefulSet , pods []* v1.Pod , revisions []* apps.ControllerRevision ) (* apps.ControllerRevision , * apps.ControllerRevision , * apps. StatefulSetStatus , error ) {
95
+ var currentStatus * apps. StatefulSetStatus
95
96
// get the current, and update revisions
96
97
currentRevision , updateRevision , collisionCount , err := ssc .getStatefulSetRevisions (set , revisions )
97
98
if err != nil {
98
- return currentRevision , updateRevision , err
99
+ return currentRevision , updateRevision , currentStatus , err
99
100
}
100
101
101
102
// perform the main update function and get the status
102
- status , err : = ssc .updateStatefulSet (set , currentRevision , updateRevision , collisionCount , pods )
103
+ currentStatus , err = ssc .updateStatefulSet (set , currentRevision , updateRevision , collisionCount , pods )
103
104
if err != nil {
104
- return currentRevision , updateRevision , err
105
+ return currentRevision , updateRevision , currentStatus , err
105
106
}
106
-
107
107
// update the set's status
108
- err = ssc .updateStatefulSetStatus (set , status )
108
+ err = ssc .updateStatefulSetStatus (set , currentStatus )
109
109
if err != nil {
110
- return currentRevision , updateRevision , err
110
+ return currentRevision , updateRevision , currentStatus , err
111
111
}
112
-
113
112
klog .V (4 ).Infof ("StatefulSet %s/%s pod status replicas=%d ready=%d current=%d updated=%d" ,
114
113
set .Namespace ,
115
114
set .Name ,
116
- status .Replicas ,
117
- status .ReadyReplicas ,
118
- status .CurrentReplicas ,
119
- status .UpdatedReplicas )
115
+ currentStatus .Replicas ,
116
+ currentStatus .ReadyReplicas ,
117
+ currentStatus .CurrentReplicas ,
118
+ currentStatus .UpdatedReplicas )
120
119
121
120
klog .V (4 ).Infof ("StatefulSet %s/%s revisions current=%s update=%s" ,
122
121
set .Namespace ,
123
122
set .Name ,
124
- status .CurrentRevision ,
125
- status .UpdateRevision )
123
+ currentStatus .CurrentRevision ,
124
+ currentStatus .UpdateRevision )
126
125
127
- return currentRevision , updateRevision , nil
126
+ return currentRevision , updateRevision , currentStatus , nil
128
127
}
129
128
130
129
func (ssc * defaultStatefulSetControl ) ListRevisions (set * apps.StatefulSet ) ([]* apps.ControllerRevision , error ) {
@@ -307,6 +306,15 @@ func (ssc *defaultStatefulSetControl) updateStatefulSet(
307
306
// count the number of running and ready replicas
308
307
if isRunningAndReady (pods [i ]) {
309
308
status .ReadyReplicas ++
309
+ // count the number of running and available replicas
310
+ if utilfeature .DefaultFeatureGate .Enabled (features .StatefulSetMinReadySeconds ) {
311
+ if isRunningAndAvailable (pods [i ], set .Spec .MinReadySeconds ) {
312
+ status .AvailableReplicas ++
313
+ }
314
+ } else {
315
+ // If the featuregate is not enabled, all the ready replicas should be considered as available replicas
316
+ status .AvailableReplicas = status .ReadyReplicas
317
+ }
310
318
}
311
319
312
320
// count the number of current and update replicas
@@ -447,6 +455,19 @@ func (ssc *defaultStatefulSetControl) updateStatefulSet(
447
455
replicas [i ].Name )
448
456
return & status , nil
449
457
}
458
+ // If we have a Pod that has been created but is not available we can not make progress.
459
+ // We must ensure that all for each Pod, when we create it, all of its predecessors, with respect to its
460
+ // ordinal, are Available.
461
+ // TODO: Since available is superset of Ready, once we have this featuregate enabled by default, we can remove the
462
+ // isRunningAndReady block as only Available pods should be brought down.
463
+ if utilfeature .DefaultFeatureGate .Enabled (features .StatefulSetMinReadySeconds ) && ! isRunningAndAvailable (replicas [i ], set .Spec .MinReadySeconds ) && monotonic {
464
+ klog .V (4 ).Infof (
465
+ "StatefulSet %s/%s is waiting for Pod %s to be Available" ,
466
+ set .Namespace ,
467
+ set .Name ,
468
+ replicas [i ].Name )
469
+ return & status , nil
470
+ }
450
471
// Enforce the StatefulSet invariants
451
472
if identityMatches (set , replicas [i ]) && storageMatches (set , replicas [i ]) {
452
473
continue
@@ -458,7 +479,7 @@ func (ssc *defaultStatefulSetControl) updateStatefulSet(
458
479
}
459
480
}
460
481
461
- // At this point, all of the current Replicas are Running and Ready , we can consider termination.
482
+ // At this point, all of the current Replicas are Running, Ready and Available , we can consider termination.
462
483
// We will wait for all predecessors to be Running and Ready prior to attempting a deletion.
463
484
// We will terminate Pods in a monotonically decreasing order over [len(pods),set.Spec.Replicas).
464
485
// Note that we do not resurrect Pods in this interval. Also note that scaling will take precedence over
@@ -486,6 +507,17 @@ func (ssc *defaultStatefulSetControl) updateStatefulSet(
486
507
firstUnhealthyPod .Name )
487
508
return & status , nil
488
509
}
510
+ // if we are in monotonic mode and the condemned target is not the first unhealthy Pod, block.
511
+ // TODO: Since available is superset of Ready, once we have this featuregate enabled by default, we can remove the
512
+ // isRunningAndReady block as only Available pods should be brought down.
513
+ if utilfeature .DefaultFeatureGate .Enabled (features .StatefulSetMinReadySeconds ) && ! isRunningAndAvailable (condemned [target ], set .Spec .MinReadySeconds ) && monotonic && condemned [target ] != firstUnhealthyPod {
514
+ klog .V (4 ).Infof (
515
+ "StatefulSet %s/%s is waiting for Pod %s to be Available prior to scale down" ,
516
+ set .Namespace ,
517
+ set .Name ,
518
+ firstUnhealthyPod .Name )
519
+ return & status , nil
520
+ }
489
521
klog .V (2 ).Infof ("StatefulSet %s/%s terminating Pod %s for scale down" ,
490
522
set .Namespace ,
491
523
set .Name ,
@@ -549,7 +581,6 @@ func (ssc *defaultStatefulSetControl) updateStatefulSet(
549
581
func (ssc * defaultStatefulSetControl ) updateStatefulSetStatus (
550
582
set * apps.StatefulSet ,
551
583
status * apps.StatefulSetStatus ) error {
552
-
553
584
// complete any in progress rolling update if necessary
554
585
completeRollingUpdate (set , status )
555
586
0 commit comments