Remove InPlacePodVerticalScaling feature gate

The InPlacePodVerticalScaling feature gate has been GA and locked to
true since v1.35, and is now removed. Code paths that handled the
disabled state are removed, along with gate overrides in tests.

The node version skew check in pod resize validation is also removed:
the oldest kubelet supported by a v1.38 apiserver is v1.35, where the
feature is always enabled.
This commit is contained in:
Tim Allclair committed 2026-11-05 19:26:45 +00:00
1 parent a6f04b605a
commit be90f569f3
51 files changed
+223 -1110

No files matched your search

+1 -1
View File
@@ -214,7 +214,7 @@ function usage {
echo " CPUMANAGER_POLICY_OPTIONS=full-pcpus-only=\"true\" \\"
echo " CPUMANAGER_RECONCILE_PERIOD=\"5s\" \\"
echo " KUBELET_FLAGS=\"--kube-reserved=cpu=1,memory=4Gi --system-reserved=cpu=1,memory=1Gi --reserved-memory 0:memory=3Gi;1:memory=2148Mi --resolv-conf=/run/systemd/resolve/resolv.conf\" \\"
echo " hack/local-up-cluster.sh ( run with Topology, CPU, Memory Management policies alongside InPlacePodVerticalScaling, extra flags for etcd and coredns)"
echo " hack/local-up-cluster.sh ( run with Topology, CPU, Memory Management policies alongside InPlacePodVerticalScalingExclusiveCPUs, extra flags for etcd and coredns)"
echo ""
echo "-d dry-run: prepare for running commands, then show their command lines instead of running them"
}
-71
View File
@@ -420,7 +420,6 @@ func GetValidationOptionsFromPodSpecAndMeta(podSpec, oldPodSpec *api.PodSpec, po
AllowNonLocalProjectedTokenPath: false,
PodLevelResourcesEnabled: utilfeature.DefaultFeatureGate.Enabled(features.PodLevelResources),
AllowInvalidLabelValueInRequiredNodeAffinity: false,
AllowSidecarResizePolicy: utilfeature.DefaultFeatureGate.Enabled(features.InPlacePodVerticalScaling),
AllowMatchLabelKeysInPodTopologySpread: utilfeature.DefaultFeatureGate.Enabled(features.MatchLabelKeysInPodTopologySpread),
AllowMatchLabelKeysInPodTopologySpreadSelectorMerge: utilfeature.DefaultFeatureGate.Enabled(features.MatchLabelKeysInPodTopologySpreadSelectorMerge),
InPlacePodLevelResourcesVerticalScalingEnabled: utilfeature.DefaultFeatureGate.Enabled(features.InPlacePodLevelResourcesVerticalScaling),
@@ -478,9 +477,6 @@ func GetValidationOptionsFromPodSpecAndMeta(podSpec, oldPodSpec *api.PodSpec, po
}
}
// If oldPod has resize policy set on the restartable init container, we must allow it
opts.AllowSidecarResizePolicy = opts.AllowSidecarResizePolicy || hasRestartableInitContainerResizePolicy(oldPodSpec)
opts.AllowContainerRestartPolicyRules = opts.AllowContainerRestartPolicyRules || containerRestartRulesInUse(oldPodSpec)
opts.AllowRestartAllContainers = opts.AllowRestartAllContainers || restartAllContainersActionInUse(oldPodSpec)
@@ -770,20 +766,6 @@ func dropDisabledFields(
dropDisabledGRPCContainerProbeTLS(podSpec, oldPodSpec)
dropDisabledEvictionResponders(podSpec, oldPodSpec)
if !utilfeature.DefaultFeatureGate.Enabled(features.InPlacePodVerticalScaling) && !inPlacePodVerticalScalingInUse(oldPodSpec) {
// Drop ResizePolicy fields. Don't drop updates to Resources field as template.spec.resources
// field is mutable for certain controllers. Let ValidatePodUpdate handle it.
for i := range podSpec.Containers {
podSpec.Containers[i].ResizePolicy = nil
}
for i := range podSpec.InitContainers {
podSpec.InitContainers[i].ResizePolicy = nil
}
for i := range podSpec.EphemeralContainers {
podSpec.EphemeralContainers[i].ResizePolicy = nil
}
}
if !utilfeature.DefaultFeatureGate.Enabled(features.ContainerRestartRules) && !containerRestartRulesInUse(oldPodSpec) {
dropContainerRestartRules(podSpec)
}
@@ -1043,28 +1025,6 @@ func dropDisabledPodStatusFields(podStatus, oldPodStatus *api.PodStatus, podSpec
podStatus.AllocatedResources = nil
}
if !utilfeature.DefaultFeatureGate.Enabled(features.InPlacePodVerticalScaling) && !inPlacePodVerticalScalingInUse(oldPodSpec) {
// Drop Resources fields
dropResourcesField := func(csl []api.ContainerStatus) {
for i := range csl {
csl[i].Resources = nil
}
}
dropResourcesField(podStatus.ContainerStatuses)
dropResourcesField(podStatus.InitContainerStatuses)
dropResourcesField(podStatus.EphemeralContainerStatuses)
// Drop AllocatedResources field
dropAllocatedResourcesField := func(csl []api.ContainerStatus) {
for i := range csl {
csl[i].AllocatedResources = nil
}
}
dropAllocatedResourcesField(podStatus.ContainerStatuses)
dropAllocatedResourcesField(podStatus.InitContainerStatuses)
dropAllocatedResourcesField(podStatus.EphemeralContainerStatuses)
}
if !utilfeature.DefaultFeatureGate.Enabled(features.DRAExtendedResource) && !draExendedResourceInUse(oldPodStatus) {
podStatus.ExtendedResourceClaimStatus = nil
}
@@ -1367,23 +1327,6 @@ func podLevelStatusResourcesInUse(podStatus *api.PodStatus) bool {
return podStatus.Resources != nil || podStatus.AllocatedResources != nil
}
// inPlacePodVerticalScalingInUse returns true if pod spec is non-nil and ResizePolicy is set
func inPlacePodVerticalScalingInUse(podSpec *api.PodSpec) bool {
if podSpec == nil {
return false
}
var inUse bool
containersMask := Containers | InitContainers
VisitContainers(podSpec, containersMask, func(c *api.Container, containerType ContainerType) bool {
if len(c.ResizePolicy) > 0 {
inUse = true
return false
}
return true
})
return inUse
}
// procMountInUse returns true if the pod spec is non-nil and has a SecurityContext's ProcMount field set to a non-default value
func procMountInUse(podSpec *api.PodSpec) bool {
if podSpec == nil {
@@ -1863,20 +1806,6 @@ func hasUserNamespacesWithVolumeDevices(podSpec *api.PodSpec) bool {
return hasVolumeDevices
}
// hasRestartableInitContainerResizePolicy returns true if the pod spec is non-nil and
// it has any init container with ContainerRestartPolicyAlways and non-nil ResizePolicy.
func hasRestartableInitContainerResizePolicy(podSpec *api.PodSpec) bool {
if podSpec == nil {
return false
}
for _, c := range podSpec.InitContainers {
if IsRestartableInitContainer(&c) && len(c.ResizePolicy) > 0 {
return true
}
}
return false
}
// hasEmptyImageVolumeReference returns true if the pod spec has any image volume with an empty reference.
func hasEmptyImageVolumeReference(podSpec *api.PodSpec) bool {
if podSpec == nil {
-312
View File
@@ -25,7 +25,6 @@ import (
"time"
"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/assert"
"k8s.io/component-base/featuregate"
"k8s.io/apimachinery/pkg/api/resource"
@@ -2564,145 +2563,6 @@ func TestDropAtomicWriteVolumeUserFields(t *testing.T) {
}
}
func TestDropInPlacePodVerticalScaling(t *testing.T) {
featuregatetesting.SetFeatureGateEmulationVersionDuringTest(t, utilfeature.DefaultFeatureGate, version.MustParse("1.34"))
podWithInPlaceVerticalScaling := func() *api.Pod {
return &api.Pod{
Spec: api.PodSpec{
Containers: []api.Container{
{
Name: "c1",
Image: "image",
Resources: api.ResourceRequirements{
Requests: api.ResourceList{api.ResourceCPU: resource.MustParse("100m")},
Limits: api.ResourceList{api.ResourceCPU: resource.MustParse("200m")},
},
ResizePolicy: []api.ContainerResizePolicy{
{ResourceName: api.ResourceCPU, RestartPolicy: api.NotRequired},
{ResourceName: api.ResourceMemory, RestartPolicy: api.RestartContainer},
},
},
},
},
Status: api.PodStatus{
ContainerStatuses: []api.ContainerStatus{
{
Name: "c1",
Image: "image",
AllocatedResources: api.ResourceList{api.ResourceCPU: resource.MustParse("100m")},
Resources: &api.ResourceRequirements{
Requests: api.ResourceList{api.ResourceCPU: resource.MustParse("200m")},
Limits: api.ResourceList{api.ResourceCPU: resource.MustParse("300m")},
},
},
},
},
}
}
podWithoutInPlaceVerticalScaling := func() *api.Pod {
return &api.Pod{
Spec: api.PodSpec{
Containers: []api.Container{
{
Name: "c1",
Image: "image",
Resources: api.ResourceRequirements{
Requests: api.ResourceList{api.ResourceCPU: resource.MustParse("100m")},
Limits: api.ResourceList{api.ResourceCPU: resource.MustParse("200m")},
},
},
},
},
Status: api.PodStatus{
ContainerStatuses: []api.ContainerStatus{
{
Name: "c1",
Image: "image",
},
},
},
}
}
podInfo := []struct {
description string
hasInPlaceVerticalScaling bool
pod func() *api.Pod
}{
{
description: "has in-place vertical scaling enabled with resources",
hasInPlaceVerticalScaling: true,
pod: podWithInPlaceVerticalScaling,
},
{
description: "has in-place vertical scaling disabled",
hasInPlaceVerticalScaling: false,
pod: podWithoutInPlaceVerticalScaling,
},
{
description: "is nil",
hasInPlaceVerticalScaling: false,
pod: func() *api.Pod { return nil },
},
}
for _, ippvsEnabled := range []bool{true, false} {
t.Run(fmt.Sprintf("InPlacePodVerticalScaling=%t", ippvsEnabled), func(t *testing.T) {
featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.InPlacePodVerticalScaling, ippvsEnabled)
for _, oldPodInfo := range podInfo {
for _, newPodInfo := range podInfo {
oldPodHasInPlaceVerticalScaling, oldPod := oldPodInfo.hasInPlaceVerticalScaling, oldPodInfo.pod()
newPodHasInPlaceVerticalScaling, newPod := newPodInfo.hasInPlaceVerticalScaling, newPodInfo.pod()
if newPod == nil {
continue
}
t.Run(fmt.Sprintf("old pod %v, new pod %v", oldPodInfo.description, newPodInfo.description), func(t *testing.T) {
var oldPodSpec *api.PodSpec
var oldPodStatus *api.PodStatus
if oldPod != nil {
oldPodSpec = &oldPod.Spec
oldPodStatus = &oldPod.Status
}
dropDisabledFields(&newPod.Spec, nil, oldPodSpec, nil)
dropDisabledPodStatusFields(&newPod.Status, oldPodStatus, &newPod.Spec, oldPodSpec)
// old pod should never be changed
if !reflect.DeepEqual(oldPod, oldPodInfo.pod()) {
t.Errorf("old pod changed: %v", cmp.Diff(oldPod, oldPodInfo.pod()))
}
switch {
case ippvsEnabled || oldPodHasInPlaceVerticalScaling:
// new pod shouldn't change if feature enabled or if old pod has ResizePolicy set
expected := newPodInfo.pod()
if !reflect.DeepEqual(newPod, expected) {
t.Errorf("new pod changed: %v", cmp.Diff(newPod, expected))
}
case newPodHasInPlaceVerticalScaling:
// new pod should be changed
if reflect.DeepEqual(newPod, newPodInfo.pod()) {
t.Errorf("new pod was not changed")
}
// new pod should not have ResizePolicy
if !reflect.DeepEqual(newPod, podWithoutInPlaceVerticalScaling()) {
t.Errorf("new pod has ResizePolicy: %v", cmp.Diff(newPod, podWithoutInPlaceVerticalScaling()))
}
default:
// new pod should not need to be changed
if !reflect.DeepEqual(newPod, newPodInfo.pod()) {
t.Errorf("new pod changed: %v", cmp.Diff(newPod, newPodInfo.pod()))
}
}
})
}
}
})
}
}
func TestDropPodLevelResources(t *testing.T) {
containers := []api.Container{
{
@@ -3783,178 +3643,6 @@ func TestDropSELinuxChangePolicy(t *testing.T) {
}
}
func TestValidateAllowSidecarResizePolicy(t *testing.T) {
featuregatetesting.SetFeatureGateEmulationVersionDuringTest(t, utilfeature.DefaultFeatureGate, version.MustParse("1.34"))
restartPolicyAlways := api.ContainerRestartPolicyAlways
testCases := []struct {
name string
oldPodSpec *api.PodSpec
wantOption bool
}{
{
name: "old pod spec is nil",
wantOption: false,
},
{
name: "one sidecar container + one regular init container, no resize policy set on any of them",
oldPodSpec: &api.PodSpec{
InitContainers: []api.Container{
{
Name: "c1-restartable-init",
Image: "image",
RestartPolicy: &restartPolicyAlways,
},
{
Name: "c1-init",
Image: "image",
},
},
},
wantOption: false,
},
{
name: "one sidecar container + one regular init container, resize policy set on regular init container",
oldPodSpec: &api.PodSpec{
InitContainers: []api.Container{
{
Name: "c1-restartable-init",
Image: "image",
RestartPolicy: &restartPolicyAlways,
},
{
Name: "c1-init",
Image: "image",
ResizePolicy: []api.ContainerResizePolicy{
{ResourceName: api.ResourceCPU, RestartPolicy: api.NotRequired},
},
},
},
},
wantOption: false,
},
{
name: "one sidecar container + one regular init container, resize policy set on sidecar container",
oldPodSpec: &api.PodSpec{
InitContainers: []api.Container{
{
Name: "c1-restartable-init",
Image: "image",
RestartPolicy: &restartPolicyAlways,
ResizePolicy: []api.ContainerResizePolicy{
{ResourceName: api.ResourceCPU, RestartPolicy: api.NotRequired},
},
},
{
Name: "c1-init",
Image: "image",
},
},
},
wantOption: true,
},
{
name: "one sidecar container + one regular init container, resize policy set on both of them",
oldPodSpec: &api.PodSpec{
InitContainers: []api.Container{
{
Name: "c1-restartable-init",
Image: "image",
RestartPolicy: &restartPolicyAlways,
ResizePolicy: []api.ContainerResizePolicy{
{ResourceName: api.ResourceCPU, RestartPolicy: api.NotRequired},
},
},
{
Name: "c1-init",
Image: "image",
ResizePolicy: []api.ContainerResizePolicy{
{ResourceName: api.ResourceCPU, RestartPolicy: api.NotRequired},
},
},
},
},
wantOption: true,
},
{
name: "two sidecar containers, resize policy set on one of them",
oldPodSpec: &api.PodSpec{
InitContainers: []api.Container{
{
Name: "c1-restartable-init",
Image: "image",
RestartPolicy: &restartPolicyAlways,
ResizePolicy: []api.ContainerResizePolicy{
{ResourceName: api.ResourceCPU, RestartPolicy: api.NotRequired},
},
},
{
Name: "c2-restartable-init",
Image: "image",
RestartPolicy: &restartPolicyAlways,
},
},
},
wantOption: true,
},
{
name: "two regular init containers, resize policy set on both of them",
oldPodSpec: &api.PodSpec{
InitContainers: []api.Container{
{
Name: "c1-init",
Image: "image",
ResizePolicy: []api.ContainerResizePolicy{
{ResourceName: api.ResourceCPU, RestartPolicy: api.NotRequired},
},
},
{
Name: "c2-init",
Image: "image",
ResizePolicy: []api.ContainerResizePolicy{
{ResourceName: api.ResourceCPU, RestartPolicy: api.NotRequired},
},
},
},
},
wantOption: false,
},
{
name: "two non-init containers, resize policy set on both of them",
oldPodSpec: &api.PodSpec{
Containers: []api.Container{
{
Name: "c1",
Image: "image",
ResizePolicy: []api.ContainerResizePolicy{
{ResourceName: api.ResourceCPU, RestartPolicy: api.NotRequired},
},
},
{
Name: "c2",
Image: "image",
ResizePolicy: []api.ContainerResizePolicy{
{ResourceName: api.ResourceCPU, RestartPolicy: api.NotRequired},
},
},
},
},
wantOption: false,
},
}
for _, tc := range testCases {
for _, ippvsEnabled := range []bool{true, false} {
t.Run(fmt.Sprintf("%s/%t", tc.name, ippvsEnabled), func(t *testing.T) {
featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.InPlacePodVerticalScaling, ippvsEnabled)
gotOptions := GetValidationOptionsFromPodSpecAndMeta(&api.PodSpec{}, tc.oldPodSpec, nil, nil)
expected := tc.wantOption || ippvsEnabled
assert.Equal(t, expected, gotOptions.AllowSidecarResizePolicy, "AllowSidecarResizePolicy")
})
}
}
}
func TestValidateInvalidLabelValueInNodeSelectorOption(t *testing.T) {
testCases := []struct {
name string
-4
View File
@@ -2873,7 +2873,6 @@ type Container struct {
Resources ResourceRequirements
// Resources resize policy for the container.
// This field cannot be set on ephemeral containers.
// +featureGate=InPlacePodVerticalScaling
// +optional
ResizePolicy []ContainerResizePolicy
// RestartPolicy defines the restart behavior of individual containers in a pod.
@@ -3238,7 +3237,6 @@ type ContainerStatus struct {
AllocatedResources ResourceList
// Resources represents the compute resource requests and limits that have been successfully
// enacted on the running container after it has been started or has been successfully resized.
// +featureGate=InPlacePodVerticalScaling
// +optional
Resources *ResourceRequirements
// Status of volume mounts.
@@ -4759,7 +4757,6 @@ type EphemeralContainerCommon struct {
// +optional
Resources ResourceRequirements
// Resources resize policy for the container.
// +featureGate=InPlacePodVerticalScaling
// +optional
ResizePolicy []ContainerResizePolicy
// Restart policy for the container to manage the restart behavior of each
@@ -4975,7 +4972,6 @@ type PodStatus struct {
// Deprecated: Resize status is moved to two pod conditions PodResizePending and PodResizeInProgress.
// PodResizePending will track states where the spec has been resized, but the Kubelet has not yet allocated the resources.
// PodResizeInProgress will track in-progress resizes, and should be present whenever allocated resources != acknowledged resources.
// +featureGate=InPlacePodVerticalScaling
// +optional
Resize PodResizeStatus
-27
View File
@@ -3985,10 +3985,6 @@ func validateInitContainers(containers []core.Container, os *core.PodOS, regular
}
}
if !opts.AllowSidecarResizePolicy && len(ctr.ResizePolicy) > 0 {
allErrs = append(allErrs, field.Invalid(idxPath.Child("resizePolicy"), ctr.ResizePolicy, "must not be set for init containers"))
}
if !restartAlways && !opts.AllowExistingRestartContainerForNonSidecarInitContainer {
for j, p := range ctr.ResizePolicy {
if p.RestartPolicy == core.RestartContainer {
@@ -4608,8 +4604,6 @@ type PodValidationOptions struct {
// Indicates whether InPlacePodVerticalScalingMemoryBackedVolumes feature is enabled
// or disabled.
InPlacePodVerticalScalingMemoryBackedVolumesEnabled bool
// Allow sidecar containers resize policy for backward compatibility
AllowSidecarResizePolicy bool
// Allow invalid label-value in RequiredNodeSelector
AllowInvalidLabelValueInRequiredNodeAffinity bool
// Allow feature of MatchLabelKeys in TopologySpreadConstraints
@@ -6583,10 +6577,6 @@ func ValidatePodResize(newPod, oldPod *core.Pod, opts PodValidationOptions) fiel
allErrs = append(allErrs, field.Invalid(specPath, newPod.Status.QOSClass, "Pod QOS Class may not change as a result of resizing"))
}
if !isPodResizeRequestSupported(*oldPod) {
allErrs = append(allErrs, field.Forbidden(specPath, "Pod running on node without support for resize"))
}
// Part 2: Validate that changes between pod-level resources in oldPod.Spec.Resources and
// newPod.Spec.Resources are allowed.
@@ -6973,23 +6963,6 @@ func validatePodLevelResourcesCoverDRA(pod *v1.Pod) (bool, string) {
return true, ""
}
// isPodResizeRequestSupported checks whether the pod is running on a node with InPlacePodVerticalScaling enabled.
func isPodResizeRequestSupported(pod core.Pod) bool {
// TODO: Remove this after GA+3 releases of InPlacePodVerticalScaling
// This code handles the version skew as described in the KEP.
// For handling version skew we're only allowing to update the Pod's Resources
// if the Pod already has Pod.Status.ContainerStatuses[i].Resources. This means
// that the apiserver would only allow updates to Pods running on Nodes with
// the InPlacePodVerticalScaling feature gate enabled.
for _, c := range pod.Status.ContainerStatuses {
if c.State.Running != nil {
return c.Resources != nil
}
}
// No running containers. We cannot tell whether the node supports resize at this point, so we assume it does.
return true
}
// validateContainerResize validates the changes to the container's resource requirements for a pod resize request.
// newRequriements and oldRequirements must be non-nil.
func validateContainerResize(newRequirements, oldRequirements *core.ResourceRequirements, resizePolicies []core.ContainerResizePolicy, fldPath *field.Path) field.ErrorList {
+3 -91
View File
@@ -8752,7 +8752,6 @@ func TestValidatePullPolicy(t *testing.T) {
}
func TestValidateResizePolicy(t *testing.T) {
featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.InPlacePodVerticalScaling, true)
tSupportedResizeResources := sets.NewString(string(core.ResourceCPU), string(core.ResourceMemory))
tSupportedResizePolicies := sets.NewString(string(core.NotRequired), string(core.RestartContainer))
type T struct {
@@ -10367,7 +10366,7 @@ func TestValidateInitContainers(t *testing.T) {
}}
var PodRestartPolicy core.RestartPolicy = "Always"
if errs := validateInitContainers(successCase, podOS, containers, volumeDevices, nil, defaultGracePeriod, field.NewPath("field"), PodValidationOptions{AllowSidecarResizePolicy: true, AllowExistingRestartContainerForNonSidecarInitContainer: true}, &PodRestartPolicy, noUserNamespace); len(errs) != 0 {
if errs := validateInitContainers(successCase, podOS, containers, volumeDevices, nil, defaultGracePeriod, field.NewPath("field"), PodValidationOptions{AllowExistingRestartContainerForNonSidecarInitContainer: true}, &PodRestartPolicy, noUserNamespace); len(errs) != 0 {
t.Errorf("expected success: %v", errs)
}
@@ -10742,19 +10741,6 @@ func TestValidateInitContainers(t *testing.T) {
},
}},
field.ErrorList{{Type: field.ErrorTypeRequired, Field: "initContainers[0].lifecycle.preStop", BadValue: ""}},
}, {
"Not supported ResizePolicy: invalid",
line(),
[]core.Container{{
Name: "init",
Image: "image",
ImagePullPolicy: "IfNotPresent",
TerminationMessagePolicy: "File",
ResizePolicy: []core.ContainerResizePolicy{
{ResourceName: "cpu", RestartPolicy: "NotRequired"},
},
}},
field.ErrorList{{Type: field.ErrorTypeInvalid, Field: "initContainers[0].resizePolicy", BadValue: []core.ContainerResizePolicy{{ResourceName: "cpu", RestartPolicy: "NotRequired"}}}},
}, {
"RestartContainer policy forbidden for non-sidecar init container",
line(),
@@ -10768,10 +10754,6 @@ func TestValidateInitContainers(t *testing.T) {
},
}},
field.ErrorList{{
Type: field.ErrorTypeInvalid,
Field: "initContainers[0].resizePolicy",
BadValue: []core.ContainerResizePolicy{{ResourceName: core.ResourceCPU, RestartPolicy: core.RestartContainer}},
}, {
Type: field.ErrorTypeInvalid,
Field: "initContainers[0].resizePolicy[0].restartPolicy",
BadValue: core.RestartContainer,
@@ -14665,7 +14647,7 @@ func TestValidatePodUpdate(t *testing.T) {
),
err: "spec.initContainers[0].resizePolicy[0].restartPolicy: Invalid value: \"RestartContainer\": must not be set to 'RestartContainer' for non-sidecar initContainers",
test: "adding RestartContainer resize policy to non-sidecar init container should fail",
opts: PodValidationOptions{AllowSidecarResizePolicy: true, AllowExistingRestartContainerForNonSidecarInitContainer: false},
opts: PodValidationOptions{AllowExistingRestartContainerForNonSidecarInitContainer: false},
}, {
new: *podtest.MakePod("foo",
podtest.SetInitContainers(core.Container{
@@ -14681,7 +14663,7 @@ func TestValidatePodUpdate(t *testing.T) {
),
err: "",
test: "existing RestartContainer resize policy on non-sidecar init container should be allowed on update",
opts: PodValidationOptions{AllowSidecarResizePolicy: true, AllowExistingRestartContainerForNonSidecarInitContainer: true},
opts: PodValidationOptions{AllowExistingRestartContainerForNonSidecarInitContainer: true},
}, {
new: *podtest.MakePod("foo",
podtest.SetOS(core.Windows),
@@ -30541,75 +30523,6 @@ func TestValidatePodResize(t *testing.T) {
))),
disableInitCtrResize: true,
err: "Forbidden: resources for non-sidecar init containers are immutable",
},
{
test: "Pod with nil Resource field in Status",
old: mkPod(core.ResourceList{}, getResources("100m", "0", "1Gi", ""), podtest.SetStatus(core.PodStatus{
ContainerStatuses: []core.ContainerStatus{{
ContainerID: "docker://numbers",
Image: "nginx:alpine",
Name: "main",
Ready: true,
Started: proto.Bool(true),
Resources: nil,
State: core.ContainerState{
Running: &core.ContainerStateRunning{
StartedAt: metav1.NewTime(time.Now()),
},
},
}},
})),
new: mkPod(core.ResourceList{}, getResources("200m", "0", "1Gi", "")),
err: "Forbidden: Pod running on node without support for resize",
},
{
test: "Pod with non-nil Resources field in Status",
old: mkPod(core.ResourceList{}, getResources("100m", "0", "1Gi", ""), podtest.SetStatus(core.PodStatus{
ContainerStatuses: []core.ContainerStatus{{
ContainerID: "docker://numbers",
Image: "nginx:alpine",
Name: "main",
Ready: true,
Started: proto.Bool(true),
Resources: &core.ResourceRequirements{},
State: core.ContainerState{
Running: &core.ContainerStateRunning{
StartedAt: metav1.NewTime(time.Now()),
},
},
}},
})),
new: mkPod(core.ResourceList{}, getResources("200m", "0", "1Gi", "")),
err: "",
},
{
test: "Pod without running containers",
old: mkPod(core.ResourceList{}, getResources("100m", "0", "1Gi", ""), podtest.SetStatus(core.PodStatus{
ContainerStatuses: []core.ContainerStatus{},
})),
new: mkPod(core.ResourceList{}, getResources("200m", "0", "1Gi", "")),
err: "",
},
{
test: "Pod with containers which are not running yet",
old: mkPod(core.ResourceList{}, getResources("100m", "0", "1Gi", ""), podtest.SetStatus(core.PodStatus{
ContainerStatuses: []core.ContainerStatus{{
ContainerID: "docker://numbers",
Image: "nginx:alpine",
Name: "main",
Ready: true,
Started: proto.Bool(true),
Resources: &core.ResourceRequirements{},
State: core.ContainerState{
Waiting: &core.ContainerStateWaiting{
Reason: "PodInitializing",
},
},
}},
})),
new: mkPod(core.ResourceList{}, getResources("200m", "0", "1Gi", "")),
err: "",
}, {
test: "cpu limit change for sidecar containers",
old: mkPodWithInitContainers(core.ResourceList{}, getResources("100m", "0", "1Gi", ""), core.ContainerRestartPolicyAlways),
@@ -31396,7 +31309,6 @@ func TestValidatePodResize(t *testing.T) {
}
errs := ValidatePodResize(test.new, test.old, PodValidationOptions{
AllowSidecarResizePolicy: true,
InPlacePodLevelResourcesVerticalScalingEnabled: true,
PodLevelResourcesEnabled: true,
InPlacePodVerticalScalingMemoryBackedVolumesEnabled: test.enableMemoryBackedVolumesResize,
+10 -24
View File
@@ -521,23 +521,17 @@ const (
// Enables specifying resources at pod-level.
InPlacePodLevelResourcesVerticalScaling featuregate.Feature = "InPlacePodLevelResourcesVerticalScaling"
// owner: @vinaykul,@tallclair
// kep: http://kep.k8s.io/1287
//
// Enables In-Place Pod Vertical Scaling
InPlacePodVerticalScaling featuregate.Feature = "InPlacePodVerticalScaling"
// owner: @tallclair @esotsal
//
// Allow resource resize for containers in Guaranteed pods with integer CPU requests ( default false ).
// Applies only in nodes with InPlacePodVerticalScaling and CPU Manager features enabled, and
// Applies only in nodes with CPU Manager features enabled, and
// CPU Manager Static Policy option set.
InPlacePodVerticalScalingExclusiveCPUs featuregate.Feature = "InPlacePodVerticalScalingExclusiveCPUs"
// owner: @tallclair @pkrishn
//
// Allow memory resize for containers in Guaranteed pods (default false) when Memory Manager Policy is set to Static.
// Applies only in nodes with InPlacePodVerticalScaling and Memory Manager features enabled.
// Applies only in nodes with Memory Manager features enabled.
InPlacePodVerticalScalingExclusiveMemory featuregate.Feature = "InPlacePodVerticalScalingExclusiveMemory"
// owner: @natasha41575
@@ -1579,12 +1573,6 @@ var defaultVersionedKubernetesFeatureGates = map[featuregate.Feature]featuregate
{Version: version.MustParse("1.36"), Default: true, PreRelease: featuregate.Beta},
},
InPlacePodVerticalScaling: {
{Version: version.MustParse("1.27"), Default: false, PreRelease: featuregate.Alpha},
{Version: version.MustParse("1.33"), Default: true, PreRelease: featuregate.Beta},
{Version: version.MustParse("1.35"), Default: true, PreRelease: featuregate.GA, LockToDefault: true}, // remove in 1.38
},
InPlacePodVerticalScalingExclusiveCPUs: {
{Version: version.MustParse("1.32"), Default: false, PreRelease: featuregate.Alpha},
},
@@ -2437,7 +2425,7 @@ var defaultKubernetesFeatureGateDependencies = map[featuregate.Feature][]feature
DisableNodeKubeProxyVersion: {},
DynamicNodeLocalEphemeralVolumes: {InPlacePodVerticalScaling, NodeDeclaredFeatures},
DynamicNodeLocalEphemeralVolumes: {NodeDeclaredFeatures},
EmptyDirVolumeMode: {},
@@ -2481,19 +2469,17 @@ var defaultKubernetesFeatureGateDependencies = map[featuregate.Feature][]feature
ImageVolumeWithDigest: {ImageVolume},
InPlacePodLevelResourcesVerticalScaling: {InPlacePodVerticalScaling, PodLevelResources, NodeDeclaredFeatures},
InPlacePodLevelResourcesVerticalScaling: {PodLevelResources, NodeDeclaredFeatures},
InPlacePodVerticalScaling: {},
InPlacePodVerticalScalingExclusiveCPUs: {},
InPlacePodVerticalScalingExclusiveCPUs: {InPlacePodVerticalScaling},
InPlacePodVerticalScalingExclusiveMemory: {},
InPlacePodVerticalScalingExclusiveMemory: {InPlacePodVerticalScaling},
InPlacePodVerticalScalingInitContainers: {NodeDeclaredFeatures},
InPlacePodVerticalScalingInitContainers: {InPlacePodVerticalScaling, NodeDeclaredFeatures},
InPlacePodVerticalScalingMemoryBackedVolumes: {NodeDeclaredFeatures},
InPlacePodVerticalScalingMemoryBackedVolumes: {InPlacePodVerticalScaling, NodeDeclaredFeatures},
InPlacePodVerticalScalingSchedulerPreemption: {InPlacePodVerticalScaling},
InPlacePodVerticalScalingSchedulerPreemption: {},
InterPodAffinityHostnameFastPath: {},
@@ -2503,7 +2489,7 @@ var defaultKubernetesFeatureGateDependencies = map[featuregate.Feature][]feature
KubeProxyNFTablesLocalhostNodePorts: {},
KubeletAllocatedPodsEndpoint: {InPlacePodVerticalScaling},
KubeletAllocatedPodsEndpoint: {},
KubeletCgroupDriverFromCRI: {},
+1 -1
View File
@@ -189,7 +189,7 @@ func DefaultOffAdmissionPlugins() sets.Set[string] {
mutatingadmissionpolicy.PluginName, // Mutatingadmissionpolicy, only active when feature gate MutatingAdmissionpolicy is enabled
validatingadmissionpolicy.PluginName, // ValidatingAdmissionPolicy, only active when feature gate ValidatingAdmissionPolicy is enabled
nodedeclaredfeatures.PluginName, // NodeDeclaredFeatureValidator, only active when feature gate NodeDeclaredFeatures is enabled
podresize.PluginName, // PodResizeValidator, only active when feature gate InPlacePodVerticalScaling is enabled
podresize.PluginName, // PodResizeValidator
conditionsenforcer.PluginName, // AuthorizationConditionsEnforcer, only active when feature gate ConditionalAuthorization is enabled
)
+4 -14
View File
@@ -157,10 +157,6 @@ func NewManager(checkpointDirectory string,
}
func newStateImpl(logger klog.Logger, checkpointDirectory, checkpointName string) state.State {
if !utilfeature.DefaultFeatureGate.Enabled(features.InPlacePodVerticalScaling) {
return state.NewNoopStateCheckpoint()
}
stateImpl, err := state.NewStateCheckpoint(logger, checkpointDirectory, checkpointName, nil)
if err != nil {
// This is a critical, non-recoverable failure.
@@ -515,17 +511,15 @@ func (m *manager) AddPod(ctx context.Context, activePods []*v1.Pod, pod *v1.Pod)
m.allocationMutex.Lock()
defer m.allocationMutex.Unlock()
if utilfeature.DefaultFeatureGate.Enabled(features.InPlacePodVerticalScaling) {
// To handle kubelet restarts, test pod admissibility using AllocatedResources values
// (for cpu & memory) from checkpoint store. If found, that is the source of truth.
pod, _ = m.UpdatePodFromAllocation(pod)
}
// To handle kubelet restarts, test pod admissibility using AllocatedResources values
// (for cpu & memory) from checkpoint store. If found, that is the source of truth.
pod, _ = m.UpdatePodFromAllocation(pod)
// Check if we can admit the pod; if so, update the allocation.
allocatedPods := m.getAllocatedPods(activePods)
ok, reason, message := m.canAdmitPod(ctx, allocatedPods, pod, lifecycle.AddOperation)
if ok && utilfeature.DefaultFeatureGate.Enabled(features.InPlacePodVerticalScaling) {
if ok {
// Checkpoint the resource values at which the Pod has been admitted or resized.
if err := m.SetAllocatedResources(logger, pod); err != nil {
// TODO(vinaykul,InPlacePodVerticalScaling): Can we recover from this in some way? Investigate
@@ -652,10 +646,6 @@ func (m *manager) canAdmitPod(ctx context.Context, allocatedPods []*v1.Pod, pod
}
func (m *manager) getAllocatedPods(activePods []*v1.Pod) []*v1.Pod {
if !utilfeature.DefaultFeatureGate.Enabled(features.InPlacePodVerticalScaling) {
return activePods
}
allocatedPods := make([]*v1.Pod, 0, len(activePods))
for _, pod := range activePods {
// Filter out pods that don't yet have an allocation, which will filter pods that
@@ -33,7 +33,6 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/apimachinery/pkg/util/version"
utilfeature "k8s.io/apiserver/pkg/util/feature"
"k8s.io/client-go/kubernetes/fake"
"k8s.io/client-go/tools/record"
@@ -301,7 +300,6 @@ func TestUpdatePodFromAllocation(t *testing.T) {
// of a container is allocated. A field that is added to the API fails it until it is classified
// here and, if it is passthrough, copied in copyPassthroughFields.
func TestUpdatePodFromAllocationFieldClassification(t *testing.T) {
featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.InPlacePodVerticalScaling, true)
featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.InPlacePodLevelResourcesVerticalScaling, true)
featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.InPlacePodVerticalScalingMemoryBackedVolumes, true)
@@ -480,7 +478,6 @@ func TestUpdatePodFromAllocationFieldClassification(t *testing.T) {
//
// TODO: Remove in v1.39 when the V1 checkpoint format is no longer supported.
func TestUpdatePodFromAllocationMigratedFromV1(t *testing.T) {
featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.InPlacePodVerticalScaling, true)
featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.InPlacePodLevelResourcesVerticalScaling, true)
featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.InPlacePodVerticalScalingMemoryBackedVolumes, true)
logger, ctx := ktesting.NewTestContext(t)
@@ -588,8 +585,6 @@ func TestUpdatePodFromAllocationMigratedFromV1(t *testing.T) {
//
// TODO: Remove the checks of the overlay in v1.39 when the V1 checkpoint format is no longer supported.
func TestUpdatePodFromAllocationPending(t *testing.T) {
featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.InPlacePodVerticalScaling, true)
cpu := func(quantity string) v1.ResourceRequirements {
return v1.ResourceRequirements{Requests: v1.ResourceList{v1.ResourceCPU: resource.MustParse(quantity)}}
}
@@ -695,7 +690,6 @@ func TestRetryPendingResizes(t *testing.T) {
if goruntime.GOOS == "windows" {
t.Skip("InPlacePodVerticalScaling is not currently supported for Windows")
}
featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.InPlacePodVerticalScaling, true)
logger, tCtx := ktesting.NewTestContext(t)
containerRestartPolicyAlways := v1.ContainerRestartPolicyAlways
@@ -1247,7 +1241,6 @@ func TestRetryPendingResizesGuanteedQOSPods(t *testing.T) {
if goruntime.GOOS == "windows" {
t.Skip("InPlacePodVerticalScaling is not currently supported for Windows")
}
featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.InPlacePodVerticalScaling, true)
logger, tCtx := ktesting.NewTestContext(t)
nodeConfig := cm.NodeConfig{}
@@ -1487,7 +1480,6 @@ func TestRetryPendingResizesMemoryBackedVolumes(t *testing.T) {
if goruntime.GOOS == "windows" {
t.Skip("InPlacePodVerticalScaling is not currently supported for Windows")
}
featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.InPlacePodVerticalScaling, true)
tCtx := ktesting.Init(t)
logger := klog.FromContext(tCtx)
@@ -1692,9 +1684,6 @@ func TestRetryPendingResizesWithSwap(t *testing.T) {
metrics.Register()
metrics.PodInfeasibleResizes.Reset()
featuregatetesting.SetFeatureGatesDuringTest(t, utilfeature.DefaultFeatureGate, featuregatetesting.FeatureOverrides{
features.InPlacePodVerticalScaling: true,
})
logger, tCtx := ktesting.NewTestContext(t)
noSwapContainerName, swapContainerName := "test-container-noswap", "test-container-limitedswap"
@@ -1856,7 +1845,6 @@ func TestRetryPendingResizesMultipleConditions(t *testing.T) {
if goruntime.GOOS == "windows" {
t.Skip("InPlacePodVerticalScaling is not currently supported for Windows")
}
featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.InPlacePodVerticalScaling, true)
logger, tCtx := ktesting.NewTestContext(t)
cpu500m := resource.MustParse("500m")
@@ -2052,7 +2040,6 @@ func TestRetryPendingResizesNonResourceChange(t *testing.T) {
if goruntime.GOOS == "windows" {
t.Skip("InPlacePodVerticalScaling is not currently supported for Windows")
}
featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.InPlacePodVerticalScaling, true)
cpu := func(quantity string) v1.ResourceRequirements {
return v1.ResourceRequirements{Requests: v1.ResourceList{v1.ResourceCPU: resource.MustParse(quantity)}}
@@ -2527,7 +2514,6 @@ func TestAllocationManagerAddPod(t *testing.T) {
testCases := []struct {
name string
ipprFeatureGate bool
initialAllocatedResourcesState map[types.UID]v1.ResourceList
currentActivePods []*v1.Pod
podToAdd *v1.Pod
@@ -2538,8 +2524,7 @@ func TestAllocationManagerAddPod(t *testing.T) {
expectedAllocatedResourcesState map[types.UID]v1.ResourceList
}{
{
name: "IPPR Enabled - New pod admitted and allocated resources updated",
ipprFeatureGate: true,
name: "New pod admitted and allocated resources updated",
initialAllocatedResourcesState: map[types.UID]v1.ResourceList{},
currentActivePods: []*v1.Pod{},
podToAdd: pod1Small,
@@ -2549,18 +2534,7 @@ func TestAllocationManagerAddPod(t *testing.T) {
expectedAllocatedResourcesState: map[types.UID]v1.ResourceList{pod1UID: cpu1Mem1G},
},
{
name: "IPPR Disabled - New pod admitted but allocated resources not updated",
ipprFeatureGate: false,
initialAllocatedResourcesState: map[types.UID]v1.ResourceList{},
currentActivePods: []*v1.Pod{},
podToAdd: pod1Small,
admitFunc: nil,
expectAdmit: true,
expectedAllocatedResourcesState: map[types.UID]v1.ResourceList{},
},
{
name: "IPPR Enabled - New pod not admititted due to insufficient resources",
ipprFeatureGate: true,
name: "New pod not admititted due to insufficient resources",
initialAllocatedResourcesState: map[types.UID]v1.ResourceList{pod1UID: cpu1Mem1G},
currentActivePods: []*v1.Pod{pod1Small},
podToAdd: pod2Large,
@@ -2582,31 +2556,7 @@ func TestAllocationManagerAddPod(t *testing.T) {
expectedAllocatedResourcesState: map[types.UID]v1.ResourceList{pod1UID: cpu1Mem1G},
},
{
name: "IPPR Disabled - New pod not admitted due to insufficient resources",
ipprFeatureGate: false,
initialAllocatedResourcesState: map[types.UID]v1.ResourceList{pod1UID: cpu1Mem1G},
currentActivePods: []*v1.Pod{pod1Small},
podToAdd: pod2Large,
admitFunc: func(attrs *lifecycle.PodAdmitAttributes) lifecycle.PodAdmitResult {
cpuRequest := attrs.Pod.Spec.Containers[0].Resources.Requests.Cpu().Value()
if cpuRequest > 1 {
return lifecycle.PodAdmitResult{
Admit: false,
Reason: "OutOfcpu",
Message: fmt.Sprintf("not enough CPUs available for pod %s/%s, requested: %d, available:1", attrs.Pod.Namespace, attrs.Pod.Name, cpuRequest),
}
}
return lifecycle.PodAdmitResult{Admit: true}
},
expectAdmit: false,
admissionFailureReason: "OutOfcpu",
admissionFailureMessage: "not enough CPUs available for pod ns2/pod2, requested: 2, available:1",
// allocated resources not modified
expectedAllocatedResourcesState: map[types.UID]v1.ResourceList{pod1UID: cpu1Mem1G},
},
{
name: "IPPR Enabled - no pod resize request. Resource request same as existing allocation",
ipprFeatureGate: true,
name: "no pod resize request. Resource request same as existing allocation",
initialAllocatedResourcesState: map[types.UID]v1.ResourceList{pod1UID: cpu1Mem1G, pod2UID: cpu1Mem1G},
currentActivePods: []*v1.Pod{pod1Small},
podToAdd: pod1Small,
@@ -2615,8 +2565,7 @@ func TestAllocationManagerAddPod(t *testing.T) {
expectedAllocatedResourcesState: map[types.UID]v1.ResourceList{pod1UID: cpu1Mem1G, pod2UID: cpu1Mem1G},
},
{
name: "IPPR Enabled - current allocation not found for added pod. Allocated resources updated.",
ipprFeatureGate: true,
name: "current allocation not found for added pod. Allocated resources updated.",
initialAllocatedResourcesState: map[types.UID]v1.ResourceList{},
currentActivePods: []*v1.Pod{pod1Small},
podToAdd: pod1Large,
@@ -2626,8 +2575,7 @@ func TestAllocationManagerAddPod(t *testing.T) {
expectedAllocatedResourcesState: map[types.UID]v1.ResourceList{pod1UID: cpu2Mem2G},
},
{
name: "IPPR Enabled - request different from current allocation. Pod still admitted based on existing allocation, but allocated resources remains unchanges.",
ipprFeatureGate: true,
name: "request different from current allocation. Pod still admitted based on existing allocation, but allocated resources remains unchanges.",
initialAllocatedResourcesState: map[types.UID]v1.ResourceList{pod1UID: cpu1Mem1G, pod2UID: cpu1Mem1G},
currentActivePods: []*v1.Pod{pod1Small, pod2Small},
podToAdd: pod1Large,
@@ -2647,37 +2595,11 @@ func TestAllocationManagerAddPod(t *testing.T) {
// allocated Resources state must not be updated.
expectedAllocatedResourcesState: map[types.UID]v1.ResourceList{pod1UID: cpu1Mem1G, pod2UID: cpu1Mem1G},
},
{
name: "IPPR Disabled - request different from current allocation. Admission fails. Allocated resources not updated.",
ipprFeatureGate: false,
initialAllocatedResourcesState: map[types.UID]v1.ResourceList{pod1UID: cpu1Mem1G},
currentActivePods: []*v1.Pod{pod1Small},
podToAdd: pod1Large,
admitFunc: func(attrs *lifecycle.PodAdmitAttributes) lifecycle.PodAdmitResult {
cpuRequest := attrs.Pod.Spec.Containers[0].Resources.Requests.Cpu().Value()
if cpuRequest > 1 {
return lifecycle.PodAdmitResult{
Admit: false,
Reason: "OutOfcpu",
Message: fmt.Sprintf("not enough CPUs available for pod %s/%s, requested: %d, available:1", attrs.Pod.Namespace, attrs.Pod.Name, cpuRequest),
}
}
return lifecycle.PodAdmitResult{Admit: true}
},
// pod is still admitted as allocated resources are considered during admission.
expectAdmit: false,
admissionFailureReason: "OutOfcpu",
admissionFailureMessage: "not enough CPUs available for pod ns1/pod1, requested: 2, available:1",
// allocated Resources state must not be updated.
expectedAllocatedResourcesState: map[types.UID]v1.ResourceList{pod1UID: cpu1Mem1G},
},
}
for _, tc := range testCases {
featuregatetesting.SetFeatureGateEmulationVersionDuringTest(t, utilfeature.DefaultFeatureGate, version.MustParse("1.34"))
t.Run(tc.name, func(t *testing.T) {
tCtx := ktesting.Init(t)
featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.InPlacePodVerticalScaling, tc.ipprFeatureGate)
allocationManager := makeAllocationManager(t, &containertest.FakeRuntime{}, []*v1.Pod{}, nil)
podForAllocation := func(uid types.UID, resources v1.ResourceList) *v1.Pod {
@@ -3118,7 +3040,6 @@ func TestAllocationManager_EmptyDirVolumeLimits_AddPod(t *testing.T) {
if goruntime.GOOS == "windows" {
t.Skip("InPlacePodVerticalScaling is not currently supported for Windows")
}
featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.InPlacePodVerticalScaling, true)
tCtx := ktesting.Init(t)
tests := []struct {
@@ -3313,7 +3234,6 @@ func TestAllocationManager_EmptyDirVolumeLimits_UpdatePodFromAllocation(t *testi
if goruntime.GOOS == "windows" {
t.Skip("InPlacePodVerticalScaling is not currently supported for Windows")
}
featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.InPlacePodVerticalScaling, true)
tests := []struct {
name string
@@ -3518,7 +3438,6 @@ func TestAllocationManager_EmptyDirVolumeLimits_RetryPendingResizes(t *testing.T
if goruntime.GOOS == "windows" {
t.Skip("InPlacePodVerticalScaling is not currently supported for Windows")
}
featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.InPlacePodVerticalScaling, true)
logger, tCtx := ktesting.NewTestContext(t)
tests := []struct {
@@ -4166,7 +4085,6 @@ func setupNonAllocatedCapacityTest(t *testing.T) (Manager, *v1.Pod, *v1.Pod, klo
if goruntime.GOOS == "windows" {
t.Skip("InPlacePodVerticalScaling is not currently supported for Windows")
}
featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.InPlacePodVerticalScaling, true)
featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.NodeDeclaredFeatures, true)
logger, _ := ktesting.NewTestContext(t)
@@ -4250,7 +4168,6 @@ func TestRetryPendingResizesTerminatingPods(t *testing.T) {
if goruntime.GOOS == "windows" {
t.Skip("InPlacePodVerticalScaling is not currently supported for Windows")
}
featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.InPlacePodVerticalScaling, true)
cpu100m := resource.MustParse("100m")
cpu200m := resource.MustParse("200m")
-3
View File
@@ -25,9 +25,6 @@ import (
)
func IsInPlacePodVerticalScalingAllowed(pod *v1.Pod) (allowed bool, msg, reason string) {
if !utilfeature.DefaultFeatureGate.Enabled(features.InPlacePodVerticalScaling) {
return false, "InPlacePodVerticalScaling is disabled", "feature_gate_off"
}
return true, "", ""
}
+88 -112
View File
@@ -2114,19 +2114,17 @@ func (kl *Kubelet) SyncPod(ctx context.Context, updateType kubetypes.SyncPodType
}
}
if utilfeature.DefaultFeatureGate.Enabled(features.InPlacePodVerticalScaling) {
// Check whether a resize is in progress so we can set the PodResizeInProgressCondition accordingly.
if kl.containerRuntime.IsPodResizeInProgress(pod, podStatus) {
kl.statusManager.SetPodResizeInProgressCondition(pod.UID, "", "", pod.Generation)
} else if generation, cleared := kl.statusManager.ClearPodResizeInProgressCondition(pod.UID); cleared {
// (Allocated == Actual) => clear the resize in-progress status.
msg := events.PodResizeCompletedMsg(logger, pod, generation)
kl.recorder.WithLogger(logger).Eventf(pod, v1.EventTypeNormal, events.ResizeCompleted, "%s", msg)
}
// TODO(natasha41575): There is a race condition here, where the goroutine in the
// allocation manager may allocate a new resize and unconditionally set the
// PodResizeInProgressCondition before we set the status below.
// Check whether a resize is in progress so we can set the PodResizeInProgressCondition accordingly.
if kl.containerRuntime.IsPodResizeInProgress(pod, podStatus) {
kl.statusManager.SetPodResizeInProgressCondition(pod.UID, "", "", pod.Generation)
} else if generation, cleared := kl.statusManager.ClearPodResizeInProgressCondition(pod.UID); cleared {
// (Allocated == Actual) => clear the resize in-progress status.
msg := events.PodResizeCompletedMsg(logger, pod, generation)
kl.recorder.WithLogger(logger).Eventf(pod, v1.EventTypeNormal, events.ResizeCompleted, "%s", msg)
}
// TODO(natasha41575): There is a race condition here, where the goroutine in the
// allocation manager may allocate a new resize and unconditionally set the
// PodResizeInProgressCondition before we set the status below.
// Generate final API pod status with pod and status manager status
apiPodStatus := kl.generateAPIPodStatus(ctx, pod, podStatus, SyncPod)
@@ -2315,14 +2313,12 @@ func (kl *Kubelet) SyncPod(ctx context.Context, updateType kubetypes.SyncPodType
}
}
if utilfeature.DefaultFeatureGate.Enabled(features.InPlacePodVerticalScaling) {
for _, r := range result.SyncResults {
if r.Action == kubecontainer.ResizePodInPlace && r.Error != nil {
// If the condition already exists, the observedGeneration does not get updated.
if generation, updated := kl.statusManager.SetPodResizeInProgressCondition(pod.UID, v1.PodReasonError, r.Message, pod.Generation); updated {
msg := events.PodResizeErrorMsg(logger, pod, generation, r.Message)
kl.recorder.WithLogger(logger).Eventf(pod, v1.EventTypeWarning, events.ResizeError, "%s", msg)
}
for _, r := range result.SyncResults {
if r.Action == kubecontainer.ResizePodInPlace && r.Error != nil {
// If the condition already exists, the observedGeneration does not get updated.
if generation, updated := kl.statusManager.SetPodResizeInProgressCondition(pod.UID, v1.PodReasonError, r.Message, pod.Generation); updated {
msg := events.PodResizeErrorMsg(logger, pod, generation, r.Message)
kl.recorder.WithLogger(logger).Eventf(pod, v1.EventTypeWarning, events.ResizeError, "%s", msg)
}
}
}
@@ -2965,14 +2961,12 @@ func (kl *Kubelet) HandlePodAdditions(ctx context.Context, pods []*v1.Pod) {
kl.podCertificateManager.TrackPod(ctx, pod)
if utilfeature.DefaultFeatureGate.Enabled(features.InPlacePodVerticalScaling) {
// Backfill the queue of pending resizes, but only after all the pods have
// been added. This ensures that no resizes get resolved until all the
// existing pods are added.
_, updatedFromAllocation := kl.allocationManager.UpdatePodFromAllocation(pod)
if updatedFromAllocation {
pendingResizes = append(pendingResizes, pod.UID)
}
// Backfill the queue of pending resizes, but only after all the pods have
// been added. This ensures that no resizes get resolved until all the
// existing pods are added.
_, updatedFromAllocation := kl.allocationManager.UpdatePodFromAllocation(pod)
if updatedFromAllocation {
pendingResizes = append(pendingResizes, pod.UID)
}
}
kl.podWorkers.UpdatePod(ctx, UpdatePodOptions{
@@ -2982,16 +2976,14 @@ func (kl *Kubelet) HandlePodAdditions(ctx context.Context, pods []*v1.Pod) {
StartTime: start,
})
}
if utilfeature.DefaultFeatureGate.Enabled(features.InPlacePodVerticalScaling) {
kl.statusManager.BackfillPodResizeConditions(pods)
// Admitted pods with DeletionTimestamp set are still pushed here so RetryPendingResizes
// clears any backfilled PodResizePending condition with DeferredResizeResolutionTerminated.
for _, uid := range pendingResizes {
kl.allocationManager.PushPendingResize(logger, uid)
}
if len(pendingResizes) > 0 {
kl.allocationManager.RetryPendingResizes(ctx, allocation.TriggerReasonPodsAdded)
}
kl.statusManager.BackfillPodResizeConditions(pods)
// Admitted pods with DeletionTimestamp set are still pushed here so RetryPendingResizes
// clears any backfilled PodResizePending condition with DeferredResizeResolutionTerminated.
for _, uid := range pendingResizes {
kl.allocationManager.PushPendingResize(logger, uid)
}
if len(pendingResizes) > 0 {
kl.allocationManager.RetryPendingResizes(ctx, allocation.TriggerReasonPodsAdded)
}
}
@@ -3004,13 +2996,11 @@ func (kl *Kubelet) HandlePodUpdates(ctx context.Context, pods []*v1.Pod) {
oldPod, _ := kl.podManager.GetPodByUID(pod.UID)
kl.podManager.UpdatePod(pod)
if utilfeature.DefaultFeatureGate.Enabled(features.InPlacePodVerticalScaling) {
// Skip pods that haven't been allocated yet to avoid counting them against
// node capacity before they've been admitted.
if !kl.allocationManager.HasPodAllocatedResources(pod.UID) {
logger.V(4).Info("Skipping pod update for non-allocated pod", "pod", klog.KObj(pod), "podUID", pod.UID)
continue
}
// Skip pods that haven't been allocated yet to avoid counting them against
// node capacity before they've been admitted.
if !kl.allocationManager.HasPodAllocatedResources(pod.UID) {
logger.V(4).Info("Skipping pod update for non-allocated pod", "pod", klog.KObj(pod), "podUID", pod.UID)
continue
}
pod, mirrorPod, wasMirror := kl.podManager.GetPodAndMirrorPod(pod)
@@ -3021,21 +3011,19 @@ func (kl *Kubelet) HandlePodUpdates(ctx context.Context, pods []*v1.Pod) {
}
}
if utilfeature.DefaultFeatureGate.Enabled(features.InPlacePodVerticalScaling) {
recordResizeOperations(oldPod, pod)
_, updatedFromAllocation := kl.allocationManager.UpdatePodFromAllocation(pod)
if updatedFromAllocation {
if !kl.IsPodTerminatingOrTerminal(pod) {
kl.allocationManager.PushPendingResize(logger, pod.UID)
}
// TODO(natasha41575): If the resize is immediately actuated, it will trigger a pod sync
// and we will end up calling UpdatePod twice. Figure out if there is a way to avoid this.
kl.allocationManager.RetryPendingResizes(ctx, allocation.TriggerReasonPodUpdated)
} else {
// We can hit this case if a pending resize has been reverted,
// so we need to clear the pending resize condition.
kl.statusManager.ClearPodResizePendingCondition(pod.UID, metrics.DeferredResizeResolutionReverted)
recordResizeOperations(oldPod, pod)
_, updatedFromAllocation := kl.allocationManager.UpdatePodFromAllocation(pod)
if updatedFromAllocation {
if !kl.IsPodTerminatingOrTerminal(pod) {
kl.allocationManager.PushPendingResize(logger, pod.UID)
}
// TODO(natasha41575): If the resize is immediately actuated, it will trigger a pod sync
// and we will end up calling UpdatePod twice. Figure out if there is a way to avoid this.
kl.allocationManager.RetryPendingResizes(ctx, allocation.TriggerReasonPodUpdated)
} else {
// We can hit this case if a pending resize has been reverted,
// so we need to clear the pending resize condition.
kl.statusManager.ClearPodResizePendingCondition(pod.UID, metrics.DeferredResizeResolutionReverted)
}
if utilfeature.DefaultFeatureGate.Enabled(features.NodeDeclaredFeatures) && oldPod != nil {
@@ -3082,10 +3070,6 @@ func recordResizeOperations(oldPod, newPod *v1.Pod) {
// recordPodLevelResourceResizeOperations records if any of the pod level resources need to be resized.
func recordPodLevelResourceResizeOperations(oldPod, newPod *v1.Pod) {
if !utilfeature.DefaultFeatureGate.Enabled(features.InPlacePodVerticalScaling) {
return
}
// TODO(ndixita): add metrics for pod-level resources resize.
}
@@ -3182,9 +3166,7 @@ func (kl *Kubelet) HandlePodRemoves(ctx context.Context, pods []*v1.Pod) {
}
}
if utilfeature.DefaultFeatureGate.Enabled(features.InPlacePodVerticalScaling) {
kl.allocationManager.RetryPendingResizes(ctx, allocation.TriggerReasonPodsRemoved)
}
kl.allocationManager.RetryPendingResizes(ctx, allocation.TriggerReasonPodsRemoved)
}
// HandlePodReconcile is the callback in the SyncHandler interface for pods
@@ -3211,46 +3193,44 @@ func (kl *Kubelet) HandlePodReconcile(ctx context.Context, pods []*v1.Pod) {
// Static pods should be reconciled the same way as regular pods
}
if utilfeature.DefaultFeatureGate.Enabled(features.InPlacePodVerticalScaling) {
if hasPendingResizes && !retryPendingResizes && oldPod != nil {
// If the pod has reached a terminal phase, we retry all pending resizes.
// A terminated pod releases capacity even if its allocation has already
// been purged, so check this before the non-allocated skip below.
if podutil.IsPodTerminal(pod) && !podutil.IsPodTerminal(oldPod) {
retryPendingResizes = true
triggerReason = allocation.TriggerReasonPodTerminated
}
// If there are pending resizes, check whether the requests shrank as a result of the status
// resources changing.
opts := resourcehelper.PodResourcesOptions{
UseStatusResources: true,
SkipPodLevelResources: !utilfeature.DefaultFeatureGate.Enabled(features.PodLevelResources),
InPlacePodLevelResourcesVerticalScalingEnabled: utilfeature.DefaultFeatureGate.Enabled(features.InPlacePodLevelResourcesVerticalScaling),
UseDRANodeAllocatableResourceClaimStatus: utilfeature.DefaultFeatureGate.Enabled(features.DRANodeAllocatableResources),
}
// Ignore desired resources when aggregating the resources.
allocatedOldPod, _ := kl.allocationManager.UpdatePodFromAllocation(oldPod)
allocatedPod, _ := kl.allocationManager.UpdatePodFromAllocation(pod)
oldRequest := resourcehelper.PodRequests(allocatedOldPod, opts)
newRequest := resourcehelper.PodRequests(allocatedPod, opts)
// If cpu or memory requests shrank, then retry the pending resizes.
if newRequest.Memory().Cmp(*oldRequest.Memory()) < 0 ||
newRequest.Cpu().Cmp(*oldRequest.Cpu()) < 0 {
retryPendingResizes = true
triggerReason = allocation.TriggerReasonPodResized
}
if hasPendingResizes && !retryPendingResizes && oldPod != nil {
// If the pod has reached a terminal phase, we retry all pending resizes.
// A terminated pod releases capacity even if its allocation has already
// been purged, so check this before the non-allocated skip below.
if podutil.IsPodTerminal(pod) && !podutil.IsPodTerminal(oldPod) {
retryPendingResizes = true
triggerReason = allocation.TriggerReasonPodTerminated
}
// Skip further reconciliation for pods that haven't been allocated yet.
// We still updated podManager above to keep status in sync with the API server.
if !kl.allocationManager.HasPodAllocatedResources(pod.UID) {
logger.V(4).Info("Skipping pod reconcile operations for non-allocated pod", "pod", klog.KObj(pod), "podUID", pod.UID)
continue
// If there are pending resizes, check whether the requests shrank as a result of the status
// resources changing.
opts := resourcehelper.PodResourcesOptions{
UseStatusResources: true,
SkipPodLevelResources: !utilfeature.DefaultFeatureGate.Enabled(features.PodLevelResources),
InPlacePodLevelResourcesVerticalScalingEnabled: utilfeature.DefaultFeatureGate.Enabled(features.InPlacePodLevelResourcesVerticalScaling),
UseDRANodeAllocatableResourceClaimStatus: utilfeature.DefaultFeatureGate.Enabled(features.DRANodeAllocatableResources),
}
// Ignore desired resources when aggregating the resources.
allocatedOldPod, _ := kl.allocationManager.UpdatePodFromAllocation(oldPod)
allocatedPod, _ := kl.allocationManager.UpdatePodFromAllocation(pod)
oldRequest := resourcehelper.PodRequests(allocatedOldPod, opts)
newRequest := resourcehelper.PodRequests(allocatedPod, opts)
// If cpu or memory requests shrank, then retry the pending resizes.
if newRequest.Memory().Cmp(*oldRequest.Memory()) < 0 ||
newRequest.Cpu().Cmp(*oldRequest.Cpu()) < 0 {
retryPendingResizes = true
triggerReason = allocation.TriggerReasonPodResized
}
}
// Skip further reconciliation for pods that haven't been allocated yet.
// We still updated podManager above to keep status in sync with the API server.
if !kl.allocationManager.HasPodAllocatedResources(pod.UID) {
logger.V(4).Info("Skipping pod reconcile operations for non-allocated pod", "pod", klog.KObj(pod), "podUID", pod.UID)
continue
}
// TODO: reconcile being calculated in the config manager is questionable, and avoiding
@@ -3282,10 +3262,8 @@ func (kl *Kubelet) HandlePodReconcile(ctx context.Context, pods []*v1.Pod) {
}
}
if utilfeature.DefaultFeatureGate.Enabled(features.InPlacePodVerticalScaling) {
if retryPendingResizes {
kl.allocationManager.RetryPendingResizes(ctx, triggerReason)
}
if retryPendingResizes {
kl.allocationManager.RetryPendingResizes(ctx, triggerReason)
}
}
@@ -3701,12 +3679,10 @@ func (a *allocatedPodManager) getAllocatedPod(pod *v1.Pod) (*v1.Pod, bool) {
if pod == nil {
return nil, false
}
if utilfeature.DefaultFeatureGate.Enabled(features.InPlacePodVerticalScaling) {
if allocatedPod, ok := a.allocationManager.UpdatePodFromAllocation(pod); ok {
return allocatedPod, true
}
if allocatedPod, ok := a.allocationManager.UpdatePodFromAllocation(pod); ok {
return allocatedPod, true
}
// When the feature gate is disabled or there is no allocation stored for this pod, return the pod as-is.
// When there is no allocation stored for this pod, return the pod as-is.
return pod, true
}
+20 -28
View File
@@ -245,10 +245,6 @@ func (kl *Kubelet) GetActivePods() []*v1.Pod {
// allocated state.
func (kl *Kubelet) getAllocatedPods() []*v1.Pod {
activePods := kl.GetActivePods()
if !utilfeature.DefaultFeatureGate.Enabled(features.InPlacePodVerticalScaling) {
return activePods
}
allocatedPods := make([]*v1.Pod, len(activePods))
for i, pod := range activePods {
allocatedPods[i], _ = kl.allocationManager.UpdatePodFromAllocation(pod)
@@ -2024,18 +2020,16 @@ func (kl *Kubelet) generateAPIPodStatus(ctx context.Context, pod *v1.Pod, podSta
s.Conditions = append(s.Conditions, c)
}
}
if utilfeature.DefaultFeatureGate.Enabled(features.InPlacePodVerticalScaling) {
resizeStatus := kl.determinePodResizeStatus(pod, podState)
for _, c := range resizeStatus {
// Clear the condition's observed generation if BOTH The FG is disabled AND the condition's
// observed generation is not already set. We avoid overwriting the condition's observedGeneration
// in other cases, because the condition may be reflecting an older podspec.
gen := podutil.CalculatePodConditionObservedGeneration(&oldPodStatus, pod.Generation, c.Type)
if gen == 0 {
c.ObservedGeneration = 0
}
s.Conditions = append(s.Conditions, *c)
resizeStatus := kl.determinePodResizeStatus(pod, podState)
for _, c := range resizeStatus {
// Clear the condition's observed generation if BOTH The FG is disabled AND the condition's
// observed generation is not already set. We avoid overwriting the condition's observedGeneration
// in other cases, because the condition may be reflecting an older podspec.
gen := podutil.CalculatePodConditionObservedGeneration(&oldPodStatus, pod.Generation, c.Type)
if gen == 0 {
c.ObservedGeneration = 0
}
s.Conditions = append(s.Conditions, *c)
}
// copy over the pod disruption conditions from state which is already
@@ -2752,19 +2746,17 @@ func (kl *Kubelet) convertToAPIContainerStatuses(ctx context.Context, pod *v1.Po
}
}
if utilfeature.DefaultFeatureGate.Enabled(features.InPlacePodVerticalScaling) {
allocatedContainer := kubecontainer.GetContainerSpec(pod, cName)
if allocatedContainer != nil {
// status.Resources reflects cgroup-actuated resources. If the container is running,
// it is populated from CRI status.
status.Resources = convertContainerStatusResources(allocatedContainer, status, cStatus, oldStatuses)
// status.AllocatedResources represents the desired cgroup resource state for resize,
// and is strictly based on the uninflated standard cgroup resources requested in the spec.
// DRA based allocations are not included in AllocatedResources at the container level.
// This is because the claims can be shared across containers in the pod and can be
// shared unevenly (enforced by the DRA driver).
status.AllocatedResources = allocatedContainer.Resources.Requests
}
allocatedContainer := kubecontainer.GetContainerSpec(pod, cName)
if allocatedContainer != nil {
// status.Resources reflects cgroup-actuated resources. If the container is running,
// it is populated from CRI status.
status.Resources = convertContainerStatusResources(allocatedContainer, status, cStatus, oldStatuses)
// status.AllocatedResources represents the desired cgroup resource state for resize,
// and is strictly based on the uninflated standard cgroup resources requested in the spec.
// DRA based allocations are not included in AllocatedResources at the container level.
// This is because the claims can be shared across containers in the pod and can be
// shared unevenly (enforced by the DRA driver).
status.AllocatedResources = allocatedContainer.Resources.Requests
}
status.User = convertContainerStatusUser(cStatus)
-4
View File
@@ -6202,7 +6202,6 @@ func Test_generateAPIPodStatusForInPlaceVPAEnabled(t *testing.T) {
if goruntime.GOOS != "linux" {
t.Skip("InPlacePodVerticalScaling cgroup resource reporting is only supported on Linux")
}
featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.InPlacePodVerticalScaling, true)
testContainerName := "ctr0"
testContainerID := kubecontainer.ContainerID{Type: "test", ID: testContainerName}
@@ -7024,7 +7023,6 @@ func TestConvertToAPIContainerStatusesForResources(t *testing.T) {
if goruntime.GOOS != "linux" {
t.Skip("InPlacePodVerticalScaling cgroup resource reporting is only supported on Linux")
}
featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.InPlacePodVerticalScaling, true)
nowTime := time.Now()
testContainerName := "ctr0"
@@ -10175,8 +10173,6 @@ func TestMakeMountsBindMountOptions(t *testing.T) {
}
func TestGenerateAPIPodStatusResizeConditionsByPodWorkerState(t *testing.T) {
featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.InPlacePodVerticalScaling, true)
tests := []struct {
name string
podState PodWorkerState
-4
View File
@@ -2855,7 +2855,6 @@ func TestHandlePodAdditionsTracksCertificatesOnlyForAdmittedPods(t *testing.T) {
func TestPodResourceAllocationReset(t *testing.T) {
logger, tCtx := ktesting.NewTestContext(t)
featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.InPlacePodVerticalScaling, true)
testKubelet := newTestKubelet(t, false)
defer testKubelet.Cleanup()
kubelet := testKubelet.kubelet
@@ -5215,7 +5214,6 @@ func TestHandlePodUpdates_VolumeResize(t *testing.T) {
t.Skip("InPlacePodVerticalScaling is not currently supported for Windows")
}
featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.InPlacePodVerticalScaling, true)
featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.InPlacePodVerticalScalingMemoryBackedVolumes, true)
tCtx := ktesting.Init(t)
@@ -5456,7 +5454,6 @@ func TestHandlePodReconcile_RetryPendingResizes(t *testing.T) {
if goruntime.GOOS == "windows" {
t.Skip("InPlacePodVerticalScaling is not currently supported for Windows")
}
featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.InPlacePodVerticalScaling, true)
featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.DRANodeAllocatableResources, true)
logger, tCtx := ktesting.NewTestContext(t)
@@ -6000,7 +5997,6 @@ func TestHandlePodUpdates_ResizeTerminatingPods(t *testing.T) {
if goruntime.GOOS == "windows" {
t.Skip("InPlacePodVerticalScaling is not currently supported for Windows")
}
featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.InPlacePodVerticalScaling, true)
metrics.Register()
const podUID types.UID = "pod-1"
@@ -701,25 +701,22 @@ func (m *kubeGenericRuntimeManager) getPodContainerStatuses(ctx context.Context,
func (m *kubeGenericRuntimeManager) toKubeContainerStatus(ctx context.Context, podUID kubetypes.UID, status *runtimeapi.ContainerStatus, runtimeName string) *kubecontainer.Status {
annotatedInfo := getContainerInfoFromAnnotations(ctx, status.Annotations)
labeledInfo := getContainerInfoFromLabels(ctx, status.Labels)
var cStatusResources *kubecontainer.ContainerResources
if utilfeature.DefaultFeatureGate.Enabled(features.InPlacePodVerticalScaling) {
// If runtime reports cpu & memory resources info, add it to container status
cStatusResources = toKubeContainerResources(status.Resources)
// If runtime reports cpu & memory resources info, add it to container status
cStatusResources := toKubeContainerResources(status.Resources)
// Fill missing resource values from the actuated resources.
if cStatusResources == nil || cStatusResources.MemoryRequest == nil {
if actuatedResources, ok := m.actuatedState.GetContainerResources(podUID, labeledInfo.ContainerName); ok {
if cStatusResources == nil {
cStatusResources = &kubecontainer.ContainerResources{
CPURequest: actuatedResources.Requests.Cpu(),
CPULimit: actuatedResources.Limits.Cpu(),
MemoryRequest: actuatedResources.Requests.Memory(),
MemoryLimit: actuatedResources.Limits.Memory(),
}
} else if cStatusResources.MemoryRequest == nil {
if memReq := actuatedResources.Requests[v1.ResourceMemory]; !memReq.IsZero() {
cStatusResources.MemoryRequest = &memReq
}
// Fill missing resource values from the actuated resources.
if cStatusResources == nil || cStatusResources.MemoryRequest == nil {
if actuatedResources, ok := m.actuatedState.GetContainerResources(podUID, labeledInfo.ContainerName); ok {
if cStatusResources == nil {
cStatusResources = &kubecontainer.ContainerResources{
CPURequest: actuatedResources.Requests.Cpu(),
CPULimit: actuatedResources.Limits.Cpu(),
MemoryRequest: actuatedResources.Requests.Memory(),
MemoryLimit: actuatedResources.Limits.Memory(),
}
} else if cStatusResources.MemoryRequest == nil {
if memReq := actuatedResources.Requests[v1.ResourceMemory]; !memReq.IsZero() {
cStatusResources.MemoryRequest = &memReq
}
}
}
@@ -32,8 +32,6 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/intstr"
utilfeature "k8s.io/apiserver/pkg/util/feature"
featuregatetesting "k8s.io/component-base/featuregate/testing"
v1 "k8s.io/api/core/v1"
runtimeapi "k8s.io/cri-api/pkg/apis/runtime/v1"
@@ -42,7 +40,6 @@ import (
apitest "k8s.io/cri-api/pkg/apis/testing"
kubelettypes "k8s.io/kubelet/pkg/types"
podutil "k8s.io/kubernetes/pkg/api/v1/pod"
"k8s.io/kubernetes/pkg/features"
"k8s.io/kubernetes/pkg/kubelet/cm"
kubecontainer "k8s.io/kubernetes/pkg/kubelet/container"
containertest "k8s.io/kubernetes/pkg/kubelet/container/testing"
@@ -316,7 +313,6 @@ func TestToKubeContainerStatusWithResources(t *testing.T) {
if goruntime.GOOS == "windows" {
t.Skip("InPlacePodVerticalScaling is not currently supported on Windows.")
}
featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.InPlacePodVerticalScaling, true)
logger, tCtx := ktesting.NewTestContext(t)
const (
@@ -831,9 +831,6 @@ func (m *kubeGenericRuntimeManager) computePodResizeAction(ctx context.Context,
// TODO: Move InitializeActuatedPod, UpdateActuatedPodLevelResources, and IsPodResizeInProgress
// out of the runtime now that actuatedState is owned by the Kubelet.
func (m *kubeGenericRuntimeManager) InitializeActuatedPod(logger klog.Logger, allocatedPod *v1.Pod) {
if !utilfeature.DefaultFeatureGate.Enabled(features.InPlacePodVerticalScaling) {
return
}
stored, hasPod := m.actuatedState.GetPod(allocatedPod.UID)
// TODO: In v1.39, when the V1 checkpoint format is no longer supported, we will still need to
// check stored.Name == "" (and preserve stored.Spec.Resources and stored.Spec.Volumes below)
@@ -2371,11 +2368,6 @@ func (m *kubeGenericRuntimeManager) ListPodSandboxMetrics(ctx context.Context) (
}
func (m *kubeGenericRuntimeManager) UpdateActuatedPodLevelResources(logger klog.Logger, actuatedPod *v1.Pod) error {
if !utilfeature.DefaultFeatureGate.Enabled(features.InPlacePodVerticalScaling) {
return nil
}
if actuatedPod.Spec.Resources == nil {
return nil
}
@@ -3118,7 +3118,6 @@ func TestComputePodActionsForPodResize(t *testing.T) {
if goruntime.GOOS == "windows" {
t.Skip("InPlacePodVerticalScaling is currently not supported on Windows")
}
featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.InPlacePodVerticalScaling, true)
logger, tCtx := ktesting.NewTestContext(t)
_, _, m, err := createTestRuntimeManager(tCtx)
@@ -4041,7 +4040,6 @@ func TestComputePodResizeActionForOOMKilledContainer(t *testing.T) {
t.Skip("in-place resize is only supported on Linux")
}
logger, tCtx := ktesting.NewTestContext(t)
featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.InPlacePodVerticalScaling, true)
_, _, m, err := createTestRuntimeManager(tCtx)
m.machineInfo.MemoryCapacity = 17179860387 // 16GB
require.NoError(t, err)
@@ -4088,7 +4086,6 @@ func TestComputePodResizeActionForOOMKilledInitContainer(t *testing.T) {
t.Skip("in-place resize is only supported on Linux")
}
logger, tCtx := ktesting.NewTestContext(t)
featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.InPlacePodVerticalScaling, true)
_, _, m, err := createTestRuntimeManager(tCtx)
m.machineInfo.MemoryCapacity = 17179860387 // 16GB
require.NoError(t, err)
@@ -4149,7 +4146,6 @@ func TestComputePodResizeActionForOOMKilledSidecarContainer(t *testing.T) {
t.Skip("in-place resize is only supported on Linux")
}
logger, tCtx := ktesting.NewTestContext(t)
featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.InPlacePodVerticalScaling, true)
_, _, m, err := createTestRuntimeManager(tCtx)
m.machineInfo.MemoryCapacity = 17179860387 // 16GB
require.NoError(t, err)
@@ -4223,7 +4219,6 @@ func TestUpdatePodContainerResources(t *testing.T) {
}
tCtx := ktesting.Init(t)
featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.InPlacePodVerticalScaling, true)
fakeRuntime, _, m, err := createTestRuntimeManager(tCtx)
m.machineInfo.MemoryCapacity = 17179860387 // 16GB
m.cpuCFSQuota = true
@@ -4519,7 +4514,6 @@ func TestDoPodResizeAction(t *testing.T) {
tCtx := ktesting.Init(t)
logger := tCtx.Logger()
featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.InPlacePodVerticalScaling, true)
metrics.Register()
metrics.PodResizeDurationMilliseconds.Reset()
@@ -7173,7 +7167,6 @@ func TestSyncResultErrorsMetric(t *testing.T) {
func TestInitializeActuatedPod_PreservesActuatedVolumes(t *testing.T) {
tCtx := ktesting.Init(t)
featuregatetesting.SetFeatureGatesDuringTest(t, utilfeature.DefaultFeatureGate, featuregatetesting.FeatureOverrides{
features.InPlacePodVerticalScaling: true,
features.InPlacePodLevelResourcesVerticalScaling: true,
features.InPlacePodVerticalScalingMemoryBackedVolumes: true,
})
-4
View File
@@ -23,10 +23,7 @@ import (
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/intstr"
utilfeature "k8s.io/apiserver/pkg/util/feature"
featuregatetesting "k8s.io/component-base/featuregate/testing"
"k8s.io/ktesting"
"k8s.io/kubernetes/pkg/features"
kubecontainer "k8s.io/kubernetes/pkg/kubelet/container"
)
@@ -163,7 +160,6 @@ func TestContainerAnnotations(t *testing.T) {
PreStopHandler: container.Lifecycle.PreStop,
}
featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.InPlacePodVerticalScaling, true)
// Test whether we can get right information from label
annotations := newContainerAnnotations(tCtx, container, pod, restartCount, opts)
containerInfo := getContainerInfoFromAnnotations(tCtx, annotations)
+7 -9
View File
@@ -1598,15 +1598,13 @@ func Register() {
legacyregistry.MustRegister(ImageVolumeMountedErrorsTotal)
}
if utilfeature.DefaultFeatureGate.Enabled(features.InPlacePodVerticalScaling) {
legacyregistry.MustRegister(ContainerRequestedResizes)
legacyregistry.MustRegister(PodResizeDurationMilliseconds)
legacyregistry.MustRegister(PodPendingResizes)
legacyregistry.MustRegister(PodInfeasibleResizes)
legacyregistry.MustRegister(PodInProgressResizes)
legacyregistry.MustRegister(PodDeferredAcceptedResizes)
legacyregistry.MustRegister(PodDeferredResizeDurationSeconds)
}
legacyregistry.MustRegister(ContainerRequestedResizes)
legacyregistry.MustRegister(PodResizeDurationMilliseconds)
legacyregistry.MustRegister(PodPendingResizes)
legacyregistry.MustRegister(PodInfeasibleResizes)
legacyregistry.MustRegister(PodInProgressResizes)
legacyregistry.MustRegister(PodDeferredAcceptedResizes)
legacyregistry.MustRegister(PodDeferredResizeDurationSeconds)
if utilfeature.DefaultFeatureGate.Enabled(features.PodLevelResourceManagers) {
legacyregistry.MustRegister(ResourceManagerAllocationsTotal)
+1 -5
View File
@@ -29,11 +29,9 @@ import (
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/runtime"
"k8s.io/apimachinery/pkg/util/wait"
utilfeature "k8s.io/apiserver/pkg/util/feature"
"k8s.io/client-go/tools/record"
runtimeapi "k8s.io/cri-api/pkg/apis/runtime/v1"
"k8s.io/klog/v2"
"k8s.io/kubernetes/pkg/features"
"k8s.io/kubernetes/pkg/kubelet/allocation"
kubecontainer "k8s.io/kubernetes/pkg/kubelet/container"
"k8s.io/kubernetes/pkg/kubelet/events"
@@ -998,9 +996,7 @@ func (p *podWorkers) UpdatePod(ctx context.Context, options UpdatePodOptions) {
// notify the pod worker there is a pending update
status.pendingUpdate = &options
if utilfeature.DefaultFeatureGate.Enabled(features.InPlacePodVerticalScaling) {
status.pendingUpdate.Pod, _ = p.allocationManager.UpdatePodFromAllocation(options.Pod)
}
status.pendingUpdate.Pod, _ = p.allocationManager.UpdatePodFromAllocation(options.Pod)
status.working = true
updateLogger.V(4).Info("Notifying pod of pending update", "workType", status.WorkType())
select {
+10 -14
View File
@@ -1133,13 +1133,11 @@ func (m *manager) deletePodStatus(uid types.UID) {
defer m.podStatusesLock.Unlock()
delete(m.podStatuses, uid)
m.podStartupLatencyHelper.DeletePodStartupState(uid)
if utilfeature.DefaultFeatureGate.Enabled(features.InPlacePodVerticalScaling) {
if conds, exists := m.podResizeConditions[uid]; exists {
m.observeDeferredResizeDuration(uid, conds.PodResizePending, metrics.DeferredResizeResolutionTerminated)
delete(m.podResizeConditions, uid)
m.recordInProgressResizeCount()
m.observePendingResizeCount()
}
if conds, exists := m.podResizeConditions[uid]; exists {
m.observeDeferredResizeDuration(uid, conds.PodResizePending, metrics.DeferredResizeResolutionTerminated)
delete(m.podResizeConditions, uid)
m.recordInProgressResizeCount()
m.observePendingResizeCount()
}
}
@@ -1151,13 +1149,11 @@ func (m *manager) RemoveOrphanedStatuses(logger klog.Logger, podUIDs map[types.U
if _, ok := podUIDs[key]; !ok {
logger.V(5).Info("Removing pod from status map", "podUID", key)
delete(m.podStatuses, key)
if utilfeature.DefaultFeatureGate.Enabled(features.InPlacePodVerticalScaling) {
if conds, exists := m.podResizeConditions[key]; exists {
m.observeDeferredResizeDuration(key, conds.PodResizePending, metrics.DeferredResizeResolutionTerminated)
delete(m.podResizeConditions, key)
m.recordInProgressResizeCount()
m.observePendingResizeCount()
}
if conds, exists := m.podResizeConditions[key]; exists {
m.observeDeferredResizeDuration(key, conds.PodResizePending, metrics.DeferredResizeResolutionTerminated)
delete(m.podResizeConditions, key)
m.recordInProgressResizeCount()
m.observePendingResizeCount()
}
}
}
+1 -1
View File
@@ -399,7 +399,7 @@ func PodUsageFunc(obj runtime.Object, clock clock.Clock) (corev1.ResourceList, e
}
opts := resourcehelper.PodResourcesOptions{
UseStatusResources: feature.DefaultFeatureGate.Enabled(features.InPlacePodVerticalScaling),
UseStatusResources: true,
// SkipPodLevelResources is set to false when PodLevelResources feature is enabled.
SkipPodLevelResources: !feature.DefaultFeatureGate.Enabled(features.PodLevelResources),
}
+1 -3
View File
@@ -20,9 +20,7 @@ import (
v1 "k8s.io/api/core/v1"
apiequality "k8s.io/apimachinery/pkg/api/equality"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apiserver/pkg/util/feature"
podutil "k8s.io/kubernetes/pkg/api/v1/pod"
"k8s.io/kubernetes/pkg/features"
"k8s.io/kubernetes/pkg/quota/v1/evaluator/core"
"k8s.io/utils/clock"
)
@@ -35,7 +33,7 @@ func DefaultUpdateFilter() func(resource schema.GroupVersionResource, oldObj, ne
oldPod := oldObj.(*v1.Pod)
newPod := newObj.(*v1.Pod)
// when Resources changed
if feature.DefaultFeatureGate.Enabled(features.InPlacePodVerticalScaling) && hasResourcesChanged(oldPod, newPod) {
if hasResourcesChanged(oldPod, newPod) {
return true
}
+1 -31
View File
@@ -4932,34 +4932,7 @@ func TestStatusPrepareForUpdate(t *testing.T) {
},
},
{
description: "drop disabled status fields/InPlacePodVerticalScaling=false",
features: map[featuregate.Feature]bool{
features.InPlacePodVerticalScaling: false,
},
oldPod: &api.Pod{
ObjectMeta: metav1.ObjectMeta{Name: "pod"},
Status: api.PodStatus{},
},
newPod: &api.Pod{
ObjectMeta: metav1.ObjectMeta{Name: "pod"},
Status: api.PodStatus{
ContainerStatuses: []api.ContainerStatus{
{Resources: &api.ResourceRequirements{}},
},
},
},
expected: &api.Pod{
ObjectMeta: metav1.ObjectMeta{Name: "pod"},
Status: api.PodStatus{
ContainerStatuses: []api.ContainerStatus{{}},
},
},
},
{
description: "drop disabled status fields/InPlacePodVerticalScaling=true",
features: map[featuregate.Feature]bool{
features.InPlacePodVerticalScaling: true,
},
description: "preserve container status resources",
oldPod: &api.Pod{
ObjectMeta: metav1.ObjectMeta{Name: "pod"},
Status: api.PodStatus{},
@@ -5500,9 +5473,6 @@ func TestStatusPrepareForUpdate(t *testing.T) {
for _, tc := range testCases {
t.Run(tc.description, func(t *testing.T) {
if v, ok := tc.features[features.InPlacePodVerticalScaling]; ok && !v {
featuregatetesting.SetFeatureGateEmulationVersionDuringTest(t, utilfeature.DefaultFeatureGate, version.MustParse("1.34"))
}
featuregatetesting.SetFeatureGatesDuringTest(t, utilfeature.DefaultFeatureGate, tc.features)
StatusStrategy.PrepareForUpdate(genericapirequest.NewContext(), tc.newPod, tc.oldPod)
if !cmp.Equal(tc.expected, tc.newPod) {
+1 -5
View File
@@ -33,7 +33,6 @@ import (
"k8s.io/apiserver/pkg/registry/rest"
genericapiserver "k8s.io/apiserver/pkg/server"
serverstorage "k8s.io/apiserver/pkg/server/storage"
utilfeature "k8s.io/apiserver/pkg/util/feature"
"k8s.io/apiserver/pkg/util/proxy"
"k8s.io/client-go/kubernetes"
admissionregistrationv1client "k8s.io/client-go/kubernetes/typed/admissionregistration/v1"
@@ -42,7 +41,6 @@ import (
"k8s.io/kubernetes/pkg/api/legacyscheme"
api "k8s.io/kubernetes/pkg/apis/core"
"k8s.io/kubernetes/pkg/cluster/ports"
"k8s.io/kubernetes/pkg/features"
kubeletclient "k8s.io/kubernetes/pkg/kubelet/client"
"k8s.io/kubernetes/pkg/registry/core/componentstatus"
endpointsstore "k8s.io/kubernetes/pkg/registry/core/endpoint/storage"
@@ -245,9 +243,7 @@ func (p *legacyProvider) NewRESTStorage(apiResourceConfigSource serverstorage.AP
storage[resource+"/eviction"] = podStorage.Eviction
}
storage[resource+"/ephemeralcontainers"] = podStorage.EphemeralContainers
if utilfeature.DefaultFeatureGate.Enabled(features.InPlacePodVerticalScaling) {
storage[resource+"/resize"] = podStorage.Resize
}
storage[resource+"/resize"] = podStorage.Resize
}
if resource := "bindings"; apiResourceConfigSource.ResourceEnabled(corev1.SchemeGroupVersion.WithResource(resource)) {
storage[resource] = podStorage.LegacyBinding
+2 -2
View File
@@ -107,7 +107,7 @@ type podChangeExtractor func(newPod *v1.Pod, oldPod *v1.Pod) fwk.ActionType
// extractPodScaleDown interprets the update of a pod and returns PodRequestScaledDown event if any pod's resource request(s) is scaled down.
func extractPodScaleDown(newPod, oldPod *v1.Pod) fwk.ActionType {
opt := resource.PodResourcesOptions{
UseStatusResources: utilfeature.DefaultFeatureGate.Enabled(features.InPlacePodVerticalScaling),
UseStatusResources: true,
InPlacePodLevelResourcesVerticalScalingEnabled: utilfeature.DefaultFeatureGate.Enabled(features.InPlacePodLevelResourcesVerticalScaling),
}
newPodRequests := resource.PodRequests(newPod, opt)
@@ -132,7 +132,7 @@ func extractPodScaleDown(newPod, oldPod *v1.Pod) fwk.ActionType {
// extractPodScaleUp interprets the update of a pod and returns PodRequestScaledUp event if any pod's resource request(s) is scaled up.
func extractPodScaleUp(newPod, oldPod *v1.Pod) fwk.ActionType {
opt := resource.PodResourcesOptions{
UseStatusResources: utilfeature.DefaultFeatureGate.Enabled(features.InPlacePodVerticalScaling),
UseStatusResources: true,
InPlacePodLevelResourcesVerticalScalingEnabled: utilfeature.DefaultFeatureGate.Enabled(features.InPlacePodLevelResourcesVerticalScaling),
}
newPodRequests := resource.PodRequests(newPod, opt)
@@ -45,7 +45,6 @@ type Features struct {
EnableVolumeLimitScaling bool
EnableNodeInclusionPolicyInPodTopologySpread bool
EnableMatchLabelKeysInPodTopologySpread bool
EnableInPlacePodVerticalScaling bool
EnableAsyncPreemption bool
EnablePodLevelResources bool
EnableStorageCapacityScoring bool
@@ -81,7 +80,6 @@ func NewSchedulerFeaturesFromGates(featureGate featuregate.FeatureGate) Features
EnableVolumeLimitScaling: featureGate.Enabled(features.VolumeLimitScaling),
EnableNodeInclusionPolicyInPodTopologySpread: featureGate.Enabled(features.NodeInclusionPolicyInPodTopologySpread),
EnableMatchLabelKeysInPodTopologySpread: featureGate.Enabled(features.MatchLabelKeysInPodTopologySpread),
EnableInPlacePodVerticalScaling: featureGate.Enabled(features.InPlacePodVerticalScaling),
EnableAsyncPreemption: featureGate.Enabled(features.SchedulerAsyncPreemption),
EnablePodLevelResources: featureGate.Enabled(features.PodLevelResources),
EnableDRAPartitionableDevices: featureGate.Enabled(features.DRAPartitionableDevices),
@@ -88,7 +88,7 @@ func ShouldDelegateResourceToDRA(rName v1.ResourceName, nodeInfo fwk.NodeInfo, d
//
// Result: CPU: 3, Memory: 3G
func ComputePodResourceRequest(pod *v1.Pod, opts ResourceRequestsOptions) framework.Resource {
// pod hasn't scheduled yet so we don't need to worry about InPlacePodVerticalScalingEnabled
// pod hasn't scheduled yet so we don't need to worry about UseStatusResources
reqs := resource.PodRequests(pod, resource.PodResourcesOptions{
// SkipPodLevelResources is set to false when PodLevelResources feature is enabled.
SkipPodLevelResources: !opts.EnablePodLevelResources,
@@ -189,13 +189,12 @@ func NewBalancedAllocation(_ context.Context, baArgs runtime.Object, h fwk.Handl
return &BalancedAllocation{
handle: h,
resourceAllocationScorer: resourceAllocationScorer{
Name: BalancedAllocationName,
enableInPlacePodVerticalScaling: fts.EnableInPlacePodVerticalScaling,
enablePodLevelResources: fts.EnablePodLevelResources,
enableDRAExtendedResource: fts.EnableDRAExtendedResource,
scorer: balancedResourceScorer,
useRequested: true,
resources: args.Resources,
Name: BalancedAllocationName,
enablePodLevelResources: fts.EnablePodLevelResources,
enableDRAExtendedResource: fts.EnableDRAExtendedResource,
scorer: balancedResourceScorer,
useRequested: true,
resources: args.Resources,
enableInPlacePodLevelResourcesVerticalScaling: fts.EnableInPlacePodLevelResourcesVerticalScaling,
draManager: h.SharedDRAManager(),
},
@@ -92,7 +92,6 @@ var nodeResourceStrategyTypeMap = map[config.ScoringStrategyType]scorer{
type Fit struct {
ignoredResources sets.Set[string]
ignoredResourceGroups sets.Set[string]
enableInPlacePodVerticalScaling bool
enablePodLevelResources bool
enableDRAExtendedResource bool
enableInPlacePodLevelResourcesVerticalScaling bool
@@ -203,7 +202,6 @@ func getScorer(strategy *config.ScoringStrategy, fts feature.Features) (*resourc
return nil, fmt.Errorf("scoring strategy %s is not supported", strategy.Type)
}
scorer := scorerFactory(strategy)
scorer.enableInPlacePodVerticalScaling = fts.EnableInPlacePodVerticalScaling
scorer.enablePodLevelResources = fts.EnablePodLevelResources
scorer.enableInPlacePodLevelResourcesVerticalScaling = fts.EnableInPlacePodLevelResourcesVerticalScaling
return scorer, nil
@@ -236,12 +234,11 @@ func NewFit(_ context.Context, plArgs runtime.Object, h fwk.Handle, fts feature.
}
pl := &Fit{
ignoredResources: sets.New(args.IgnoredResources...),
ignoredResourceGroups: sets.New(args.IgnoredResourceGroups...),
enableInPlacePodVerticalScaling: fts.EnableInPlacePodVerticalScaling,
handle: h,
enablePodLevelResources: fts.EnablePodLevelResources,
enableDRAExtendedResource: fts.EnableDRAExtendedResource,
ignoredResources: sets.New(args.IgnoredResources...),
ignoredResourceGroups: sets.New(args.IgnoredResourceGroups...),
handle: h,
enablePodLevelResources: fts.EnablePodLevelResources,
enableDRAExtendedResource: fts.EnableDRAExtendedResource,
enableInPlacePodLevelResourcesVerticalScaling: fts.EnableInPlacePodLevelResourcesVerticalScaling,
@@ -313,19 +310,14 @@ func (f *Fit) EventsToRegister(_ context.Context) ([]fwk.ClusterEventWithHint, e
events := []fwk.ClusterEventWithHint{
{Event: fwk.ClusterEvent{Resource: fwk.AssignedPod, ActionType: fwk.Delete}, QueueingHintFn: f.isSchedulableAfterAssignedPodDelete},
{Event: fwk.ClusterEvent{Resource: fwk.Node, ActionType: fwk.Add | fwk.UpdateNodeAllocatable}, QueueingHintFn: f.isSchedulableAfterNodeChange},
{Event: fwk.ClusterEvent{Resource: fwk.AssignedPod, ActionType: fwk.UpdatePodScaleDown}, QueueingHintFn: f.isSchedulableAfterAssignedPodScaleDown},
{Event: fwk.ClusterEvent{Resource: fwk.TargetPod, ActionType: fwk.UpdatePodScaleDown}, QueueingHintFn: f.isSchedulableAfterTargetPodScaleDown},
}
if f.enableDRAExtendedResource {
events = append(events,
// A pod might be waiting for an exteneded resurce from a class to get created or modified.
fwk.ClusterEventWithHint{Event: fwk.ClusterEvent{Resource: fwk.DeviceClass, ActionType: fwk.Add | fwk.Update}, QueueingHintFn: f.isSchedulableAfterDeviceClassEvent})
}
if f.enableInPlacePodVerticalScaling {
// If InPlacePodVerticalScaling (KEP 1287) is enabled, then UpdatePodScaleDown event should be registered
// for this plugin since a Pod update may free up resources that make other Pods schedulable.
events = append(events,
fwk.ClusterEventWithHint{Event: fwk.ClusterEvent{Resource: fwk.AssignedPod, ActionType: fwk.UpdatePodScaleDown}, QueueingHintFn: f.isSchedulableAfterAssignedPodScaleDown},
fwk.ClusterEventWithHint{Event: fwk.ClusterEvent{Resource: fwk.TargetPod, ActionType: fwk.UpdatePodScaleDown}, QueueingHintFn: f.isSchedulableAfterTargetPodScaleDown})
}
if f.enableInPlacePodVerticalScalingSchedulerPreemption {
events = append(events,
fwk.ClusterEventWithHint{Event: fwk.ClusterEvent{Resource: fwk.AssignedPod, ActionType: fwk.UpdatePodScaleUp}, QueueingHintFn: f.isSchedulableAfterAssignedPodScaleUp},
@@ -436,7 +428,7 @@ func (f *Fit) haveEnoughRelevantResourcesDecreased(targetPod *v1.Pod, originalPo
// the other pod was scheduled, so modification or deletion may free up some resources.
originalMaxResourceReq, modifiedMaxResourceReq := &framework.Resource{}, &framework.Resource{}
opts := resource.PodResourcesOptions{UseStatusResources: f.enableInPlacePodVerticalScaling, InPlacePodLevelResourcesVerticalScalingEnabled: f.enableInPlacePodLevelResourcesVerticalScaling}
opts := resource.PodResourcesOptions{UseStatusResources: true, InPlacePodLevelResourcesVerticalScalingEnabled: f.enableInPlacePodLevelResourcesVerticalScaling}
originalMaxResourceReq.SetMaxResource(resource.PodRequests(originalPod, opts))
modifiedMaxResourceReq.SetMaxResource(resource.PodRequests(modifiedPod, opts))
@@ -1459,14 +1459,12 @@ func BenchmarkTestFitScore(b *testing.B) {
func TestEventsToRegister(t *testing.T) {
tests := []struct {
name string
enableInPlacePodVerticalScaling bool
enableDRAExtendedResource bool
expectedClusterEvents []fwk.ClusterEventWithHint
name string
enableDRAExtendedResource bool
expectedClusterEvents []fwk.ClusterEventWithHint
}{
{
name: "Register events with InPlacePodVerticalScaling feature enabled",
enableInPlacePodVerticalScaling: true,
name: "Register events with default features",
expectedClusterEvents: []fwk.ClusterEventWithHint{
{Event: fwk.ClusterEvent{Resource: fwk.AssignedPod, ActionType: fwk.Delete}},
{Event: fwk.ClusterEvent{Resource: fwk.Node, ActionType: fwk.Add | fwk.UpdateNodeAllocatable}},
@@ -1474,19 +1472,14 @@ func TestEventsToRegister(t *testing.T) {
{Event: fwk.ClusterEvent{Resource: fwk.TargetPod, ActionType: fwk.UpdatePodScaleDown}},
},
},
{
name: "Register events with default features",
expectedClusterEvents: []fwk.ClusterEventWithHint{
{Event: fwk.ClusterEvent{Resource: fwk.AssignedPod, ActionType: fwk.Delete}},
{Event: fwk.ClusterEvent{Resource: fwk.Node, ActionType: fwk.Add | fwk.UpdateNodeAllocatable}},
},
},
{
name: "Register events with DRAExtendedResource feature enabled",
enableDRAExtendedResource: true,
expectedClusterEvents: []fwk.ClusterEventWithHint{
{Event: fwk.ClusterEvent{Resource: fwk.AssignedPod, ActionType: fwk.Delete}},
{Event: fwk.ClusterEvent{Resource: fwk.Node, ActionType: fwk.Add | fwk.UpdateNodeAllocatable}},
{Event: fwk.ClusterEvent{Resource: fwk.AssignedPod, ActionType: fwk.UpdatePodScaleDown}},
{Event: fwk.ClusterEvent{Resource: fwk.TargetPod, ActionType: fwk.UpdatePodScaleDown}},
{Event: fwk.ClusterEvent{Resource: fwk.DeviceClass, ActionType: fwk.Add | fwk.Update}},
},
},
@@ -1494,7 +1487,7 @@ func TestEventsToRegister(t *testing.T) {
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
fp := &Fit{enableInPlacePodVerticalScaling: test.enableInPlacePodVerticalScaling, enableDRAExtendedResource: test.enableDRAExtendedResource}
fp := &Fit{enableDRAExtendedResource: test.enableDRAExtendedResource}
_, ctx := ktesting.NewTestContext(t)
actualClusterEvents, err := fp.EventsToRegister(ctx)
if err != nil {
@@ -1594,9 +1587,7 @@ func Test_isSchedulableAfterTargetPodScaleDown(t *testing.T) {
for name, tc := range testcases {
t.Run(name, func(t *testing.T) {
logger, ctx := ktesting.NewTestContext(t)
p, err := NewFit(ctx, &config.NodeResourcesFitArgs{ScoringStrategy: defaultScoringStrategy}, nil, plfeature.Features{
EnableInPlacePodVerticalScaling: true,
})
p, err := NewFit(ctx, &config.NodeResourcesFitArgs{ScoringStrategy: defaultScoringStrategy}, nil, plfeature.Features{})
if err != nil {
t.Fatal(err)
}
@@ -1678,7 +1669,6 @@ func Test_isSchedulableAfterAssignedPodScaleDown(t *testing.T) {
t.Run(name, func(t *testing.T) {
logger, ctx := ktesting.NewTestContext(t)
p, err := NewFit(ctx, &config.NodeResourcesFitArgs{ScoringStrategy: defaultScoringStrategy}, nil, plfeature.Features{
EnableInPlacePodVerticalScaling: true,
EnableInPlacePodVerticalScalingSchedulerPreemption: true,
})
if err != nil {
@@ -3042,7 +3032,7 @@ func TestScorePlacement_Resources(t *testing.T) {
expectedScore: 30,
},
{
name: "Ignores container status resources when InPlacePodVerticalScaling is disabled",
name: "Honors container status resources",
nodes: []*v1.Node{
st.MakeNode().Name("node1").Capacity(cpuAndMemory("5000m", "5000")).Obj(),
st.MakeNode().Name("node2").Capacity(cpuAndMemory("5000m", "5000")).Obj(),
@@ -3056,25 +3046,6 @@ func TestScorePlacement_Resources(t *testing.T) {
{Name: "cpu", Weight: 1},
{Name: "memory", Weight: 1},
},
features: plfeature.Features{EnableInPlacePodVerticalScaling: false},
expectedScore: 10,
},
{
name: "Honors container status resources when InPlacePodVerticalScaling is enabled",
nodes: []*v1.Node{
st.MakeNode().Name("node1").Capacity(cpuAndMemory("5000m", "5000")).Obj(),
st.MakeNode().Name("node2").Capacity(cpuAndMemory("5000m", "5000")).Obj(),
},
placementNodeNames: []string{"node1", "node2"},
podGroupPods: []*v1.Pod{
withContainerStatusResources(st.MakePod().UID("foo").Req(cpuAndMemory("1000m", "1000")).Obj(), cpuAndMemory("4000m", "4000")),
},
podGroupAssignments: map[types.UID]string{"foo": "node1"},
resourceSpec: []config.ResourceSpec{
{Name: "cpu", Weight: 1},
{Name: "memory", Weight: 1},
},
features: plfeature.Features{EnableInPlacePodVerticalScaling: true},
expectedScore: 40,
},
{
@@ -3097,7 +3068,6 @@ func TestScorePlacement_Resources(t *testing.T) {
},
features: plfeature.Features{
EnablePodLevelResources: true,
EnableInPlacePodVerticalScaling: true,
EnableInPlacePodLevelResourcesVerticalScaling: false,
},
expectedScore: 10,
@@ -3122,7 +3092,6 @@ func TestScorePlacement_Resources(t *testing.T) {
},
features: plfeature.Features{
EnablePodLevelResources: true,
EnableInPlacePodVerticalScaling: true,
EnableInPlacePodLevelResourcesVerticalScaling: true,
},
expectedScore: 40,
@@ -3880,25 +3849,11 @@ func TestFitScoreFeatureGates(t *testing.T) {
},
},
{
name: "InPlacePodVerticalScaling disabled ignores container status resources",
name: "Uses container status resources",
pod: withContainerStatusResources(
st.MakePod().Req(cpuAndMemory("1000m", "400Mi")).Obj(),
cpuAndMemory("2000m", "800Mi"),
),
features: plfeature.Features{EnableInPlacePodVerticalScaling: false},
expectedScores: map[config.ScoringStrategyType]fwk.NodeScoreList{
config.LeastAllocated: {{Name: "node1", Score: 80}, {Name: "node2", Score: 85}},
config.MostAllocated: {{Name: "node1", Score: 20}, {Name: "node2", Score: 15}},
config.RequestedToCapacityRatio: {{Name: "node1", Score: 40}, {Name: "node2", Score: 30}},
},
},
{
name: "InPlacePodVerticalScaling enabled uses container status resources",
pod: withContainerStatusResources(
st.MakePod().Req(cpuAndMemory("1000m", "400Mi")).Obj(),
cpuAndMemory("2000m", "800Mi"),
),
features: plfeature.Features{EnableInPlacePodVerticalScaling: true},
expectedScores: map[config.ScoringStrategyType]fwk.NodeScoreList{
config.LeastAllocated: {{Name: "node1", Score: 60}, {Name: "node2", Score: 70}},
config.MostAllocated: {{Name: "node1", Score: 40}, {Name: "node2", Score: 30}},
@@ -3916,7 +3871,6 @@ func TestFitScoreFeatureGates(t *testing.T) {
),
features: plfeature.Features{
EnablePodLevelResources: true,
EnableInPlacePodVerticalScaling: true,
EnableInPlacePodLevelResourcesVerticalScaling: false,
},
expectedScores: map[config.ScoringStrategyType]fwk.NodeScoreList{
@@ -3936,7 +3890,6 @@ func TestFitScoreFeatureGates(t *testing.T) {
),
features: plfeature.Features{
EnablePodLevelResources: true,
EnableInPlacePodVerticalScaling: true,
EnableInPlacePodLevelResourcesVerticalScaling: true,
},
// Effective request: cpu=3000m, memory=1200Mi -> node1: 60% cpu, 60% mem; node2: 30% cpu, 60% mem
@@ -53,7 +53,6 @@ type DRACaches struct {
// resourceAllocationScorer contains information to calculate resource allocation score.
type resourceAllocationScorer struct {
Name string
enableInPlacePodVerticalScaling bool
enablePodLevelResources bool
enableDRAExtendedResource bool
enableInPlacePodLevelResourcesVerticalScaling bool
@@ -238,7 +237,7 @@ func (r *resourceAllocationScorer) calculateResourceAllocatableRequest(
func (r *resourceAllocationScorer) calculatePodResourceRequest(pod *v1.Pod, resourceName v1.ResourceName) int64 {
opts := resourcehelper.PodResourcesOptions{
UseStatusResources: r.enableInPlacePodVerticalScaling,
UseStatusResources: true,
InPlacePodLevelResourcesVerticalScalingEnabled: r.enableInPlacePodLevelResourcesVerticalScaling,
// SkipPodLevelResources is set to false when PodLevelResources feature is enabled.
SkipPodLevelResources: !r.enablePodLevelResources,
+3 -5
View File
@@ -1283,8 +1283,7 @@ type PodInfo struct {
PreferredAffinityTerms []fwk.WeightedAffinityTerm
PreferredAntiAffinityTerms []fwk.WeightedAffinityTerm
// cachedResource contains precomputed resources for Pod (podResource).
// The value can change only if InPlacePodVerticalScaling is enabled.
// In that case, the whole PodInfo object is recreated (for assigned pods in cache).
// When a pod is resized in-place, the whole PodInfo object is recreated (for assigned pods in cache).
// cachedResource contains a podResource, computed when adding a scheduled pod to NodeInfo.
// When removing a pod from a NodeInfo, i.e. finding victims for preemption or removing a pod from a cluster,
// cachedResource is used instead, what provides a noticeable performance boost.
@@ -1355,13 +1354,12 @@ func (pi *PodInfo) CalculateResource() fwk.PodResource {
if pi.cachedResource != nil {
return *pi.cachedResource
}
inPlacePodVerticalScalingEnabled := utilfeature.DefaultFeatureGate.Enabled(features.InPlacePodVerticalScaling)
podLevelResourcesEnabled := utilfeature.DefaultFeatureGate.Enabled(features.PodLevelResources)
inPlacePodLevelResourcesVerticalScalingEnabled := utilfeature.DefaultMutableFeatureGate.Enabled(features.InPlacePodLevelResourcesVerticalScaling)
nodeAllocatableResourcesDRAEnabled := utilfeature.DefaultFeatureGate.Enabled(features.DRANodeAllocatableResources)
requests := resourcehelper.PodRequests(pi.Pod, resourcehelper.PodResourcesOptions{
UseStatusResources: inPlacePodVerticalScalingEnabled,
UseStatusResources: true,
InPlacePodLevelResourcesVerticalScalingEnabled: inPlacePodLevelResourcesVerticalScalingEnabled,
// SkipPodLevelResources is set to false when PodLevelResources feature is enabled.
SkipPodLevelResources: !podLevelResourcesEnabled,
@@ -1372,7 +1370,7 @@ func (pi *PodInfo) CalculateResource() fwk.PodResource {
non0Requests := requests
if len(nonMissingContainerRequests) > 0 {
non0Requests = resourcehelper.PodRequests(pi.Pod, resourcehelper.PodResourcesOptions{
UseStatusResources: inPlacePodVerticalScalingEnabled,
UseStatusResources: true,
InPlacePodLevelResourcesVerticalScalingEnabled: inPlacePodLevelResourcesVerticalScalingEnabled,
// SkipPodLevelResources is set to false when PodLevelResources feature is enabled.
SkipPodLevelResources: !podLevelResourcesEnabled,
-1
View File
@@ -3159,7 +3159,6 @@ func TestPodInfoCalculateResources(t *testing.T) {
}
func TestCalculatePodResourcesWithResize(t *testing.T) {
featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.InPlacePodVerticalScaling, true)
featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.NodeDeclaredFeatures, true)
featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.InPlacePodLevelResourcesVerticalScaling, true)
-1
View File
@@ -1404,7 +1404,6 @@ func TestHandleSchedulingFailureSkipsRecreatedPod(t *testing.T) {
}
func TestHandleSchedulingFailureForDeferredResizePod(t *testing.T) {
featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.InPlacePodVerticalScaling, true)
featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.InPlacePodVerticalScalingSchedulerPreemption, true)
logger, ctx := ktesting.NewTestContext(t)
+7 -54
View File
@@ -20,7 +20,6 @@ import (
"context"
"errors"
"fmt"
"slices"
"sort"
"strings"
"sync"
@@ -36,7 +35,6 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/apimachinery/pkg/util/version"
"k8s.io/apiserver/pkg/util/feature"
clientfeatures "k8s.io/client-go/features"
clientfeaturestesting "k8s.io/client-go/features/testing"
@@ -925,11 +923,10 @@ func Test_buildQueueingHintMap(t *testing.T) {
// It verifies that a given set of plugins is correctly registered for a specific set of cluster events.
func Test_UnionedGVKs(t *testing.T) {
tests := []struct {
name string
plugins schedulerapi.PluginSet
want map[fwk.EventResource]fwk.ActionType
enableInPlacePodVerticalScaling bool
enableGenericWorkload bool
name string
plugins schedulerapi.PluginSet
want map[fwk.EventResource]fwk.ActionType
enableGenericWorkload bool
}{
{
name: "filter without EnqueueExtensions plugin",
@@ -981,8 +978,7 @@ func Test_UnionedGVKs(t *testing.T) {
fwk.DeviceClass: fwk.All,
fwk.PodGroup: fwk.All,
},
enableGenericWorkload: true,
enableInPlacePodVerticalScaling: true,
enableGenericWorkload: true,
},
{
name: "node plugin",
@@ -1044,25 +1040,6 @@ func Test_UnionedGVKs(t *testing.T) {
},
want: map[fwk.EventResource]fwk.ActionType{},
},
{
name: "plugins with default profile (InPlacePodVerticalScaling disabled)",
plugins: schedulerapi.PluginSet{Enabled: defaults.PluginsV1.MultiPoint.Enabled},
want: map[fwk.EventResource]fwk.ActionType{
fwk.AssignedPod: fwk.Add | fwk.UpdatePodLabel | fwk.Delete,
fwk.TargetPod: fwk.UpdatePodLabel | fwk.UpdatePodGeneratedResourceClaim | fwk.UpdatePodToleration | fwk.UpdatePodSchedulingGatesEliminated,
fwk.Node: fwk.Add | fwk.UpdateNodeAllocatable | fwk.UpdateNodeLabel | fwk.UpdateNodeTaint | fwk.Delete,
fwk.CSINode: fwk.All - fwk.Delete,
fwk.CSIDriver: fwk.Update,
fwk.CSIStorageCapacity: fwk.All - fwk.Delete,
fwk.PersistentVolume: fwk.All - fwk.Delete,
fwk.PersistentVolumeClaim: fwk.All - fwk.Delete,
fwk.StorageClass: fwk.All - fwk.Delete,
fwk.VolumeAttachment: fwk.Delete,
fwk.DeviceClass: fwk.All - fwk.Delete,
fwk.ResourceClaim: fwk.All,
fwk.ResourceSlice: fwk.All - fwk.Delete,
},
},
{
name: "plugins with default profile",
plugins: defaults.PluginsV1.MultiPoint,
@@ -1083,7 +1060,6 @@ func Test_UnionedGVKs(t *testing.T) {
fwk.ResourceClaim: fwk.All,
fwk.ResourceSlice: fwk.All - fwk.Delete,
},
enableInPlacePodVerticalScaling: true,
},
{
name: "plugin registering to a specific pod resource 'AssignedPod'",
@@ -1119,38 +1095,15 @@ func Test_UnionedGVKs(t *testing.T) {
fwk.ResourceSlice: fwk.All - fwk.Delete,
fwk.PodGroup: fwk.Add | fwk.Update,
},
enableGenericWorkload: true,
enableInPlacePodVerticalScaling: true,
enableGenericWorkload: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
pluginConfig := defaults.PluginConfigsV1
if !tt.enableInPlacePodVerticalScaling {
// In place pod resize GA'd in 1.35. Set emulation version to 1.34 for tests that do not have the flag set
featuregatetesting.SetFeatureGateEmulationVersionDuringTest(t, feature.DefaultFeatureGate, version.MustParse("1.34"))
// DRADeviceBindingConditions is alpha in 1.34 (disabled by default).
// Strip BindingTimeout from DynamicResources args to avoid validation failure.
// StorageCapacityScoring is alpha in 1.34 (disabled by default).
// Strip Shape from VolumeBinding args to avoid validation failure.
// BindTimeoutSeconds: 600 is the default value of VolumeBindingArgs when StorageCapacityScoring is disabled.
pluginConfig = slices.Clone(pluginConfig)
for i := range pluginConfig {
switch pluginConfig[i].Name {
case "DynamicResources":
pluginConfig[i].Args = &schedulerapi.DynamicResourcesArgs{}
case "VolumeBinding":
pluginConfig[i].Args = &schedulerapi.VolumeBindingArgs{BindTimeoutSeconds: 600}
}
}
} else {
featuregatetesting.SetFeatureGatesDuringTest(t, feature.DefaultFeatureGate, featuregatetesting.FeatureOverrides{
features.GenericWorkload: tt.enableGenericWorkload,
})
}
featuregatetesting.SetFeatureGatesDuringTest(t, feature.DefaultFeatureGate, featuregatetesting.FeatureOverrides{
features.InPlacePodVerticalScaling: tt.enableInPlacePodVerticalScaling,
features.GenericWorkload: tt.enableGenericWorkload,
})
_, ctx := ktesting.NewTestContext(t)
@@ -821,7 +821,6 @@ func TestLimitRangerAllowPodResize(t *testing.T) {
informerFactory.Start(wait.NeverStop)
testPod := validPod("testPod", 1, api.ResourceRequirements{})
featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.InPlacePodVerticalScaling, true)
err = handler.Validate(context.TODO(), admission.NewAttributesRecord(&testPod, nil, api.Kind("Pod").WithVersion("version"), limitRange.Namespace, "testPod", api.Resource("pods").WithVersion("version"), "resize", admission.Update, &metav1.UpdateOptions{}, false, nil), nil)
if err == nil {
t.Errorf("expect error, but got nil")
@@ -833,7 +832,6 @@ func TestLimitRangerAllowPodResize(t *testing.T) {
// already holds is valid by definition and must not be rejected by a constraint
// it no longer satisfies; a value the resize changes is checked as on create.
func TestLimitRangerValidatePodResize(t *testing.T) {
featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.InPlacePodVerticalScaling, true)
featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.PodLevelResources, true)
cpu := func(request, limit string) api.ResourceRequirements {
+1 -14
View File
@@ -30,11 +30,9 @@ import (
genericadmissioninitializer "k8s.io/apiserver/pkg/admission/initializer"
"k8s.io/client-go/informers"
corev1listers "k8s.io/client-go/listers/core/v1"
"k8s.io/component-base/featuregate"
"k8s.io/component-helpers/resource"
"k8s.io/kubernetes/pkg/apis/core"
v1 "k8s.io/kubernetes/pkg/apis/core/v1"
"k8s.io/kubernetes/pkg/features"
)
const (
@@ -55,8 +53,7 @@ func Register(plugins *admission.Plugins) {
// Plugin is an admission controller that validates pod resize requests against the node's capabilities.
type Plugin struct {
*admission.Handler
nodeLister corev1listers.NodeLister
inPlacePodVerticalScalingEnabled bool
nodeLister corev1listers.NodeLister
}
var _ admission.ValidationInterface = &Plugin{}
@@ -76,11 +73,6 @@ func (p *Plugin) SetExternalKubeInformerFactory(f informers.SharedInformerFactor
p.SetReadyFunc(nodeInformer.Informer().HasSynced)
}
// InspectFeatureGates sets the feature gates for the plugin.
func (p *Plugin) InspectFeatureGates(featureGates featuregate.FeatureGate) {
p.inPlacePodVerticalScalingEnabled = featureGates.Enabled(features.InPlacePodVerticalScaling)
}
// ValidateInitialization ensures that the plugin is properly initialized.
func (p *Plugin) ValidateInitialization() error {
if p.nodeLister == nil {
@@ -92,11 +84,6 @@ func (p *Plugin) ValidateInitialization() error {
// Validate is the core of the admission controller logic.
func (p *Plugin) Validate(ctx context.Context, a admission.Attributes, o admission.ObjectInterfaces) error {
// Ignore if the feature gate is not enabled.
if !p.inPlacePodVerticalScalingEnabled {
return nil
}
if a.GetOperation() != admission.Update {
return nil
}
@@ -255,7 +255,6 @@ func TestResizeAdmission(t *testing.T) {
plugin, err := NewPlugin()
require.NoError(t, err)
plugin.SetExternalKubeInformerFactory(informerFactory)
plugin.inPlacePodVerticalScalingEnabled = true
stopCh := make(chan struct{})
defer close(stopCh)
@@ -801,7 +801,6 @@ message Container {
// resizePolicy is the resize policy for the container.
// This field cannot be set on ephemeral containers.
// +featureGate=InPlacePodVerticalScaling
// +optional
// +listType=atomic
repeated ContainerResizePolicy resizePolicy = 23;
@@ -1176,7 +1175,6 @@ message ContainerStatus {
// resources represents the compute resource requests and limits that have been successfully
// enacted on the running container after it has been started or has been successfully resized.
// +featureGate=InPlacePodVerticalScaling
// +optional
optional ResourceRequirements resources = 11;
@@ -1630,7 +1628,6 @@ message EphemeralContainerCommon {
optional ResourceRequirements resources = 8;
// resizePolicy is the resources resize policy for the container.
// +featureGate=InPlacePodVerticalScaling
// +optional
// +listType=atomic
repeated ContainerResizePolicy resizePolicy = 23;
@@ -5275,7 +5272,6 @@ message PodStatus {
// Deprecated: Resize status is moved to two pod conditions PodResizePending and PodResizeInProgress.
// PodResizePending will track states where the spec has been resized, but the Kubelet has not yet allocated the resources.
// PodResizeInProgress will track in-progress resizes, and should be present whenever allocated resources != acknowledged resources.
// +featureGate=InPlacePodVerticalScaling
// +optional
optional string resize = 14;
-4
View File
@@ -3327,7 +3327,6 @@ type Container struct {
Resources ResourceRequirements `json:"resources,omitempty" protobuf:"bytes,8,opt,name=resources"`
// resizePolicy is the resize policy for the container.
// This field cannot be set on ephemeral containers.
// +featureGate=InPlacePodVerticalScaling
// +optional
// +listType=atomic
ResizePolicy []ContainerResizePolicy `json:"resizePolicy,omitempty" protobuf:"bytes,23,rep,name=resizePolicy"`
@@ -3721,7 +3720,6 @@ type ContainerStatus struct {
AllocatedResources ResourceList `json:"allocatedResources,omitempty" protobuf:"bytes,10,rep,name=allocatedResources,casttype=ResourceList,castkey=ResourceName"`
// resources represents the compute resource requests and limits that have been successfully
// enacted on the running container after it has been started or has been successfully resized.
// +featureGate=InPlacePodVerticalScaling
// +optional
Resources *ResourceRequirements `json:"resources,omitempty" protobuf:"bytes,11,opt,name=resources"`
// volumeMounts represents the status of volume mounts.
@@ -5656,7 +5654,6 @@ type EphemeralContainerCommon struct {
// +optional
Resources ResourceRequirements `json:"resources,omitempty" protobuf:"bytes,8,opt,name=resources"`
// resizePolicy is the resources resize policy for the container.
// +featureGate=InPlacePodVerticalScaling
// +optional
// +listType=atomic
ResizePolicy []ContainerResizePolicy `json:"resizePolicy,omitempty" protobuf:"bytes,23,rep,name=resizePolicy"`
@@ -5958,7 +5955,6 @@ type PodStatus struct {
// Deprecated: Resize status is moved to two pod conditions PodResizePending and PodResizeInProgress.
// PodResizePending will track states where the spec has been resized, but the Kubelet has not yet allocated the resources.
// PodResizeInProgress will track in-progress resizes, and should be present whenever allocated resources != acknowledged resources.
// +featureGate=InPlacePodVerticalScaling
// +optional
Resize PodResizeStatus `json:"resize,omitempty" protobuf:"bytes,14,opt,name=resize,casttype=PodResizeStatus"`
@@ -41,8 +41,7 @@ type PodResourcesOptions struct {
// functions. All existing values in Reuse will be lost.
Reuse v1.ResourceList
// UseStatusResources indicates whether resources reported by the PodStatus should be considered
// when evaluating the pod resources. This MUST be false if the InPlacePodVerticalScaling
// feature is not enabled.
// when evaluating the pod resources.
UseStatusResources bool
// InPlacePodLevelResourcesVerticalScalingEnabled indicates whether resources reported by the
// PodStatus should be considered when evaluating the pod resources.
@@ -313,7 +312,7 @@ func AggregateContainerRequests(pod *v1.Pod, opts PodResourcesOptions) v1.Resour
var allocatedReqs, actuatedReqs v1.ResourceList
// When pod-level status maps are populated, they already contain the aggregate values across all containers.
// When unpopulated (e.g., at creation time or when feature gates are disabled), we fall back to container status aggregation.
// Once InPlacePodLevelResourcesVerticalScaling and InPlacePodVerticalScaling are GA and feature gates are removed,
// Once InPlacePodLevelResourcesVerticalScaling is GA and its feature gate is removed,
// container-level fallback becomes redundant because max(spec, actuated, allocated) naturally evaluates to spec at creation time.
if opts.InPlacePodLevelResourcesVerticalScalingEnabled && pod.Status.AllocatedResources != nil && pod.Status.Resources != nil && pod.Status.Resources.Requests != nil {
allocatedReqs = pod.Status.AllocatedResources
@@ -461,7 +460,7 @@ func AggregateContainerLimits(pod *v1.Pod, opts PodResourcesOptions) v1.Resource
var actuatedLimits v1.ResourceList
// When pod-level status maps are populated, they already contain the aggregate values across all containers.
// When unpopulated (e.g., at creation time or when feature gates are disabled), we fall back to container status aggregation.
// Once InPlacePodLevelResourcesVerticalScaling and InPlacePodVerticalScaling are GA and feature gates are removed,
// Once InPlacePodLevelResourcesVerticalScaling is GA and its feature gate is removed,
// container-level fallback becomes redundant because max(spec, actuated) naturally evaluates to spec at creation time.
if opts.InPlacePodLevelResourcesVerticalScalingEnabled && pod.Status.Resources != nil && pod.Status.Resources.Limits != nil {
actuatedLimits = pod.Status.Resources.Limits
@@ -81,7 +81,7 @@
| DetectCacheInconsistency | :ballot_box_with_check:&nbsp;1.34+ | | | 1.34– | | | | [code](https://cs.k8s.io/?q=%5CbDetectCacheInconsistency%5Cb&i=nope&files=&excludeFiles=CHANGELOG&repos=kubernetes/kubernetes) [KEPs](https://cs.k8s.io/?q=%5CbDetectCacheInconsistency%5Cb&i=nope&files=&excludeFiles=CHANGELOG&repos=kubernetes/enhancements) |
| DisableCPUQuotaWithExclusiveCPUs | :ballot_box_with_check:&nbsp;1.33+ | :closed_lock_with_key:&nbsp;1.37+ | | 1.33–1.35 | | 1.36– | | [code](https://cs.k8s.io/?q=%5CbDisableCPUQuotaWithExclusiveCPUs%5Cb&i=nope&files=&excludeFiles=CHANGELOG&repos=kubernetes/kubernetes) [KEPs](https://cs.k8s.io/?q=%5CbDisableCPUQuotaWithExclusiveCPUs%5Cb&i=nope&files=&excludeFiles=CHANGELOG&repos=kubernetes/enhancements) |
| DisableNodeKubeProxyVersion | :ballot_box_with_check:&nbsp;1.33+ | :closed_lock_with_key:&nbsp;1.36+ | 1.29–1.30 | | | 1.31– | | [code](https://cs.k8s.io/?q=%5CbDisableNodeKubeProxyVersion%5Cb&i=nope&files=&excludeFiles=CHANGELOG&repos=kubernetes/kubernetes) [KEPs](https://cs.k8s.io/?q=%5CbDisableNodeKubeProxyVersion%5Cb&i=nope&files=&excludeFiles=CHANGELOG&repos=kubernetes/enhancements) |
| DynamicNodeLocalEphemeralVolumes | | | 1.38– | | | | InPlacePodVerticalScaling<br>NodeDeclaredFeatures | [code](https://cs.k8s.io/?q=%5CbDynamicNodeLocalEphemeralVolumes%5Cb&i=nope&files=&excludeFiles=CHANGELOG&repos=kubernetes/kubernetes) [KEPs](https://cs.k8s.io/?q=%5CbDynamicNodeLocalEphemeralVolumes%5Cb&i=nope&files=&excludeFiles=CHANGELOG&repos=kubernetes/enhancements) |
| DynamicNodeLocalEphemeralVolumes | | | 1.38– | | | | NodeDeclaredFeatures | [code](https://cs.k8s.io/?q=%5CbDynamicNodeLocalEphemeralVolumes%5Cb&i=nope&files=&excludeFiles=CHANGELOG&repos=kubernetes/kubernetes) [KEPs](https://cs.k8s.io/?q=%5CbDynamicNodeLocalEphemeralVolumes%5Cb&i=nope&files=&excludeFiles=CHANGELOG&repos=kubernetes/enhancements) |
| EmptyDirVolumeMode | | | 1.37– | | | | | [code](https://cs.k8s.io/?q=%5CbEmptyDirVolumeMode%5Cb&i=nope&files=&excludeFiles=CHANGELOG&repos=kubernetes/kubernetes) [KEPs](https://cs.k8s.io/?q=%5CbEmptyDirVolumeMode%5Cb&i=nope&files=&excludeFiles=CHANGELOG&repos=kubernetes/enhancements) |
| EnvFiles | :ballot_box_with_check:&nbsp;1.35+ | :closed_lock_with_key:&nbsp;1.38+ | 1.34 | 1.35–1.37 | 1.38– | | | [code](https://cs.k8s.io/?q=%5CbEnvFiles%5Cb&i=nope&files=&excludeFiles=CHANGELOG&repos=kubernetes/kubernetes) [KEPs](https://cs.k8s.io/?q=%5CbEnvFiles%5Cb&i=nope&files=&excludeFiles=CHANGELOG&repos=kubernetes/enhancements) |
| EtcdRangeStream | :ballot_box_with_check:&nbsp;1.37+ | | | 1.37– | | | | [code](https://cs.k8s.io/?q=%5CbEtcdRangeStream%5Cb&i=nope&files=&excludeFiles=CHANGELOG&repos=kubernetes/kubernetes) [KEPs](https://cs.k8s.io/?q=%5CbEtcdRangeStream%5Cb&i=nope&files=&excludeFiles=CHANGELOG&repos=kubernetes/enhancements) |
@@ -107,20 +107,19 @@
| ImageVolumeWithDigest | | | 1.35– | | | | ImageVolume | [code](https://cs.k8s.io/?q=%5CbImageVolumeWithDigest%5Cb&i=nope&files=&excludeFiles=CHANGELOG&repos=kubernetes/kubernetes) [KEPs](https://cs.k8s.io/?q=%5CbImageVolumeWithDigest%5Cb&i=nope&files=&excludeFiles=CHANGELOG&repos=kubernetes/enhancements) |
| InOrderInformers | :ballot_box_with_check:&nbsp;1.33+ | :closed_lock_with_key:&nbsp;1.36+ | | 1.33–1.35 | 1.36– | | | [code](https://cs.k8s.io/?q=%5CbInOrderInformers%5Cb&i=nope&files=&excludeFiles=CHANGELOG&repos=kubernetes/kubernetes) [KEPs](https://cs.k8s.io/?q=%5CbInOrderInformers%5Cb&i=nope&files=&excludeFiles=CHANGELOG&repos=kubernetes/enhancements) |
| InOrderInformersBatchProcess | :ballot_box_with_check:&nbsp;1.35+ | | | 1.35– | | | | [code](https://cs.k8s.io/?q=%5CbInOrderInformersBatchProcess%5Cb&i=nope&files=&excludeFiles=CHANGELOG&repos=kubernetes/kubernetes) [KEPs](https://cs.k8s.io/?q=%5CbInOrderInformersBatchProcess%5Cb&i=nope&files=&excludeFiles=CHANGELOG&repos=kubernetes/enhancements) |
| InPlacePodLevelResourcesVerticalScaling | :ballot_box_with_check:&nbsp;1.36+ | | 1.35 | 1.36– | | | InPlacePodVerticalScaling<br>NodeDeclaredFeatures<br>PodLevelResources | [code](https://cs.k8s.io/?q=%5CbInPlacePodLevelResourcesVerticalScaling%5Cb&i=nope&files=&excludeFiles=CHANGELOG&repos=kubernetes/kubernetes) [KEPs](https://cs.k8s.io/?q=%5CbInPlacePodLevelResourcesVerticalScaling%5Cb&i=nope&files=&excludeFiles=CHANGELOG&repos=kubernetes/enhancements) |
| InPlacePodVerticalScaling | :ballot_box_with_check:&nbsp;1.33+ | :closed_lock_with_key:&nbsp;1.35+ | 1.27–1.32 | 1.33–1.34 | 1.35– | | | [code](https://cs.k8s.io/?q=%5CbInPlacePodVerticalScaling%5Cb&i=nope&files=&excludeFiles=CHANGELOG&repos=kubernetes/kubernetes) [KEPs](https://cs.k8s.io/?q=%5CbInPlacePodVerticalScaling%5Cb&i=nope&files=&excludeFiles=CHANGELOG&repos=kubernetes/enhancements) |
| InPlacePodVerticalScalingExclusiveCPUs | | | 1.32– | | | | InPlacePodVerticalScaling | [code](https://cs.k8s.io/?q=%5CbInPlacePodVerticalScalingExclusiveCPUs%5Cb&i=nope&files=&excludeFiles=CHANGELOG&repos=kubernetes/kubernetes) [KEPs](https://cs.k8s.io/?q=%5CbInPlacePodVerticalScalingExclusiveCPUs%5Cb&i=nope&files=&excludeFiles=CHANGELOG&repos=kubernetes/enhancements) |
| InPlacePodVerticalScalingExclusiveMemory | | | 1.34– | | | | InPlacePodVerticalScaling | [code](https://cs.k8s.io/?q=%5CbInPlacePodVerticalScalingExclusiveMemory%5Cb&i=nope&files=&excludeFiles=CHANGELOG&repos=kubernetes/kubernetes) [KEPs](https://cs.k8s.io/?q=%5CbInPlacePodVerticalScalingExclusiveMemory%5Cb&i=nope&files=&excludeFiles=CHANGELOG&repos=kubernetes/enhancements) |
| InPlacePodVerticalScalingInitContainers | :ballot_box_with_check:&nbsp;1.36+ | :closed_lock_with_key:&nbsp;1.37+ | | 1.36 | 1.37– | | InPlacePodVerticalScaling<br>NodeDeclaredFeatures | [code](https://cs.k8s.io/?q=%5CbInPlacePodVerticalScalingInitContainers%5Cb&i=nope&files=&excludeFiles=CHANGELOG&repos=kubernetes/kubernetes) [KEPs](https://cs.k8s.io/?q=%5CbInPlacePodVerticalScalingInitContainers%5Cb&i=nope&files=&excludeFiles=CHANGELOG&repos=kubernetes/enhancements) |
| InPlacePodVerticalScalingMemoryBackedVolumes | | | 1.37– | | | | InPlacePodVerticalScaling<br>NodeDeclaredFeatures | [code](https://cs.k8s.io/?q=%5CbInPlacePodVerticalScalingMemoryBackedVolumes%5Cb&i=nope&files=&excludeFiles=CHANGELOG&repos=kubernetes/kubernetes) [KEPs](https://cs.k8s.io/?q=%5CbInPlacePodVerticalScalingMemoryBackedVolumes%5Cb&i=nope&files=&excludeFiles=CHANGELOG&repos=kubernetes/enhancements) |
| InPlacePodVerticalScalingSchedulerPreemption | | | 1.37– | | | | InPlacePodVerticalScaling | [code](https://cs.k8s.io/?q=%5CbInPlacePodVerticalScalingSchedulerPreemption%5Cb&i=nope&files=&excludeFiles=CHANGELOG&repos=kubernetes/kubernetes) [KEPs](https://cs.k8s.io/?q=%5CbInPlacePodVerticalScalingSchedulerPreemption%5Cb&i=nope&files=&excludeFiles=CHANGELOG&repos=kubernetes/enhancements) |
| InPlacePodLevelResourcesVerticalScaling | :ballot_box_with_check:&nbsp;1.36+ | | 1.35 | 1.36– | | | NodeDeclaredFeatures<br>PodLevelResources | [code](https://cs.k8s.io/?q=%5CbInPlacePodLevelResourcesVerticalScaling%5Cb&i=nope&files=&excludeFiles=CHANGELOG&repos=kubernetes/kubernetes) [KEPs](https://cs.k8s.io/?q=%5CbInPlacePodLevelResourcesVerticalScaling%5Cb&i=nope&files=&excludeFiles=CHANGELOG&repos=kubernetes/enhancements) |
| InPlacePodVerticalScalingExclusiveCPUs | | | 1.32– | | | | | [code](https://cs.k8s.io/?q=%5CbInPlacePodVerticalScalingExclusiveCPUs%5Cb&i=nope&files=&excludeFiles=CHANGELOG&repos=kubernetes/kubernetes) [KEPs](https://cs.k8s.io/?q=%5CbInPlacePodVerticalScalingExclusiveCPUs%5Cb&i=nope&files=&excludeFiles=CHANGELOG&repos=kubernetes/enhancements) |
| InPlacePodVerticalScalingExclusiveMemory | | | 1.34– | | | | | [code](https://cs.k8s.io/?q=%5CbInPlacePodVerticalScalingExclusiveMemory%5Cb&i=nope&files=&excludeFiles=CHANGELOG&repos=kubernetes/kubernetes) [KEPs](https://cs.k8s.io/?q=%5CbInPlacePodVerticalScalingExclusiveMemory%5Cb&i=nope&files=&excludeFiles=CHANGELOG&repos=kubernetes/enhancements) |
| InPlacePodVerticalScalingInitContainers | :ballot_box_with_check:&nbsp;1.36+ | :closed_lock_with_key:&nbsp;1.37+ | | 1.36 | 1.37– | | NodeDeclaredFeatures | [code](https://cs.k8s.io/?q=%5CbInPlacePodVerticalScalingInitContainers%5Cb&i=nope&files=&excludeFiles=CHANGELOG&repos=kubernetes/kubernetes) [KEPs](https://cs.k8s.io/?q=%5CbInPlacePodVerticalScalingInitContainers%5Cb&i=nope&files=&excludeFiles=CHANGELOG&repos=kubernetes/enhancements) |
| InPlacePodVerticalScalingMemoryBackedVolumes | | | 1.37– | | | | NodeDeclaredFeatures | [code](https://cs.k8s.io/?q=%5CbInPlacePodVerticalScalingMemoryBackedVolumes%5Cb&i=nope&files=&excludeFiles=CHANGELOG&repos=kubernetes/kubernetes) [KEPs](https://cs.k8s.io/?q=%5CbInPlacePodVerticalScalingMemoryBackedVolumes%5Cb&i=nope&files=&excludeFiles=CHANGELOG&repos=kubernetes/enhancements) |
| InPlacePodVerticalScalingSchedulerPreemption | | | 1.37– | | | | | [code](https://cs.k8s.io/?q=%5CbInPlacePodVerticalScalingSchedulerPreemption%5Cb&i=nope&files=&excludeFiles=CHANGELOG&repos=kubernetes/kubernetes) [KEPs](https://cs.k8s.io/?q=%5CbInPlacePodVerticalScalingSchedulerPreemption%5Cb&i=nope&files=&excludeFiles=CHANGELOG&repos=kubernetes/enhancements) |
| InformerResourceVersion | :ballot_box_with_check:&nbsp;1.35+ | | 1.30–1.34 | | 1.35– | | | [code](https://cs.k8s.io/?q=%5CbInformerResourceVersion%5Cb&i=nope&files=&excludeFiles=CHANGELOG&repos=kubernetes/kubernetes) [KEPs](https://cs.k8s.io/?q=%5CbInformerResourceVersion%5Cb&i=nope&files=&excludeFiles=CHANGELOG&repos=kubernetes/enhancements) |
| InterPodAffinityHostnameFastPath | | | 1.37– | | | | | [code](https://cs.k8s.io/?q=%5CbInterPodAffinityHostnameFastPath%5Cb&i=nope&files=&excludeFiles=CHANGELOG&repos=kubernetes/kubernetes) [KEPs](https://cs.k8s.io/?q=%5CbInterPodAffinityHostnameFastPath%5Cb&i=nope&files=&excludeFiles=CHANGELOG&repos=kubernetes/enhancements) |
| KMSv1 | :ballot_box_with_check:&nbsp;1.0+ | | | | 1.0–1.27 | 1.28– | | [code](https://cs.k8s.io/?q=%5CbKMSv1%5Cb&i=nope&files=&excludeFiles=CHANGELOG&repos=kubernetes/kubernetes) [KEPs](https://cs.k8s.io/?q=%5CbKMSv1%5Cb&i=nope&files=&excludeFiles=CHANGELOG&repos=kubernetes/enhancements) |
| KubeAPIServerWorkloadsTrust | | | 1.38– | | | | ClusterTrustBundle<br>ClusterTrustBundleProjection | [code](https://cs.k8s.io/?q=%5CbKubeAPIServerWorkloadsTrust%5Cb&i=nope&files=&excludeFiles=CHANGELOG&repos=kubernetes/kubernetes) [KEPs](https://cs.k8s.io/?q=%5CbKubeAPIServerWorkloadsTrust%5Cb&i=nope&files=&excludeFiles=CHANGELOG&repos=kubernetes/enhancements) |
| KubeProxyIPVS | :ballot_box_with_check:&nbsp;1.0+ | | | | 1.0–1.36 | 1.37– | | [code](https://cs.k8s.io/?q=%5CbKubeProxyIPVS%5Cb&i=nope&files=&excludeFiles=CHANGELOG&repos=kubernetes/kubernetes) [KEPs](https://cs.k8s.io/?q=%5CbKubeProxyIPVS%5Cb&i=nope&files=&excludeFiles=CHANGELOG&repos=kubernetes/enhancements) |
| KubeProxyNFTablesLocalhostNodePorts | :ballot_box_with_check:&nbsp;1.38+ | | 1.37 | 1.38– | | | | [code](https://cs.k8s.io/?q=%5CbKubeProxyNFTablesLocalhostNodePorts%5Cb&i=nope&files=&excludeFiles=CHANGELOG&repos=kubernetes/kubernetes) [KEPs](https://cs.k8s.io/?q=%5CbKubeProxyNFTablesLocalhostNodePorts%5Cb&i=nope&files=&excludeFiles=CHANGELOG&repos=kubernetes/enhancements) |
| KubeletAllocatedPodsEndpoint | | | 1.37– | | | | InPlacePodVerticalScaling | [code](https://cs.k8s.io/?q=%5CbKubeletAllocatedPodsEndpoint%5Cb&i=nope&files=&excludeFiles=CHANGELOG&repos=kubernetes/kubernetes) [KEPs](https://cs.k8s.io/?q=%5CbKubeletAllocatedPodsEndpoint%5Cb&i=nope&files=&excludeFiles=CHANGELOG&repos=kubernetes/enhancements) |
| KubeletAllocatedPodsEndpoint | | | 1.37– | | | | | [code](https://cs.k8s.io/?q=%5CbKubeletAllocatedPodsEndpoint%5Cb&i=nope&files=&excludeFiles=CHANGELOG&repos=kubernetes/kubernetes) [KEPs](https://cs.k8s.io/?q=%5CbKubeletAllocatedPodsEndpoint%5Cb&i=nope&files=&excludeFiles=CHANGELOG&repos=kubernetes/enhancements) |
| KubeletCgroupDriverFromCRI | :ballot_box_with_check:&nbsp;1.31+ | :closed_lock_with_key:&nbsp;1.34+ | 1.28–1.30 | 1.31–1.33 | 1.34– | | | [code](https://cs.k8s.io/?q=%5CbKubeletCgroupDriverFromCRI%5Cb&i=nope&files=&excludeFiles=CHANGELOG&repos=kubernetes/kubernetes) [KEPs](https://cs.k8s.io/?q=%5CbKubeletCgroupDriverFromCRI%5Cb&i=nope&files=&excludeFiles=CHANGELOG&repos=kubernetes/enhancements) |
| KubeletCrashLoopBackOffMax | :ballot_box_with_check:&nbsp;1.35+ | | 1.32–1.34 | 1.35– | | | | [code](https://cs.k8s.io/?q=%5CbKubeletCrashLoopBackOffMax%5Cb&i=nope&files=&excludeFiles=CHANGELOG&repos=kubernetes/kubernetes) [KEPs](https://cs.k8s.io/?q=%5CbKubeletCrashLoopBackOffMax%5Cb&i=nope&files=&excludeFiles=CHANGELOG&repos=kubernetes/enhancements) |
| KubeletEnsureSecretPulledImages | :ballot_box_with_check:&nbsp;1.35+ | | 1.33–1.34 | 1.35– | | | | [code](https://cs.k8s.io/?q=%5CbKubeletEnsureSecretPulledImages%5Cb&i=nope&files=&excludeFiles=CHANGELOG&repos=kubernetes/kubernetes) [KEPs](https://cs.k8s.io/?q=%5CbKubeletEnsureSecretPulledImages%5Cb&i=nope&files=&excludeFiles=CHANGELOG&repos=kubernetes/enhancements) |
@@ -917,20 +917,6 @@
lockToDefault: false
preRelease: Beta
version: "1.36"
- name: InPlacePodVerticalScaling
versionedSpecs:
- default: false
lockToDefault: false
preRelease: Alpha
version: "1.27"
- default: true
lockToDefault: false
preRelease: Beta
version: "1.33"
- default: true
lockToDefault: true
preRelease: GA
version: "1.35"
- name: InPlacePodVerticalScalingExclusiveCPUs
versionedSpecs:
- default: false
+4 -5
View File
@@ -1317,7 +1317,7 @@ func doPodResizeDeferredPreemptionTests(f *framework.Framework) {
})
}
var _ = SIGDescribe(framework.WithSerial(), "Pod InPlace Resize Container (scheduler-focused)", framework.WithFeatureGate(features.InPlacePodVerticalScaling), func() {
var _ = SIGDescribe(framework.WithSerial(), "Pod InPlace Resize Container (scheduler-focused)", func() {
f := framework.NewDefaultFramework("pod-resize-scheduler-tests")
ginkgo.BeforeEach(func(ctx context.Context) {
node, err := e2enode.GetRandomReadySchedulableNode(ctx, f.ClientSet)
@@ -1329,7 +1329,7 @@ var _ = SIGDescribe(framework.WithSerial(), "Pod InPlace Resize Container (sched
doPodResizeSchedulerTests(f)
})
var _ = SIGDescribe(framework.WithSerial(), "Pod InPlace Resize Container (deferred-resizes)", framework.WithFeatureGate(features.InPlacePodVerticalScaling), func() {
var _ = SIGDescribe(framework.WithSerial(), "Pod InPlace Resize Container (deferred-resizes)", func() {
f := framework.NewDefaultFramework("pod-resize-deferred-resize-tests")
ginkgo.BeforeEach(func(ctx context.Context) {
node, err := e2enode.GetRandomReadySchedulableNode(ctx, f.ClientSet)
@@ -1341,7 +1341,7 @@ var _ = SIGDescribe(framework.WithSerial(), "Pod InPlace Resize Container (defer
doPodResizeRetryDeferredTests(f)
})
var _ = SIGDescribe("Pod InPlace Resize Container (resource-quota)", framework.WithFeatureGate(features.InPlacePodVerticalScaling), func() {
var _ = SIGDescribe("Pod InPlace Resize Container (resource-quota)", func() {
f := framework.NewDefaultFramework("pod-resize-resource-quota-tests")
ginkgo.BeforeEach(func(ctx context.Context) {
@@ -1354,7 +1354,7 @@ var _ = SIGDescribe("Pod InPlace Resize Container (resource-quota)", framework.W
doPodResizeResourceQuotaTests(f)
})
var _ = SIGDescribe("Pod InPlace Resize Container (limit-ranger)", framework.WithFeatureGate(features.InPlacePodVerticalScaling), func() {
var _ = SIGDescribe("Pod InPlace Resize Container (limit-ranger)", func() {
f := framework.NewDefaultFramework("pod-resize-limit-ranger-tests")
ginkgo.BeforeEach(func(ctx context.Context) {
@@ -1368,7 +1368,6 @@ var _ = SIGDescribe("Pod InPlace Resize Container (limit-ranger)", framework.Wit
})
var _ = SIGDescribe(framework.WithSerial(), framework.WithSlow(), "Pod InPlace Resize Container (deferred-resize-preemption)",
framework.WithFeatureGate(features.InPlacePodVerticalScaling),
framework.WithFeatureGate(features.InPlacePodVerticalScalingSchedulerPreemption),
func() {
f := framework.NewDefaultFramework("pod-resize-deferred-preemption-tests")
-2
View File
@@ -857,7 +857,6 @@ func TestPodUpdateEphemeralContainers(t *testing.T) {
}
func TestPodResizeRBAC(t *testing.T) {
featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.InPlacePodVerticalScaling, true)
server := kubeapiservertesting.StartTestServerOrDie(t, nil,
append(framework.DefaultTestServerFlags(), "--authorization-mode=RBAC"), framework.SharedEtcd())
defer server.TearDownFn()
@@ -961,7 +960,6 @@ func TestPodResizeRBAC(t *testing.T) {
}
func TestPodResize(t *testing.T) {
featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.InPlacePodVerticalScaling, true)
// Disable ServiceAccount admission plugin as we don't have serviceaccount controller running.
server := kubeapiservertesting.StartTestServerOrDie(t, nil, framework.DefaultTestServerFlags(), framework.SharedEtcd())
defer server.TearDownFn()
@@ -42,7 +42,6 @@ import (
func TestDeferredResizePodPreemption(t *testing.T) {
// Setup API server with feature gates enabled
featuregatetesting.SetFeatureGatesDuringTest(t, utilfeature.DefaultFeatureGate, featuregatetesting.FeatureOverrides{
features.InPlacePodVerticalScaling: true,
features.InPlacePodVerticalScalingSchedulerPreemption: true,
})
@@ -451,7 +450,6 @@ func setUpPreemptionTestWithContext(t *testing.T, testCtx *testutils.TestContext
func TestDeferredResizeQueueingHints(t *testing.T) {
featuregatetesting.SetFeatureGatesDuringTest(t, utilfeature.DefaultFeatureGate, featuregatetesting.FeatureOverrides{
features.InPlacePodVerticalScaling: true,
features.InPlacePodVerticalScalingSchedulerPreemption: true,
})
@@ -616,7 +614,6 @@ func TestDeferredResizeQueueingHints(t *testing.T) {
func TestDeferredResizeNodePreemptionPolicy(t *testing.T) {
featuregatetesting.SetFeatureGatesDuringTest(t, utilfeature.DefaultFeatureGate, featuregatetesting.FeatureOverrides{
features.InPlacePodVerticalScaling: true,
features.InPlacePodVerticalScalingSchedulerPreemption: true,
})
@@ -743,7 +740,6 @@ func TestDeferredResizeNodePreemptionPolicy(t *testing.T) {
func TestDeferredResizeQueueingHandlers(t *testing.T) {
t.Run("AddPod handler enqueues existing deferred pod on scheduler startup", func(t *testing.T) {
featuregatetesting.SetFeatureGatesDuringTest(t, utilfeature.DefaultFeatureGate, featuregatetesting.FeatureOverrides{
features.InPlacePodVerticalScaling: true,
features.InPlacePodVerticalScalingSchedulerPreemption: true,
})
@@ -833,7 +829,6 @@ func TestDeferredResizeQueueingHandlers(t *testing.T) {
t.Run("UpdatePod handler enqueues pod when it transitions to deferred resize", func(t *testing.T) {
featuregatetesting.SetFeatureGatesDuringTest(t, utilfeature.DefaultFeatureGate, featuregatetesting.FeatureOverrides{
features.InPlacePodVerticalScaling: true,
features.InPlacePodVerticalScalingSchedulerPreemption: true,
})
@@ -940,7 +935,6 @@ func TestDeferredResizeQueueingHandlers(t *testing.T) {
t.Run("UpdatePod handler removes pod from queue when deferred condition is cleared", func(t *testing.T) {
featuregatetesting.SetFeatureGatesDuringTest(t, utilfeature.DefaultFeatureGate, featuregatetesting.FeatureOverrides{
features.InPlacePodVerticalScaling: true,
features.InPlacePodVerticalScalingSchedulerPreemption: true,
})
@@ -1035,7 +1029,6 @@ func TestDeferredResizeQueueingHandlers(t *testing.T) {
t.Run("DeletePod handler removes pod from both cache and scheduling queue when deleted", func(t *testing.T) {
featuregatetesting.SetFeatureGatesDuringTest(t, utilfeature.DefaultFeatureGate, featuregatetesting.FeatureOverrides{
features.InPlacePodVerticalScaling: true,
features.InPlacePodVerticalScalingSchedulerPreemption: true,
})
@@ -2579,7 +2579,6 @@ var CoreResourceEnqueueTestCases = []*CoreResourceEnqueueTestCase{
// moved properly upon their registered events.
func RunTestCoreResourceEnqueue(t *testing.T, tt *CoreResourceEnqueueTestCase) { //nolint:forbidigo // calls ndftesting.NewMockFeature/ktesting.NewTestContext, which require *testing.T
t.Helper()
featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.InPlacePodVerticalScaling, true)
if tt.EnableDRAExtendedResource {
featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.DRAExtendedResource, true)
}
@@ -144,8 +144,6 @@
# that through QHints make the pods schedulable.
# Plugins covered: InterPodAffinity, NodeResources, NodeUnschedulable, PodTopologySpread, SchedulingGates and TaintToleration.
- name: EventHandlingPodUpdate
featureGates:
InPlacePodVerticalScaling: true
workloadTemplate:
# Collect metrics from all createPods ops that initially create unschedulable pods (type: unsched).
- opcode: startCollectingMetrics