Merge pull request #111194 from ravisantoshgudimetla/promote-maxSurge-ga

Promote DS max surge to GA
This commit is contained in:
Kubernetes Prow Robot 2022-07-25 06:20:46 -07:00 committed by GitHub
commit 7156c96e5d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 1363 additions and 1868 deletions

View File

@ -1178,7 +1178,7 @@
"properties": {
"maxSurge": {
"$ref": "#/definitions/io.k8s.apimachinery.pkg.util.intstr.IntOrString",
"description": "The maximum number of nodes with an existing available DaemonSet pod that can have an updated DaemonSet pod during during an update. Value can be an absolute number (ex: 5) or a percentage of desired pods (ex: 10%). This can not be 0 if MaxUnavailable is 0. Absolute number is calculated from percentage by rounding up to a minimum of 1. Default value is 0. Example: when this is set to 30%, at most 30% of the total number of nodes that should be running the daemon pod (i.e. status.desiredNumberScheduled) can have their a new pod created before the old pod is marked as deleted. The update starts by launching new pods on 30% of nodes. Once an updated pod is available (Ready for at least minReadySeconds) the old DaemonSet pod on that node is marked deleted. If the old pod becomes unavailable for any reason (Ready transitions to false, is evicted, or is drained) an updated pod is immediatedly created on that node without considering surge limits. Allowing surge implies the possibility that the resources consumed by the daemonset on any given node can double if the readiness check fails, and so resource intensive daemonsets should take into account that they may cause evictions during disruption. This is beta field and enabled/disabled by DaemonSetUpdateSurge feature gate."
"description": "The maximum number of nodes with an existing available DaemonSet pod that can have an updated DaemonSet pod during during an update. Value can be an absolute number (ex: 5) or a percentage of desired pods (ex: 10%). This can not be 0 if MaxUnavailable is 0. Absolute number is calculated from percentage by rounding up to a minimum of 1. Default value is 0. Example: when this is set to 30%, at most 30% of the total number of nodes that should be running the daemon pod (i.e. status.desiredNumberScheduled) can have their a new pod created before the old pod is marked as deleted. The update starts by launching new pods on 30% of nodes. Once an updated pod is available (Ready for at least minReadySeconds) the old DaemonSet pod on that node is marked deleted. If the old pod becomes unavailable for any reason (Ready transitions to false, is evicted, or is drained) an updated pod is immediatedly created on that node without considering surge limits. Allowing surge implies the possibility that the resources consumed by the daemonset on any given node can double if the readiness check fails, and so resource intensive daemonsets should take into account that they may cause evictions during disruption."
},
"maxUnavailable": {
"$ref": "#/definitions/io.k8s.apimachinery.pkg.util.intstr.IntOrString",

View File

@ -860,7 +860,7 @@
"$ref": "#/components/schemas/io.k8s.apimachinery.pkg.util.intstr.IntOrString"
}
],
"description": "The maximum number of nodes with an existing available DaemonSet pod that can have an updated DaemonSet pod during during an update. Value can be an absolute number (ex: 5) or a percentage of desired pods (ex: 10%). This can not be 0 if MaxUnavailable is 0. Absolute number is calculated from percentage by rounding up to a minimum of 1. Default value is 0. Example: when this is set to 30%, at most 30% of the total number of nodes that should be running the daemon pod (i.e. status.desiredNumberScheduled) can have their a new pod created before the old pod is marked as deleted. The update starts by launching new pods on 30% of nodes. Once an updated pod is available (Ready for at least minReadySeconds) the old DaemonSet pod on that node is marked deleted. If the old pod becomes unavailable for any reason (Ready transitions to false, is evicted, or is drained) an updated pod is immediatedly created on that node without considering surge limits. Allowing surge implies the possibility that the resources consumed by the daemonset on any given node can double if the readiness check fails, and so resource intensive daemonsets should take into account that they may cause evictions during disruption. This is beta field and enabled/disabled by DaemonSetUpdateSurge feature gate."
"description": "The maximum number of nodes with an existing available DaemonSet pod that can have an updated DaemonSet pod during during an update. Value can be an absolute number (ex: 5) or a percentage of desired pods (ex: 10%). This can not be 0 if MaxUnavailable is 0. Absolute number is calculated from percentage by rounding up to a minimum of 1. Default value is 0. Example: when this is set to 30%, at most 30% of the total number of nodes that should be running the daemon pod (i.e. status.desiredNumberScheduled) can have their a new pod created before the old pod is marked as deleted. The update starts by launching new pods on 30% of nodes. Once an updated pod is available (Ready for at least minReadySeconds) the old DaemonSet pod on that node is marked deleted. If the old pod becomes unavailable for any reason (Ready transitions to false, is evicted, or is drained) an updated pod is immediatedly created on that node without considering surge limits. Allowing surge implies the possibility that the resources consumed by the daemonset on any given node can double if the readiness check fails, and so resource intensive daemonsets should take into account that they may cause evictions during disruption."
},
"maxUnavailable": {
"allOf": [

View File

@ -625,7 +625,6 @@ type RollingUpdateDaemonSet struct {
// daemonset on any given node can double if the readiness check fails, and
// so resource intensive daemonsets should take into account that they may
// cause evictions during disruption.
// This is beta field and enabled/disabled by DaemonSetUpdateSurge feature gate.
// +optional
MaxSurge intstr.IntOrString
}

View File

@ -28,11 +28,9 @@ import (
"k8s.io/apimachinery/pkg/util/intstr"
"k8s.io/apimachinery/pkg/util/validation"
"k8s.io/apimachinery/pkg/util/validation/field"
utilfeature "k8s.io/apiserver/pkg/util/feature"
"k8s.io/kubernetes/pkg/apis/apps"
api "k8s.io/kubernetes/pkg/apis/core"
apivalidation "k8s.io/kubernetes/pkg/apis/core/validation"
"k8s.io/kubernetes/pkg/features"
)
// ValidateStatefulSetName can be used to check whether the given StatefulSet name is valid.
@ -373,34 +371,25 @@ func ValidateDaemonSetSpec(spec *apps.DaemonSetSpec, fldPath *field.Path, opts a
// ValidateRollingUpdateDaemonSet validates a given RollingUpdateDaemonSet.
func ValidateRollingUpdateDaemonSet(rollingUpdate *apps.RollingUpdateDaemonSet, fldPath *field.Path) field.ErrorList {
var allErrs field.ErrorList
if utilfeature.DefaultFeatureGate.Enabled(features.DaemonSetUpdateSurge) {
// Validate both fields are positive ints or have a percentage value
allErrs = append(allErrs, ValidatePositiveIntOrPercent(rollingUpdate.MaxUnavailable, fldPath.Child("maxUnavailable"))...)
allErrs = append(allErrs, ValidatePositiveIntOrPercent(rollingUpdate.MaxSurge, fldPath.Child("maxSurge"))...)
// Validate that MaxUnavailable and MaxSurge are not more than 100%.
allErrs = append(allErrs, IsNotMoreThan100Percent(rollingUpdate.MaxUnavailable, fldPath.Child("maxUnavailable"))...)
allErrs = append(allErrs, IsNotMoreThan100Percent(rollingUpdate.MaxSurge, fldPath.Child("maxSurge"))...)
// Validate both fields are positive ints or have a percentage value
allErrs = append(allErrs, ValidatePositiveIntOrPercent(rollingUpdate.MaxUnavailable, fldPath.Child("maxUnavailable"))...)
allErrs = append(allErrs, ValidatePositiveIntOrPercent(rollingUpdate.MaxSurge, fldPath.Child("maxSurge"))...)
// Validate exactly one of MaxSurge or MaxUnavailable is non-zero
hasUnavailable := getIntOrPercentValue(rollingUpdate.MaxUnavailable) != 0
hasSurge := getIntOrPercentValue(rollingUpdate.MaxSurge) != 0
switch {
case hasUnavailable && hasSurge:
allErrs = append(allErrs, field.Invalid(fldPath.Child("maxSurge"), rollingUpdate.MaxSurge, "may not be set when maxUnavailable is non-zero"))
case !hasUnavailable && !hasSurge:
allErrs = append(allErrs, field.Required(fldPath.Child("maxUnavailable"), "cannot be 0 when maxSurge is 0"))
}
// Validate that MaxUnavailable and MaxSurge are not more than 100%.
allErrs = append(allErrs, IsNotMoreThan100Percent(rollingUpdate.MaxUnavailable, fldPath.Child("maxUnavailable"))...)
allErrs = append(allErrs, IsNotMoreThan100Percent(rollingUpdate.MaxSurge, fldPath.Child("maxSurge"))...)
} else {
allErrs = append(allErrs, ValidatePositiveIntOrPercent(rollingUpdate.MaxUnavailable, fldPath.Child("maxUnavailable"))...)
if getIntOrPercentValue(rollingUpdate.MaxUnavailable) == 0 {
// MaxUnavailable cannot be 0.
allErrs = append(allErrs, field.Invalid(fldPath.Child("maxUnavailable"), rollingUpdate.MaxUnavailable, "cannot be 0"))
}
// Validate that MaxUnavailable is not more than 100%.
allErrs = append(allErrs, IsNotMoreThan100Percent(rollingUpdate.MaxUnavailable, fldPath.Child("maxUnavailable"))...)
// Validate exactly one of MaxSurge or MaxUnavailable is non-zero
hasUnavailable := getIntOrPercentValue(rollingUpdate.MaxUnavailable) != 0
hasSurge := getIntOrPercentValue(rollingUpdate.MaxSurge) != 0
switch {
case hasUnavailable && hasSurge:
allErrs = append(allErrs, field.Invalid(fldPath.Child("maxSurge"), rollingUpdate.MaxSurge, "may not be set when maxUnavailable is non-zero"))
case !hasUnavailable && !hasSurge:
allErrs = append(allErrs, field.Required(fldPath.Child("maxUnavailable"), "cannot be 0 when maxSurge is 0"))
}
return allErrs
}

View File

@ -3591,7 +3591,6 @@ func TestValidateReplicaSet(t *testing.T) {
func TestDaemonSetUpdateMaxSurge(t *testing.T) {
testCases := map[string]struct {
ds *apps.RollingUpdateDaemonSet
enableSurge bool
expectError bool
}{
"invalid: unset": {
@ -3637,45 +3636,40 @@ func TestDaemonSetUpdateMaxSurge(t *testing.T) {
MaxUnavailable: intstr.FromString("1%"),
MaxSurge: intstr.FromString("1%"),
},
expectError: true,
},
"invalid: surge enabled, unavailable zero percent": {
ds: &apps.RollingUpdateDaemonSet{
MaxUnavailable: intstr.FromString("0%"),
},
enableSurge: true,
expectError: true,
},
"invalid: surge enabled, unavailable zero": {
ds: &apps.RollingUpdateDaemonSet{
MaxUnavailable: intstr.FromInt(0),
},
enableSurge: true,
expectError: true,
},
"valid: surge enabled, unavailable one": {
ds: &apps.RollingUpdateDaemonSet{
MaxUnavailable: intstr.FromInt(1),
},
enableSurge: true,
},
"valid: surge enabled, unavailable one percent": {
ds: &apps.RollingUpdateDaemonSet{
MaxUnavailable: intstr.FromString("1%"),
},
enableSurge: true,
},
"valid: surge enabled, unavailable 100%": {
ds: &apps.RollingUpdateDaemonSet{
MaxUnavailable: intstr.FromString("100%"),
},
enableSurge: true,
},
"invalid: surge enabled, unavailable greater than 100%": {
ds: &apps.RollingUpdateDaemonSet{
MaxUnavailable: intstr.FromString("101%"),
},
enableSurge: true,
expectError: true,
},
@ -3683,39 +3677,33 @@ func TestDaemonSetUpdateMaxSurge(t *testing.T) {
ds: &apps.RollingUpdateDaemonSet{
MaxSurge: intstr.FromString("0%"),
},
enableSurge: true,
expectError: true,
},
"invalid: surge enabled, surge zero": {
ds: &apps.RollingUpdateDaemonSet{
MaxSurge: intstr.FromInt(0),
},
enableSurge: true,
expectError: true,
},
"valid: surge enabled, surge one": {
ds: &apps.RollingUpdateDaemonSet{
MaxSurge: intstr.FromInt(1),
},
enableSurge: true,
},
"valid: surge enabled, surge one percent": {
ds: &apps.RollingUpdateDaemonSet{
MaxSurge: intstr.FromString("1%"),
},
enableSurge: true,
},
"valid: surge enabled, surge 100%": {
ds: &apps.RollingUpdateDaemonSet{
MaxSurge: intstr.FromString("100%"),
},
enableSurge: true,
},
"invalid: surge enabled, surge greater than 100%": {
ds: &apps.RollingUpdateDaemonSet{
MaxSurge: intstr.FromString("101%"),
},
enableSurge: true,
expectError: true,
},
@ -3724,7 +3712,6 @@ func TestDaemonSetUpdateMaxSurge(t *testing.T) {
MaxUnavailable: intstr.FromString("1%"),
MaxSurge: intstr.FromString("1%"),
},
enableSurge: true,
expectError: true,
},
@ -3733,7 +3720,6 @@ func TestDaemonSetUpdateMaxSurge(t *testing.T) {
MaxUnavailable: intstr.FromString("0%"),
MaxSurge: intstr.FromString("0%"),
},
enableSurge: true,
expectError: true,
},
"invalid: surge enabled, surge and unavailable zero": {
@ -3741,7 +3727,6 @@ func TestDaemonSetUpdateMaxSurge(t *testing.T) {
MaxUnavailable: intstr.FromInt(0),
MaxSurge: intstr.FromInt(0),
},
enableSurge: true,
expectError: true,
},
"invalid: surge enabled, surge and unavailable mixed zero": {
@ -3749,13 +3734,11 @@ func TestDaemonSetUpdateMaxSurge(t *testing.T) {
MaxUnavailable: intstr.FromInt(0),
MaxSurge: intstr.FromString("0%"),
},
enableSurge: true,
expectError: true,
},
}
for tcName, tc := range testCases {
t.Run(tcName, func(t *testing.T) {
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.DaemonSetUpdateSurge, tc.enableSurge)()
errs := ValidateRollingUpdateDaemonSet(tc.ds, field.NewPath("spec", "updateStrategy", "rollingUpdate"))
if tc.expectError && len(errs) == 0 {
t.Errorf("Unexpected success")

File diff suppressed because it is too large Load Diff

View File

@ -27,12 +27,9 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/util/intstr"
utilfeature "k8s.io/apiserver/pkg/util/feature"
featuregatetesting "k8s.io/component-base/featuregate/testing"
"k8s.io/klog/v2"
podutil "k8s.io/kubernetes/pkg/api/v1/pod"
"k8s.io/kubernetes/pkg/controller/daemon/util"
"k8s.io/kubernetes/pkg/features"
testingclock "k8s.io/utils/clock/testing"
)
@ -78,7 +75,6 @@ func TestDaemonSetUpdatesPods(t *testing.T) {
}
func TestDaemonSetUpdatesPodsWithMaxSurge(t *testing.T) {
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.DaemonSetUpdateSurge, true)()
ds := newDaemonSet("foo")
manager, podControl, _, err := newTestController(ds)
if err != nil {
@ -191,7 +187,6 @@ func TestDaemonSetUpdatesAllOldPodsNotReady(t *testing.T) {
}
func TestDaemonSetUpdatesAllOldPodsNotReadyMaxSurge(t *testing.T) {
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.DaemonSetUpdateSurge, true)()
ds := newDaemonSet("foo")
manager, podControl, _, err := newTestController(ds)
if err != nil {
@ -381,7 +376,6 @@ func TestGetUnavailableNumbers(t *testing.T) {
Manager *daemonSetsController
ds *apps.DaemonSet
nodeToPods map[string][]*v1.Pod
enableSurge bool
maxSurge int
maxUnavailable int
emptyNodes int
@ -536,7 +530,6 @@ func TestGetUnavailableNumbers(t *testing.T) {
mapping["node-1"] = []*v1.Pod{pod1}
return mapping
}(),
enableSurge: true,
maxSurge: 1,
maxUnavailable: 0,
emptyNodes: 0,
@ -566,7 +559,6 @@ func TestGetUnavailableNumbers(t *testing.T) {
mapping["node-1"] = []*v1.Pod{pod1}
return mapping
}(),
enableSurge: true,
maxSurge: 2,
maxUnavailable: 0,
emptyNodes: 0,
@ -605,8 +597,6 @@ func TestGetUnavailableNumbers(t *testing.T) {
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.DaemonSetUpdateSurge, c.enableSurge)()
c.Manager.dsStore.Add(c.ds)
nodeList, err := c.Manager.nodeLister.List(labels.Everything())
if err != nil {

View File

@ -25,9 +25,7 @@ import (
extensions "k8s.io/api/extensions/v1beta1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
intstrutil "k8s.io/apimachinery/pkg/util/intstr"
utilfeature "k8s.io/apiserver/pkg/util/feature"
v1helper "k8s.io/kubernetes/pkg/apis/core/v1/helper"
"k8s.io/kubernetes/pkg/features"
)
// GetTemplateGeneration gets the template generation associated with a v1.DaemonSet by extracting it from the
@ -137,9 +135,7 @@ func SurgeCount(ds *apps.DaemonSet, numberToSchedule int) (int, error) {
if ds.Spec.UpdateStrategy.Type != apps.RollingUpdateDaemonSetStrategyType {
return 0, nil
}
if !utilfeature.DefaultFeatureGate.Enabled(features.DaemonSetUpdateSurge) {
return 0, nil
}
r := ds.Spec.UpdateStrategy.RollingUpdate
if r == nil {
return 0, nil

View File

@ -215,6 +215,7 @@ const (
// owner: @smarterclayton
// alpha: v1.21
// beta: v1.22
// GA: v1.25
// DaemonSets allow workloads to maintain availability during update per node
DaemonSetUpdateSurge featuregate.Feature = "DaemonSetUpdateSurge"
@ -862,7 +863,7 @@ var defaultKubernetesFeatureGates = map[featuregate.Feature]featuregate.FeatureS
CronJobTimeZone: {Default: false, PreRelease: featuregate.Alpha},
DaemonSetUpdateSurge: {Default: true, PreRelease: featuregate.Beta}, // on by default in 1.22
DaemonSetUpdateSurge: {Default: true, PreRelease: featuregate.GA, LockToDefault: true}, // remove in 1.27
DefaultPodTopologySpread: {Default: true, PreRelease: featuregate.GA, LockToDefault: true}, // remove in 1.26

View File

@ -3728,7 +3728,7 @@ func schema_k8sio_api_apps_v1_RollingUpdateDaemonSet(ref common.ReferenceCallbac
},
"maxSurge": {
SchemaProps: spec.SchemaProps{
Description: "The maximum number of nodes with an existing available DaemonSet pod that can have an updated DaemonSet pod during during an update. Value can be an absolute number (ex: 5) or a percentage of desired pods (ex: 10%). This can not be 0 if MaxUnavailable is 0. Absolute number is calculated from percentage by rounding up to a minimum of 1. Default value is 0. Example: when this is set to 30%, at most 30% of the total number of nodes that should be running the daemon pod (i.e. status.desiredNumberScheduled) can have their a new pod created before the old pod is marked as deleted. The update starts by launching new pods on 30% of nodes. Once an updated pod is available (Ready for at least minReadySeconds) the old DaemonSet pod on that node is marked deleted. If the old pod becomes unavailable for any reason (Ready transitions to false, is evicted, or is drained) an updated pod is immediatedly created on that node without considering surge limits. Allowing surge implies the possibility that the resources consumed by the daemonset on any given node can double if the readiness check fails, and so resource intensive daemonsets should take into account that they may cause evictions during disruption. This is beta field and enabled/disabled by DaemonSetUpdateSurge feature gate.",
Description: "The maximum number of nodes with an existing available DaemonSet pod that can have an updated DaemonSet pod during during an update. Value can be an absolute number (ex: 5) or a percentage of desired pods (ex: 10%). This can not be 0 if MaxUnavailable is 0. Absolute number is calculated from percentage by rounding up to a minimum of 1. Default value is 0. Example: when this is set to 30%, at most 30% of the total number of nodes that should be running the daemon pod (i.e. status.desiredNumberScheduled) can have their a new pod created before the old pod is marked as deleted. The update starts by launching new pods on 30% of nodes. Once an updated pod is available (Ready for at least minReadySeconds) the old DaemonSet pod on that node is marked deleted. If the old pod becomes unavailable for any reason (Ready transitions to false, is evicted, or is drained) an updated pod is immediatedly created on that node without considering surge limits. Allowing surge implies the possibility that the resources consumed by the daemonset on any given node can double if the readiness check fails, and so resource intensive daemonsets should take into account that they may cause evictions during disruption.",
Ref: ref("k8s.io/apimachinery/pkg/util/intstr.IntOrString"),
},
},
@ -6376,7 +6376,7 @@ func schema_k8sio_api_apps_v1beta2_RollingUpdateDaemonSet(ref common.ReferenceCa
},
"maxSurge": {
SchemaProps: spec.SchemaProps{
Description: "The maximum number of nodes with an existing available DaemonSet pod that can have an updated DaemonSet pod during during an update. Value can be an absolute number (ex: 5) or a percentage of desired pods (ex: 10%). This can not be 0 if MaxUnavailable is 0. Absolute number is calculated from percentage by rounding up to a minimum of 1. Default value is 0. Example: when this is set to 30%, at most 30% of the total number of nodes that should be running the daemon pod (i.e. status.desiredNumberScheduled) can have their a new pod created before the old pod is marked as deleted. The update starts by launching new pods on 30% of nodes. Once an updated pod is available (Ready for at least minReadySeconds) the old DaemonSet pod on that node is marked deleted. If the old pod becomes unavailable for any reason (Ready transitions to false, is evicted, or is drained) an updated pod is immediatedly created on that node without considering surge limits. Allowing surge implies the possibility that the resources consumed by the daemonset on any given node can double if the readiness check fails, and so resource intensive daemonsets should take into account that they may cause evictions during disruption. This is beta field and enabled/disabled by DaemonSetUpdateSurge feature gate.",
Description: "The maximum number of nodes with an existing available DaemonSet pod that can have an updated DaemonSet pod during during an update. Value can be an absolute number (ex: 5) or a percentage of desired pods (ex: 10%). This can not be 0 if MaxUnavailable is 0. Absolute number is calculated from percentage by rounding up to a minimum of 1. Default value is 0. Example: when this is set to 30%, at most 30% of the total number of nodes that should be running the daemon pod (i.e. status.desiredNumberScheduled) can have their a new pod created before the old pod is marked as deleted. The update starts by launching new pods on 30% of nodes. Once an updated pod is available (Ready for at least minReadySeconds) the old DaemonSet pod on that node is marked deleted. If the old pod becomes unavailable for any reason (Ready transitions to false, is evicted, or is drained) an updated pod is immediatedly created on that node without considering surge limits. Allowing surge implies the possibility that the resources consumed by the daemonset on any given node can double if the readiness check fails, and so resource intensive daemonsets should take into account that they may cause evictions during disruption.",
Ref: ref("k8s.io/apimachinery/pkg/util/intstr.IntOrString"),
},
},

View File

@ -24,17 +24,14 @@ import (
apivalidation "k8s.io/apimachinery/pkg/api/validation"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/util/intstr"
"k8s.io/apimachinery/pkg/util/validation/field"
genericapirequest "k8s.io/apiserver/pkg/endpoints/request"
"k8s.io/apiserver/pkg/registry/rest"
"k8s.io/apiserver/pkg/storage/names"
utilfeature "k8s.io/apiserver/pkg/util/feature"
"k8s.io/kubernetes/pkg/api/legacyscheme"
"k8s.io/kubernetes/pkg/api/pod"
"k8s.io/kubernetes/pkg/apis/apps"
"k8s.io/kubernetes/pkg/apis/apps/validation"
"k8s.io/kubernetes/pkg/features"
"sigs.k8s.io/structured-merge-diff/v4/fieldpath"
)
@ -82,7 +79,6 @@ func (daemonSetStrategy) PrepareForCreate(ctx context.Context, obj runtime.Objec
daemonSet.Spec.TemplateGeneration = 1
}
dropDaemonSetDisabledFields(daemonSet, nil)
pod.DropDisabledTemplateFields(&daemonSet.Spec.Template, nil)
}
@ -91,7 +87,6 @@ func (daemonSetStrategy) PrepareForUpdate(ctx context.Context, obj, old runtime.
newDaemonSet := obj.(*apps.DaemonSet)
oldDaemonSet := old.(*apps.DaemonSet)
dropDaemonSetDisabledFields(newDaemonSet, oldDaemonSet)
pod.DropDisabledTemplateFields(&newDaemonSet.Spec.Template, &oldDaemonSet.Spec.Template)
// update is not allowed to set status
@ -121,35 +116,6 @@ func (daemonSetStrategy) PrepareForUpdate(ctx context.Context, obj, old runtime.
}
}
// dropDaemonSetDisabledFields drops fields that are not used if their associated feature gates
// are not enabled. The typical pattern is:
// if !utilfeature.DefaultFeatureGate.Enabled(features.MyFeature) && !myFeatureInUse(oldSvc) {
// newSvc.Spec.MyFeature = nil
// }
func dropDaemonSetDisabledFields(newDS *apps.DaemonSet, oldDS *apps.DaemonSet) {
if !utilfeature.DefaultFeatureGate.Enabled(features.DaemonSetUpdateSurge) {
if r := newDS.Spec.UpdateStrategy.RollingUpdate; r != nil {
if daemonSetSurgeFieldsInUse(oldDS) {
// we need to ensure that MaxUnavailable is non-zero to preserve previous behavior
if r.MaxUnavailable.IntVal == 0 && r.MaxUnavailable.StrVal == "0%" {
r.MaxUnavailable = intstr.FromInt(1)
}
} else {
// clear the MaxSurge field and let validation deal with MaxUnavailable
r.MaxSurge = intstr.IntOrString{}
}
}
}
}
// daemonSetSurgeFieldsInUse returns true if fields related to daemonset update surge are set
func daemonSetSurgeFieldsInUse(ds *apps.DaemonSet) bool {
if ds == nil {
return false
}
return ds.Spec.UpdateStrategy.RollingUpdate != nil && (ds.Spec.UpdateStrategy.RollingUpdate.MaxSurge.IntVal != 0 || ds.Spec.UpdateStrategy.RollingUpdate.MaxSurge.StrVal != "")
}
// Validate validates a new daemon set.
func (daemonSetStrategy) Validate(ctx context.Context, obj runtime.Object) field.ErrorList {
daemonSet := obj.(*apps.DaemonSet)

View File

@ -21,15 +21,10 @@ import (
"testing"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/diff"
"k8s.io/apimachinery/pkg/util/intstr"
"k8s.io/apimachinery/pkg/util/validation/field"
genericapirequest "k8s.io/apiserver/pkg/endpoints/request"
utilfeature "k8s.io/apiserver/pkg/util/feature"
featuregatetesting "k8s.io/component-base/featuregate/testing"
"k8s.io/kubernetes/pkg/apis/apps"
api "k8s.io/kubernetes/pkg/apis/core"
"k8s.io/kubernetes/pkg/features"
)
const (
@ -139,207 +134,3 @@ func newDaemonSetWithSelectorLabels(selectorLabels map[string]string, templateGe
},
}
}
func makeDaemonSetWithSurge(unavailable intstr.IntOrString, surge intstr.IntOrString) *apps.DaemonSet {
return &apps.DaemonSet{
Spec: apps.DaemonSetSpec{
UpdateStrategy: apps.DaemonSetUpdateStrategy{
Type: apps.RollingUpdateDaemonSetStrategyType,
RollingUpdate: &apps.RollingUpdateDaemonSet{
MaxUnavailable: unavailable,
MaxSurge: surge,
},
},
},
}
}
func TestDropDisabledField(t *testing.T) {
testCases := []struct {
name string
enableSurge bool
ds *apps.DaemonSet
old *apps.DaemonSet
expect *apps.DaemonSet
}{
{
name: "not surge, no update",
enableSurge: false,
ds: &apps.DaemonSet{},
old: nil,
expect: &apps.DaemonSet{},
},
{
name: "not surge, field not used",
enableSurge: false,
ds: makeDaemonSetWithSurge(intstr.FromInt(1), intstr.IntOrString{}),
old: nil,
expect: makeDaemonSetWithSurge(intstr.FromInt(1), intstr.IntOrString{}),
},
{
name: "not surge, field not used in old and new",
enableSurge: false,
ds: makeDaemonSetWithSurge(intstr.FromInt(1), intstr.IntOrString{}),
old: makeDaemonSetWithSurge(intstr.FromInt(1), intstr.IntOrString{}),
expect: makeDaemonSetWithSurge(intstr.FromInt(1), intstr.IntOrString{}),
},
{
name: "not surge, field used",
enableSurge: false,
ds: makeDaemonSetWithSurge(intstr.FromInt(2), intstr.FromInt(1)),
old: makeDaemonSetWithSurge(intstr.FromInt(2), intstr.FromInt(1)),
expect: makeDaemonSetWithSurge(intstr.FromInt(2), intstr.FromInt(1)),
},
{
name: "not surge, field used, percent",
enableSurge: false,
ds: makeDaemonSetWithSurge(intstr.FromInt(2), intstr.FromString("1%")),
old: makeDaemonSetWithSurge(intstr.FromInt(2), intstr.FromString("1%")),
expect: makeDaemonSetWithSurge(intstr.FromInt(2), intstr.FromString("1%")),
},
{
name: "not surge, field used and cleared",
enableSurge: false,
ds: makeDaemonSetWithSurge(intstr.FromInt(2), intstr.IntOrString{}),
old: makeDaemonSetWithSurge(intstr.FromInt(2), intstr.FromInt(1)),
expect: makeDaemonSetWithSurge(intstr.FromInt(2), intstr.IntOrString{}),
},
{
name: "not surge, field used and cleared, percent",
enableSurge: false,
ds: makeDaemonSetWithSurge(intstr.FromInt(2), intstr.IntOrString{}),
old: makeDaemonSetWithSurge(intstr.FromInt(2), intstr.FromString("1%")),
expect: makeDaemonSetWithSurge(intstr.FromInt(2), intstr.IntOrString{}),
},
{
name: "surge, field not used",
enableSurge: true,
ds: makeDaemonSetWithSurge(intstr.FromInt(1), intstr.IntOrString{}),
old: nil,
expect: makeDaemonSetWithSurge(intstr.FromInt(1), intstr.IntOrString{}),
},
{
name: "surge, field not used in old and new",
enableSurge: true,
ds: makeDaemonSetWithSurge(intstr.FromInt(1), intstr.IntOrString{}),
old: makeDaemonSetWithSurge(intstr.FromInt(1), intstr.IntOrString{}),
expect: makeDaemonSetWithSurge(intstr.FromInt(1), intstr.IntOrString{}),
},
{
name: "surge, field used",
enableSurge: true,
ds: makeDaemonSetWithSurge(intstr.IntOrString{}, intstr.FromInt(1)),
old: nil,
expect: makeDaemonSetWithSurge(intstr.IntOrString{}, intstr.FromInt(1)),
},
{
name: "surge, field used, percent",
enableSurge: true,
ds: makeDaemonSetWithSurge(intstr.FromInt(2), intstr.FromString("1%")),
old: makeDaemonSetWithSurge(intstr.FromInt(2), intstr.FromString("1%")),
expect: makeDaemonSetWithSurge(intstr.FromInt(2), intstr.FromString("1%")),
},
{
name: "surge, field used in old and new",
enableSurge: true,
ds: makeDaemonSetWithSurge(intstr.IntOrString{}, intstr.FromInt(1)),
old: makeDaemonSetWithSurge(intstr.IntOrString{}, intstr.FromInt(1)),
expect: makeDaemonSetWithSurge(intstr.IntOrString{}, intstr.FromInt(1)),
},
{
name: "surge, allows both fields (validation must catch)",
enableSurge: true,
ds: makeDaemonSetWithSurge(intstr.FromInt(2), intstr.FromInt(1)),
old: makeDaemonSetWithSurge(intstr.FromInt(2), intstr.FromInt(1)),
expect: makeDaemonSetWithSurge(intstr.FromInt(2), intstr.FromInt(1)),
},
{
name: "surge, allows change from unavailable to surge",
enableSurge: true,
ds: makeDaemonSetWithSurge(intstr.FromInt(2), intstr.IntOrString{}),
old: makeDaemonSetWithSurge(intstr.IntOrString{}, intstr.FromInt(1)),
expect: makeDaemonSetWithSurge(intstr.FromInt(2), intstr.IntOrString{}),
},
{
name: "surge, allows change from surge to unvailable",
enableSurge: true,
ds: makeDaemonSetWithSurge(intstr.IntOrString{}, intstr.FromInt(1)),
old: makeDaemonSetWithSurge(intstr.FromInt(2), intstr.IntOrString{}),
expect: makeDaemonSetWithSurge(intstr.IntOrString{}, intstr.FromInt(1)),
},
{
name: "not surge, allows change from unavailable to surge",
enableSurge: false,
ds: makeDaemonSetWithSurge(intstr.FromInt(2), intstr.IntOrString{}),
old: makeDaemonSetWithSurge(intstr.IntOrString{}, intstr.FromInt(1)),
expect: makeDaemonSetWithSurge(intstr.FromInt(2), intstr.IntOrString{}),
},
{
name: "not surge, allows change from surge to unvailable",
enableSurge: false,
ds: makeDaemonSetWithSurge(intstr.IntOrString{}, intstr.FromInt(1)),
old: makeDaemonSetWithSurge(intstr.FromInt(2), intstr.IntOrString{}),
expect: makeDaemonSetWithSurge(intstr.IntOrString{}, intstr.IntOrString{}),
},
{
name: "not surge, allows change from unavailable to surge, percent",
enableSurge: false,
ds: makeDaemonSetWithSurge(intstr.FromString("2%"), intstr.IntOrString{}),
old: makeDaemonSetWithSurge(intstr.IntOrString{}, intstr.FromString("1%")),
expect: makeDaemonSetWithSurge(intstr.FromString("2%"), intstr.IntOrString{}),
},
{
name: "not surge, allows change from surge to unvailable, percent",
enableSurge: false,
ds: makeDaemonSetWithSurge(intstr.IntOrString{}, intstr.FromString("1%")),
old: makeDaemonSetWithSurge(intstr.FromString("2%"), intstr.IntOrString{}),
expect: makeDaemonSetWithSurge(intstr.IntOrString{}, intstr.IntOrString{}),
},
{
name: "not surge, resets zero percent, one percent",
enableSurge: false,
ds: makeDaemonSetWithSurge(intstr.FromString("0%"), intstr.FromString("1%")),
old: makeDaemonSetWithSurge(intstr.FromString("0%"), intstr.FromString("1%")),
expect: makeDaemonSetWithSurge(intstr.FromInt(1), intstr.FromString("1%")),
},
{
name: "not surge, resets and clears when zero percent",
enableSurge: false,
ds: makeDaemonSetWithSurge(intstr.FromString("0%"), intstr.IntOrString{}),
old: makeDaemonSetWithSurge(intstr.FromString("0%"), intstr.FromString("1%")),
expect: makeDaemonSetWithSurge(intstr.FromInt(1), intstr.IntOrString{}),
},
{
name: "not surge, sets zero percent, one percent",
enableSurge: false,
ds: makeDaemonSetWithSurge(intstr.FromString("0%"), intstr.FromString("1%")),
old: nil,
expect: makeDaemonSetWithSurge(intstr.FromString("0%"), intstr.IntOrString{}),
},
{
name: "not surge, sets and clears zero percent",
enableSurge: false,
ds: makeDaemonSetWithSurge(intstr.FromString("0%"), intstr.IntOrString{}),
old: nil,
expect: makeDaemonSetWithSurge(intstr.FromString("0%"), intstr.IntOrString{}),
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.DaemonSetUpdateSurge, tc.enableSurge)()
old := tc.old.DeepCopy()
dropDaemonSetDisabledFields(tc.ds, tc.old)
// old obj should never be changed
if !reflect.DeepEqual(tc.old, old) {
t.Fatalf("old ds changed: %v", diff.ObjectReflectDiff(tc.old, old))
}
if !reflect.DeepEqual(tc.ds, tc.expect) {
t.Fatalf("unexpected ds spec: %v", diff.ObjectReflectDiff(tc.expect, tc.ds))
}
})
}
}

View File

@ -513,7 +513,6 @@ message RollingUpdateDaemonSet {
// daemonset on any given node can double if the readiness check fails, and
// so resource intensive daemonsets should take into account that they may
// cause evictions during disruption.
// This is beta field and enabled/disabled by DaemonSetUpdateSurge feature gate.
// +optional
optional k8s.io.apimachinery.pkg.util.intstr.IntOrString maxSurge = 2;
}

View File

@ -596,7 +596,6 @@ type RollingUpdateDaemonSet struct {
// daemonset on any given node can double if the readiness check fails, and
// so resource intensive daemonsets should take into account that they may
// cause evictions during disruption.
// This is beta field and enabled/disabled by DaemonSetUpdateSurge feature gate.
// +optional
MaxSurge *intstr.IntOrString `json:"maxSurge,omitempty" protobuf:"bytes,2,opt,name=maxSurge"`
}

View File

@ -263,7 +263,7 @@ func (ReplicaSetStatus) SwaggerDoc() map[string]string {
var map_RollingUpdateDaemonSet = map[string]string{
"": "Spec to control the desired behavior of daemon set rolling update.",
"maxUnavailable": "The maximum number of DaemonSet pods that can be unavailable during the update. Value can be an absolute number (ex: 5) or a percentage of total number of DaemonSet pods at the start of the update (ex: 10%). Absolute number is calculated from percentage by rounding up. This cannot be 0 if MaxSurge is 0 Default value is 1. Example: when this is set to 30%, at most 30% of the total number of nodes that should be running the daemon pod (i.e. status.desiredNumberScheduled) can have their pods stopped for an update at any given time. The update starts by stopping at most 30% of those DaemonSet pods and then brings up new DaemonSet pods in their place. Once the new pods are available, it then proceeds onto other DaemonSet pods, thus ensuring that at least 70% of original number of DaemonSet pods are available at all times during the update.",
"maxSurge": "The maximum number of nodes with an existing available DaemonSet pod that can have an updated DaemonSet pod during during an update. Value can be an absolute number (ex: 5) or a percentage of desired pods (ex: 10%). This can not be 0 if MaxUnavailable is 0. Absolute number is calculated from percentage by rounding up to a minimum of 1. Default value is 0. Example: when this is set to 30%, at most 30% of the total number of nodes that should be running the daemon pod (i.e. status.desiredNumberScheduled) can have their a new pod created before the old pod is marked as deleted. The update starts by launching new pods on 30% of nodes. Once an updated pod is available (Ready for at least minReadySeconds) the old DaemonSet pod on that node is marked deleted. If the old pod becomes unavailable for any reason (Ready transitions to false, is evicted, or is drained) an updated pod is immediatedly created on that node without considering surge limits. Allowing surge implies the possibility that the resources consumed by the daemonset on any given node can double if the readiness check fails, and so resource intensive daemonsets should take into account that they may cause evictions during disruption. This is beta field and enabled/disabled by DaemonSetUpdateSurge feature gate.",
"maxSurge": "The maximum number of nodes with an existing available DaemonSet pod that can have an updated DaemonSet pod during during an update. Value can be an absolute number (ex: 5) or a percentage of desired pods (ex: 10%). This can not be 0 if MaxUnavailable is 0. Absolute number is calculated from percentage by rounding up to a minimum of 1. Default value is 0. Example: when this is set to 30%, at most 30% of the total number of nodes that should be running the daemon pod (i.e. status.desiredNumberScheduled) can have their a new pod created before the old pod is marked as deleted. The update starts by launching new pods on 30% of nodes. Once an updated pod is available (Ready for at least minReadySeconds) the old DaemonSet pod on that node is marked deleted. If the old pod becomes unavailable for any reason (Ready transitions to false, is evicted, or is drained) an updated pod is immediatedly created on that node without considering surge limits. Allowing surge implies the possibility that the resources consumed by the daemonset on any given node can double if the readiness check fails, and so resource intensive daemonsets should take into account that they may cause evictions during disruption.",
}
func (RollingUpdateDaemonSet) SwaggerDoc() map[string]string {

View File

@ -519,7 +519,6 @@ message RollingUpdateDaemonSet {
// daemonset on any given node can double if the readiness check fails, and
// so resource intensive daemonsets should take into account that they may
// cause evictions during disruption.
// This is beta field and enabled/disabled by DaemonSetUpdateSurge feature gate.
// +optional
optional k8s.io.apimachinery.pkg.util.intstr.IntOrString maxSurge = 2;
}

View File

@ -648,7 +648,6 @@ type RollingUpdateDaemonSet struct {
// daemonset on any given node can double if the readiness check fails, and
// so resource intensive daemonsets should take into account that they may
// cause evictions during disruption.
// This is beta field and enabled/disabled by DaemonSetUpdateSurge feature gate.
// +optional
MaxSurge *intstr.IntOrString `json:"maxSurge,omitempty" protobuf:"bytes,2,opt,name=maxSurge"`
}

View File

@ -263,7 +263,7 @@ func (ReplicaSetStatus) SwaggerDoc() map[string]string {
var map_RollingUpdateDaemonSet = map[string]string{
"": "Spec to control the desired behavior of daemon set rolling update.",
"maxUnavailable": "The maximum number of DaemonSet pods that can be unavailable during the update. Value can be an absolute number (ex: 5) or a percentage of total number of DaemonSet pods at the start of the update (ex: 10%). Absolute number is calculated from percentage by rounding up. This cannot be 0 if MaxSurge is 0 Default value is 1. Example: when this is set to 30%, at most 30% of the total number of nodes that should be running the daemon pod (i.e. status.desiredNumberScheduled) can have their pods stopped for an update at any given time. The update starts by stopping at most 30% of those DaemonSet pods and then brings up new DaemonSet pods in their place. Once the new pods are available, it then proceeds onto other DaemonSet pods, thus ensuring that at least 70% of original number of DaemonSet pods are available at all times during the update.",
"maxSurge": "The maximum number of nodes with an existing available DaemonSet pod that can have an updated DaemonSet pod during during an update. Value can be an absolute number (ex: 5) or a percentage of desired pods (ex: 10%). This can not be 0 if MaxUnavailable is 0. Absolute number is calculated from percentage by rounding up to a minimum of 1. Default value is 0. Example: when this is set to 30%, at most 30% of the total number of nodes that should be running the daemon pod (i.e. status.desiredNumberScheduled) can have their a new pod created before the old pod is marked as deleted. The update starts by launching new pods on 30% of nodes. Once an updated pod is available (Ready for at least minReadySeconds) the old DaemonSet pod on that node is marked deleted. If the old pod becomes unavailable for any reason (Ready transitions to false, is evicted, or is drained) an updated pod is immediatedly created on that node without considering surge limits. Allowing surge implies the possibility that the resources consumed by the daemonset on any given node can double if the readiness check fails, and so resource intensive daemonsets should take into account that they may cause evictions during disruption. This is beta field and enabled/disabled by DaemonSetUpdateSurge feature gate.",
"maxSurge": "The maximum number of nodes with an existing available DaemonSet pod that can have an updated DaemonSet pod during during an update. Value can be an absolute number (ex: 5) or a percentage of desired pods (ex: 10%). This can not be 0 if MaxUnavailable is 0. Absolute number is calculated from percentage by rounding up to a minimum of 1. Default value is 0. Example: when this is set to 30%, at most 30% of the total number of nodes that should be running the daemon pod (i.e. status.desiredNumberScheduled) can have their a new pod created before the old pod is marked as deleted. The update starts by launching new pods on 30% of nodes. Once an updated pod is available (Ready for at least minReadySeconds) the old DaemonSet pod on that node is marked deleted. If the old pod becomes unavailable for any reason (Ready transitions to false, is evicted, or is drained) an updated pod is immediatedly created on that node without considering surge limits. Allowing surge implies the possibility that the resources consumed by the daemonset on any given node can double if the readiness check fails, and so resource intensive daemonsets should take into account that they may cause evictions during disruption.",
}
func (RollingUpdateDaemonSet) SwaggerDoc() map[string]string {