Rename HardReservation to TieredReservation for memoryReservationPolicy

The tiered approach uses memory.min for Guaranteed pods (hard protection)
and memory.low for Burstable pods (soft protection). Rename the policy
value to reflect this design.
This commit is contained in:
Sohan Kunkerkar 2026-03-18 14:44:39 -04:00
parent dbd3f16787
commit d2b77a8133
12 changed files with 42 additions and 40 deletions

View file

@ -72000,7 +72000,7 @@ func schema_k8sio_kubelet_config_v1beta1_KubeletConfiguration(ref common.Referen
},
"memoryReservationPolicy": {
SchemaProps: spec.SchemaProps{
Description: "MemoryReservationPolicy controls how the kubelet applies cgroup v2 memory protection. \"None\" (default): The kubelet does not set memory.min for containers and pods, ensuring no hard memory is locked by the kernel. \"HardReservation\": The kubelet sets the cgroup v2 memory.min value based on pod and container memory requests. This ensures the requested memory is never reclaimed by the kernel, but may trigger an OOM if the reservation cannot be satisfied. See https://kep.k8s.io/2570 for more details. Default: None",
Description: "MemoryReservationPolicy controls how the kubelet applies cgroup v2 memory protection. \"None\" (default): The kubelet does not set memory.min for containers and pods, ensuring no hard memory is locked by the kernel. \"TieredReservation\": The kubelet sets cgroup v2 memory.min for Guaranteed pods and memory.low for Burstable pods based on memory requests. Guaranteed memory is never reclaimed by the kernel; Burstable memory is preferentially retained but may be reclaimed under extreme pressure. See https://kep.k8s.io/2570 for more details. Default: None",
Type: []string{"string"},
Format: "",
},

View file

@ -506,8 +506,8 @@ type KubeletConfiguration struct {
// MemoryReservationPolicy controls how the kubelet applies cgroup v2 memory protection.
// "None" (default): The kubelet does not set memory.min for containers and pods,
// ensuring no hard memory is locked by the kernel.
// "HardReservation": The kubelet sets the cgroup v2 memory.min value based on pod and container memory requests.
// This ensures the requested memory is never reclaimed by the kernel, but may trigger an OOM if the reservation cannot be satisfied.
// "TieredReservation": The kubelet sets cgroup v2 memory.min for Guaranteed pods and memory.low for Burstable pods based on memory requests.
// Guaranteed memory is never reclaimed by the kernel; Burstable memory is preferentially retained but may be reclaimed under extreme pressure.
// See https://kep.k8s.io/2570 for more details.
// Default: None
// +featureGate=MemoryQoS
@ -869,8 +869,9 @@ const (
// NoneMemoryReservationPolicy disables memory.min protection for containers and pods.
// This is the default to maintain node stability by preventing "locked" memory.
NoneMemoryReservationPolicy MemoryReservationPolicy = "None"
// HardReservationMemoryReservationPolicy enables memory.min for containers and pods.
HardReservationMemoryReservationPolicy MemoryReservationPolicy = "HardReservation"
// TieredReservationMemoryReservationPolicy enables tiered memory protection:
// memory.min for Guaranteed pods, memory.low for Burstable pods.
TieredReservationMemoryReservationPolicy MemoryReservationPolicy = "TieredReservation"
)
// ImagePullIntent is a record of the kubelet attempting to pull an image.

View file

@ -352,14 +352,14 @@ func ValidateKubeletConfiguration(kc *kubeletconfig.KubeletConfiguration, featur
}
if !localFeatureGate.Enabled(features.MemoryQoS) &&
kc.MemoryReservationPolicy == kubeletconfig.HardReservationMemoryReservationPolicy {
kc.MemoryReservationPolicy == kubeletconfig.TieredReservationMemoryReservationPolicy {
allErrors = append(allErrors, fmt.Errorf("invalid configuration: memoryReservationPolicy %q requires MemoryQoS feature gate to be enabled",
kc.MemoryReservationPolicy))
}
switch kc.MemoryReservationPolicy {
case kubeletconfig.NoneMemoryReservationPolicy, kubeletconfig.HardReservationMemoryReservationPolicy:
case kubeletconfig.NoneMemoryReservationPolicy, kubeletconfig.TieredReservationMemoryReservationPolicy:
default:
allErrors = append(allErrors, fmt.Errorf("invalid configuration: option %q specified for memoryReservationPolicy. Valid options are %q or %q", kc.MemoryReservationPolicy, kubeletconfig.NoneMemoryReservationPolicy, kubeletconfig.HardReservationMemoryReservationPolicy))
allErrors = append(allErrors, fmt.Errorf("invalid configuration: option %q specified for memoryReservationPolicy. Valid options are %q or %q", kc.MemoryReservationPolicy, kubeletconfig.NoneMemoryReservationPolicy, kubeletconfig.TieredReservationMemoryReservationPolicy))
}
if kc.ContainerRuntimeEndpoint == "" {

View file

@ -568,17 +568,17 @@ func TestValidateKubeletConfiguration(t *testing.T) {
name: "MemoryReservationPolicy requires MemoryQoS",
configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration {
conf.FeatureGates = map[string]bool{"MemoryQoS": false}
conf.MemoryReservationPolicy = kubeletconfig.HardReservationMemoryReservationPolicy
conf.MemoryReservationPolicy = kubeletconfig.TieredReservationMemoryReservationPolicy
return conf
},
errMsg: "invalid configuration: memoryReservationPolicy \"HardReservation\" requires MemoryQoS feature gate to be enabled",
errMsg: "invalid configuration: memoryReservationPolicy \"TieredReservation\" requires MemoryQoS feature gate to be enabled",
}, {
name: "invalid MemoryReservationPolicy",
configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration {
conf.MemoryReservationPolicy = "invalid"
return conf
},
errMsg: "invalid configuration: option \"invalid\" specified for memoryReservationPolicy. Valid options are \"None\" or \"HardReservation\"",
errMsg: "invalid configuration: option \"invalid\" specified for memoryReservationPolicy. Valid options are \"None\" or \"TieredReservation\"",
}, {
name: "invalid Taint.TimeAdded",
configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration {

View file

@ -208,7 +208,7 @@ func ResourceConfigForPod(allocatedPod *v1.Pod, enforceCPULimits bool, cpuPeriod
}
result.HugePageLimit = hugePageLimits
if enforceMemoryQoS && memoryReservationPolicy == kubeletconfig.HardReservationMemoryReservationPolicy {
if enforceMemoryQoS && memoryReservationPolicy == kubeletconfig.TieredReservationMemoryReservationPolicy {
memoryRequest := int64(0)
if request, found := reqs[v1.ResourceMemory]; found {
memoryRequest = request.Value()

View file

@ -850,7 +850,7 @@ func TestResourceConfigForPodWithEnforceMemoryQoS(t *testing.T) {
for testName, testCase := range testCases {
actual := ResourceConfigForPod(testCase.pod, testCase.enforceCPULimits, testCase.quotaPeriod, true, kubeletconfig.HardReservationMemoryReservationPolicy)
actual := ResourceConfigForPod(testCase.pod, testCase.enforceCPULimits, testCase.quotaPeriod, true, kubeletconfig.TieredReservationMemoryReservationPolicy)
if !reflect.DeepEqual(actual.Unified, testCase.expected.Unified) {
t.Errorf("unexpected result, test: %v, unified not as expected", testName)

View file

@ -300,7 +300,7 @@ func (m *qosContainerManagerImpl) setMemoryQoS(logger klog.Logger, configs map[v
logger.V(4).Info("MemoryQoS config for qos", "qos", qos, "key", key, "value", value)
}
if m.memoryReservationPolicy != kubeletconfig.HardReservationMemoryReservationPolicy {
if m.memoryReservationPolicy != kubeletconfig.TieredReservationMemoryReservationPolicy {
setUnified(v1.PodQOSGuaranteed, Cgroup2MemoryMin, 0)
setUnified(v1.PodQOSBurstable, Cgroup2MemoryLow, 0)
kubeletmetrics.MemoryQoSNodeMemoryMinBytes.Set(0)
@ -331,7 +331,7 @@ func (m *qosContainerManagerImpl) setMemoryQoS(logger klog.Logger, configs map[v
}
// reconcilePodMemoryProtection clears stale memory.min and memory.low on pod-level cgroups
// when MemoryQoS is disabled or memoryReservationPolicy is not HardReservation.
// when MemoryQoS is disabled or memoryReservationPolicy is not TieredReservation.
func (m *qosContainerManagerImpl) reconcilePodMemoryProtection(logger klog.Logger) {
pods := m.activePods()
for _, pod := range pods {

View file

@ -208,8 +208,8 @@ func TestQoSContainerCgroup(t *testing.T) {
logger, _ := ktesting.NewTestContext(t)
m, err := createTestQOSContainerManager(logger)
require.NoError(t, err)
// Set memory reservation policy to HardReservation to enable memory.min
m.memoryReservationPolicy = kubeletconfig.HardReservationMemoryReservationPolicy
// Set memory reservation policy to TieredReservation to enable memory.min
m.memoryReservationPolicy = kubeletconfig.TieredReservationMemoryReservationPolicy
m.activePods = func() []*v1.Pod { return tc.pods }
guaranteedUnified := map[string]string{}

View file

@ -155,7 +155,7 @@ func (m *kubeGenericRuntimeManager) generateLinuxContainerResources(ctx context.
unified := map[string]string{}
memoryRequest := container.Resources.Requests.Memory().Value()
memoryLimit := container.Resources.Limits.Memory().Value()
if memoryRequest != 0 && m.memoryReservationPolicy == kubeletconfiginternal.HardReservationMemoryReservationPolicy {
if memoryRequest != 0 && m.memoryReservationPolicy == kubeletconfiginternal.TieredReservationMemoryReservationPolicy {
// Guaranteed pods get memory.min (hard protection).
// Burstable pods get memory.low (soft protection).
if kubeapiqos.GetPodQOS(pod) == v1.PodQOSGuaranteed {

View file

@ -559,7 +559,7 @@ func TestGenerateContainerConfigWithMemoryQoSEnforced(t *testing.T) {
tCtx := ktesting.Init(t)
_, _, m, err := createTestRuntimeManager(tCtx)
assert.NoError(t, err)
m.memoryReservationPolicy = kubeletconfiginternal.HardReservationMemoryReservationPolicy
m.memoryReservationPolicy = kubeletconfiginternal.TieredReservationMemoryReservationPolicy
podRequestMemory := resource.MustParse("128Mi")
pod1LimitMemory := resource.MustParse("256Mi")

View file

@ -109,8 +109,9 @@ const (
// NoneMemoryReservationPolicy disables memory.min protection for containers and pods.
// This is the default to maintain node stability by preventing "locked" memory.
NoneMemoryReservationPolicy MemoryReservationPolicy = "None"
// HardReservationMemoryReservationPolicy enables memory.min for containers and pods.
HardReservationMemoryReservationPolicy MemoryReservationPolicy = "HardReservation"
// TieredReservationMemoryReservationPolicy enables tiered memory protection:
// memory.min for Guaranteed pods, memory.low for Burstable pods.
TieredReservationMemoryReservationPolicy MemoryReservationPolicy = "TieredReservation"
)
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
@ -902,8 +903,8 @@ type KubeletConfiguration struct {
// MemoryReservationPolicy controls how the kubelet applies cgroup v2 memory protection.
// "None" (default): The kubelet does not set memory.min for containers and pods,
// ensuring no hard memory is locked by the kernel.
// "HardReservation": The kubelet sets the cgroup v2 memory.min value based on pod and container memory requests.
// This ensures the requested memory is never reclaimed by the kernel, but may trigger an OOM if the reservation cannot be satisfied.
// "TieredReservation": The kubelet sets cgroup v2 memory.min for Guaranteed pods and memory.low for Burstable pods based on memory requests.
// Guaranteed memory is never reclaimed by the kernel; Burstable memory is preferentially retained but may be reclaimed under extreme pressure.
// See https://kep.k8s.io/2570 for more details.
// Default: None
// +featureGate=MemoryQoS

View file

@ -235,11 +235,11 @@ var _ = SIGDescribe("MemoryQoS", framework.WithSerial(), func() {
}
}
f.Describe("memory protection [memoryReservationPolicy=HardReservation]", func() {
f.Describe("memory protection [memoryReservationPolicy=TieredReservation]", func() {
ginkgo.AfterEach(func(ctx context.Context) { restoreConfig(ctx) })
ginkgo.It("should set memory.low = requests.memory for Burstable pod containers", func(ctx context.Context) {
configureMemoryQoSWithPolicy(ctx, 0.9, kubeletconfig.HardReservationMemoryReservationPolicy)
configureMemoryQoSWithPolicy(ctx, 0.9, kubeletconfig.TieredReservationMemoryReservationPolicy)
requestsMem := resource.MustParse("256Mi")
limitsMem := resource.MustParse("512Mi")
@ -277,7 +277,7 @@ var _ = SIGDescribe("MemoryQoS", framework.WithSerial(), func() {
})
ginkgo.It("should set memory.min for Guaranteed pod containers", func(ctx context.Context) {
configureMemoryQoSWithPolicy(ctx, 0.9, kubeletconfig.HardReservationMemoryReservationPolicy)
configureMemoryQoSWithPolicy(ctx, 0.9, kubeletconfig.TieredReservationMemoryReservationPolicy)
mem := resource.MustParse("256Mi")
cpu := resource.MustParse("100m")
@ -299,7 +299,7 @@ var _ = SIGDescribe("MemoryQoS", framework.WithSerial(), func() {
})
ginkgo.It("should NOT set memory.min for BestEffort pod", func(ctx context.Context) {
configureMemoryQoSWithPolicy(ctx, 0.9, kubeletconfig.HardReservationMemoryReservationPolicy)
configureMemoryQoSWithPolicy(ctx, 0.9, kubeletconfig.TieredReservationMemoryReservationPolicy)
pod := &v1.Pod{
ObjectMeta: metav1.ObjectMeta{
@ -329,7 +329,7 @@ var _ = SIGDescribe("MemoryQoS", framework.WithSerial(), func() {
})
ginkgo.It("should set pod-level memory.low = sum(container requests) for multi-container pod", func(ctx context.Context) {
configureMemoryQoSWithPolicy(ctx, 0.9, kubeletconfig.HardReservationMemoryReservationPolicy)
configureMemoryQoSWithPolicy(ctx, 0.9, kubeletconfig.TieredReservationMemoryReservationPolicy)
req1 := resource.MustParse("128Mi")
req2 := resource.MustParse("256Mi")
@ -377,7 +377,7 @@ var _ = SIGDescribe("MemoryQoS", framework.WithSerial(), func() {
})
ginkgo.It("should propagate memory protection through QoS cgroup hierarchy", func(ctx context.Context) {
configureMemoryQoSWithPolicy(ctx, 0.9, kubeletconfig.HardReservationMemoryReservationPolicy)
configureMemoryQoSWithPolicy(ctx, 0.9, kubeletconfig.TieredReservationMemoryReservationPolicy)
requestsMem := resource.MustParse("128Mi")
limitsMem := resource.MustParse("256Mi")
@ -587,7 +587,7 @@ var _ = SIGDescribe("MemoryQoS", framework.WithSerial(), func() {
ginkgo.AfterEach(func(ctx context.Context) { restoreConfig(ctx) })
ginkgo.It("should reset memory protection to 0 when MemoryQoS is disabled", func(ctx context.Context) {
configureMemoryQoSWithPolicy(ctx, 0.9, kubeletconfig.HardReservationMemoryReservationPolicy)
configureMemoryQoSWithPolicy(ctx, 0.9, kubeletconfig.TieredReservationMemoryReservationPolicy)
pod := memqosMakePod("memqos-rollback", f.Namespace.Name,
v1.ResourceList{v1.ResourceMemory: resource.MustParse("128Mi")},
@ -745,8 +745,8 @@ var _ = SIGDescribe("MemoryQoS", framework.WithSerial(), func() {
}
})
ginkgo.It("should set memory protection when policy is HardReservation", func(ctx context.Context) {
configureMemoryQoSWithPolicy(ctx, 0.9, kubeletconfig.HardReservationMemoryReservationPolicy)
ginkgo.It("should set memory protection when policy is TieredReservation", func(ctx context.Context) {
configureMemoryQoSWithPolicy(ctx, 0.9, kubeletconfig.TieredReservationMemoryReservationPolicy)
requestsMem := resource.MustParse("256Mi")
limitsMem := resource.MustParse("512Mi")
@ -761,12 +761,12 @@ var _ = SIGDescribe("MemoryQoS", framework.WithSerial(), func() {
podMemMin, err := memqosReadCgroupInt64(podCgroupPath, cgroupMemoryLow)
framework.ExpectNoError(err)
framework.Logf("Policy=HardReservation: pod memory.low=%d, expected=%d", podMemMin, requestsMem.Value())
framework.Logf("Policy=TieredReservation: pod memory.low=%d, expected=%d", podMemMin, requestsMem.Value())
gomega.Expect(podMemMin).To(gomega.Equal(requestsMem.Value()))
})
ginkgo.It("should clear memory protection at QoS level when switching from HardReservation to None", func(ctx context.Context) {
configureMemoryQoSWithPolicy(ctx, 0.9, kubeletconfig.HardReservationMemoryReservationPolicy)
ginkgo.It("should clear memory protection at QoS level when switching from TieredReservation to None", func(ctx context.Context) {
configureMemoryQoSWithPolicy(ctx, 0.9, kubeletconfig.TieredReservationMemoryReservationPolicy)
pod := memqosMakePod("memqos-policy-rollback", f.Namespace.Name,
v1.ResourceList{v1.ResourceMemory: resource.MustParse("128Mi")},
@ -911,7 +911,7 @@ var _ = SIGDescribe("MemoryQoS", framework.WithSerial(), func() {
ginkgo.AfterEach(func(ctx context.Context) { restoreConfig(ctx) })
ginkgo.It("should use memory.low for Burstable pod where memory req == limit but CPU differs", func(ctx context.Context) {
configureMemoryQoSWithPolicy(ctx, 0.9, kubeletconfig.HardReservationMemoryReservationPolicy)
configureMemoryQoSWithPolicy(ctx, 0.9, kubeletconfig.TieredReservationMemoryReservationPolicy)
memSize := resource.MustParse("128Mi")
pod := &v1.Pod{
@ -962,13 +962,13 @@ var _ = SIGDescribe("MemoryQoS", framework.WithSerial(), func() {
})
ginkgo.It("should include burstable requests in kubepods root memory.min", func(ctx context.Context) {
configureMemoryQoSWithPolicy(ctx, 0.9, kubeletconfig.HardReservationMemoryReservationPolicy)
configureMemoryQoSWithPolicy(ctx, 0.9, kubeletconfig.TieredReservationMemoryReservationPolicy)
requestsMem := resource.MustParse("200Mi")
pod := memqosMakePod("memqos-hierarchy-check", f.Namespace.Name,
v1.ResourceList{v1.ResourceMemory: requestsMem},
v1.ResourceList{v1.ResourceMemory: resource.MustParse("400Mi")})
pod = e2epod.NewPodClient(f).CreateSync(ctx, pod)
e2epod.NewPodClient(f).CreateSync(ctx, pod)
var kubepodsCgroupPath string
if cgroupDriver == "systemd" {
@ -985,7 +985,7 @@ var _ = SIGDescribe("MemoryQoS", framework.WithSerial(), func() {
})
ginkgo.It("should remove burstable memory.low contribution when pod is deleted", func(ctx context.Context) {
configureMemoryQoSWithPolicy(ctx, 0.9, kubeletconfig.HardReservationMemoryReservationPolicy)
configureMemoryQoSWithPolicy(ctx, 0.9, kubeletconfig.TieredReservationMemoryReservationPolicy)
var burstableCgroupPath string
if cgroupDriver == "systemd" {
@ -1022,7 +1022,7 @@ var _ = SIGDescribe("MemoryQoS", framework.WithSerial(), func() {
})
ginkgo.It("should persist container-level memory.low after rollback [known limitation]", func(ctx context.Context) {
configureMemoryQoSWithPolicy(ctx, 0.9, kubeletconfig.HardReservationMemoryReservationPolicy)
configureMemoryQoSWithPolicy(ctx, 0.9, kubeletconfig.TieredReservationMemoryReservationPolicy)
requestsMem := resource.MustParse("128Mi")
pod := memqosMakePod("memqos-container-rollback", f.Namespace.Name,