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