From 1f8ada572cae748bbbb6a196994c2c4dda214685 Mon Sep 17 00:00:00 2001 From: Arsolitt Date: Tue, 21 Apr 2026 18:27:34 +0300 Subject: [PATCH] fix(kubernetes): validate absolute eviction format and guard against memory over-reservation Add regex format validation for absolute eviction threshold values before they reach toFloat conversion, preventing silent zero returns on invalid input like "banana". Add a check that systemReservedMemory + kubeReservedMemory does not exceed effective node memory. Include helm unit tests for both validations. Signed-off-by: Arsolitt --- .../apps/kubernetes/templates/cluster.yaml | 21 ++ .../tests/kubelet-reservation_test.yaml | 185 ++++++++++++++++++ 2 files changed, 206 insertions(+) diff --git a/packages/apps/kubernetes/templates/cluster.yaml b/packages/apps/kubernetes/templates/cluster.yaml index c3e5b5a66..5e591c36e 100644 --- a/packages/apps/kubernetes/templates/cluster.yaml +++ b/packages/apps/kubernetes/templates/cluster.yaml @@ -224,6 +224,17 @@ metadata: {{- if and $kubeletOverride.kubeReservedCpu (not (regexMatch `^[0-9]+(\.[0-9]+)?m?$` ($kubeletOverride.kubeReservedCpu | toString))) }} {{- fail (printf "nodeGroup %s: invalid kubeReservedCpu value %q — must be a valid Kubernetes CPU quantity (e.g. 100m, 0.5, 1)" $groupName ($kubeletOverride.kubeReservedCpu | toString)) }} {{- end }} +{{/* Validate absolute eviction threshold formats before converting */}} +{{- if not (hasSuffix "%" $evictionHardMemory) }} +{{- if not (regexMatch `^[0-9]+(\.[0-9]+)?(Ki|Mi|Gi|Ti|Pi|Ei)?$` $evictionHardMemory) }} +{{- fail (printf "nodeGroup %s: invalid evictionHardMemory value %q — must be a percentage (e.g. 7%%) or a valid Kubernetes memory quantity (e.g. 500Mi, 1Gi)" $groupName $evictionHardMemory) }} +{{- end }} +{{- end }} +{{- if not (hasSuffix "%" $evictionSoftMemory) }} +{{- if not (regexMatch `^[0-9]+(\.[0-9]+)?(Ki|Mi|Gi|Ti|Pi|Ei)?$` $evictionSoftMemory) }} +{{- fail (printf "nodeGroup %s: invalid evictionSoftMemory value %q — must be a percentage (e.g. 10%%) or a valid Kubernetes memory quantity (e.g. 500Mi, 1Gi)" $groupName $evictionSoftMemory) }} +{{- end }} +{{- end }} {{- if and (hasSuffix "%" $evictionHardMemory) (hasSuffix "%" $evictionSoftMemory) }} {{- $hardPct := trimSuffix "%" $evictionHardMemory | float64 }} {{- $softPct := trimSuffix "%" $evictionSoftMemory | float64 }} @@ -239,6 +250,16 @@ metadata: {{- else }} {{- fail (printf "nodeGroup %s: evictionHardMemory and evictionSoftMemory must use the same unit type (both percentage or both absolute) — got evictionHardMemory=%s, evictionSoftMemory=%s" $groupName $evictionHardMemory $evictionSoftMemory) }} {{- end }} +{{/* Validate that reserved memory does not exceed effective node memory */}} +{{- if $effectiveMemory }} +{{- $sysBytes := include "cozy-lib.resources.toFloat" $systemReservedMemory | float64 }} +{{- $kubeBytes := include "cozy-lib.resources.toFloat" $kubeReservedMemory | float64 }} +{{- $totalReservedBytes := mulf $sysBytes 1.0 | addf $kubeBytes }} +{{- $effectiveMemBytes := include "cozy-lib.resources.toFloat" $effectiveMemory | float64 }} +{{- if ge $totalReservedBytes $effectiveMemBytes }} +{{- fail (printf "nodeGroup %s: total reserved memory (systemReservedMemory=%s + kubeReservedMemory=%s) must be less than effective node memory (%s)" $groupName $systemReservedMemory $kubeReservedMemory $effectiveMemory) }} +{{- end }} +{{- end }} --- apiVersion: bootstrap.cluster.x-k8s.io/v1beta1 kind: KubeadmConfigTemplate diff --git a/packages/apps/kubernetes/tests/kubelet-reservation_test.yaml b/packages/apps/kubernetes/tests/kubelet-reservation_test.yaml index 2be93f994..2fa3eecce 100644 --- a/packages/apps/kubernetes/tests/kubelet-reservation_test.yaml +++ b/packages/apps/kubernetes/tests/kubelet-reservation_test.yaml @@ -925,3 +925,188 @@ tests: path: spec.template.spec.joinConfiguration.nodeRegistration.kubeletExtraArgs.eviction-soft value: "memory.available<15%" documentIndex: 9 + + ################################################ + # Absolute eviction format validation # + ################################################ + + - it: rejects invalid format for absolute evictionHardMemory + release: + name: test-k8s + namespace: tenant-test + set: + _namespace: + etcd: etcd + ingress: nginx + host: example.com + _cluster: + cluster-domain: cozy.local + nodeGroups: + md0: + minReplicas: 0 + maxReplicas: 10 + instanceType: "" + ephemeralStorage: 20Gi + roles: + - worker + resources: {} + kubelet: + evictionHardMemory: "banana" + evictionSoftMemory: "500Mi" + asserts: + - failedTemplate: + errorMessage: "nodeGroup md0: invalid evictionHardMemory value \"banana\" — must be a percentage (e.g. 7%) or a valid Kubernetes memory quantity (e.g. 500Mi, 1Gi)" + + - it: rejects invalid format for absolute evictionSoftMemory + release: + name: test-k8s + namespace: tenant-test + set: + _namespace: + etcd: etcd + ingress: nginx + host: example.com + _cluster: + cluster-domain: cozy.local + nodeGroups: + md0: + minReplicas: 0 + maxReplicas: 10 + instanceType: "" + ephemeralStorage: 20Gi + roles: + - worker + resources: {} + kubelet: + evictionHardMemory: "200Mi" + evictionSoftMemory: "not-a-quantity" + asserts: + - failedTemplate: + errorMessage: "nodeGroup md0: invalid evictionSoftMemory value \"not-a-quantity\" — must be a percentage (e.g. 10%) or a valid Kubernetes memory quantity (e.g. 500Mi, 1Gi)" + + - it: accepts valid absolute eviction thresholds with correct format + release: + name: test-k8s + namespace: tenant-test + set: + _namespace: + etcd: etcd + ingress: nginx + host: example.com + _cluster: + cluster-domain: cozy.local + nodeGroups: + md0: + minReplicas: 0 + maxReplicas: 10 + instanceType: "" + ephemeralStorage: 20Gi + roles: + - worker + resources: {} + kubelet: + evictionHardMemory: "500Mi" + evictionSoftMemory: "1Gi" + asserts: + - equal: + path: spec.template.spec.joinConfiguration.nodeRegistration.kubeletExtraArgs.eviction-hard + value: "memory.available<500Mi" + documentIndex: 4 + - equal: + path: spec.template.spec.joinConfiguration.nodeRegistration.kubeletExtraArgs.eviction-soft + value: "memory.available<1Gi" + documentIndex: 4 + + #################################################### + # Reserved memory exceeds node memory validation # + #################################################### + + - it: rejects reserved memory exceeding effective node memory + release: + name: test-k8s + namespace: tenant-test + set: + _namespace: + etcd: etcd + ingress: nginx + host: example.com + _cluster: + cluster-domain: cozy.local + nodeGroups: + md0: + minReplicas: 0 + maxReplicas: 10 + instanceType: "" + ephemeralStorage: 20Gi + roles: + - worker + resources: + memory: "1Gi" + kubelet: + systemReservedMemory: "512Mi" + kubeReservedMemory: "600Mi" + asserts: + - failedTemplate: + errorMessage: "nodeGroup md0: total reserved memory (systemReservedMemory=512Mi + kubeReservedMemory=600Mi) must be less than effective node memory (1Gi)" + + - it: rejects reserved memory equal to effective node memory + release: + name: test-k8s + namespace: tenant-test + set: + _namespace: + etcd: etcd + ingress: nginx + host: example.com + _cluster: + cluster-domain: cozy.local + nodeGroups: + md0: + minReplicas: 0 + maxReplicas: 10 + instanceType: "" + ephemeralStorage: 20Gi + roles: + - worker + resources: + memory: "1Gi" + kubelet: + systemReservedMemory: "512Mi" + kubeReservedMemory: "512Mi" + asserts: + - failedTemplate: + errorMessage: "nodeGroup md0: total reserved memory (systemReservedMemory=512Mi + kubeReservedMemory=512Mi) must be less than effective node memory (1Gi)" + + - it: allows reserved memory within effective node memory bounds + release: + name: test-k8s + namespace: tenant-test + set: + _namespace: + etcd: etcd + ingress: nginx + host: example.com + _cluster: + cluster-domain: cozy.local + nodeGroups: + md0: + minReplicas: 0 + maxReplicas: 10 + instanceType: "" + ephemeralStorage: 20Gi + roles: + - worker + resources: + memory: "4Gi" + kubelet: + systemReservedMemory: "512Mi" + kubeReservedMemory: "512Mi" + asserts: + - equal: + path: spec.template.spec.joinConfiguration.nodeRegistration.kubeletExtraArgs.system-reserved + value: "memory=512Mi,cpu=100m" + documentIndex: 4 + - equal: + path: spec.template.spec.joinConfiguration.nodeRegistration.kubeletExtraArgs.kube-reserved + value: "memory=512Mi,cpu=100m" + documentIndex: 4