mirror of
				https://github.com/k3s-io/kubernetes.git
				synced 2025-10-31 05:40:42 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			357 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			357 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| /*
 | |
| Copyright 2021 The Kubernetes Authors.
 | |
| 
 | |
| Licensed under the Apache License, Version 2.0 (the "License");
 | |
| you may not use this file except in compliance with the License.
 | |
| You may obtain a copy of the License at
 | |
| 
 | |
|     http://www.apache.org/licenses/LICENSE-2.0
 | |
| 
 | |
| Unless required by applicable law or agreed to in writing, software
 | |
| distributed under the License is distributed on an "AS IS" BASIS,
 | |
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | |
| See the License for the specific language governing permissions and
 | |
| limitations under the License.
 | |
| */
 | |
| 
 | |
| package pod
 | |
| 
 | |
| import (
 | |
| 	"context"
 | |
| 	"fmt"
 | |
| 	"strings"
 | |
| 
 | |
| 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 | |
| 	"k8s.io/apimachinery/pkg/util/sets"
 | |
| 	"k8s.io/apimachinery/pkg/util/validation/field"
 | |
| 	nodeapi "k8s.io/kubernetes/pkg/api/node"
 | |
| 	pvcutil "k8s.io/kubernetes/pkg/api/persistentvolumeclaim"
 | |
| 	api "k8s.io/kubernetes/pkg/apis/core"
 | |
| 	"k8s.io/kubernetes/pkg/apis/core/pods"
 | |
| )
 | |
| 
 | |
| func GetWarningsForPod(ctx context.Context, pod, oldPod *api.Pod) []string {
 | |
| 	if pod == nil {
 | |
| 		return nil
 | |
| 	}
 | |
| 
 | |
| 	var (
 | |
| 		oldSpec *api.PodSpec
 | |
| 		oldMeta *metav1.ObjectMeta
 | |
| 	)
 | |
| 	if oldPod != nil {
 | |
| 		oldSpec = &oldPod.Spec
 | |
| 		oldMeta = &oldPod.ObjectMeta
 | |
| 	}
 | |
| 	return warningsForPodSpecAndMeta(nil, &pod.Spec, &pod.ObjectMeta, oldSpec, oldMeta)
 | |
| }
 | |
| 
 | |
| func GetWarningsForPodTemplate(ctx context.Context, fieldPath *field.Path, podTemplate, oldPodTemplate *api.PodTemplateSpec) []string {
 | |
| 	if podTemplate == nil {
 | |
| 		return nil
 | |
| 	}
 | |
| 
 | |
| 	var (
 | |
| 		oldSpec *api.PodSpec
 | |
| 		oldMeta *metav1.ObjectMeta
 | |
| 	)
 | |
| 	if oldPodTemplate != nil {
 | |
| 		oldSpec = &oldPodTemplate.Spec
 | |
| 		oldMeta = &oldPodTemplate.ObjectMeta
 | |
| 	}
 | |
| 	return warningsForPodSpecAndMeta(fieldPath, &podTemplate.Spec, &podTemplate.ObjectMeta, oldSpec, oldMeta)
 | |
| }
 | |
| 
 | |
| var deprecatedAnnotations = []struct {
 | |
| 	key     string
 | |
| 	prefix  string
 | |
| 	message string
 | |
| }{
 | |
| 	{
 | |
| 		key:     `scheduler.alpha.kubernetes.io/critical-pod`,
 | |
| 		message: `non-functional in v1.16+; use the "priorityClassName" field instead`,
 | |
| 	},
 | |
| 	{
 | |
| 		key:     `security.alpha.kubernetes.io/sysctls`,
 | |
| 		message: `non-functional in v1.11+; use the "sysctls" field instead`,
 | |
| 	},
 | |
| 	{
 | |
| 		key:     `security.alpha.kubernetes.io/unsafe-sysctls`,
 | |
| 		message: `non-functional in v1.11+; use the "sysctls" field instead`,
 | |
| 	},
 | |
| }
 | |
| 
 | |
