Skip to content

Commit a09cb22

Browse files
committed
disable in-place vertical pod scaling for non-restartable swappable containers
1 parent e3baee3 commit a09cb22

14 files changed

+482
-51
lines changed

cmd/kubelet/app/server.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -834,7 +834,7 @@ func run(ctx context.Context, s *options.KubeletServer, kubeDeps *kubelet.Depend
834834
s.TopologyManagerPolicyOptions, features.TopologyManagerPolicyOptions)
835835
}
836836
if utilfeature.DefaultFeatureGate.Enabled(features.NodeSwap) {
837-
if !kubeletutil.IsCgroup2UnifiedMode() && s.MemorySwap.SwapBehavior == kubelettypes.LimitedSwap {
837+
if !kubeletutil.IsCgroup2UnifiedMode() && s.MemorySwap.SwapBehavior == string(kubelettypes.LimitedSwap) {
838838
// This feature is not supported for cgroupv1 so we are failing early.
839839
return fmt.Errorf("swap feature is enabled and LimitedSwap but it is only supported with cgroupv2")
840840
}

pkg/kubelet/apis/config/validation/validation.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -203,8 +203,8 @@ func ValidateKubeletConfiguration(kc *kubeletconfig.KubeletConfiguration, featur
203203
if localFeatureGate.Enabled(features.NodeSwap) {
204204
switch kc.MemorySwap.SwapBehavior {
205205
case "":
206-
case kubetypes.NoSwap:
207-
case kubetypes.LimitedSwap:
206+
case string(kubetypes.NoSwap):
207+
case string(kubetypes.LimitedSwap):
208208
default:
209209
allErrors = append(allErrors, fmt.Errorf("invalid configuration: memorySwap.swapBehavior %q must be one of: \"\", %q or %q", kc.MemorySwap.SwapBehavior, kubetypes.LimitedSwap, kubetypes.NoSwap))
210210
}

pkg/kubelet/apis/config/validation/validation_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -385,7 +385,7 @@ func TestValidateKubeletConfiguration(t *testing.T) {
385385
name: "specify MemorySwap.SwapBehavior without enabling NodeSwap",
386386
configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration {
387387
conf.FeatureGates = map[string]bool{"NodeSwap": false}
388-
conf.MemorySwap.SwapBehavior = kubetypes.LimitedSwap
388+
conf.MemorySwap.SwapBehavior = string(kubetypes.LimitedSwap)
389389
return conf
390390
},
391391
errMsg: "invalid configuration: memorySwap.swapBehavior cannot be set when NodeSwap feature flag is disabled",

pkg/kubelet/container/runtime.go

+4
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ import (
3434
runtimeapi "k8s.io/cri-api/pkg/apis/runtime/v1"
3535
"k8s.io/klog/v2"
3636
"k8s.io/kubernetes/pkg/credentialprovider"
37+
kubelettypes "k8s.io/kubernetes/pkg/kubelet/types"
3738
"k8s.io/kubernetes/pkg/volume"
3839
)
3940

@@ -138,6 +139,9 @@ type Runtime interface {
138139
ListPodSandboxMetrics(ctx context.Context) ([]*runtimeapi.PodSandboxMetrics, error)
139140
// GetContainerStatus returns the status for the container.
140141
GetContainerStatus(ctx context.Context, id ContainerID) (*Status, error)
142+
// GetContainerSwapBehavior reports whether a container could be swappable.
143+
// This is used to decide whether to handle InPlacePodVerticalScaling for containers.
144+
GetContainerSwapBehavior(pod *v1.Pod, container *v1.Container) kubelettypes.SwapBehavior
141145
}
142146

143147
// StreamingRuntime is the interface implemented by runtimes that handle the serving of the

pkg/kubelet/container/testing/fake_runtime.go

+9
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import (
3030
runtimeapi "k8s.io/cri-api/pkg/apis/runtime/v1"
3131
"k8s.io/kubernetes/pkg/credentialprovider"
3232
kubecontainer "k8s.io/kubernetes/pkg/kubelet/container"
33+
kubetypes "k8s.io/kubernetes/pkg/kubelet/types"
3334
"k8s.io/kubernetes/pkg/volume"
3435
)
3536

@@ -70,6 +71,7 @@ type FakeRuntime struct {
7071
// from container runtime.
7172
BlockImagePulls bool
7273
imagePullTokenBucket chan bool
74+
SwapBehavior map[string]kubetypes.SwapBehavior
7375
T TB
7476
}
7577

@@ -536,3 +538,10 @@ func (f *FakeRuntime) GetContainerStatus(_ context.Context, _ kubecontainer.Cont
536538
f.CalledFunctions = append(f.CalledFunctions, "GetContainerStatus")
537539
return nil, f.Err
538540
}
541+
542+
func (f *FakeRuntime) GetContainerSwapBehavior(pod *v1.Pod, container *v1.Container) kubetypes.SwapBehavior {
543+
if f.SwapBehavior != nil && f.SwapBehavior[container.Name] != "" {
544+
return f.SwapBehavior[container.Name]
545+
}
546+
return kubetypes.NoSwap
547+
}

pkg/kubelet/container/testing/runtime_mock.go

+58-9
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pkg/kubelet/kubelet.go

+38
Original file line numberDiff line numberDiff line change
@@ -2920,6 +2920,40 @@ func (kl *Kubelet) canResizePod(pod *v1.Pod) (bool, string, string) {
29202920
return true, "", ""
29212921
}
29222922

2923+
func disallowResizeForSwappableContainers(runtime kubecontainer.Runtime, desiredPod, allocatedPod *v1.Pod) (bool, string) {
2924+
if desiredPod == nil || allocatedPod == nil {
2925+
return false, ""
2926+
}
2927+
restartableMemoryResizePolicy := func(resizePolicies []v1.ContainerResizePolicy) bool {
2928+
for _, policy := range resizePolicies {
2929+
if policy.ResourceName == v1.ResourceMemory {
2930+
return policy.RestartPolicy == v1.RestartContainer
2931+
}
2932+
}
2933+
return false
2934+
}
2935+
allocatedContainers := make(map[string]v1.Container)
2936+
for _, container := range append(allocatedPod.Spec.Containers, allocatedPod.Spec.InitContainers...) {
2937+
allocatedContainers[container.Name] = container
2938+
}
2939+
for _, desiredContainer := range append(desiredPod.Spec.Containers, desiredPod.Spec.InitContainers...) {
2940+
allocatedContainer, ok := allocatedContainers[desiredContainer.Name]
2941+
if !ok {
2942+
continue
2943+
}
2944+
origMemRequest := desiredContainer.Resources.Requests[v1.ResourceMemory]
2945+
newMemRequest := allocatedContainer.Resources.Requests[v1.ResourceMemory]
2946+
if !origMemRequest.Equal(newMemRequest) && !restartableMemoryResizePolicy(allocatedContainer.ResizePolicy) {
2947+
aSwapBehavior := runtime.GetContainerSwapBehavior(desiredPod, &desiredContainer)
2948+
bSwapBehavior := runtime.GetContainerSwapBehavior(allocatedPod, &allocatedContainer)
2949+
if aSwapBehavior != kubetypes.NoSwap || bSwapBehavior != kubetypes.NoSwap {
2950+
return true, "In-place resize of containers with swap is not supported."
2951+
}
2952+
}
2953+
}
2954+
return false, ""
2955+
}
2956+
29232957
// handlePodResourcesResize returns the "allocated pod", which should be used for all resource
29242958
// calculations after this function is called. It also updates the cached ResizeStatus according to
29252959
// the allocation decision and pod status.
@@ -2949,6 +2983,10 @@ func (kl *Kubelet) handlePodResourcesResize(pod *v1.Pod, podStatus *kubecontaine
29492983
// If there is a pending resize but the resize is not allowed, always use the allocated resources.
29502984
kl.statusManager.SetPodResizePendingCondition(pod.UID, v1.PodReasonInfeasible, msg)
29512985
return podFromAllocation, nil
2986+
} else if resizeNotAllowed, msg := disallowResizeForSwappableContainers(kl.containerRuntime, pod, podFromAllocation); resizeNotAllowed {
2987+
// If this resize involve swap recalculation, set as infeasible, as IPPR with swap is not supported for beta.
2988+
kl.statusManager.SetPodResizePendingCondition(pod.UID, v1.PodReasonInfeasible, msg)
2989+
return podFromAllocation, nil
29522990
}
29532991

29542992
kl.podResizeMutex.Lock()

0 commit comments

Comments
 (0)