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 }