| func warningsForPodSpecAndMeta(fieldPath *field.Path, podSpec *api.PodSpec, meta *metav1.ObjectMeta, oldPodSpec *api.PodSpec, oldMeta *metav1.ObjectMeta) []string {
 | |
| 	var warnings []string
 | |
| 
 | |
| 	// use of deprecated node labels in selectors/affinity/topology
 | |
| 	for k := range podSpec.NodeSelector {
 | |
| 		if msg, deprecated := nodeapi.GetNodeLabelDeprecatedMessage(k); deprecated {
 | |
| 			warnings = append(warnings, fmt.Sprintf("%s: %s", fieldPath.Child("spec", "nodeSelector").Key(k), msg))
 | |
| 		}
 | |
| 	}
 | |
| 	if podSpec.Affinity != nil && podSpec.Affinity.NodeAffinity != nil {
 | |
| 		n := podSpec.Affinity.NodeAffinity
 | |
| 		if n.RequiredDuringSchedulingIgnoredDuringExecution != nil {
 | |
| 			termFldPath := fieldPath.Child("spec", "affinity", "nodeAffinity", "requiredDuringSchedulingIgnoredDuringExecution", "nodeSelectorTerms")
 | |
| 			for i, term := range n.RequiredDuringSchedulingIgnoredDuringExecution.NodeSelectorTerms {
 | |
| 				warnings = append(warnings, nodeapi.GetWarningsForNodeSelectorTerm(term, termFldPath.Index(i))...)
 | |
| 			}
 | |
| 		}
 | |
| 		preferredFldPath := fieldPath.Child("spec", "affinity", "nodeAffinity", "preferredDuringSchedulingIgnoredDuringExecution")
 | |
| 		for i, term := range n.PreferredDuringSchedulingIgnoredDuringExecution {
 | |
| 			warnings = append(warnings, nodeapi.GetWarningsForNodeSelectorTerm(term.Preference, preferredFldPath.Index(i).Child("preference"))...)
 | |
| 		}
 | |
| 	}
 | |
| 	for i, t := range podSpec.TopologySpreadConstraints {
 | |
| 		if msg, deprecated := nodeapi.GetNodeLabelDeprecatedMessage(t.TopologyKey); deprecated {
 | |
| 			warnings = append(warnings, fmt.Sprintf(
 | |
| 				"%s: %s is %s",
 | |
| 				fieldPath.Child("spec", "topologySpreadConstraints").Index(i).Child("topologyKey"),
 | |
| 				t.TopologyKey,
 | |
| 				msg,
 | |
| 			))
 | |
| 		}
 | |
| 
 | |
| 		// warn if labelSelector is empty which is no-match.
 | |
| 		if t.LabelSelector == nil {
 | |
| 			warnings = append(warnings, fmt.Sprintf("%s: a null labelSelector results in matching no pod", fieldPath.Child("spec", "topologySpreadConstraints").Index(i).Child("labelSelector")))
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	// use of deprecated annotations
 | |
| 	for _, deprecated := range deprecatedAnnotations {
 | |
| 		if _, exists := meta.Annotations[deprecated.key]; exists {
 | |
| 			warnings = append(warnings, fmt.Sprintf("%s: %s", fieldPath.Child("metadata", "annotations").Key(deprecated.key), deprecated.message))
 | |
| 		}
 | |
| 		if len(deprecated.prefix) > 0 {
 | |
| 			for k := range meta.Annotations {
 | |
| 				if strings.HasPrefix(k, deprecated.prefix) {
 | |
| 					warnings = append(warnings, fmt.Sprintf("%s: %s", fieldPath.Child("metadata", "annotations").Key(k), deprecated.message))
 | |
| 					break
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	// deprecated and removed volume plugins
 | |
| 	for i, v := range podSpec.Volumes {
 | |
| 		if v.PhotonPersistentDisk != nil {
 | |
| 			warnings = append(warnings, fmt.Sprintf("%s: deprecated in v1.11, non-functional in v1.16+", fieldPath.Child("spec", "volumes").Index(i).Child("photonPersistentDisk")))
 | |
| 		}
 | |
| 		if v.GitRepo != nil {
 | |
| 			warnings = append(warnings, fmt.Sprintf("%s: deprecated in v1.11", fieldPath.Child("spec", "volumes").Index(i).Child("gitRepo")))
 | |
| 		}
 | |
| 		if v.ScaleIO != nil {
 | |
| 			warnings = append(warnings, fmt.Sprintf("%s: deprecated in v1.16, non-functional in v1.22+", fieldPath.Child("spec", "volumes").Index(i).Child("scaleIO")))
 | |
| 		}
 | |
| 		if v.Flocker != nil {
 | |
| 			warnings = append(warnings, fmt.Sprintf("%s: deprecated in v1.22, non-functional in v1.25+", fieldPath.Child("spec", "volumes").Index(i).Child("flocker")))
 | |
| 		}
 | |
| 		if v.StorageOS != nil {
 | |
| 			warnings = append(warnings, fmt.Sprintf("%s: deprecated in v1.22, non-functional in v1.25+", fieldPath.Child("spec", "volumes").Index(i).Child("storageOS")))
 | |
| 		}
 | |
| 		if v.Quobyte != nil {
 | |
| 			warnings = append(warnings, fmt.Sprintf("%s: deprecated in v1.22, non-functional in v1.25+", fieldPath.Child("spec", "volumes").Index(i).Child("quobyte")))
 | |
| 		}
 | |
| 		if v.Glusterfs != nil {
 | |
| 			warnings = append(warnings, fmt.Sprintf("%s: deprecated in v1.25, non-functional in v1.26+", fieldPath.Child("spec", "volumes").Index(i).Child("glusterfs")))
 | |
| 		}
 | |
| 		if v.Ephemeral != nil && v.Ephemeral.VolumeClaimTemplate != nil {
 | |
| 			warnings = append(warnings, pvcutil.GetWarningsForPersistentVolumeClaimSpec(fieldPath.Child("spec", "volumes").Index(i).Child("ephemeral").Child("volumeClaimTemplate").Child("spec"), v.Ephemeral.VolumeClaimTemplate.Spec)...)
 | |
| 		}
 | |
| 		if v.CephFS != nil {
 | |
| 			warnings = append(warnings, fmt.Sprintf("%s: deprecated in v1.28, non-functional in v1.31+", fieldPath.Child("spec", "volumes").Index(i).Child("cephfs")))
 | |
| 		}
 | |
| 		if v.RBD != nil {
 | |
| 			warnings = append(warnings, fmt.Sprintf("%s: deprecated in v1.28, non-functional in v1.31+", fieldPath.Child("spec", "volumes").Index(i).Child("rbd")))
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	// duplicate hostAliases (#91670, #58477)
 | |
| 	if len(podSpec.HostAliases) > 1 {
 | |
| 		items := sets.New[string]()
 | |
| 		for i, item := range podSpec.HostAliases {
 | |
| 			if items.Has(item.IP) {
 | |
| 				warnings = append(warnings, fmt.Sprintf("%s: duplicate ip %q", fieldPath.Child("spec", "hostAliases").Index(i).Child("ip"), item.IP))
 | |
| 			} else {
 | |
| 				items.Insert(item.IP)
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	// duplicate imagePullSecrets (#91629, #58477)
 | |
| 	if len(podSpec.ImagePullSecrets) > 1 {
 | |
| 		items := sets.New[string]()
 | |
| 		for i, item := range podSpec.ImagePullSecrets {
 | |
| 			if items.Has(item.Name) {
 | |
| 				warnings = append(warnings, fmt.Sprintf("%s: duplicate name %q", fieldPath.Child("spec", "imagePullSecrets").Index(i).Child("name"), item.Name))
 | |
| 			} else {
 | |
| 				items.Insert(item.Name)
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 	// imagePullSecrets with empty name (#99454#issuecomment-787838112)
 | |
| 	for i, item := range podSpec.ImagePullSecrets {
 | |
| 		if len(item.Name) == 0 {
 | |
| 			warnings = append(warnings, fmt.Sprintf("%s: invalid empty name %q", fieldPath.Child("spec", "imagePullSecrets").Index(i).Child("name"), item.Name))
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	// fractional memory/ephemeral-storage requests/limits (#79950, #49442, #18538)
 | |
| 	if value, ok := podSpec.Overhead[api.ResourceMemory]; ok && value.MilliValue()%int64(1000) != int64(0) {
 | |
| 		warnings = append(warnings, fmt.Sprintf("%s: fractional byte value %q is invalid, must be an integer", fieldPath.Child("spec", "overhead").Key(string(api.ResourceMemory)), value.String()))
 | |
| 	}
 | |
| 	if value, ok := podSpec.Overhead[api.ResourceEphemeralStorage]; ok && value.MilliValue()%int64(1000) != int64(0) {
 | |
| 		warnings = append(warnings, fmt.Sprintf("%s: fractional byte value %q is invalid, must be an integer", fieldPath.Child("spec", "overhead").Key(string(api.ResourceEphemeralStorage)), value.String()))
 | |
| 	}
 | |
| 
 | |
| 	// use of pod seccomp annotation without accompanying field
 | |
| 	if podSpec.SecurityContext == nil || podSpec.SecurityContext.SeccompProfile == nil {
 | |
| 		if _, exists := meta.Annotations[api.SeccompPodAnnotationKey]; exists {
 | |
| 			warnings = append(warnings, fmt.Sprintf(`%s: non-functional in v1.27+; use the "seccompProfile" field instead`, fieldPath.Child("metadata", "annotations").Key(api.SeccompPodAnnotationKey)))
 | |
| 		}
 | |
| 	}
 | |
| 	hasPodAppArmorProfile := podSpec.SecurityContext != nil && podSpec.SecurityContext.AppArmorProfile != nil
 | |
| 
 | |
| 	pods.VisitContainersWithPath(podSpec, fieldPath.Child("spec"), func(c *api.Container, p *field.Path) bool {
 | |
| 		// use of container seccomp annotation without accompanying field
 | |
| 		if c.SecurityContext == nil || c.SecurityContext.SeccompProfile == nil {
 | |
| 			if _, exists := meta.Annotations[api.SeccompContainerAnnotationKeyPrefix+c.Name]; exists {
 | |
| 				warnings = append(warnings, fmt.Sprintf(`%s: non-functional in v1.27+; use the "seccompProfile" field instead`, fieldPath.Child("metadata", "annotations").Key(api.SeccompContainerAnnotationKeyPrefix+c.Name)))
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		// use of container AppArmor annotation without accompanying field
 | |
| 
 | |
| 		isPodTemplate := fieldPath != nil // Pod warnings are emitted through applyAppArmorVersionSkew instead.
 | |
| 		hasAppArmorField := hasPodAppArmorProfile || (c.SecurityContext != nil && c.SecurityContext.AppArmorProfile != nil)
 | |
| 		if isPodTemplate && !hasAppArmorField {
 | |
| 			key := api.DeprecatedAppArmorAnnotationKeyPrefix + c.Name
 | |
| 			if _, exists := meta.Annotations[key]; exists {
 | |
| 				warnings = append(warnings, fmt.Sprintf(`%s: deprecated since v1.30; use the "appArmorProfile" field instead`, fieldPath.Child("metadata", "annotations").Key(key)))
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		// fractional memory/ephemeral-storage requests/limits (#79950, #49442, #18538)
 | |
| 		if value, ok := c.Resources.Limits[api.ResourceMemory]; ok && value.MilliValue()%int64(1000) != int64(0) {
 | |
| 			warnings = append(warnings, fmt.Sprintf("%s: fractional byte value %q is invalid, must be an integer", p.Child("resources", "limits").Key(string(api.ResourceMemory)), value.String()))
 | |
| 		}
 | |
| 		if value, ok := c.Resources.Requests[api.ResourceMemory]; ok && value.MilliValue()%int64(1000) != int64(0) {
 | |
| 			warnings = append(warnings, fmt.Sprintf("%s: fractional byte value %q is invalid, must be an integer", p.Child("resources", "requests").Key(string(api.ResourceMemory)), value.String()))
 | |
| 		}
 | |
| 		if value, ok := c.Resources.Limits[api.ResourceEphemeralStorage]; ok && value.MilliValue()%int64(1000) != int64(0) {
 | |
| 			warnings = append(warnings, fmt.Sprintf("%s: fractional byte value %q is invalid, must be an integer", p.Child("resources", "limits").Key(string(api.ResourceEphemeralStorage)), value.String()))
 | |
| 		}
 | |
| 		if value, ok := c.Resources.Requests[api.ResourceEphemeralStorage]; ok && value.MilliValue()%int64(1000) != int64(0) {
 | |
| 			warnings = append(warnings, fmt.Sprintf("%s: fractional byte value %q is invalid, must be an integer", p.Child("resources", "requests").Key(string(api.ResourceEphemeralStorage)), value.String()))
 | |
| 		}
 | |
| 
 | |
| 		// duplicate containers[*].env (#86163, #93266, #58477)
 | |
| 		if len(c.Env) > 1 {
 | |
| 			items := sets.New[string]()
 | |
| 			for i, item := range c.Env {
 | |
| 				if items.Has(item.Name) {
 | |
| 					// a previous value exists, but it might be OK
 | |
| 					bad := false
 | |
| 					ref := fmt.Sprintf("$(%s)", item.Name) // what does a ref to this name look like
 | |
| 					// if we are replacing it with a valueFrom, warn
 | |
| 					if item.ValueFrom != nil {
 | |
| 						bad = true
 | |
| 					}
 | |
| 					// if this is X="$(X)", warn
 | |
| 					if item.Value == ref {
 | |
| 						bad = true
 | |
| 					}
 | |
| 					// if the new value does not contain a reference to the old
 | |
| 					// value (e.g. X="abc"; X="$(X)123"), warn
 | |
| 					if !strings.Contains(item.Value, ref) {
 | |
| 						bad = true
 | |
| 					}
 | |
| 					if bad {
 | |
| 						warnings = append(warnings, fmt.Sprintf("%s: hides previous definition of %q, which may be dropped when using apply", p.Child("env").Index(i), item.Name))
 | |
| 					}
 | |
| 				} else {
 | |
| 					items.Insert(item.Name)
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 		return true
 | |
| 	})
 | |
| 
 | |
| 	type portBlock struct {
 | |
| 		field *field.Path
 | |
| 		port  api.ContainerPort
 | |
| 	}
 | |
| 
 | |
| 	// Accumulate ports across all containers
 | |
| 	allPorts := map[string][]portBlock{}
 | |
| 	pods.VisitContainersWithPath(podSpec, fieldPath.Child("spec"), func(c *api.Container, fldPath *field.Path) bool {
 | |
| 		for i, port := range c.Ports {
 | |
| 			if port.HostIP != "" && port.HostPort == 0 {
 | |
| 				warnings = append(warnings, fmt.Sprintf("%s: hostIP set without hostPort: %+v",
 | |
| 					fldPath.Child("ports").Index(i), port))
 | |
| 			}
 | |
| 			k := fmt.Sprintf("%d/%s", port.ContainerPort, port.Protocol)
 | |
| 			if others, found := allPorts[k]; found {
 | |
| 				// Someone else has this protcol+port, but it still might not be a conflict.
 | |
| 				for _, other := range others {
 | |
| 					if port.HostIP == other.port.HostIP && port.HostPort == other.port.HostPort {
 | |
| 						// Exactly-equal is obvious. Validation should already filter for this except when these are unspecified.
 | |
| 						warnings = append(warnings, fmt.Sprintf("%s: duplicate port definition with %s", fldPath.Child("ports").Index(i), other.field))
 | |
| 					} else if port.HostPort == 0 || other.port.HostPort == 0 {
 | |
| 						// HostPort = 0 is redundant with any other value, which is odd but not really dangerous.  HostIP doesn't matter here.
 | |
| 						warnings = append(warnings, fmt.Sprintf("%s: overlapping port definition with %s", fldPath.Child("ports").Index(i), other.field))
 | |
| 					} else if a, b := port.HostIP == "", other.port.HostIP == ""; port.HostPort == other.port.HostPort && ((a || b) && !(a && b)) {
 | |
| 						// If the HostPorts are the same and either HostIP is not specified while the other is not, the behavior is undefined.
 | |
| 						warnings = append(warnings, fmt.Sprintf("%s: dangerously ambiguous port definition with %s", fldPath.Child("ports").Index(i), other.field))
 | |
| 					}
 | |
| 				}
 | |
| 				allPorts[k] = append(allPorts[k], portBlock{field: fldPath.Child("ports").Index(i), port: port})
 | |
| 			} else {
 | |
| 				allPorts[k] = []portBlock{{field: fldPath.Child("ports").Index(i), port: port}}
 | |
| 			}
 | |
| 		}
 | |
| 		return true
 | |
| 	})
 | |
| 
 | |
| 	// warn if the terminationGracePeriodSeconds is negative.
 | |
| 	if podSpec.TerminationGracePeriodSeconds != nil && *podSpec.TerminationGracePeriodSeconds < 0 {
 | |
| 		warnings = append(warnings, fmt.Sprintf("%s: must be >= 0; negative values are invalid and will be treated as 1", fieldPath.Child("spec", "terminationGracePeriodSeconds")))
 | |
| 	}
 | |
| 
 | |
| 	if podSpec.Affinity != nil {
 | |
| 		if affinity := podSpec.Affinity.PodAffinity; affinity != nil {
 | |
| 			warnings = append(warnings, warningsForPodAffinityTerms(affinity.RequiredDuringSchedulingIgnoredDuringExecution, fieldPath.Child("spec", "affinity", "podAffinity", "requiredDuringSchedulingIgnoredDuringExecution"))...)
 | |
| 			warnings = append(warnings, warningsForWeightedPodAffinityTerms(affinity.PreferredDuringSchedulingIgnoredDuringExecution, fieldPath.Child("spec", "affinity", "podAffinity", "preferredDuringSchedulingIgnoredDuringExecution"))...)
 | |
| 		}
 | |
| 		if affinity := podSpec.Affinity.PodAntiAffinity; affinity != nil {
 | |
| 			warnings = append(warnings, warningsForPodAffinityTerms(affinity.RequiredDuringSchedulingIgnoredDuringExecution, fieldPath.Child("spec", "affinity", "podAntiAffinity", "requiredDuringSchedulingIgnoredDuringExecution"))...)
 | |
| 			warnings = append(warnings, warningsForWeightedPodAffinityTerms(affinity.PreferredDuringSchedulingIgnoredDuringExecution, fieldPath.Child("spec", "affinity", "podAntiAffinity", "preferredDuringSchedulingIgnoredDuringExecution"))...)
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return warnings
 | |
| }
 | |
| 
 | |
| func warningsForPodAffinityTerms(terms []api.PodAffinityTerm, fieldPath *field.Path) []string {
 | |
| 	var warnings []string
 | |
| 	for i, t := range terms {
 | |
| 		if t.LabelSelector == nil {
 | |
| 			warnings = append(warnings, fmt.Sprintf("%s: a null labelSelector results in matching no pod", fieldPath.Index(i).Child("labelSelector")))
 | |
| 		}
 | |
| 	}
 | |
| 	return warnings
 | |
| }
 | |
| 
 | |
| func warningsForWeightedPodAffinityTerms(terms []api.WeightedPodAffinityTerm, fieldPath *field.Path) []string {
 | |
| 	var warnings []string
 | |
| 	for i, t := range terms {
 | |
| 		// warn if labelSelector is empty which is no-match.
 | |
| 		if t.PodAffinityTerm.LabelSelector == nil {
 | |
| 			warnings = append(warnings, fmt.Sprintf("%s: a null labelSelector results in matching no pod", fieldPath.Index(i).Child("podAffinityTerm", "labelSelector")))
 | |
| 		}
 | |
| 	}
 | |
| 	return warnings
 | |
| }
 |