ValidationOptions

ValidationOptions

预备方法

检查 Huge Pages 的 usesIndivisibleHugePagesValues() 方法

如果其中一个容器使用非整数倍的 huge page 单位大小,则 usesIndivisibleHugePagesValues() 方法 返回 true

// usesIndivisibleHugePagesValues returns true if the one of the containers uses non-integer multiple
// of huge page unit size
func usesIndivisibleHugePagesValues(podSpec *api.PodSpec) bool {
	// 初始化一个布尔变量,用于记录是否找到非整数倍的 huge page 单位大小
	foundIndivisibleHugePagesValue := false
	// 遍历 podSpec 中的所有容器
	VisitContainers(podSpec, AllContainers, func(c *api.Container, containerType ContainerType) bool {
		// 检查容器是否使用非整数倍的 huge page 单位大小
		if checkContainerUseIndivisibleHugePagesValues(*c) {
			// 如果找到,则将 foundIndivisibleHugePagesValue 设置为 true
			foundIndivisibleHugePagesValue = true
		}
		// 如果还没有找到非整数倍的 huge page 单位大小,则继续遍历
		return !foundIndivisibleHugePagesValue // continue visiting if we haven't seen an invalid value yet
	})

	// 如果找到非整数倍的 huge page 单位大小,则返回 true
	if foundIndivisibleHugePagesValue {
		return true
	}

	// 游历检查 pod spec 中的 Overhead
	for resourceName, quantity := range podSpec.Overhead {
		if helper.IsHugePageResourceName(resourceName) {
			if !helper.IsHugePageResourceValueDivisible(resourceName, quantity) {
				return true
			}
		}
	}

	return false
}

checkContainerUseIndivisibleHugePagesValues() 方法

func checkContainerUseIndivisibleHugePagesValues(container api.Container) bool {
	for resourceName, quantity := range container.Resources.Limits {
		if helper.IsHugePageResourceName(resourceName) {
			if !helper.IsHugePageResourceValueDivisible(resourceName, quantity) {
				return true
			}
		}
	}

	for resourceName, quantity := range container.Resources.Requests {
		if helper.IsHugePageResourceName(resourceName) {
			if !helper.IsHugePageResourceValueDivisible(resourceName, quantity) {
				return true
			}
		}
	}

	return false
}

检查 Topology Spread Constraints 的 hasInvalidTopologySpreadConstraintLabelSelector() 方法

hasInvalidTopologySpreadConstraintLabelSelector 如果 spec.TopologySpreadConstraints 有任何标签选择器无效的条目,则返回 true

// hasInvalidTopologySpreadConstraintLabelSelector return true if spec.TopologySpreadConstraints have any entry with invalid labelSelector
func hasInvalidTopologySpreadConstraintLabelSelector(spec *api.PodSpec) bool {
	for _, constraint := range spec.TopologySpreadConstraints {
		errs := metavalidation.ValidateLabelSelector(constraint.LabelSelector, metavalidation.LabelSelectorValidationOptions{AllowInvalidLabelValueInSelector: false}, nil)
		if len(errs) != 0 {
			return true
		}
	}
	return false
}

TopologySpreadConstraints = 拓扑扩展限制

检查 Volumes 的 hasNonLocalProjectedTokenPath() 方法

hasNonLocalProjectedTokenPath 如果 spec.Volumes 有任何条目具有非本地投影标记路径(non-local projected token path),则返回 true

// hasNonLocalProjectedTokenPath return true if spec.Volumes have any entry with non-local projected token path
func hasNonLocalProjectedTokenPath(spec *api.PodSpec) bool {
	for _, volume := range spec.Volumes {
		if volume.Projected != nil {
			for _, source := range volume.Projected.Sources {
				if source.ServiceAccountToken == nil {
					continue
				}
				errs := apivalidation.ValidateLocalNonReservedPath(source.ServiceAccountToken.Path, nil)
				if len(errs) != 0 {
					return true
				}
			}
		}
	}
	return false
}

GetValidationOptionsFromPodSpecAndMeta() 方法

GetValidationOptionsFromPodSpecAndMeta() 方法 根据 pod spec和元数据返回验证选项:

