fix(kubernetes): validate reservation formats and reject mixed eviction types

Signed-off-by: Arsolitt <arsolitt@gmail.com>
This commit is contained in:
Arsolitt committed 2026-04-23 17:02:52 +03:00
1 parent fe48c726c8
commit b21dedad64
2 files changed
+220

No files matched your search

@@ -211,6 +211,19 @@ metadata:
{{- $kubeReservedCpu := $kubeletOverride.kubeReservedCpu | default "100m" }}
{{- $evictionHardMemory := $kubeletOverride.evictionHardMemory | default "7%" }}
{{- $evictionSoftMemory := $kubeletOverride.evictionSoftMemory | default "10%" }}
{{/* Validate reservation field formats */}}
{{- if and $kubeletOverride.systemReservedMemory (not (regexMatch `^[0-9]+(\.[0-9]+)?(Ki|Mi|Gi|Ti|Pi|Ei)?$` ($kubeletOverride.systemReservedMemory | toString))) }}
{{- fail (printf "nodeGroup %s: invalid systemReservedMemory value %q — must be a valid Kubernetes memory quantity (e.g. 128Mi, 1Gi)" $groupName ($kubeletOverride.systemReservedMemory | toString)) }}
{{- end }}
{{- if and $kubeletOverride.kubeReservedMemory (not (regexMatch `^[0-9]+(\.[0-9]+)?(Ki|Mi|Gi|Ti|Pi|Ei)?$` ($kubeletOverride.kubeReservedMemory | toString))) }}
{{- fail (printf "nodeGroup %s: invalid kubeReservedMemory value %q — must be a valid Kubernetes memory quantity (e.g. 128Mi, 1Gi)" $groupName ($kubeletOverride.kubeReservedMemory | toString)) }}
{{- end }}
{{- if and $kubeletOverride.systemReservedCpu (not (regexMatch `^[0-9]+(\.[0-9]+)?m?$` ($kubeletOverride.systemReservedCpu | toString))) }}
{{- fail (printf "nodeGroup %s: invalid systemReservedCpu value %q — must be a valid Kubernetes CPU quantity (e.g. 100m, 0.5, 1)" $groupName ($kubeletOverride.systemReservedCpu | toString)) }}
{{- end }}
{{- 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 }}
{{- if and (hasSuffix "%" $evictionHardMemory) (hasSuffix "%" $evictionSoftMemory) }}
{{- $hardPct := trimSuffix "%" $evictionHardMemory | float64 }}
{{- $softPct := trimSuffix "%" $evictionSoftMemory | float64 }}
@@ -223,6 +236,8 @@ metadata:
{{- if ge $hardBytes $softBytes }}
{{- fail (printf "nodeGroup %s: evictionHardMemory (%s) must be strictly less than evictionSoftMemory (%s)" $groupName $evictionHardMemory $evictionSoftMemory) }}
{{- end }}
{{- 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 }}
---
apiVersion: bootstrap.cluster.x-k8s.io/v1beta1
@@ -720,3 +720,208 @@ tests:
path: spec.template.spec.joinConfiguration.nodeRegistration.kubeletExtraArgs.eviction-minimum-reclaim
value: "memory.available=256Mi"
documentIndex: 4
################################################
# Mixed eviction threshold types must be rejected
################################################
- it: rejects mixed eviction types (hard absolute, soft percentage)
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: "1Gi"
evictionSoftMemory: "10%"
asserts:
- failedTemplate:
errorMessage: "nodeGroup md0: evictionHardMemory and evictionSoftMemory must use the same unit type (both percentage or both absolute) — got evictionHardMemory=1Gi, evictionSoftMemory=10%"
- it: rejects mixed eviction types reversed (hard percentage, soft absolute)
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: "10%"
evictionSoftMemory: "1Gi"
asserts:
- failedTemplate:
errorMessage: "nodeGroup md0: evictionHardMemory and evictionSoftMemory must use the same unit type (both percentage or both absolute) — got evictionHardMemory=10%, evictionSoftMemory=1Gi"
################################################
# Reservation field format validation #
################################################
- it: rejects invalid CPU format for kubeReservedCpu
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:
kubeReservedCpu: "banana"
asserts:
- failedTemplate:
errorMessage: "nodeGroup md0: invalid kubeReservedCpu value \"banana\" — must be a valid Kubernetes CPU quantity (e.g. 100m, 0.5, 1)"
- it: rejects invalid memory format for systemReservedMemory
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:
systemReservedMemory: "abc"
asserts:
- failedTemplate:
errorMessage: "nodeGroup md0: invalid systemReservedMemory value \"abc\" — must be a valid Kubernetes memory quantity (e.g. 128Mi, 1Gi)"
################################################
# Multi-nodeGroup isolation test #
################################################
- it: renders independent kubelet args for multiple node groups
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: {}
md1:
minReplicas: 1
maxReplicas: 5
instanceType: ""
ephemeralStorage: 40Gi
roles:
- worker
resources: {}
kubelet:
systemReservedMemory: "512Mi"
kubeReservedMemory: "256Mi"
systemReservedCpu: "200m"
kubeReservedCpu: "150m"
evictionHardMemory: "5%"
evictionSoftMemory: "15%"
asserts:
# md0 at documentIndex 4: defaults
- isKind:
of: KubeadmConfigTemplate
documentIndex: 4
- equal:
path: metadata.name
value: test-k8s-md0
documentIndex: 4
- equal:
path: spec.template.spec.joinConfiguration.nodeRegistration.kubeletExtraArgs.system-reserved
value: "memory=64Mi,cpu=100m"
documentIndex: 4
- equal:
path: spec.template.spec.joinConfiguration.nodeRegistration.kubeletExtraArgs.kube-reserved
value: "memory=64Mi,cpu=100m"
documentIndex: 4
- equal:
path: spec.template.spec.joinConfiguration.nodeRegistration.kubeletExtraArgs.eviction-hard
value: "memory.available<7%"
documentIndex: 4
- equal:
path: spec.template.spec.joinConfiguration.nodeRegistration.kubeletExtraArgs.eviction-soft
value: "memory.available<10%"
documentIndex: 4
# md1 at documentIndex 9: custom overrides
# (each nodeGroup emits 5 documents: KubeadmConfigTemplate, KubevirtMachineTemplate,
# MachineDeployment, MachineHealthCheck, WorkloadMonitor)
- isKind:
of: KubeadmConfigTemplate
documentIndex: 9
- equal:
path: metadata.name
value: test-k8s-md1
documentIndex: 9
- equal:
path: spec.template.spec.joinConfiguration.nodeRegistration.kubeletExtraArgs.system-reserved
value: "memory=512Mi,cpu=200m"
documentIndex: 9
- equal:
path: spec.template.spec.joinConfiguration.nodeRegistration.kubeletExtraArgs.kube-reserved
value: "memory=256Mi,cpu=150m"
documentIndex: 9
- equal:
path: spec.template.spec.joinConfiguration.nodeRegistration.kubeletExtraArgs.eviction-hard
value: "memory.available<5%"
documentIndex: 9
- equal:
path: spec.template.spec.joinConfiguration.nodeRegistration.kubeletExtraArgs.eviction-soft
value: "memory.available<15%"
documentIndex: 9