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 }