k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/pkg/kubelet/apis/config/validation/validation.go (about) 1 /* 2 Copyright 2017 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package validation 18 19 import ( 20 "fmt" 21 "time" 22 "unicode" 23 24 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 25 utilerrors "k8s.io/apimachinery/pkg/util/errors" 26 utilvalidation "k8s.io/apimachinery/pkg/util/validation" 27 "k8s.io/apimachinery/pkg/util/validation/field" 28 "k8s.io/component-base/featuregate" 29 logsapi "k8s.io/component-base/logs/api/v1" 30 "k8s.io/component-base/metrics" 31 tracingapi "k8s.io/component-base/tracing/api/v1" 32 "k8s.io/kubernetes/pkg/features" 33 kubeletconfig "k8s.io/kubernetes/pkg/kubelet/apis/config" 34 kubetypes "k8s.io/kubernetes/pkg/kubelet/types" 35 utilfs "k8s.io/kubernetes/pkg/util/filesystem" 36 utiltaints "k8s.io/kubernetes/pkg/util/taints" 37 "k8s.io/utils/cpuset" 38 ) 39 40 var ( 41 defaultCFSQuota = metav1.Duration{Duration: 100 * time.Millisecond} 42 ) 43 44 // ValidateKubeletConfiguration validates `kc` and returns an error if it is invalid 45 func ValidateKubeletConfiguration(kc *kubeletconfig.KubeletConfiguration, featureGate featuregate.FeatureGate) error { 46 allErrors := []error{} 47 48 // Make a local copy of the feature gates and combine it with the gates set by this configuration. 49 // This allows us to validate the config against the set of gates it will actually run against. 50 localFeatureGate := featureGate.DeepCopy() 51 if err := localFeatureGate.SetFromMap(kc.FeatureGates); err != nil { 52 return err 53 } 54 55 if kc.NodeLeaseDurationSeconds <= 0 { 56 allErrors = append(allErrors, fmt.Errorf("invalid configuration: nodeLeaseDurationSeconds must be greater than 0")) 57 } 58 if !kc.CgroupsPerQOS && len(kc.EnforceNodeAllocatable) > 0 { 59 allErrors = append(allErrors, fmt.Errorf("invalid configuration: enforceNodeAllocatable (--enforce-node-allocatable) is not supported unless cgroupsPerQOS (--cgroups-per-qos) is set to true")) 60 } 61 if kc.SystemCgroups != "" && kc.CgroupRoot == "" { 62 allErrors = append(allErrors, fmt.Errorf("invalid configuration: systemCgroups (--system-cgroups) was specified and cgroupRoot (--cgroup-root) was not specified")) 63 } 64 if kc.EventBurst < 0 { 65 allErrors = append(allErrors, fmt.Errorf("invalid configuration: eventBurst (--event-burst) %v must not be a negative number", kc.EventBurst)) 66 } 67 if kc.EventRecordQPS < 0 { 68 allErrors = append(allErrors, fmt.Errorf("invalid configuration: eventRecordQPS (--event-qps) %v must not be a negative number", kc.EventRecordQPS)) 69 } 70 if kc.HealthzPort != 0 && utilvalidation.IsValidPortNum(int(kc.HealthzPort)) != nil { 71 allErrors = append(allErrors, fmt.Errorf("invalid configuration: healthzPort (--healthz-port) %v must be between 1 and 65535, inclusive", kc.HealthzPort)) 72 } 73 if !localFeatureGate.Enabled(features.CPUCFSQuotaPeriod) && kc.CPUCFSQuotaPeriod != defaultCFSQuota { 74 allErrors = append(allErrors, fmt.Errorf("invalid configuration: cpuCFSQuotaPeriod (--cpu-cfs-quota-period) %v requires feature gate CustomCPUCFSQuotaPeriod", kc.CPUCFSQuotaPeriod)) 75 } 76 if localFeatureGate.Enabled(features.CPUCFSQuotaPeriod) && utilvalidation.IsInRange(int(kc.CPUCFSQuotaPeriod.Duration), int(1*time.Millisecond), int(time.Second)) != nil { 77 allErrors = append(allErrors, fmt.Errorf("invalid configuration: cpuCFSQuotaPeriod (--cpu-cfs-quota-period) %v must be between 1ms and 1sec, inclusive", kc.CPUCFSQuotaPeriod)) 78 } 79 if utilvalidation.IsInRange(int(kc.ImageGCHighThresholdPercent), 0, 100) != nil { 80 allErrors = append(allErrors, fmt.Errorf("invalid configuration: imageGCHighThresholdPercent (--image-gc-high-threshold) %v must be between 0 and 100, inclusive", kc.ImageGCHighThresholdPercent)) 81 } 82 if utilvalidation.IsInRange(int(kc.ImageGCLowThresholdPercent), 0, 100) != nil { 83 allErrors = append(allErrors, fmt.Errorf("invalid configuration: imageGCLowThresholdPercent (--image-gc-low-threshold) %v must be between 0 and 100, inclusive", kc.ImageGCLowThresholdPercent)) 84 } 85 if kc.ImageGCLowThresholdPercent >= kc.ImageGCHighThresholdPercent { 86 allErrors = append(allErrors, fmt.Errorf("invalid configuration: imageGCLowThresholdPercent (--image-gc-low-threshold) %v must be less than imageGCHighThresholdPercent (--image-gc-high-threshold) %v", kc.ImageGCLowThresholdPercent, kc.ImageGCHighThresholdPercent)) 87 } 88 if kc.ImageMaximumGCAge.Duration != 0 && !localFeatureGate.Enabled(features.ImageMaximumGCAge) { 89 allErrors = append(allErrors, fmt.Errorf("invalid configuration: ImageMaximumGCAge feature gate is required for Kubelet configuration option imageMaximumGCAge")) 90 } 91 if kc.ImageMaximumGCAge.Duration < 0 { 92 allErrors = append(allErrors, fmt.Errorf("invalid configuration: imageMaximumGCAge %v must not be negative", kc.ImageMaximumGCAge.Duration)) 93 } 94 if kc.ImageMaximumGCAge.Duration > 0 && kc.ImageMaximumGCAge.Duration <= kc.ImageMinimumGCAge.Duration { 95 allErrors = append(allErrors, fmt.Errorf("invalid configuration: imageMaximumGCAge %v must be greater than imageMinimumGCAge %v", kc.ImageMaximumGCAge.Duration, kc.ImageMinimumGCAge.Duration)) 96 } 97 if utilvalidation.IsInRange(int(kc.IPTablesDropBit), 0, 31) != nil { 98 allErrors = append(allErrors, fmt.Errorf("invalid configuration: iptablesDropBit (--iptables-drop-bit) %v must be between 0 and 31, inclusive", kc.IPTablesDropBit)) 99 } 100 if utilvalidation.IsInRange(int(kc.IPTablesMasqueradeBit), 0, 31) != nil { 101 allErrors = append(allErrors, fmt.Errorf("invalid configuration: iptablesMasqueradeBit (--iptables-masquerade-bit) %v must be between 0 and 31, inclusive", kc.IPTablesMasqueradeBit)) 102 } 103 if kc.KubeAPIBurst < 0 { 104 allErrors = append(allErrors, fmt.Errorf("invalid configuration: kubeAPIBurst (--kube-api-burst) %v must not be a negative number", kc.KubeAPIBurst)) 105 } 106 if kc.KubeAPIQPS < 0 { 107 allErrors = append(allErrors, fmt.Errorf("invalid configuration: kubeAPIQPS (--kube-api-qps) %v must not be a negative number", kc.KubeAPIQPS)) 108 } 109 if kc.NodeStatusMaxImages < -1 { 110 allErrors = append(allErrors, fmt.Errorf("invalid configuration: nodeStatusMaxImages (--node-status-max-images) %v must be -1 or greater", kc.NodeStatusMaxImages)) 111 } 112 if kc.MaxOpenFiles < 0 { 113 allErrors = append(allErrors, fmt.Errorf("invalid configuration: maxOpenFiles (--max-open-files) %v must not be a negative number", kc.MaxOpenFiles)) 114 } 115 if kc.MaxPods < 0 { 116 allErrors = append(allErrors, fmt.Errorf("invalid configuration: maxPods (--max-pods) %v must not be a negative number", kc.MaxPods)) 117 } 118 if utilvalidation.IsInRange(int(kc.OOMScoreAdj), -1000, 1000) != nil { 119 allErrors = append(allErrors, fmt.Errorf("invalid configuration: oomScoreAdj (--oom-score-adj) %v must be between -1000 and 1000, inclusive", kc.OOMScoreAdj)) 120 } 121 if kc.PodsPerCore < 0 { 122 allErrors = append(allErrors, fmt.Errorf("invalid configuration: podsPerCore (--pods-per-core) %v must not be a negative number", kc.PodsPerCore)) 123 } 124 if utilvalidation.IsValidPortNum(int(kc.Port)) != nil { 125 allErrors = append(allErrors, fmt.Errorf("invalid configuration: port (--port) %v must be between 1 and 65535, inclusive", kc.Port)) 126 } 127 if kc.ReadOnlyPort != 0 && utilvalidation.IsValidPortNum(int(kc.ReadOnlyPort)) != nil { 128 allErrors = append(allErrors, fmt.Errorf("invalid configuration: readOnlyPort (--read-only-port) %v must be between 0 and 65535, inclusive", kc.ReadOnlyPort)) 129 } 130 if kc.RegistryBurst < 0 { 131 allErrors = append(allErrors, fmt.Errorf("invalid configuration: registryBurst (--registry-burst) %v must not be a negative number", kc.RegistryBurst)) 132 } 133 if kc.RegistryPullQPS < 0 { 134 allErrors = append(allErrors, fmt.Errorf("invalid configuration: registryPullQPS (--registry-qps) %v must not be a negative number", kc.RegistryPullQPS)) 135 } 136 if kc.MaxParallelImagePulls != nil && *kc.MaxParallelImagePulls < 1 { 137 allErrors = append(allErrors, fmt.Errorf("invalid configuration: maxParallelImagePulls %v must be a positive number", *kc.MaxParallelImagePulls)) 138 } 139 if kc.SerializeImagePulls && kc.MaxParallelImagePulls != nil && *kc.MaxParallelImagePulls > 1 { 140 allErrors = append(allErrors, fmt.Errorf("invalid configuration: maxParallelImagePulls cannot be larger than 1 unless SerializeImagePulls (--serialize-image-pulls) is set to false")) 141 } 142 if kc.ServerTLSBootstrap && !localFeatureGate.Enabled(features.RotateKubeletServerCertificate) { 143 allErrors = append(allErrors, fmt.Errorf("invalid configuration: serverTLSBootstrap %v requires feature gate RotateKubeletServerCertificate", kc.ServerTLSBootstrap)) 144 } 145 146 for _, nodeTaint := range kc.RegisterWithTaints { 147 if err := utiltaints.CheckTaintValidation(nodeTaint); err != nil { 148 allErrors = append(allErrors, fmt.Errorf("invalid taint: %v", nodeTaint)) 149 } 150 if nodeTaint.TimeAdded != nil { 151 allErrors = append(allErrors, fmt.Errorf("invalid configuration: taint.TimeAdded is not nil")) 152 } 153 } 154 155 switch kc.TopologyManagerPolicy { 156 case kubeletconfig.NoneTopologyManagerPolicy: 157 case kubeletconfig.BestEffortTopologyManagerPolicy: 158 case kubeletconfig.RestrictedTopologyManagerPolicy: 159 case kubeletconfig.SingleNumaNodeTopologyManagerPolicy: 160 default: 161 allErrors = append(allErrors, fmt.Errorf("invalid configuration: topologyManagerPolicy (--topology-manager-policy) %q must be one of: %q", kc.TopologyManagerPolicy, []string{kubeletconfig.NoneTopologyManagerPolicy, kubeletconfig.BestEffortTopologyManagerPolicy, kubeletconfig.RestrictedTopologyManagerPolicy, kubeletconfig.SingleNumaNodeTopologyManagerPolicy})) 162 } 163 164 switch kc.TopologyManagerScope { 165 case kubeletconfig.ContainerTopologyManagerScope: 166 case kubeletconfig.PodTopologyManagerScope: 167 default: 168 allErrors = append(allErrors, fmt.Errorf("invalid configuration: topologyManagerScope (--topology-manager-scope) %q must be one of: %q, or %q", kc.TopologyManagerScope, kubeletconfig.ContainerTopologyManagerScope, kubeletconfig.PodTopologyManagerScope)) 169 } 170 171 if localFeatureGate.Enabled(features.GracefulNodeShutdown) { 172 if kc.ShutdownGracePeriodCriticalPods.Duration > kc.ShutdownGracePeriod.Duration { 173 allErrors = append(allErrors, fmt.Errorf("invalid configuration: shutdownGracePeriodCriticalPods %v must be <= shutdownGracePeriod %v", kc.ShutdownGracePeriodCriticalPods, kc.ShutdownGracePeriod)) 174 } 175 if kc.ShutdownGracePeriod.Duration < 0 || (kc.ShutdownGracePeriod.Duration > 0 && kc.ShutdownGracePeriod.Duration < time.Second) { 176 allErrors = append(allErrors, fmt.Errorf("invalid configuration: shutdownGracePeriod %v must be either zero or otherwise >= 1 sec", kc.ShutdownGracePeriod)) 177 } 178 if kc.ShutdownGracePeriodCriticalPods.Duration < 0 || (kc.ShutdownGracePeriodCriticalPods.Duration > 0 && kc.ShutdownGracePeriodCriticalPods.Duration < time.Second) { 179 allErrors = append(allErrors, fmt.Errorf("invalid configuration: shutdownGracePeriodCriticalPods %v must be either zero or otherwise >= 1 sec", kc.ShutdownGracePeriodCriticalPods)) 180 } 181 } 182 if (kc.ShutdownGracePeriod.Duration > 0 || kc.ShutdownGracePeriodCriticalPods.Duration > 0) && !localFeatureGate.Enabled(features.GracefulNodeShutdown) { 183 allErrors = append(allErrors, fmt.Errorf("invalid configuration: specifying shutdownGracePeriod or shutdownGracePeriodCriticalPods requires feature gate GracefulNodeShutdown")) 184 } 185 if localFeatureGate.Enabled(features.GracefulNodeShutdownBasedOnPodPriority) { 186 if len(kc.ShutdownGracePeriodByPodPriority) != 0 && (kc.ShutdownGracePeriod.Duration > 0 || kc.ShutdownGracePeriodCriticalPods.Duration > 0) { 187 allErrors = append(allErrors, fmt.Errorf("invalid configuration: Cannot specify both shutdownGracePeriodByPodPriority and shutdownGracePeriod at the same time")) 188 } 189 } 190 if !localFeatureGate.Enabled(features.GracefulNodeShutdownBasedOnPodPriority) { 191 if len(kc.ShutdownGracePeriodByPodPriority) != 0 { 192 allErrors = append(allErrors, fmt.Errorf("invalid configuration: Specifying shutdownGracePeriodByPodPriority requires feature gate GracefulNodeShutdownBasedOnPodPriority")) 193 } 194 } 195 if localFeatureGate.Enabled(features.NodeSwap) { 196 switch kc.MemorySwap.SwapBehavior { 197 case "": 198 case kubetypes.NoSwap: 199 case kubetypes.LimitedSwap: 200 default: 201 allErrors = append(allErrors, fmt.Errorf("invalid configuration: memorySwap.swapBehavior %q must be one of: \"\", %q or %q", kc.MemorySwap.SwapBehavior, kubetypes.LimitedSwap, kubetypes.NoSwap)) 202 } 203 } 204 if !localFeatureGate.Enabled(features.NodeSwap) && kc.MemorySwap != (kubeletconfig.MemorySwapConfiguration{}) { 205 allErrors = append(allErrors, fmt.Errorf("invalid configuration: memorySwap.swapBehavior cannot be set when NodeSwap feature flag is disabled")) 206 } 207 208 for _, val := range kc.EnforceNodeAllocatable { 209 switch val { 210 case kubetypes.NodeAllocatableEnforcementKey: 211 case kubetypes.SystemReservedEnforcementKey: 212 if kc.SystemReservedCgroup == "" { 213 allErrors = append(allErrors, fmt.Errorf("invalid configuration: systemReservedCgroup (--system-reserved-cgroup) must be specified when %q contained in enforceNodeAllocatable (--enforce-node-allocatable)", kubetypes.SystemReservedEnforcementKey)) 214 } 215 case kubetypes.KubeReservedEnforcementKey: 216 if kc.KubeReservedCgroup == "" { 217 allErrors = append(allErrors, fmt.Errorf("invalid configuration: kubeReservedCgroup (--kube-reserved-cgroup) must be specified when %q contained in enforceNodeAllocatable (--enforce-node-allocatable)", kubetypes.KubeReservedEnforcementKey)) 218 } 219 case kubetypes.NodeAllocatableNoneKey: 220 if len(kc.EnforceNodeAllocatable) > 1 { 221 allErrors = append(allErrors, fmt.Errorf("invalid configuration: enforceNodeAllocatable (--enforce-node-allocatable) may not contain additional enforcements when %q is specified", kubetypes.NodeAllocatableNoneKey)) 222 } 223 default: 224 allErrors = append(allErrors, fmt.Errorf("invalid configuration: option %q specified for enforceNodeAllocatable (--enforce-node-allocatable). Valid options are %q, %q, %q, or %q", 225 val, kubetypes.NodeAllocatableEnforcementKey, kubetypes.SystemReservedEnforcementKey, kubetypes.KubeReservedEnforcementKey, kubetypes.NodeAllocatableNoneKey)) 226 } 227 } 228 switch kc.HairpinMode { 229 case kubeletconfig.HairpinNone: 230 case kubeletconfig.HairpinVeth: 231 case kubeletconfig.PromiscuousBridge: 232 default: 233 allErrors = append(allErrors, fmt.Errorf("invalid configuration: option %q specified for hairpinMode (--hairpin-mode). Valid options are %q, %q or %q", 234 kc.HairpinMode, kubeletconfig.HairpinNone, kubeletconfig.HairpinVeth, kubeletconfig.PromiscuousBridge)) 235 } 236 if kc.ReservedSystemCPUs != "" { 237 // --reserved-cpus does not support --system-reserved-cgroup or --kube-reserved-cgroup 238 if kc.SystemReservedCgroup != "" || kc.KubeReservedCgroup != "" { 239 allErrors = append(allErrors, fmt.Errorf("invalid configuration: can't use reservedSystemCPUs (--reserved-cpus) with systemReservedCgroup (--system-reserved-cgroup) or kubeReservedCgroup (--kube-reserved-cgroup)")) 240 } 241 if _, err := cpuset.Parse(kc.ReservedSystemCPUs); err != nil { 242 allErrors = append(allErrors, fmt.Errorf("invalid configuration: unable to parse reservedSystemCPUs (--reserved-cpus) %v, error: %w", kc.ReservedSystemCPUs, err)) 243 } 244 } 245 246 allErrors = append(allErrors, validateReservedMemoryConfiguration(kc)...) 247 248 if err := validateKubeletOSConfiguration(kc); err != nil { 249 allErrors = append(allErrors, err) 250 } 251 allErrors = append(allErrors, metrics.ValidateShowHiddenMetricsVersion(kc.ShowHiddenMetricsForVersion)...) 252 253 if errs := logsapi.Validate(&kc.Logging, localFeatureGate, field.NewPath("logging")); len(errs) > 0 { 254 allErrors = append(allErrors, errs.ToAggregate().Errors()...) 255 } 256 257 if localFeatureGate.Enabled(features.KubeletTracing) { 258 if errs := tracingapi.ValidateTracingConfiguration(kc.Tracing, localFeatureGate, field.NewPath("tracing")); len(errs) > 0 { 259 allErrors = append(allErrors, errs.ToAggregate().Errors()...) 260 } 261 } else if kc.Tracing != nil { 262 allErrors = append(allErrors, fmt.Errorf("invalid configuration: tracing should not be configured if KubeletTracing feature flag is disabled.")) 263 } 264 265 if localFeatureGate.Enabled(features.MemoryQoS) && kc.MemoryThrottlingFactor == nil { 266 allErrors = append(allErrors, fmt.Errorf("invalid configuration: memoryThrottlingFactor is required when MemoryQoS feature flag is enabled")) 267 } 268 if kc.MemoryThrottlingFactor != nil && (*kc.MemoryThrottlingFactor <= 0 || *kc.MemoryThrottlingFactor > 1.0) { 269 allErrors = append(allErrors, fmt.Errorf("invalid configuration: memoryThrottlingFactor %v must be greater than 0 and less than or equal to 1.0", *kc.MemoryThrottlingFactor)) 270 } 271 272 if kc.ContainerRuntimeEndpoint == "" { 273 allErrors = append(allErrors, fmt.Errorf("invalid configuration: the containerRuntimeEndpoint was not specified or empty")) 274 } 275 276 if kc.EnableSystemLogQuery && !localFeatureGate.Enabled(features.NodeLogQuery) { 277 allErrors = append(allErrors, fmt.Errorf("invalid configuration: NodeLogQuery feature gate is required for enableSystemLogHandler")) 278 } 279 if kc.EnableSystemLogQuery && !kc.EnableSystemLogHandler { 280 allErrors = append(allErrors, 281 fmt.Errorf("invalid configuration: enableSystemLogHandler is required for enableSystemLogQuery")) 282 } 283 284 if kc.ContainerLogMaxWorkers < 1 { 285 allErrors = append(allErrors, fmt.Errorf("invalid configuration: containerLogMaxWorkers must be greater than or equal to 1")) 286 } 287 288 if kc.ContainerLogMonitorInterval.Duration.Seconds() < 3 { 289 allErrors = append(allErrors, fmt.Errorf("invalid configuration: containerLogMonitorInterval must be a positive time duration greater than or equal to 3s")) 290 } 291 292 if kc.PodLogsDir == "" { 293 allErrors = append(allErrors, fmt.Errorf("invalid configuration: podLogsDir was not specified")) 294 } 295 296 if !utilfs.IsAbs(kc.PodLogsDir) { 297 allErrors = append(allErrors, fmt.Errorf("invalid configuration: pod logs path %q must be absolute path", kc.PodLogsDir)) 298 } 299 300 if !utilfs.IsPathClean(kc.PodLogsDir) { 301 allErrors = append(allErrors, fmt.Errorf("invalid configuration: pod logs path %q must be normalized", kc.PodLogsDir)) 302 } 303 304 // Since pod logs path is used in metrics, make sure it contains only ASCII characters. 305 for _, c := range kc.PodLogsDir { 306 if c > unicode.MaxASCII { 307 allErrors = append(allErrors, fmt.Errorf("invalid configuration: pod logs path %q mut contains ASCII characters only", kc.PodLogsDir)) 308 break 309 } 310 } 311 312 return utilerrors.NewAggregate(allErrors) 313 }