1
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
45 func ValidateKubeletConfiguration(kc *kubeletconfig.KubeletConfiguration, featureGate featuregate.FeatureGate) error {
46 allErrors := []error{}
47
48
49
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
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
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 }
314
View as plain text