mirror of https://github.com/openkruise/kruise.git
Support maxSurge for CloneSet (#256)
Signed-off-by: Siyu Wang <FillZpp.pub@gmail.com>
This commit is contained in:
parent
06e7faee29
commit
75dfe17c99
|
|
@ -373,8 +373,12 @@
|
|||
"description": "InPlaceUpdateStrategy contains strategies for in-place update.",
|
||||
"$ref": "#/definitions/kruise.apps.v1alpha1.InPlaceUpdateStrategy"
|
||||
},
|
||||
"maxSurge": {
|
||||
"description": "The maximum number of pods that can be scheduled above the desired replicas during the update. Value can be an absolute number (ex: 5) or a percentage of desired pods (ex: 10%). Absolute number is calculated from percentage by rounding up. Defaults to nil.",
|
||||
"$ref": "#/definitions/io.k8s.apimachinery.pkg.util.intstr.IntOrString"
|
||||
},
|
||||
"maxUnavailable": {
|
||||
"description": "The maximum number of pods that can be unavailable during the update. Value can be an absolute number (ex: 5) or a percentage of desired pods (ex: 10%). Absolute number is calculated from percentage by rounding down. Defaults to 20%.",
|
||||
"description": "The maximum number of pods that can be unavailable during the update. Value can be an absolute number (ex: 5) or a percentage of desired pods (ex: 10%). Absolute number is calculated from percentage by rounding up. Defaults to 20%.",
|
||||
"$ref": "#/definitions/io.k8s.apimachinery.pkg.util.intstr.IntOrString"
|
||||
},
|
||||
"partition": {
|
||||
|
|
|
|||
|
|
@ -151,6 +151,30 @@ spec:
|
|||
maxUnavailable: 2
|
||||
```
|
||||
|
||||
## Graceful in-place update
|
||||
|
||||
When a Pod being in-place update, controller will firstly update Pod status to make it become not-ready using readinessGate,
|
||||
and then update images in Pod spec to trigger Kubelet recreate the container on Node.
|
||||
|
||||
However, sometimes Kubelet recreate containers so fast that other controllers such as endpoints-controller in kcm
|
||||
have not noticed the Pod has turned to not-ready. This may lead to requests damaged.
|
||||
|
||||
So we bring **graceful period** into in-place update. Advanced StatefulSet has supported `gracePeriodSeconds`, which is a period
|
||||
duration between controller update pod status and update pod images.
|
||||
So that endpoints-controller could have enough time to remove this Pod from endpoints.
|
||||
|
||||
```yaml
|
||||
apiVersion: apps.kruise.io/v1alpha1
|
||||
kind: StatefulSet
|
||||
spec:
|
||||
# ...
|
||||
updateStrategy:
|
||||
type: RollingUpdate
|
||||
rollingUpdate:
|
||||
inPlaceUpdateStrategy:
|
||||
gracePeriodSeconds: 10
|
||||
```
|
||||
|
||||
## `Priority` Unordered Rolling Update Strategy
|
||||
|
||||
This controller adds a `unorderedUpdate` field in `spec.updateStrategy.rollingUpdate`, which contains strategies for non-ordered update.
|
||||
|
|
|
|||
|
|
@ -218,6 +218,59 @@ MaxUnavailable is the maximum number of Pods that can be unavailable during the
|
|||
Value can be an absolute number (e.g., 5) or a percentage of desired number of Pods (e.g., 10%).
|
||||
Default value is 20%.
|
||||
|
||||
```yaml
|
||||
apiVersion: apps.kruise.io/v1alpha1
|
||||
kind: CloneSet
|
||||
spec:
|
||||
# ...
|
||||
updateStrategy:
|
||||
maxUnavailable: 20%
|
||||
```
|
||||
|
||||
### MaxSurge
|
||||
|
||||
MaxSurge is the maximum number of pods that can be scheduled above the desired replicas during the update.
|
||||
Value can be an absolute number (ex: 5) or a percentage of desired pods (ex: 10%).
|
||||
Defaults to 0.
|
||||
|
||||
If maxSurge is set somewhere, cloneset-controller will create `maxSurge` number of Pods above the `replicas`,
|
||||
when it finds multiple active revisions of Pods which means the CloneSet is in the update stage.
|
||||
|
||||
What's more, maxSurge is forbidden to use with `InPlaceOnly` policy.
|
||||
When maxSurge is used with `InPlaceIfPossible`, controller will create additional Pods with latest revision first,
|
||||
and then update the rest Pods with old revisions,
|
||||
|
||||
```yaml
|
||||
apiVersion: apps.kruise.io/v1alpha1
|
||||
kind: CloneSet
|
||||
spec:
|
||||
# ...
|
||||
updateStrategy:
|
||||
maxSurge: 3
|
||||
```
|
||||
|
||||
### Graceful in-place update
|
||||
|
||||
When a Pod being in-place update, controller will firstly update Pod status to make it become not-ready using readinessGate,
|
||||
and then update images in Pod spec to trigger Kubelet recreate the container on Node.
|
||||
|
||||
However, sometimes Kubelet recreate containers so fast that other controllers such as endpoints-controller in kcm
|
||||
have not noticed the Pod has turned to not-ready. This may lead to requests damaged.
|
||||
|
||||
So we bring **graceful period** into in-place update. CloneSet has supported `gracePeriodSeconds`, which is a period
|
||||
duration between controller update pod status and update pod images.
|
||||
So that endpoints-controller could have enough time to remove this Pod from endpoints.
|
||||
|
||||
```yaml
|
||||
apiVersion: apps.kruise.io/v1alpha1
|
||||
kind: CloneSet
|
||||
spec:
|
||||
# ...
|
||||
updateStrategy:
|
||||
inPlaceUpdateStrategy:
|
||||
gracePeriodSeconds: 10
|
||||
```
|
||||
|
||||
### Update sequence
|
||||
|
||||
When controller chooses Pods to update, it has default sort logic based on Pod phase and conditions:
|
||||
|
|
|
|||
|
|
@ -89,9 +89,14 @@ type CloneSetUpdateStrategy struct {
|
|||
Partition *int32 `json:"partition,omitempty"`
|
||||
// The maximum number of pods that can be unavailable during the update.
|
||||
// Value can be an absolute number (ex: 5) or a percentage of desired pods (ex: 10%).
|
||||
// Absolute number is calculated from percentage by rounding down.
|
||||
// Absolute number is calculated from percentage by rounding up.
|
||||
// Defaults to 20%.
|
||||
MaxUnavailable *intstr.IntOrString `json:"maxUnavailable,omitempty"`
|
||||
// The maximum number of pods that can be scheduled above the desired replicas during the update.
|
||||
// Value can be an absolute number (ex: 5) or a percentage of desired pods (ex: 10%).
|
||||
// Absolute number is calculated from percentage by rounding up.
|
||||
// Defaults to 0.
|
||||
MaxSurge *intstr.IntOrString `json:"maxSurge,omitempty"`
|
||||
// Paused indicates that the CloneSet is paused.
|
||||
// Default value is false
|
||||
Paused bool `json:"paused,omitempty"`
|
||||
|
|
|
|||
|
|
@ -219,4 +219,8 @@ func SetDefaults_CloneSet(obj *CloneSet) {
|
|||
maxUnavailable := intstr.FromString(DefaultCloneSetMaxUnavailable)
|
||||
obj.Spec.UpdateStrategy.MaxUnavailable = &maxUnavailable
|
||||
}
|
||||
if obj.Spec.UpdateStrategy.MaxSurge == nil {
|
||||
maxSurge := intstr.FromInt(0)
|
||||
obj.Spec.UpdateStrategy.MaxSurge = &maxSurge
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -706,7 +706,13 @@ func schema_pkg_apis_apps_v1alpha1_CloneSetUpdateStrategy(ref common.ReferenceCa
|
|||
},
|
||||
"maxUnavailable": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "The maximum number of pods that can be unavailable during the update. Value can be an absolute number (ex: 5) or a percentage of desired pods (ex: 10%). Absolute number is calculated from percentage by rounding down. Defaults to 20%.",
|
||||
Description: "The maximum number of pods that can be unavailable during the update. Value can be an absolute number (ex: 5) or a percentage of desired pods (ex: 10%). Absolute number is calculated from percentage by rounding up. Defaults to 20%.",
|
||||
Ref: ref("k8s.io/apimachinery/pkg/util/intstr.IntOrString"),
|
||||
},
|
||||
},
|
||||
"maxSurge": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "The maximum number of pods that can be scheduled above the desired replicas during the update. Value can be an absolute number (ex: 5) or a percentage of desired pods (ex: 10%). Absolute number is calculated from percentage by rounding up. Defaults to nil.",
|
||||
Ref: ref("k8s.io/apimachinery/pkg/util/intstr.IntOrString"),
|
||||
},
|
||||
},
|
||||
|
|
|
|||
|
|
@ -378,6 +378,11 @@ func (in *CloneSetUpdateStrategy) DeepCopyInto(out *CloneSetUpdateStrategy) {
|
|||
*out = new(intstr.IntOrString)
|
||||
**out = **in
|
||||
}
|
||||
if in.MaxSurge != nil {
|
||||
in, out := &in.MaxSurge, &out.MaxSurge
|
||||
*out = new(intstr.IntOrString)
|
||||
**out = **in
|
||||
}
|
||||
if in.PriorityStrategy != nil {
|
||||
in, out := &in.PriorityStrategy, &out.PriorityStrategy
|
||||
*out = new(UpdatePriorityStrategy)
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ package scale
|
|||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sort"
|
||||
"sync"
|
||||
|
||||
appsv1alpha1 "github.com/openkruise/kruise/pkg/apis/apps/v1alpha1"
|
||||
|
|
@ -16,7 +15,6 @@ import (
|
|||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
"k8s.io/client-go/tools/record"
|
||||
"k8s.io/klog"
|
||||
kubecontroller "k8s.io/kubernetes/pkg/controller"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
)
|
||||
|
||||
|
|
@ -69,20 +67,22 @@ func (r *realControl) Manage(
|
|||
return true, r.deletePods(updateCS, podsToDelete, pvcs)
|
||||
}
|
||||
|
||||
updatedPods, notUpdatedPods := clonesetutils.SplitPodsByRevision(pods, updateRevision)
|
||||
|
||||
var err error
|
||||
diff := len(pods) - int(*updateCS.Spec.Replicas)
|
||||
diff, currentRevDiff := calculateDiffs(updateCS, updateRevision == currentRevision, len(pods), len(notUpdatedPods))
|
||||
|
||||
if diff < 0 {
|
||||
// total number of this creation
|
||||
expectedCreations := diff * -1
|
||||
// lack number of current version
|
||||
expectedCurrentCreations := 0
|
||||
if updateCS.Spec.UpdateStrategy.Partition != nil {
|
||||
notUpdatedNum := len(pods) - len(clonesetutils.GetUpdatedPods(pods, updateRevision))
|
||||
expectedCurrentCreations = int(*updateCS.Spec.UpdateStrategy.Partition) - notUpdatedNum
|
||||
if currentRevDiff < 0 {
|
||||
expectedCurrentCreations = currentRevDiff * -1
|
||||
}
|
||||
|
||||
klog.V(3).Infof("CloneSet %s begin to scale out %d pods", controllerKey, expectedCreations)
|
||||
klog.V(3).Infof("CloneSet %s begin to scale out %d pods including %d (current rev)",
|
||||
controllerKey, expectedCreations, expectedCurrentCreations)
|
||||
|
||||
// available instance-id come from free pvc
|
||||
availableIDs := getOrGenAvailableIDs(expectedCreations, pods, pvcs)
|
||||
|
|
@ -97,11 +97,12 @@ func (r *realControl) Manage(
|
|||
return true, err
|
||||
|
||||
} else if diff > 0 {
|
||||
klog.V(3).Infof("CloneSet %s begin to scale in %d pods", controllerKey, diff)
|
||||
klog.V(3).Infof("CloneSet %s begin to scale in %d pods including %d (current rev)",
|
||||
controllerKey, diff, currentRevDiff)
|
||||
|
||||
podsToDelete := choosePodsToDelete(diff, currentRevDiff, notUpdatedPods, updatedPods)
|
||||
|
||||
podsToDelete := r.choosePodsToDelete(pods, diff)
|
||||
err = r.deletePods(updateCS, podsToDelete, pvcs)
|
||||
|
||||
return true, err
|
||||
}
|
||||
|
||||
|
|
@ -205,15 +206,3 @@ func (r *realControl) deletePods(cs *appsv1alpha1.CloneSet, podsToDelete []*v1.P
|
|||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *realControl) choosePodsToDelete(pods []*v1.Pod, diff int) []*v1.Pod {
|
||||
// No need to sort pods if we are about to delete all of them.
|
||||
// diff will always be <= len(filteredPods), so not need to handle > case.
|
||||
if diff < len(pods) {
|
||||
// Sort the pods in the order such that not-ready < ready, unscheduled
|
||||
// < scheduled, and pending < running. This ensures that we delete pods
|
||||
// in the earlier stages whenever possible.
|
||||
sort.Sort(kubecontroller.ActivePods(pods))
|
||||
}
|
||||
return pods[:diff]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,15 @@
|
|||
package scale
|
||||
|
||||
import (
|
||||
"sort"
|
||||
|
||||
appsv1alpha1 "github.com/openkruise/kruise/pkg/apis/apps/v1alpha1"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
intstrutil "k8s.io/apimachinery/pkg/util/intstr"
|
||||
"k8s.io/apimachinery/pkg/util/rand"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
"k8s.io/klog"
|
||||
kubecontroller "k8s.io/kubernetes/pkg/controller"
|
||||
)
|
||||
|
||||
func getPodsToDelete(cs *appsv1alpha1.CloneSet, pods []*v1.Pod) []*v1.Pod {
|
||||
|
|
@ -60,3 +65,49 @@ func getOrGenInstanceID(existingIDs, availableIDs sets.String) string {
|
|||
}
|
||||
return id
|
||||
}
|
||||
|
||||
func calculateDiffs(cs *appsv1alpha1.CloneSet, revConsistent bool, totalPods int, notUpdatedPods int) (totalDiff int, currentRevDiff int) {
|
||||
var maxSurge int
|
||||
|
||||
if !revConsistent {
|
||||
if cs.Spec.UpdateStrategy.MaxSurge != nil {
|
||||
maxSurge, _ = intstrutil.GetValueFromIntOrPercent(cs.Spec.UpdateStrategy.MaxSurge, int(*cs.Spec.Replicas), true)
|
||||
}
|
||||
|
||||
if cs.Spec.UpdateStrategy.Partition != nil {
|
||||
currentRevDiff = notUpdatedPods - int(*cs.Spec.UpdateStrategy.Partition)
|
||||
}
|
||||
}
|
||||
totalDiff = totalPods - int(*cs.Spec.Replicas) - maxSurge
|
||||
|
||||
if totalDiff != 0 && maxSurge > 0 {
|
||||
klog.V(3).Infof("CloneSet scale diff(%d),currentRevDiff(%d) with maxSurge %d", totalDiff, currentRevDiff, maxSurge)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func choosePodsToDelete(totalDiff int, currentRevDiff int, notUpdatedPods, updatedPods []*v1.Pod) []*v1.Pod {
|
||||
choose := func(pods []*v1.Pod, diff int) []*v1.Pod {
|
||||
// No need to sort pods if we are about to delete all of them.
|
||||
// diff will always be <= len(filteredPods), so not need to handle > case.
|
||||
if diff < len(pods) {
|
||||
// Sort the pods in the order such that not-ready < ready, unscheduled
|
||||
// < scheduled, and pending < running. This ensures that we delete pods
|
||||
// in the earlier stages whenever possible.
|
||||
sort.Sort(kubecontroller.ActivePods(pods))
|
||||
}
|
||||
return pods[:diff]
|
||||
}
|
||||
|
||||
var podsToDelete []*v1.Pod
|
||||
if currentRevDiff >= totalDiff {
|
||||
podsToDelete = choose(notUpdatedPods, totalDiff)
|
||||
} else if currentRevDiff > 0 {
|
||||
podsToDelete = choose(notUpdatedPods, currentRevDiff)
|
||||
podsToDelete = append(podsToDelete, choose(updatedPods, totalDiff-currentRevDiff)...)
|
||||
} else {
|
||||
podsToDelete = choose(updatedPods, totalDiff)
|
||||
}
|
||||
|
||||
return podsToDelete
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,9 @@ package scale
|
|||
import (
|
||||
"testing"
|
||||
|
||||
"k8s.io/apimachinery/pkg/util/intstr"
|
||||
utilpointer "k8s.io/utils/pointer"
|
||||
|
||||
appsv1alpha1 "github.com/openkruise/kruise/pkg/apis/apps/v1alpha1"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
|
@ -49,3 +52,267 @@ func TestGetOrGenAvailableIDs(t *testing.T) {
|
|||
t.Fatalf("expected got random id, but actually %v", id)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCalculateDiffs(t *testing.T) {
|
||||
intOrStr0 := intstr.FromInt(0)
|
||||
intOrStr1 := intstr.FromInt(1)
|
||||
|
||||
cases := []struct {
|
||||
name string
|
||||
cs *appsv1alpha1.CloneSet
|
||||
revConsistent bool
|
||||
totalPods int
|
||||
notUpdatedPods int
|
||||
expectedTotalDiff int
|
||||
expectedCurrentRevDiff int
|
||||
}{
|
||||
{
|
||||
name: "normal 1",
|
||||
cs: &appsv1alpha1.CloneSet{
|
||||
Spec: appsv1alpha1.CloneSetSpec{
|
||||
Replicas: utilpointer.Int32Ptr(10),
|
||||
UpdateStrategy: appsv1alpha1.CloneSetUpdateStrategy{
|
||||
MaxSurge: &intOrStr0,
|
||||
Partition: utilpointer.Int32Ptr(0),
|
||||
},
|
||||
},
|
||||
},
|
||||
revConsistent: true,
|
||||
totalPods: 10,
|
||||
notUpdatedPods: 0,
|
||||
expectedTotalDiff: 0,
|
||||
expectedCurrentRevDiff: 0,
|
||||
},
|
||||
{
|
||||
name: "normal 2",
|
||||
cs: &appsv1alpha1.CloneSet{
|
||||
Spec: appsv1alpha1.CloneSetSpec{
|
||||
Replicas: utilpointer.Int32Ptr(10),
|
||||
UpdateStrategy: appsv1alpha1.CloneSetUpdateStrategy{
|
||||
MaxSurge: &intOrStr1,
|
||||
Partition: utilpointer.Int32Ptr(0),
|
||||
},
|
||||
},
|
||||
},
|
||||
revConsistent: true,
|
||||
totalPods: 10,
|
||||
notUpdatedPods: 0,
|
||||
expectedTotalDiff: 0,
|
||||
expectedCurrentRevDiff: 0,
|
||||
},
|
||||
{
|
||||
name: "scale out with revConsistent",
|
||||
cs: &appsv1alpha1.CloneSet{
|
||||
Spec: appsv1alpha1.CloneSetSpec{
|
||||
Replicas: utilpointer.Int32Ptr(10),
|
||||
UpdateStrategy: appsv1alpha1.CloneSetUpdateStrategy{
|
||||
MaxSurge: &intOrStr1,
|
||||
Partition: utilpointer.Int32Ptr(0),
|
||||
},
|
||||
},
|
||||
},
|
||||
revConsistent: true,
|
||||
totalPods: 8,
|
||||
notUpdatedPods: 0,
|
||||
expectedTotalDiff: -2,
|
||||
expectedCurrentRevDiff: 0,
|
||||
},
|
||||
{
|
||||
name: "scale out without revConsistent 1",
|
||||
cs: &appsv1alpha1.CloneSet{
|
||||
Spec: appsv1alpha1.CloneSetSpec{
|
||||
Replicas: utilpointer.Int32Ptr(10),
|
||||
UpdateStrategy: appsv1alpha1.CloneSetUpdateStrategy{
|
||||
MaxSurge: &intOrStr0,
|
||||
Partition: utilpointer.Int32Ptr(0),
|
||||
},
|
||||
},
|
||||
},
|
||||
revConsistent: false,
|
||||
totalPods: 8,
|
||||
notUpdatedPods: 0,
|
||||
expectedTotalDiff: -2,
|
||||
expectedCurrentRevDiff: 0,
|
||||
},
|
||||
{
|
||||
name: "scale out without revConsistent 2",
|
||||
cs: &appsv1alpha1.CloneSet{
|
||||
Spec: appsv1alpha1.CloneSetSpec{
|
||||
Replicas: utilpointer.Int32Ptr(10),
|
||||
UpdateStrategy: appsv1alpha1.CloneSetUpdateStrategy{
|
||||
MaxSurge: &intOrStr1,
|
||||
Partition: utilpointer.Int32Ptr(0),
|
||||
},
|
||||
},
|
||||
},
|
||||
revConsistent: false,
|
||||
totalPods: 8,
|
||||
notUpdatedPods: 0,
|
||||
expectedTotalDiff: -3,
|
||||
expectedCurrentRevDiff: 0,
|
||||
},
|
||||
{
|
||||
name: "scale out without revConsistent 3",
|
||||
cs: &appsv1alpha1.CloneSet{
|
||||
Spec: appsv1alpha1.CloneSetSpec{
|
||||
Replicas: utilpointer.Int32Ptr(10),
|
||||
UpdateStrategy: appsv1alpha1.CloneSetUpdateStrategy{
|
||||
MaxSurge: &intOrStr1,
|
||||
Partition: utilpointer.Int32Ptr(0),
|
||||
},
|
||||
},
|
||||
},
|
||||
revConsistent: false,
|
||||
totalPods: 10,
|
||||
notUpdatedPods: 0,
|
||||
expectedTotalDiff: -1,
|
||||
expectedCurrentRevDiff: 0,
|
||||
},
|
||||
{
|
||||
name: "scale out without revConsistent 4",
|
||||
cs: &appsv1alpha1.CloneSet{
|
||||
Spec: appsv1alpha1.CloneSetSpec{
|
||||
Replicas: utilpointer.Int32Ptr(10),
|
||||
UpdateStrategy: appsv1alpha1.CloneSetUpdateStrategy{
|
||||
MaxSurge: &intOrStr1,
|
||||
Partition: utilpointer.Int32Ptr(0),
|
||||
},
|
||||
},
|
||||
},
|
||||
revConsistent: false,
|
||||
totalPods: 11,
|
||||
notUpdatedPods: 0,
|
||||
expectedTotalDiff: 0,
|
||||
expectedCurrentRevDiff: 0,
|
||||
},
|
||||
{
|
||||
name: "scale out without revConsistent 5",
|
||||
cs: &appsv1alpha1.CloneSet{
|
||||
Spec: appsv1alpha1.CloneSetSpec{
|
||||
Replicas: utilpointer.Int32Ptr(10),
|
||||
UpdateStrategy: appsv1alpha1.CloneSetUpdateStrategy{
|
||||
MaxSurge: &intOrStr1,
|
||||
Partition: utilpointer.Int32Ptr(8),
|
||||
},
|
||||
},
|
||||
},
|
||||
revConsistent: false,
|
||||
totalPods: 8,
|
||||
notUpdatedPods: 6,
|
||||
expectedTotalDiff: -3,
|
||||
expectedCurrentRevDiff: -2,
|
||||
},
|
||||
{
|
||||
name: "scale in with revConsistent 1",
|
||||
cs: &appsv1alpha1.CloneSet{
|
||||
Spec: appsv1alpha1.CloneSetSpec{
|
||||
Replicas: utilpointer.Int32Ptr(10),
|
||||
UpdateStrategy: appsv1alpha1.CloneSetUpdateStrategy{
|
||||
MaxSurge: &intOrStr0,
|
||||
Partition: utilpointer.Int32Ptr(0),
|
||||
},
|
||||
},
|
||||
},
|
||||
revConsistent: true,
|
||||
totalPods: 11,
|
||||
notUpdatedPods: 0,
|
||||
expectedTotalDiff: 1,
|
||||
expectedCurrentRevDiff: 0,
|
||||
},
|
||||
{
|
||||
name: "scale in with revConsistent 2",
|
||||
cs: &appsv1alpha1.CloneSet{
|
||||
Spec: appsv1alpha1.CloneSetSpec{
|
||||
Replicas: utilpointer.Int32Ptr(10),
|
||||
UpdateStrategy: appsv1alpha1.CloneSetUpdateStrategy{
|
||||
MaxSurge: &intOrStr1,
|
||||
Partition: utilpointer.Int32Ptr(0),
|
||||
},
|
||||
},
|
||||
},
|
||||
revConsistent: true,
|
||||
totalPods: 11,
|
||||
notUpdatedPods: 0,
|
||||
expectedTotalDiff: 1,
|
||||
expectedCurrentRevDiff: 0,
|
||||
},
|
||||
{
|
||||
name: "scale in without revConsistent 1",
|
||||
cs: &appsv1alpha1.CloneSet{
|
||||
Spec: appsv1alpha1.CloneSetSpec{
|
||||
Replicas: utilpointer.Int32Ptr(10),
|
||||
UpdateStrategy: appsv1alpha1.CloneSetUpdateStrategy{
|
||||
MaxSurge: &intOrStr0,
|
||||
Partition: utilpointer.Int32Ptr(0),
|
||||
},
|
||||
},
|
||||
},
|
||||
revConsistent: false,
|
||||
totalPods: 11,
|
||||
notUpdatedPods: 0,
|
||||
expectedTotalDiff: 1,
|
||||
expectedCurrentRevDiff: 0,
|
||||
},
|
||||
{
|
||||
name: "scale in without revConsistent 2",
|
||||
cs: &appsv1alpha1.CloneSet{
|
||||
Spec: appsv1alpha1.CloneSetSpec{
|
||||
Replicas: utilpointer.Int32Ptr(10),
|
||||
UpdateStrategy: appsv1alpha1.CloneSetUpdateStrategy{
|
||||
MaxSurge: &intOrStr1,
|
||||
Partition: utilpointer.Int32Ptr(0),
|
||||
},
|
||||
},
|
||||
},
|
||||
revConsistent: false,
|
||||
totalPods: 11,
|
||||
notUpdatedPods: 0,
|
||||
expectedTotalDiff: 0,
|
||||
expectedCurrentRevDiff: 0,
|
||||
},
|
||||
{
|
||||
name: "scale in without revConsistent 2",
|
||||
cs: &appsv1alpha1.CloneSet{
|
||||
Spec: appsv1alpha1.CloneSetSpec{
|
||||
Replicas: utilpointer.Int32Ptr(10),
|
||||
UpdateStrategy: appsv1alpha1.CloneSetUpdateStrategy{
|
||||
MaxSurge: &intOrStr1,
|
||||
Partition: utilpointer.Int32Ptr(0),
|
||||
},
|
||||
},
|
||||
},
|
||||
revConsistent: false,
|
||||
totalPods: 12,
|
||||
notUpdatedPods: 0,
|
||||
expectedTotalDiff: 1,
|
||||
expectedCurrentRevDiff: 0,
|
||||
},
|
||||
{
|
||||
name: "scale in without revConsistent 3",
|
||||
cs: &appsv1alpha1.CloneSet{
|
||||
Spec: appsv1alpha1.CloneSetSpec{
|
||||
Replicas: utilpointer.Int32Ptr(10),
|
||||
UpdateStrategy: appsv1alpha1.CloneSetUpdateStrategy{
|
||||
MaxSurge: &intOrStr1,
|
||||
Partition: utilpointer.Int32Ptr(6),
|
||||
},
|
||||
},
|
||||
},
|
||||
revConsistent: false,
|
||||
totalPods: 13,
|
||||
notUpdatedPods: 7,
|
||||
expectedTotalDiff: 2,
|
||||
expectedCurrentRevDiff: 1,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(*testing.T) {
|
||||
totalDiff, currentRevDiff := calculateDiffs(tc.cs, tc.revConsistent, tc.totalPods, tc.notUpdatedPods)
|
||||
if totalDiff != tc.expectedTotalDiff || currentRevDiff != tc.expectedCurrentRevDiff {
|
||||
t.Fatalf("failed calculateDiffs, expected totalDiff %d, got %d, expected currentRevDiff %d, got %d",
|
||||
tc.expectedTotalDiff, totalDiff, tc.expectedCurrentRevDiff, currentRevDiff)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -94,15 +94,16 @@ func IsRunningAndAvailable(pod *v1.Pod, minReadySeconds int32) bool {
|
|||
return pod.Status.Phase == v1.PodRunning && podutil.IsPodAvailable(pod, minReadySeconds, metav1.Now())
|
||||
}
|
||||
|
||||
// GetUpdatedPods returns Pods already reach latest revision
|
||||
func GetUpdatedPods(pods []*v1.Pod, updateRevision string) []*v1.Pod {
|
||||
var updated []*v1.Pod
|
||||
// SplitPodsByRevision returns Pods matched and unmatched the given revision
|
||||
func SplitPodsByRevision(pods []*v1.Pod, rev string) (matched, unmatched []*v1.Pod) {
|
||||
for _, p := range pods {
|
||||
if GetPodRevision(p) == updateRevision {
|
||||
updated = append(updated, p)
|
||||
if GetPodRevision(p) == rev {
|
||||
matched = append(matched, p)
|
||||
} else {
|
||||
unmatched = append(unmatched, p)
|
||||
}
|
||||
}
|
||||
return updated
|
||||
return
|
||||
}
|
||||
|
||||
// UpdateStorage insert volumes generated by cs.Spec.VolumeClaimTemplates into Pod.
|
||||
|
|
|
|||
|
|
@ -60,7 +60,7 @@ func (h *CloneSetCreateUpdateHandler) validateCloneSetSpec(spec *appsv1alpha1.Cl
|
|||
}
|
||||
|
||||
allErrs = append(allErrs, h.validateScaleStrategy(&spec.ScaleStrategy, metadata, fldPath.Child("scaleStrategy"))...)
|
||||
allErrs = append(allErrs, h.validateUpdateStrategy(&spec.UpdateStrategy, fldPath.Child("updateStrategy"))...)
|
||||
allErrs = append(allErrs, h.validateUpdateStrategy(&spec.UpdateStrategy, int(*spec.Replicas), fldPath.Child("updateStrategy"))...)
|
||||
|
||||
return allErrs
|
||||
}
|
||||
|
|
@ -87,8 +87,9 @@ func (h *CloneSetCreateUpdateHandler) validateScaleStrategy(strategy *appsv1alph
|
|||
return allErrs
|
||||
}
|
||||
|
||||
func (h *CloneSetCreateUpdateHandler) validateUpdateStrategy(strategy *appsv1alpha1.CloneSetUpdateStrategy, fldPath *field.Path) field.ErrorList {
|
||||
func (h *CloneSetCreateUpdateHandler) validateUpdateStrategy(strategy *appsv1alpha1.CloneSetUpdateStrategy, replicas int, fldPath *field.Path) field.ErrorList {
|
||||
allErrs := field.ErrorList{}
|
||||
var err error
|
||||
|
||||
switch strategy.Type {
|
||||
case appsv1alpha1.RecreateCloneSetUpdateStrategyType,
|
||||
|
|
@ -111,12 +112,31 @@ func (h *CloneSetCreateUpdateHandler) validateUpdateStrategy(strategy *appsv1alp
|
|||
allErrs = append(allErrs, field.Required(fldPath.Child("scatterStrategy"), err.Error()))
|
||||
}
|
||||
|
||||
if maxUnavailable, err := intstrutil.GetValueFromIntOrPercent(intstrutil.ValueOrDefault(strategy.MaxUnavailable, intstrutil.FromString(appsv1alpha1.DefaultCloneSetMaxUnavailable)), 1, true); err != nil {
|
||||
var maxUnavailable int
|
||||
if strategy.MaxUnavailable != nil {
|
||||
maxUnavailable, err = intstrutil.GetValueFromIntOrPercent(strategy.MaxUnavailable, replicas, true)
|
||||
if err != nil {
|
||||
allErrs = append(allErrs, field.Invalid(fldPath.Child("maxUnavailable"), strategy.MaxUnavailable.String(),
|
||||
fmt.Sprintf("failed getValueFromIntOrPercent for maxUnavailable: %v", err)))
|
||||
}
|
||||
}
|
||||
|
||||
var maxSurge int
|
||||
if strategy.MaxSurge != nil {
|
||||
if strategy.Type == appsv1alpha1.InPlaceOnlyCloneSetUpdateStrategyType {
|
||||
allErrs = append(allErrs, field.Invalid(fldPath.Child("maxSurge"), strategy.MaxSurge.String(),
|
||||
fmt.Sprintf("can not use maxSurge with strategy type InPlaceOnly")))
|
||||
}
|
||||
maxSurge, err = intstrutil.GetValueFromIntOrPercent(strategy.MaxSurge, replicas, true)
|
||||
if err != nil {
|
||||
allErrs = append(allErrs, field.Invalid(fldPath.Child("maxSurge"), strategy.MaxSurge.String(),
|
||||
fmt.Sprintf("failed getValueFromIntOrPercent for maxSurge: %v", err)))
|
||||
}
|
||||
}
|
||||
|
||||
if replicas > 0 && maxUnavailable < 1 && maxSurge < 1 {
|
||||
allErrs = append(allErrs, field.Invalid(fldPath.Child("maxUnavailable"), strategy.MaxUnavailable,
|
||||
fmt.Sprintf("failed getValueFromIntOrPercent for maxUnavailable: %v", err)))
|
||||
} else if maxUnavailable < 1 {
|
||||
allErrs = append(allErrs, field.Invalid(fldPath.Child("maxUnavailable"), strategy.MaxUnavailable,
|
||||
"getValueFromIntOrPercent for maxUnavailable should not be less than 1"))
|
||||
"maxUnavailable and maxSurge should not both be less than 1"))
|
||||
}
|
||||
|
||||
return allErrs
|
||||
|
|
|
|||
|
|
@ -75,8 +75,8 @@ func TestValidate(t *testing.T) {
|
|||
var val1 int32 = 1
|
||||
var val2 int32 = 2
|
||||
var minus1 int32 = -1
|
||||
maxUnavailable0 := intstr.FromInt(0)
|
||||
maxUnavailable1 := intstr.FromInt(1)
|
||||
intOrStr0 := intstr.FromInt(0)
|
||||
intOrStr1 := intstr.FromInt(1)
|
||||
maxUnavailable120Percent := intstr.FromString("120%")
|
||||
|
||||
uid := uuid.NewUUID()
|
||||
|
|
@ -97,13 +97,39 @@ func TestValidate(t *testing.T) {
|
|||
UpdateStrategy: appsv1alpha1.CloneSetUpdateStrategy{
|
||||
Type: appsv1alpha1.InPlaceIfPossibleCloneSetUpdateStrategyType,
|
||||
Partition: &val2,
|
||||
MaxUnavailable: &maxUnavailable1,
|
||||
MaxUnavailable: &intOrStr1,
|
||||
},
|
||||
ScaleStrategy: appsv1alpha1.CloneSetScaleStrategy{
|
||||
PodsToDelete: []string{"p0"},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
spec: &appsv1alpha1.CloneSetSpec{
|
||||
Replicas: &val1,
|
||||
Selector: &metav1.LabelSelector{MatchLabels: validLabels},
|
||||
Template: validPodTemplate.Template,
|
||||
UpdateStrategy: appsv1alpha1.CloneSetUpdateStrategy{
|
||||
Type: appsv1alpha1.InPlaceIfPossibleCloneSetUpdateStrategyType,
|
||||
Partition: &val2,
|
||||
MaxUnavailable: &intOrStr1,
|
||||
MaxSurge: &intOrStr1,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
spec: &appsv1alpha1.CloneSetSpec{
|
||||
Replicas: &val1,
|
||||
Selector: &metav1.LabelSelector{MatchLabels: validLabels},
|
||||
Template: validPodTemplate.Template,
|
||||
UpdateStrategy: appsv1alpha1.CloneSetUpdateStrategy{
|
||||
Type: appsv1alpha1.InPlaceIfPossibleCloneSetUpdateStrategyType,
|
||||
Partition: &val2,
|
||||
MaxUnavailable: &intOrStr0,
|
||||
MaxSurge: &intOrStr1,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
spec: &appsv1alpha1.CloneSetSpec{
|
||||
Replicas: &val1,
|
||||
|
|
@ -141,7 +167,7 @@ func TestValidate(t *testing.T) {
|
|||
UpdateStrategy: appsv1alpha1.CloneSetUpdateStrategy{
|
||||
Type: appsv1alpha1.InPlaceOnlyCloneSetUpdateStrategyType,
|
||||
Partition: &val2,
|
||||
MaxUnavailable: &maxUnavailable1,
|
||||
MaxUnavailable: &intOrStr1,
|
||||
},
|
||||
},
|
||||
oldSpec: &appsv1alpha1.CloneSetSpec{
|
||||
|
|
@ -151,7 +177,7 @@ func TestValidate(t *testing.T) {
|
|||
UpdateStrategy: appsv1alpha1.CloneSetUpdateStrategy{
|
||||
Type: appsv1alpha1.InPlaceOnlyCloneSetUpdateStrategyType,
|
||||
Partition: &val2,
|
||||
MaxUnavailable: &maxUnavailable1,
|
||||
MaxUnavailable: &intOrStr1,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
@ -189,7 +215,7 @@ func TestValidate(t *testing.T) {
|
|||
UpdateStrategy: appsv1alpha1.CloneSetUpdateStrategy{
|
||||
Type: appsv1alpha1.InPlaceIfPossibleCloneSetUpdateStrategyType,
|
||||
Partition: &val2,
|
||||
MaxUnavailable: &maxUnavailable1,
|
||||
MaxUnavailable: &intOrStr1,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
@ -201,7 +227,7 @@ func TestValidate(t *testing.T) {
|
|||
UpdateStrategy: appsv1alpha1.CloneSetUpdateStrategy{
|
||||
Type: appsv1alpha1.InPlaceIfPossibleCloneSetUpdateStrategyType,
|
||||
Partition: &val2,
|
||||
MaxUnavailable: &maxUnavailable1,
|
||||
MaxUnavailable: &intOrStr1,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
@ -213,7 +239,7 @@ func TestValidate(t *testing.T) {
|
|||
UpdateStrategy: appsv1alpha1.CloneSetUpdateStrategy{
|
||||
Type: appsv1alpha1.InPlaceIfPossibleCloneSetUpdateStrategyType,
|
||||
Partition: &val2,
|
||||
MaxUnavailable: &maxUnavailable1,
|
||||
MaxUnavailable: &intOrStr1,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
@ -225,7 +251,7 @@ func TestValidate(t *testing.T) {
|
|||
UpdateStrategy: appsv1alpha1.CloneSetUpdateStrategy{
|
||||
Type: "",
|
||||
Partition: &val2,
|
||||
MaxUnavailable: &maxUnavailable1,
|
||||
MaxUnavailable: &intOrStr1,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
@ -237,7 +263,7 @@ func TestValidate(t *testing.T) {
|
|||
UpdateStrategy: appsv1alpha1.CloneSetUpdateStrategy{
|
||||
Type: appsv1alpha1.InPlaceIfPossibleCloneSetUpdateStrategyType,
|
||||
Partition: &minus1,
|
||||
MaxUnavailable: &maxUnavailable1,
|
||||
MaxUnavailable: &intOrStr1,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
@ -249,7 +275,20 @@ func TestValidate(t *testing.T) {
|
|||
UpdateStrategy: appsv1alpha1.CloneSetUpdateStrategy{
|
||||
Type: appsv1alpha1.InPlaceIfPossibleCloneSetUpdateStrategyType,
|
||||
Partition: &val2,
|
||||
MaxUnavailable: &maxUnavailable0,
|
||||
MaxUnavailable: &intOrStr0,
|
||||
},
|
||||
},
|
||||
},
|
||||
"invalid-maxSurge": {
|
||||
spec: &appsv1alpha1.CloneSetSpec{
|
||||
Replicas: &val1,
|
||||
Selector: &metav1.LabelSelector{MatchLabels: validLabels},
|
||||
Template: validPodTemplate.Template,
|
||||
UpdateStrategy: appsv1alpha1.CloneSetUpdateStrategy{
|
||||
Type: appsv1alpha1.InPlaceIfPossibleCloneSetUpdateStrategyType,
|
||||
Partition: &val2,
|
||||
MaxUnavailable: &intOrStr0,
|
||||
MaxSurge: &intOrStr0,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
@ -261,7 +300,7 @@ func TestValidate(t *testing.T) {
|
|||
UpdateStrategy: appsv1alpha1.CloneSetUpdateStrategy{
|
||||
Type: appsv1alpha1.InPlaceIfPossibleCloneSetUpdateStrategyType,
|
||||
Partition: &val2,
|
||||
MaxUnavailable: &maxUnavailable1,
|
||||
MaxUnavailable: &intOrStr1,
|
||||
},
|
||||
ScaleStrategy: appsv1alpha1.CloneSetScaleStrategy{
|
||||
PodsToDelete: []string{"p0", "p0"},
|
||||
|
|
@ -276,7 +315,7 @@ func TestValidate(t *testing.T) {
|
|||
UpdateStrategy: appsv1alpha1.CloneSetUpdateStrategy{
|
||||
Type: appsv1alpha1.InPlaceIfPossibleCloneSetUpdateStrategyType,
|
||||
Partition: &val2,
|
||||
MaxUnavailable: &maxUnavailable1,
|
||||
MaxUnavailable: &intOrStr1,
|
||||
},
|
||||
ScaleStrategy: appsv1alpha1.CloneSetScaleStrategy{
|
||||
PodsToDelete: []string{"p0", "p1"},
|
||||
|
|
@ -292,7 +331,7 @@ func TestValidate(t *testing.T) {
|
|||
UpdateStrategy: appsv1alpha1.CloneSetUpdateStrategy{
|
||||
Type: appsv1alpha1.InPlaceIfPossibleCloneSetUpdateStrategyType,
|
||||
Partition: &val2,
|
||||
MaxUnavailable: &maxUnavailable1,
|
||||
MaxUnavailable: &intOrStr1,
|
||||
},
|
||||
},
|
||||
oldSpec: &appsv1alpha1.CloneSetSpec{
|
||||
|
|
@ -303,7 +342,7 @@ func TestValidate(t *testing.T) {
|
|||
UpdateStrategy: appsv1alpha1.CloneSetUpdateStrategy{
|
||||
Type: appsv1alpha1.InPlaceIfPossibleCloneSetUpdateStrategyType,
|
||||
Partition: &val2,
|
||||
MaxUnavailable: &maxUnavailable1,
|
||||
MaxUnavailable: &intOrStr1,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
@ -315,7 +354,7 @@ func TestValidate(t *testing.T) {
|
|||
UpdateStrategy: appsv1alpha1.CloneSetUpdateStrategy{
|
||||
Type: appsv1alpha1.InPlaceOnlyCloneSetUpdateStrategyType,
|
||||
Partition: &val2,
|
||||
MaxUnavailable: &maxUnavailable1,
|
||||
MaxUnavailable: &intOrStr1,
|
||||
},
|
||||
},
|
||||
oldSpec: &appsv1alpha1.CloneSetSpec{
|
||||
|
|
@ -325,7 +364,7 @@ func TestValidate(t *testing.T) {
|
|||
UpdateStrategy: appsv1alpha1.CloneSetUpdateStrategy{
|
||||
Type: appsv1alpha1.InPlaceOnlyCloneSetUpdateStrategyType,
|
||||
Partition: &val2,
|
||||
MaxUnavailable: &maxUnavailable1,
|
||||
MaxUnavailable: &intOrStr1,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
|
|||
Loading…
Reference in New Issue