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