k8s.io/kubernetes@v1.29.3/pkg/api/pod/warnings.go (about)

     1  /*
     2  Copyright 2021 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 pod
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  	"strings"
    23  
    24  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    25  	"k8s.io/apimachinery/pkg/util/sets"
    26  	"k8s.io/apimachinery/pkg/util/validation/field"
    27  	nodeapi "k8s.io/kubernetes/pkg/api/node"
    28  	pvcutil "k8s.io/kubernetes/pkg/api/persistentvolumeclaim"
    29  	api "k8s.io/kubernetes/pkg/apis/core"
    30  	"k8s.io/kubernetes/pkg/apis/core/pods"
    31  )
    32  
    33  func GetWarningsForPod(ctx context.Context, pod, oldPod *api.Pod) []string {
    34  	if pod == nil {
    35  		return nil
    36  	}
    37  
    38  	var (
    39  		oldSpec *api.PodSpec
    40  		oldMeta *metav1.ObjectMeta
    41  	)
    42  	if oldPod != nil {
    43  		oldSpec = &oldPod.Spec
    44  		oldMeta = &oldPod.ObjectMeta
    45  	}
    46  	return warningsForPodSpecAndMeta(nil, &pod.Spec, &pod.ObjectMeta, oldSpec, oldMeta)
    47  }
    48  
    49  func GetWarningsForPodTemplate(ctx context.Context, fieldPath *field.Path, podTemplate, oldPodTemplate *api.PodTemplateSpec) []string {
    50  	if podTemplate == nil {
    51  		return nil
    52  	}
    53  
    54  	var (
    55  		oldSpec *api.PodSpec
    56  		oldMeta *metav1.ObjectMeta
    57  	)
    58  	if oldPodTemplate != nil {
    59  		oldSpec = &oldPodTemplate.Spec
    60  		oldMeta = &oldPodTemplate.ObjectMeta
    61  	}
    62  	return warningsForPodSpecAndMeta(fieldPath, &podTemplate.Spec, &podTemplate.ObjectMeta, oldSpec, oldMeta)
    63  }
    64  
    65  var deprecatedAnnotations = []struct {
    66  	key     string
    67  	prefix  string
    68  	message string
    69  }{
    70  	{
    71  		key:     `scheduler.alpha.kubernetes.io/critical-pod`,
    72  		message: `non-functional in v1.16+; use the "priorityClassName" field instead`,
    73  	},
    74  	{
    75  		key:     `security.alpha.kubernetes.io/sysctls`,
    76  		message: `non-functional in v1.11+; use the "sysctls" field instead`,
    77  	},
    78  	{
    79  		key:     `security.alpha.kubernetes.io/unsafe-sysctls`,
    80  		message: `non-functional in v1.11+; use the "sysctls" field instead`,
    81  	},
    82  }
    83  
    84  func warningsForPodSpecAndMeta(fieldPath *field.Path, podSpec *api.PodSpec, meta *metav1.ObjectMeta, oldPodSpec *api.PodSpec, oldMeta *metav1.ObjectMeta) []string {
    85  	var warnings []string
    86  
    87  	// use of deprecated node labels in selectors/affinity/topology
    88  	for k := range podSpec.NodeSelector {
    89  		if msg, deprecated := nodeapi.GetNodeLabelDeprecatedMessage(k); deprecated {
    90  			warnings = append(warnings, fmt.Sprintf("%s: %s", fieldPath.Child("spec", "nodeSelector").Key(k), msg))
    91  		}
    92  	}
    93  	if podSpec.Affinity != nil && podSpec.Affinity.NodeAffinity != nil {
    94  		n := podSpec.Affinity.NodeAffinity
    95  		if n.RequiredDuringSchedulingIgnoredDuringExecution != nil {
    96  			termFldPath := fieldPath.Child("spec", "affinity", "nodeAffinity", "requiredDuringSchedulingIgnoredDuringExecution", "nodeSelectorTerms")
    97  			for i, term := range n.RequiredDuringSchedulingIgnoredDuringExecution.NodeSelectorTerms {
    98  				warnings = append(warnings, nodeapi.GetWarningsForNodeSelectorTerm(term, termFldPath.Index(i))...)
    99  			}
   100  		}
   101  		preferredFldPath := fieldPath.Child("spec", "affinity", "nodeAffinity", "preferredDuringSchedulingIgnoredDuringExecution")
   102  		for i, term := range n.PreferredDuringSchedulingIgnoredDuringExecution {
   103  			warnings = append(warnings, nodeapi.GetWarningsForNodeSelectorTerm(term.Preference, preferredFldPath.Index(i).Child("preference"))...)
   104  		}
   105  	}
   106  	for i, t := range podSpec.TopologySpreadConstraints {
   107  		if msg, deprecated := nodeapi.GetNodeLabelDeprecatedMessage(t.TopologyKey); deprecated {
   108  			warnings = append(warnings, fmt.Sprintf(
   109  				"%s: %s is %s",
   110  				fieldPath.Child("spec", "topologySpreadConstraints").Index(i).Child("topologyKey"),
   111  				t.TopologyKey,
   112  				msg,
   113  			))
   114  		}
   115  
   116  		// warn if labelSelector is empty which is no-match.
   117  		if t.LabelSelector == nil {
   118  			warnings = append(warnings, fmt.Sprintf("%s: a null labelSelector results in matching no pod", fieldPath.Child("spec", "topologySpreadConstraints").Index(i).Child("labelSelector")))
   119  		}
   120  	}
   121  
   122  	// use of deprecated annotations
   123  	for _, deprecated := range deprecatedAnnotations {
   124  		if _, exists := meta.Annotations[deprecated.key]; exists {
   125  			warnings = append(warnings, fmt.Sprintf("%s: %s", fieldPath.Child("metadata", "annotations").Key(deprecated.key), deprecated.message))
   126  		}
   127  		if len(deprecated.prefix) > 0 {
   128  			for k := range meta.Annotations {
   129  				if strings.HasPrefix(k, deprecated.prefix) {
   130  					warnings = append(warnings, fmt.Sprintf("%s: %s", fieldPath.Child("metadata", "annotations").Key(k), deprecated.message))
   131  					break
   132  				}
   133  			}
   134  		}
   135  	}
   136  
   137  	// deprecated and removed volume plugins
   138  	for i, v := range podSpec.Volumes {
   139  		if v.PhotonPersistentDisk != nil {
   140  			warnings = append(warnings, fmt.Sprintf("%s: deprecated in v1.11, non-functional in v1.16+", fieldPath.Child("spec", "volumes").Index(i).Child("photonPersistentDisk")))
   141  		}
   142  		if v.GitRepo != nil {
   143  			warnings = append(warnings, fmt.Sprintf("%s: deprecated in v1.11", fieldPath.Child("spec", "volumes").Index(i).Child("gitRepo")))
   144  		}
   145  		if v.ScaleIO != nil {
   146  			warnings = append(warnings, fmt.Sprintf("%s: deprecated in v1.16, non-functional in v1.22+", fieldPath.Child("spec", "volumes").Index(i).Child("scaleIO")))
   147  		}
   148  		if v.Flocker != nil {
   149  			warnings = append(warnings, fmt.Sprintf("%s: deprecated in v1.22, non-functional in v1.25+", fieldPath.Child("spec", "volumes").Index(i).Child("flocker")))
   150  		}
   151  		if v.StorageOS != nil {
   152  			warnings = append(warnings, fmt.Sprintf("%s: deprecated in v1.22, non-functional in v1.25+", fieldPath.Child("spec", "volumes").Index(i).Child("storageOS")))
   153  		}
   154  		if v.Quobyte != nil {
   155  			warnings = append(warnings, fmt.Sprintf("%s: deprecated in v1.22, non-functional in v1.25+", fieldPath.Child("spec", "volumes").Index(i).Child("quobyte")))
   156  		}
   157  		if v.Glusterfs != nil {
   158  			warnings = append(warnings, fmt.Sprintf("%s: deprecated in v1.25, non-functional in v1.26+", fieldPath.Child("spec", "volumes").Index(i).Child("glusterfs")))
   159  		}
   160  		if v.Ephemeral != nil && v.Ephemeral.VolumeClaimTemplate != nil {
   161  			warnings = append(warnings, pvcutil.GetWarningsForPersistentVolumeClaimSpec(fieldPath.Child("spec", "volumes").Index(i).Child("ephemeral").Child("volumeClaimTemplate").Child("spec"), v.Ephemeral.VolumeClaimTemplate.Spec)...)
   162  		}
   163  		if v.CephFS != nil {
   164  			warnings = append(warnings, fmt.Sprintf("%s: deprecated in v1.28, non-functional in v1.31+", fieldPath.Child("spec", "volumes").Index(i).Child("cephfs")))
   165  		}
   166  		if v.RBD != nil {
   167  			warnings = append(warnings, fmt.Sprintf("%s: deprecated in v1.28, non-functional in v1.31+", fieldPath.Child("spec", "volumes").Index(i).Child("rbd")))
   168  		}
   169  	}
   170  
   171  	// duplicate hostAliases (#91670, #58477)
   172  	if len(podSpec.HostAliases) > 1 {
   173  		items := sets.NewString()
   174  		for i, item := range podSpec.HostAliases {
   175  			if items.Has(item.IP) {
   176  				warnings = append(warnings, fmt.Sprintf("%s: duplicate ip %q", fieldPath.Child("spec", "hostAliases").Index(i).Child("ip"), item.IP))
   177  			} else {
   178  				items.Insert(item.IP)
   179  			}
   180  		}
   181  	}
   182  
   183  	// duplicate imagePullSecrets (#91629, #58477)
   184  	if len(podSpec.ImagePullSecrets) > 1 {
   185  		items := sets.NewString()
   186  		for i, item := range podSpec.ImagePullSecrets {
   187  			if items.Has(item.Name) {
   188  				warnings = append(warnings, fmt.Sprintf("%s: duplicate name %q", fieldPath.Child("spec", "imagePullSecrets").Index(i).Child("name"), item.Name))
   189  			} else {
   190  				items.Insert(item.Name)
   191  			}
   192  		}
   193  	}
   194  	// imagePullSecrets with empty name (#99454#issuecomment-787838112)
   195  	for i, item := range podSpec.ImagePullSecrets {
   196  		if len(item.Name) == 0 {
   197  			warnings = append(warnings, fmt.Sprintf("%s: invalid empty name %q", fieldPath.Child("spec", "imagePullSecrets").Index(i).Child("name"), item.Name))
   198  		}
   199  	}
   200  
   201  	// fractional memory/ephemeral-storage requests/limits (#79950, #49442, #18538)
   202  	if value, ok := podSpec.Overhead[api.ResourceMemory]; ok && value.MilliValue()%int64(1000) != int64(0) {
   203  		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()))
   204  	}
   205  	if value, ok := podSpec.Overhead[api.ResourceEphemeralStorage]; ok && value.MilliValue()%int64(1000) != int64(0) {
   206  		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()))
   207  	}
   208  
   209  	// use of pod seccomp annotation without accompanying field
   210  	if podSpec.SecurityContext == nil || podSpec.SecurityContext.SeccompProfile == nil {
   211  		if _, exists := meta.Annotations[api.SeccompPodAnnotationKey]; exists {
   212  			warnings = append(warnings, fmt.Sprintf(`%s: non-functional in v1.27+; use the "seccompProfile" field instead`, fieldPath.Child("metadata", "annotations").Key(api.SeccompPodAnnotationKey)))
   213  		}
   214  	}
   215  
   216  	pods.VisitContainersWithPath(podSpec, fieldPath.Child("spec"), func(c *api.Container, p *field.Path) bool {
   217  		// use of container seccomp annotation without accompanying field
   218  		if c.SecurityContext == nil || c.SecurityContext.SeccompProfile == nil {
   219  			if _, exists := meta.Annotations[api.SeccompContainerAnnotationKeyPrefix+c.Name]; exists {
   220  				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)))
   221  			}
   222  		}
   223  
   224  		// fractional memory/ephemeral-storage requests/limits (#79950, #49442, #18538)
   225  		if value, ok := c.Resources.Limits[api.ResourceMemory]; ok && value.MilliValue()%int64(1000) != int64(0) {
   226  			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()))
   227  		}
   228  		if value, ok := c.Resources.Requests[api.ResourceMemory]; ok && value.MilliValue()%int64(1000) != int64(0) {
   229  			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()))
   230  		}
   231  		if value, ok := c.Resources.Limits[api.ResourceEphemeralStorage]; ok && value.MilliValue()%int64(1000) != int64(0) {
   232  			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()))
   233  		}
   234  		if value, ok := c.Resources.Requests[api.ResourceEphemeralStorage]; ok && value.MilliValue()%int64(1000) != int64(0) {
   235  			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()))
   236  		}
   237  
   238  		// duplicate containers[*].env (#86163, #93266, #58477)
   239  		if len(c.Env) > 1 {
   240  			items := sets.NewString()
   241  			for i, item := range c.Env {
   242  				if items.Has(item.Name) {
   243  					// a previous value exists, but it might be OK
   244  					bad := false
   245  					ref := fmt.Sprintf("$(%s)", item.Name) // what does a ref to this name look like
   246  					// if we are replacing it with a valueFrom, warn
   247  					if item.ValueFrom != nil {
   248  						bad = true
   249  					}
   250  					// if this is X="$(X)", warn
   251  					if item.Value == ref {
   252  						bad = true
   253  					}
   254  					// if the new value does not contain a reference to the old
   255  					// value (e.g. X="abc"; X="$(X)123"), warn
   256  					if !strings.Contains(item.Value, ref) {
   257  						bad = true
   258  					}
   259  					if bad {
   260  						warnings = append(warnings, fmt.Sprintf("%s: hides previous definition of %q", p.Child("env").Index(i), item.Name))
   261  					}
   262  				} else {
   263  					items.Insert(item.Name)
   264  				}
   265  			}
   266  		}
   267  		return true
   268  	})
   269  
   270  	type portBlock struct {
   271  		field *field.Path
   272  		port  api.ContainerPort
   273  	}
   274  
   275  	// Accumulate ports across all containers
   276  	allPorts := map[string][]portBlock{}
   277  	pods.VisitContainersWithPath(podSpec, fieldPath.Child("spec"), func(c *api.Container, fldPath *field.Path) bool {
   278  		for i, port := range c.Ports {
   279  			if port.HostIP != "" && port.HostPort == 0 {
   280  				warnings = append(warnings, fmt.Sprintf("%s: hostIP set without hostPort: %+v",
   281  					fldPath.Child("ports").Index(i), port))
   282  			}
   283  			k := fmt.Sprintf("%d/%s", port.ContainerPort, port.Protocol)
   284  			if others, found := allPorts[k]; found {
   285  				// Someone else has this protcol+port, but it still might not be a conflict.
   286  				for _, other := range others {
   287  					if port.HostIP == other.port.HostIP && port.HostPort == other.port.HostPort {
   288  						// Exactly-equal is obvious. Validation should already filter for this except when these are unspecified.
   289  						warnings = append(warnings, fmt.Sprintf("%s: duplicate port definition with %s", fldPath.Child("ports").Index(i), other.field))
   290  					} else if port.HostPort == 0 || other.port.HostPort == 0 {
   291  						// HostPort = 0 is redundant with any other value, which is odd but not really dangerous.  HostIP doesn't matter here.
   292  						warnings = append(warnings, fmt.Sprintf("%s: overlapping port definition with %s", fldPath.Child("ports").Index(i), other.field))
   293  					} else if a, b := port.HostIP == "", other.port.HostIP == ""; port.HostPort == other.port.HostPort && ((a || b) && !(a && b)) {
   294  						// If the HostPorts are the same and either HostIP is not specified while the other is not, the behavior is undefined.
   295  						warnings = append(warnings, fmt.Sprintf("%s: dangerously ambiguous port definition with %s", fldPath.Child("ports").Index(i), other.field))
   296  					}
   297  				}
   298  				allPorts[k] = append(allPorts[k], portBlock{field: fldPath.Child("ports").Index(i), port: port})
   299  			} else {
   300  				allPorts[k] = []portBlock{{field: fldPath.Child("ports").Index(i), port: port}}
   301  			}
   302  		}
   303  		return true
   304  	})
   305  
   306  	// warn if the terminationGracePeriodSeconds is negative.
   307  	if podSpec.TerminationGracePeriodSeconds != nil && *podSpec.TerminationGracePeriodSeconds < 0 {
   308  		warnings = append(warnings, fmt.Sprintf("%s: must be >= 0; negative values are invalid and will be treated as 1", fieldPath.Child("spec", "terminationGracePeriodSeconds")))
   309  	}
   310  
   311  	if podSpec.Affinity != nil {
   312  		if affinity := podSpec.Affinity.PodAffinity; affinity != nil {
   313  			warnings = append(warnings, warningsForPodAffinityTerms(affinity.RequiredDuringSchedulingIgnoredDuringExecution, fieldPath.Child("spec", "affinity", "podAffinity", "requiredDuringSchedulingIgnoredDuringExecution"))...)
   314  			warnings = append(warnings, warningsForWeightedPodAffinityTerms(affinity.PreferredDuringSchedulingIgnoredDuringExecution, fieldPath.Child("spec", "affinity", "podAffinity", "preferredDuringSchedulingIgnoredDuringExecution"))...)
   315  		}
   316  		if affinity := podSpec.Affinity.PodAntiAffinity; affinity != nil {
   317  			warnings = append(warnings, warningsForPodAffinityTerms(affinity.RequiredDuringSchedulingIgnoredDuringExecution, fieldPath.Child("spec", "affinity", "podAntiAffinity", "requiredDuringSchedulingIgnoredDuringExecution"))...)
   318  			warnings = append(warnings, warningsForWeightedPodAffinityTerms(affinity.PreferredDuringSchedulingIgnoredDuringExecution, fieldPath.Child("spec", "affinity", "podAntiAffinity", "preferredDuringSchedulingIgnoredDuringExecution"))...)
   319  		}
   320  	}
   321  
   322  	return warnings
   323  }
   324  
   325  func warningsForPodAffinityTerms(terms []api.PodAffinityTerm, fieldPath *field.Path) []string {
   326  	var warnings []string
   327  	for i, t := range terms {
   328  		if t.LabelSelector == nil {
   329  			warnings = append(warnings, fmt.Sprintf("%s: a null labelSelector results in matching no pod", fieldPath.Index(i).Child("labelSelector")))
   330  		}
   331  	}
   332  	return warnings
   333  }
   334  
   335  func warningsForWeightedPodAffinityTerms(terms []api.WeightedPodAffinityTerm, fieldPath *field.Path) []string {
   336  	var warnings []string
   337  	for i, t := range terms {
   338  		// warn if labelSelector is empty which is no-match.
   339  		if t.PodAffinityTerm.LabelSelector == nil {
   340  			warnings = append(warnings, fmt.Sprintf("%s: a null labelSelector results in matching no pod", fieldPath.Index(i).Child("podAffinityTerm", "labelSelector")))
   341  		}
   342  	}
   343  	return warnings
   344  }