// GetValidationOptionsFromPodSpecAndMeta returns validation options based on pod specs and metadata
func GetValidationOptionsFromPodSpecAndMeta(podSpec, oldPodSpec *api.PodSpec, podMeta, oldPodMeta *metav1.ObjectMeta) apivalidation.PodValidationOptions {
	// 默认的 pod 验证选项基于 feature gate
	opts := apivalidation.PodValidationOptions{
		AllowInvalidPodDeletionCost: !utilfeature.DefaultFeatureGate.Enabled(features.PodDeletionCost),
		// Do not allow pod spec to use non-integer multiple of huge page unit size default
		AllowIndivisibleHugePagesValues:                   false,
		AllowInvalidLabelValueInSelector:                  false,
		AllowInvalidTopologySpreadConstraintLabelSelector: false,
		AllowNamespacedSysctlsForHostNetAndHostIPC:        false,
		AllowNonLocalProjectedTokenPath:                   false,
		AllowPodLifecycleSleepActionZeroValue:             utilfeature.DefaultFeatureGate.Enabled(features.PodLifecycleSleepActionAllowZero),
		PodLevelResourcesEnabled:                          utilfeature.DefaultFeatureGate.Enabled(features.PodLevelResources),
		AllowInvalidLabelValueInRequiredNodeAffinity:      false,
		AllowSidecarResizePolicy:                          utilfeature.DefaultFeatureGate.Enabled(features.InPlacePodVerticalScaling),
	}

	// 如果旧的 spec 使用宽松的验证或启用了 RelaxedEnvironmentVariableValidation feature gate,
	// 我们必须允许它
	opts.AllowRelaxedEnvironmentVariableValidation = useRelaxedEnvironmentVariableValidation(podSpec, oldPodSpec)
	opts.AllowRelaxedDNSSearchValidation = useRelaxedDNSSearchValidation(oldPodSpec)

	opts.AllowOnlyRecursiveSELinuxChangePolicy = useOnlyRecursiveSELinuxChangePolicy(oldPodSpec)

	if oldPodSpec != nil {
		// 如果旧的 spec 使用非整数倍的 huge page 单位大小,我们必须允许它
		opts.AllowIndivisibleHugePagesValues = usesIndivisibleHugePagesValues(oldPodSpec)

		opts.AllowInvalidLabelValueInSelector = hasInvalidLabelValueInAffinitySelector(oldPodSpec)
		opts.AllowInvalidLabelValueInRequiredNodeAffinity = hasInvalidLabelValueInRequiredNodeAffinity(oldPodSpec)
		// 如果旧的 spec 有无效的标签选择器在 topologySpreadConstraint,我们必须允许它
		opts.AllowInvalidTopologySpreadConstraintLabelSelector = hasInvalidTopologySpreadConstraintLabelSelector(oldPodSpec)
		// 如果旧的 spec 有无效的投影标记卷路径,我们必须允许它
		opts.AllowNonLocalProjectedTokenPath = hasNonLocalProjectedTokenPath(oldPodSpec)

		// 如果旧的 spec 有无效的 sysctl 与 hostNet 或 hostIPC,我们必须允许它在更新时
		if oldPodSpec.SecurityContext != nil && len(oldPodSpec.SecurityContext.Sysctls) != 0 {
			for _, s := range oldPodSpec.SecurityContext.Sysctls {
				err := apivalidation.ValidateHostSysctl(s.Name, oldPodSpec.SecurityContext, nil)
				if err != nil {
					opts.AllowNamespacedSysctlsForHostNetAndHostIPC = true
					break
				}
			}
		}

		opts.AllowPodLifecycleSleepActionZeroValue = opts.AllowPodLifecycleSleepActionZeroValue || podLifecycleSleepActionZeroValueInUse(oldPodSpec)
		// 如果旧的 pod 有在可重启的 init 容器上设置的 resize 策略,我们必须允许它
		opts.AllowSidecarResizePolicy = opts.AllowSidecarResizePolicy || hasRestartableInitContainerResizePolicy(oldPodSpec)
	}
	if oldPodMeta != nil && !opts.AllowInvalidPodDeletionCost {
		// 这是一个更新,所以只验证现有的对象是否有效
		_, err := helper.GetDeletionCostFromPodAnnotations(oldPodMeta.Annotations)
		opts.AllowInvalidPodDeletionCost = err != nil
	}

	return opts
}

useRelaxedEnvironmentVariableValidation() 方法, 如果旧的 spec 使用宽松的验证或启用了 RelaxedEnvironmentVariableValidation feature gate,则返回 true

