...

Source file src/k8s.io/kubernetes/pkg/kubelet/apis/config/validation/validation.go

Documentation: k8s.io/kubernetes/pkg/kubelet/apis/config/validation

     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  }
   314  

View as plain text