func useRelaxedEnvironmentVariableValidation(podSpec, oldPodSpec *api.PodSpec) bool {
	if utilfeature.DefaultFeatureGate.Enabled(features.RelaxedEnvironmentVariableValidation) {
		return true
	}

	var oldPodEnvVarNames, podEnvVarNames sets.Set[string]
	if oldPodSpec != nil {
		oldPodEnvVarNames = gatherPodEnvVarNames(oldPodSpec)
	}

	if podSpec != nil {
		podEnvVarNames = gatherPodEnvVarNames(podSpec)
	}

	for env := range podEnvVarNames {
		if relaxedEnvVarUsed(env, oldPodEnvVarNames) {
			return true
		}
	}

	return false
}

useRelaxedDNSSearchValidation() 方法, 如果旧的 spec 使用宽松的验证或启用了 RelaxedDNSSearchValidation feature gate,则返回 true

func useRelaxedDNSSearchValidation(oldPodSpec *api.PodSpec) bool {
	// Return true early if feature gate is enabled
	if utilfeature.DefaultFeatureGate.Enabled(features.RelaxedDNSSearchValidation) {
		return true
	}

	// Return false early if there is no DNSConfig or Searches.
	if oldPodSpec == nil || oldPodSpec.DNSConfig == nil || oldPodSpec.DNSConfig.Searches == nil {
		return false
	}

	return hasDotOrUnderscore(oldPodSpec.DNSConfig.Searches)
}

hasDotOrUnderscore() 方法检查是否存在点或下划线:

// Helper function to check if any domain is a dot or contains an underscore.
func hasDotOrUnderscore(searches []string) bool {
	for _, domain := range searches {
		if domain == "." || strings.Contains(domain, "_") {
			return true
		}
	}
	return false
}

gatherPodEnvVarNames() 方法收集 pod 环境变量名称:

func gatherPodEnvVarNames(podSpec *api.PodSpec) sets.Set[string] {
	podEnvVarNames := sets.Set[string]{}

	for _, c := range podSpec.Containers {
		for _, env := range c.Env {
			podEnvVarNames.Insert(env.Name)
		}

		for _, env := range c.EnvFrom {
			podEnvVarNames.Insert(env.Prefix)
		}
	}

	for _, c := range podSpec.InitContainers {
		for _, env := range c.Env {
			podEnvVarNames.Insert(env.Name)
		}

		for _, env := range c.EnvFrom {
			podEnvVarNames.Insert(env.Prefix)
		}
	}

	for _, c := range podSpec.EphemeralContainers {
		for _, env := range c.Env {
			podEnvVarNames.Insert(env.Name)
		}

		for _, env := range c.EnvFrom {
			podEnvVarNames.Insert(env.Prefix)
		}
	}

	return podEnvVarNames
}

relaxedEnvVarUsed() 检查是否使用宽松的环境变量名称:

func relaxedEnvVarUsed(name string, oldPodEnvVarNames sets.Set[string]) bool {
	// A length of 0 means this is not an update request,
	// or the old pod does not exist in the env.
	// We will let the feature gate decide whether to use relaxed rules.
	if oldPodEnvVarNames.Len() == 0 {
		return false
	}

	if len(validation.IsEnvVarName(name)) == 0 || len(validation.IsRelaxedEnvVarName(name)) != 0 {
		// It's either a valid name by strict rules or an invalid name under relaxed rules.
		// Either way, we'll use strict rules to validate.
		return false
	}

	// The name in question failed strict rules but passed relaxed rules.
	if oldPodEnvVarNames.Has(name) {
		// This relaxed-rules name was already in use.
		return true
	}

	return false
}

GetValidationOptionsFromPodTemplate() 方法

GetValidationOptionsFromPodTemplate() 方法 将返回指定模板的 pod 验证选项。

// GetValidationOptionsFromPodTemplate will return pod validation options for specified template.
func GetValidationOptionsFromPodTemplate(podTemplate, oldPodTemplate *api.PodTemplateSpec) apivalidation.PodValidationOptions {
	var newPodSpec, oldPodSpec *api.PodSpec
	var newPodMeta, oldPodMeta *metav1.ObjectMeta
	// 我们必须小心这里关于 nil 指针的问题
	// 特别是 replication controller 容易传递 nil
	if podTemplate != nil {
		newPodSpec = &podTemplate.Spec
		newPodMeta = &podTemplate.ObjectMeta
	}
	if oldPodTemplate != nil {
		oldPodSpec = &oldPodTemplate.Spec
		oldPodMeta = &oldPodTemplate.ObjectMeta
	}
	return GetValidationOptionsFromPodSpecAndMeta(newPodSpec, oldPodSpec, newPodMeta, oldPodMeta)
}