k8s.io/kubernetes@v1.29.3/pkg/kubelet/status/status_manager.go (about) 1 /* 2 Copyright 2014 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 //go:generate mockgen -source=status_manager.go -destination=testing/mock_pod_status_provider.go -package=testing PodStatusProvider 18 package status 19 20 import ( 21 "context" 22 "fmt" 23 "sort" 24 "strings" 25 "sync" 26 "time" 27 28 "github.com/google/go-cmp/cmp" 29 clientset "k8s.io/client-go/kubernetes" 30 31 v1 "k8s.io/api/core/v1" 32 apiequality "k8s.io/apimachinery/pkg/api/equality" 33 "k8s.io/apimachinery/pkg/api/errors" 34 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 35 "k8s.io/apimachinery/pkg/types" 36 "k8s.io/apimachinery/pkg/util/wait" 37 utilfeature "k8s.io/apiserver/pkg/util/feature" 38 "k8s.io/klog/v2" 39 podutil "k8s.io/kubernetes/pkg/api/v1/pod" 40 "k8s.io/kubernetes/pkg/features" 41 kubecontainer "k8s.io/kubernetes/pkg/kubelet/container" 42 "k8s.io/kubernetes/pkg/kubelet/metrics" 43 "k8s.io/kubernetes/pkg/kubelet/status/state" 44 kubetypes "k8s.io/kubernetes/pkg/kubelet/types" 45 kubeutil "k8s.io/kubernetes/pkg/kubelet/util" 46 statusutil "k8s.io/kubernetes/pkg/util/pod" 47 ) 48 49 // podStatusManagerStateFile is the file name where status manager stores its state 50 const podStatusManagerStateFile = "pod_status_manager_state" 51 52 // A wrapper around v1.PodStatus that includes a version to enforce that stale pod statuses are 53 // not sent to the API server. 54 type versionedPodStatus struct { 55 // version is a monotonically increasing version number (per pod). 56 version uint64 57 // Pod name & namespace, for sending updates to API server. 58 podName string 59 podNamespace string 60 // at is the time at which the most recent status update was detected 61 at time.Time 62 63 // True if the status is generated at the end of SyncTerminatedPod, or after it is completed. 64 podIsFinished bool 65 66 status v1.PodStatus 67 } 68 69 // Updates pod statuses in apiserver. Writes only when new status has changed. 70 // All methods are thread-safe. 71 type manager struct { 72 kubeClient clientset.Interface 73 podManager PodManager 74 // Map from pod UID to sync status of the corresponding pod. 75 podStatuses map[types.UID]versionedPodStatus 76 podStatusesLock sync.RWMutex 77 podStatusChannel chan struct{} 78 // Map from (mirror) pod UID to latest status version successfully sent to the API server. 79 // apiStatusVersions must only be accessed from the sync thread. 80 apiStatusVersions map[kubetypes.MirrorPodUID]uint64 81 podDeletionSafety PodDeletionSafetyProvider 82 83 podStartupLatencyHelper PodStartupLatencyStateHelper 84 // state allows to save/restore pod resource allocation and tolerate kubelet restarts. 85 state state.State 86 // stateFileDirectory holds the directory where the state file for checkpoints is held. 87 stateFileDirectory string 88 } 89 90 // PodManager is the subset of methods the manager needs to observe the actual state of the kubelet. 91 // See pkg/k8s.io/kubernetes/pkg/kubelet/pod.Manager for method godoc. 92 type PodManager interface { 93 GetPodByUID(types.UID) (*v1.Pod, bool) 94 GetMirrorPodByPod(*v1.Pod) (*v1.Pod, bool) 95 TranslatePodUID(uid types.UID) kubetypes.ResolvedPodUID 96 GetUIDTranslations() (podToMirror map[kubetypes.ResolvedPodUID]kubetypes.MirrorPodUID, mirrorToPod map[kubetypes.MirrorPodUID]kubetypes.ResolvedPodUID) 97 } 98 99 // PodStatusProvider knows how to provide status for a pod. It is intended to be used by other components 100 // that need to introspect the authoritative status of a pod. The PodStatusProvider represents the actual 101 // status of a running pod as the kubelet sees it. 102 type PodStatusProvider interface { 103 // GetPodStatus returns the cached status for the provided pod UID, as well as whether it 104 // was a cache hit. 105 GetPodStatus(uid types.UID) (v1.PodStatus, bool) 106 } 107 108 // PodDeletionSafetyProvider provides guarantees that a pod can be safely deleted. 109 type PodDeletionSafetyProvider interface { 110 // PodCouldHaveRunningContainers returns true if the pod could have running containers. 111 PodCouldHaveRunningContainers(pod *v1.Pod) bool 112 } 113 114 type PodStartupLatencyStateHelper interface { 115 RecordStatusUpdated(pod *v1.Pod) 116 DeletePodStartupState(podUID types.UID) 117 } 118 119 // Manager is the Source of truth for kubelet pod status, and should be kept up-to-date with 120 // the latest v1.PodStatus. It also syncs updates back to the API server. 121 type Manager interface { 122 PodStatusProvider 123 124 // Start the API server status sync loop. 125 Start() 126 127 // SetPodStatus caches updates the cached status for the given pod, and triggers a status update. 128 SetPodStatus(pod *v1.Pod, status v1.PodStatus) 129 130 // SetContainerReadiness updates the cached container status with the given readiness, and 131 // triggers a status update. 132 SetContainerReadiness(podUID types.UID, containerID kubecontainer.ContainerID, ready bool) 133 134 // SetContainerStartup updates the cached container status with the given startup, and 135 // triggers a status update. 136 SetContainerStartup(podUID types.UID, containerID kubecontainer.ContainerID, started bool) 137 138 // TerminatePod resets the container status for the provided pod to terminated and triggers 139 // a status update. 140 TerminatePod(pod *v1.Pod) 141 142 // RemoveOrphanedStatuses scans the status cache and removes any entries for pods not included in 143 // the provided podUIDs. 144 RemoveOrphanedStatuses(podUIDs map[types.UID]bool) 145 146 // GetContainerResourceAllocation returns checkpointed AllocatedResources value for the container 147 GetContainerResourceAllocation(podUID string, containerName string) (v1.ResourceList, bool) 148 149 // GetPodResizeStatus returns checkpointed PodStatus.Resize value 150 GetPodResizeStatus(podUID string) (v1.PodResizeStatus, bool) 151 152 // SetPodAllocation checkpoints the resources allocated to a pod's containers. 153 SetPodAllocation(pod *v1.Pod) error 154 155 // SetPodResizeStatus checkpoints the last resizing decision for the pod. 156 SetPodResizeStatus(podUID types.UID, resize v1.PodResizeStatus) error 157 } 158 159 const syncPeriod = 10 * time.Second 160 161 // NewManager returns a functional Manager. 162 func NewManager(kubeClient clientset.Interface, podManager PodManager, podDeletionSafety PodDeletionSafetyProvider, podStartupLatencyHelper PodStartupLatencyStateHelper, stateFileDirectory string) Manager { 163 return &manager{ 164 kubeClient: kubeClient, 165 podManager: podManager, 166 podStatuses: make(map[types.UID]versionedPodStatus), 167 podStatusChannel: make(chan struct{}, 1), 168 apiStatusVersions: make(map[kubetypes.MirrorPodUID]uint64), 169 podDeletionSafety: podDeletionSafety, 170 podStartupLatencyHelper: podStartupLatencyHelper, 171 stateFileDirectory: stateFileDirectory, 172 } 173 } 174 175 // isPodStatusByKubeletEqual returns true if the given pod statuses are equal when non-kubelet-owned 176 // pod conditions are excluded. 177 // This method normalizes the status before comparing so as to make sure that meaningless 178 // changes will be ignored. 179 func isPodStatusByKubeletEqual(oldStatus, status *v1.PodStatus) bool { 180 oldCopy := oldStatus.DeepCopy() 181 for _, c := range status.Conditions { 182 // both owned and shared conditions are used for kubelet status equality 183 if kubetypes.PodConditionByKubelet(c.Type) || kubetypes.PodConditionSharedByKubelet(c.Type) { 184 _, oc := podutil.GetPodCondition(oldCopy, c.Type) 185 if oc == nil || oc.Status != c.Status || oc.Message != c.Message || oc.Reason != c.Reason { 186 return false 187 } 188 } 189 } 190 oldCopy.Conditions = status.Conditions 191 return apiequality.Semantic.DeepEqual(oldCopy, status) 192 } 193 194 func (m *manager) Start() { 195 // Initialize m.state to no-op state checkpoint manager 196 m.state = state.NewNoopStateCheckpoint() 197 198 // Create pod allocation checkpoint manager even if client is nil so as to allow local get/set of AllocatedResources & Resize 199 if utilfeature.DefaultFeatureGate.Enabled(features.InPlacePodVerticalScaling) { 200 stateImpl, err := state.NewStateCheckpoint(m.stateFileDirectory, podStatusManagerStateFile) 201 if err != nil { 202 // This is a crictical, non-recoverable failure. 203 klog.ErrorS(err, "Could not initialize pod allocation checkpoint manager, please drain node and remove policy state file") 204 panic(err) 205 } 206 m.state = stateImpl 207 } 208 209 // Don't start the status manager if we don't have a client. This will happen 210 // on the master, where the kubelet is responsible for bootstrapping the pods 211 // of the master components. 212 if m.kubeClient == nil { 213 klog.InfoS("Kubernetes client is nil, not starting status manager") 214 return 215 } 216 217 klog.InfoS("Starting to sync pod status with apiserver") 218 219 //nolint:staticcheck // SA1015 Ticker can leak since this is only called once and doesn't handle termination. 220 syncTicker := time.NewTicker(syncPeriod).C 221 222 // syncPod and syncBatch share the same go routine to avoid sync races. 223 go wait.Forever(func() { 224 for { 225 select { 226 case <-m.podStatusChannel: 227 klog.V(4).InfoS("Syncing updated statuses") 228 m.syncBatch(false) 229 case <-syncTicker: 230 klog.V(4).InfoS("Syncing all statuses") 231 m.syncBatch(true) 232 } 233 } 234 }, 0) 235 } 236 237 // GetContainerResourceAllocation returns the last checkpointed AllocatedResources values 238 // If checkpoint manager has not been initialized, it returns nil, false 239 func (m *manager) GetContainerResourceAllocation(podUID string, containerName string) (v1.ResourceList, bool) { 240 m.podStatusesLock.RLock() 241 defer m.podStatusesLock.RUnlock() 242 return m.state.GetContainerResourceAllocation(podUID, containerName) 243 } 244 245 // GetPodResizeStatus returns the last checkpointed ResizeStaus value 246 // If checkpoint manager has not been initialized, it returns nil, false 247 func (m *manager) GetPodResizeStatus(podUID string) (v1.PodResizeStatus, bool) { 248 m.podStatusesLock.RLock() 249 defer m.podStatusesLock.RUnlock() 250 return m.state.GetPodResizeStatus(podUID) 251 } 252 253 // SetPodAllocation checkpoints the resources allocated to a pod's containers 254 func (m *manager) SetPodAllocation(pod *v1.Pod) error { 255 m.podStatusesLock.RLock() 256 defer m.podStatusesLock.RUnlock() 257 for _, container := range pod.Spec.Containers { 258 var alloc v1.ResourceList 259 if container.Resources.Requests != nil { 260 alloc = container.Resources.Requests.DeepCopy() 261 } 262 if err := m.state.SetContainerResourceAllocation(string(pod.UID), container.Name, alloc); err != nil { 263 return err 264 } 265 } 266 return nil 267 } 268 269 // SetPodResizeStatus checkpoints the last resizing decision for the pod. 270 func (m *manager) SetPodResizeStatus(podUID types.UID, resizeStatus v1.PodResizeStatus) error { 271 m.podStatusesLock.RLock() 272 defer m.podStatusesLock.RUnlock() 273 return m.state.SetPodResizeStatus(string(podUID), resizeStatus) 274 } 275 276 func (m *manager) GetPodStatus(uid types.UID) (v1.PodStatus, bool) { 277 m.podStatusesLock.RLock() 278 defer m.podStatusesLock.RUnlock() 279 status, ok := m.podStatuses[types.UID(m.podManager.TranslatePodUID(uid))] 280 return status.status, ok 281 } 282 283 func (m *manager) SetPodStatus(pod *v1.Pod, status v1.PodStatus) { 284 m.podStatusesLock.Lock() 285 defer m.podStatusesLock.Unlock() 286 287 // Make sure we're caching a deep copy. 288 status = *status.DeepCopy() 289 290 // Force a status update if deletion timestamp is set. This is necessary 291 // because if the pod is in the non-running state, the pod worker still 292 // needs to be able to trigger an update and/or deletion. 293 m.updateStatusInternal(pod, status, pod.DeletionTimestamp != nil, false) 294 } 295 296 func (m *manager) SetContainerReadiness(podUID types.UID, containerID kubecontainer.ContainerID, ready bool) { 297 m.podStatusesLock.Lock() 298 defer m.podStatusesLock.Unlock() 299 300 pod, ok := m.podManager.GetPodByUID(podUID) 301 if !ok { 302 klog.V(4).InfoS("Pod has been deleted, no need to update readiness", "podUID", string(podUID)) 303 return 304 } 305 306 oldStatus, found := m.podStatuses[pod.UID] 307 if !found { 308 klog.InfoS("Container readiness changed before pod has synced", 309 "pod", klog.KObj(pod), 310 "containerID", containerID.String()) 311 return 312 } 313 314 // Find the container to update. 315 containerStatus, _, ok := findContainerStatus(&oldStatus.status, containerID.String()) 316 if !ok { 317 klog.InfoS("Container readiness changed for unknown container", 318 "pod", klog.KObj(pod), 319 "containerID", containerID.String()) 320 return 321 } 322 323 if containerStatus.Ready == ready { 324 klog.V(4).InfoS("Container readiness unchanged", 325 "ready", ready, 326 "pod", klog.KObj(pod), 327 "containerID", containerID.String()) 328 return 329 } 330 331 // Make sure we're not updating the cached version. 332 status := *oldStatus.status.DeepCopy() 333 containerStatus, _, _ = findContainerStatus(&status, containerID.String()) 334 containerStatus.Ready = ready 335 336 // updateConditionFunc updates the corresponding type of condition 337 updateConditionFunc := func(conditionType v1.PodConditionType, condition v1.PodCondition) { 338 conditionIndex := -1 339 for i, condition := range status.Conditions { 340 if condition.Type == conditionType { 341 conditionIndex = i 342 break 343 } 344 } 345 if conditionIndex != -1 { 346 status.Conditions[conditionIndex] = condition 347 } else { 348 klog.InfoS("PodStatus missing condition type", "conditionType", conditionType, "status", status) 349 status.Conditions = append(status.Conditions, condition) 350 } 351 } 352 allContainerStatuses := append(status.InitContainerStatuses, status.ContainerStatuses...) 353 updateConditionFunc(v1.PodReady, GeneratePodReadyCondition(&pod.Spec, status.Conditions, allContainerStatuses, status.Phase)) 354 updateConditionFunc(v1.ContainersReady, GenerateContainersReadyCondition(&pod.Spec, allContainerStatuses, status.Phase)) 355 m.updateStatusInternal(pod, status, false, false) 356 } 357 358 func (m *manager) SetContainerStartup(podUID types.UID, containerID kubecontainer.ContainerID, started bool) { 359 m.podStatusesLock.Lock() 360 defer m.podStatusesLock.Unlock() 361 362 pod, ok := m.podManager.GetPodByUID(podUID) 363 if !ok { 364 klog.V(4).InfoS("Pod has been deleted, no need to update startup", "podUID", string(podUID)) 365 return 366 } 367 368 oldStatus, found := m.podStatuses[pod.UID] 369 if !found { 370 klog.InfoS("Container startup changed before pod has synced", 371 "pod", klog.KObj(pod), 372 "containerID", containerID.String()) 373 return 374 } 375 376 // Find the container to update. 377 containerStatus, _, ok := findContainerStatus(&oldStatus.status, containerID.String()) 378 if !ok { 379 klog.InfoS("Container startup changed for unknown container", 380 "pod", klog.KObj(pod), 381 "containerID", containerID.String()) 382 return 383 } 384 385 if containerStatus.Started != nil && *containerStatus.Started == started { 386 klog.V(4).InfoS("Container startup unchanged", 387 "pod", klog.KObj(pod), 388 "containerID", containerID.String()) 389 return 390 } 391 392 // Make sure we're not updating the cached version. 393 status := *oldStatus.status.DeepCopy() 394 containerStatus, _, _ = findContainerStatus(&status, containerID.String()) 395 containerStatus.Started = &started 396 397 m.updateStatusInternal(pod, status, false, false) 398 } 399 400 func findContainerStatus(status *v1.PodStatus, containerID string) (containerStatus *v1.ContainerStatus, init bool, ok bool) { 401 // Find the container to update. 402 for i, c := range status.ContainerStatuses { 403 if c.ContainerID == containerID { 404 return &status.ContainerStatuses[i], false, true 405 } 406 } 407 408 for i, c := range status.InitContainerStatuses { 409 if c.ContainerID == containerID { 410 return &status.InitContainerStatuses[i], true, true 411 } 412 } 413 414 return nil, false, false 415 416 } 417 418 // TerminatePod ensures that the status of containers is properly defaulted at the end of the pod 419 // lifecycle. As the Kubelet must reconcile with the container runtime to observe container status 420 // there is always the possibility we are unable to retrieve one or more container statuses due to 421 // garbage collection, admin action, or loss of temporary data on a restart. This method ensures 422 // that any absent container status is treated as a failure so that we do not incorrectly describe 423 // the pod as successful. If we have not yet initialized the pod in the presence of init containers, 424 // the init container failure status is sufficient to describe the pod as failing, and we do not need 425 // to override waiting containers (unless there is evidence the pod previously started those containers). 426 // It also makes sure that pods are transitioned to a terminal phase (Failed or Succeeded) before 427 // their deletion. 428 func (m *manager) TerminatePod(pod *v1.Pod) { 429 m.podStatusesLock.Lock() 430 defer m.podStatusesLock.Unlock() 431 432 // ensure that all containers have a terminated state - because we do not know whether the container 433 // was successful, always report an error 434 oldStatus := &pod.Status 435 cachedStatus, isCached := m.podStatuses[pod.UID] 436 if isCached { 437 oldStatus = &cachedStatus.status 438 } 439 status := *oldStatus.DeepCopy() 440 441 // once a pod has initialized, any missing status is treated as a failure 442 if hasPodInitialized(pod) { 443 for i := range status.ContainerStatuses { 444 if status.ContainerStatuses[i].State.Terminated != nil { 445 continue 446 } 447 status.ContainerStatuses[i].State = v1.ContainerState{ 448 Terminated: &v1.ContainerStateTerminated{ 449 Reason: "ContainerStatusUnknown", 450 Message: "The container could not be located when the pod was terminated", 451 ExitCode: 137, 452 }, 453 } 454 } 455 } 456 457 // all but the final suffix of init containers which have no evidence of a container start are 458 // marked as failed containers 459 for i := range initializedContainers(status.InitContainerStatuses) { 460 if status.InitContainerStatuses[i].State.Terminated != nil { 461 continue 462 } 463 status.InitContainerStatuses[i].State = v1.ContainerState{ 464 Terminated: &v1.ContainerStateTerminated{ 465 Reason: "ContainerStatusUnknown", 466 Message: "The container could not be located when the pod was terminated", 467 ExitCode: 137, 468 }, 469 } 470 } 471 472 // Make sure all pods are transitioned to a terminal phase. 473 // TODO(#116484): Also assign terminal phase to static an pods. 474 if !kubetypes.IsStaticPod(pod) { 475 switch status.Phase { 476 case v1.PodSucceeded, v1.PodFailed: 477 // do nothing, already terminal 478 case v1.PodPending, v1.PodRunning: 479 if status.Phase == v1.PodRunning && isCached { 480 klog.InfoS("Terminal running pod should have already been marked as failed, programmer error", "pod", klog.KObj(pod), "podUID", pod.UID) 481 } 482 klog.V(3).InfoS("Marking terminal pod as failed", "oldPhase", status.Phase, "pod", klog.KObj(pod), "podUID", pod.UID) 483 status.Phase = v1.PodFailed 484 default: 485 klog.ErrorS(fmt.Errorf("unknown phase: %v", status.Phase), "Unknown phase, programmer error", "pod", klog.KObj(pod), "podUID", pod.UID) 486 status.Phase = v1.PodFailed 487 } 488 } 489 490 klog.V(5).InfoS("TerminatePod calling updateStatusInternal", "pod", klog.KObj(pod), "podUID", pod.UID) 491 m.updateStatusInternal(pod, status, true, true) 492 } 493 494 // hasPodInitialized returns true if the pod has no evidence of ever starting a regular container, which 495 // implies those containers should not be transitioned to terminated status. 496 func hasPodInitialized(pod *v1.Pod) bool { 497 // a pod without init containers is always initialized 498 if len(pod.Spec.InitContainers) == 0 { 499 return true 500 } 501 // if any container has ever moved out of waiting state, the pod has initialized 502 for _, status := range pod.Status.ContainerStatuses { 503 if status.LastTerminationState.Terminated != nil || status.State.Waiting == nil { 504 return true 505 } 506 } 507 // if the last init container has ever completed with a zero exit code, the pod is initialized 508 if l := len(pod.Status.InitContainerStatuses); l > 0 { 509 container, ok := kubeutil.GetContainerByIndex(pod.Spec.InitContainers, pod.Status.InitContainerStatuses, l-1) 510 if !ok { 511 klog.V(4).InfoS("Mismatch between pod spec and status, likely programmer error", "pod", klog.KObj(pod), "containerName", container.Name) 512 return false 513 } 514 515 containerStatus := pod.Status.InitContainerStatuses[l-1] 516 if kubetypes.IsRestartableInitContainer(&container) { 517 if containerStatus.State.Running != nil && 518 containerStatus.Started != nil && *containerStatus.Started { 519 return true 520 } 521 } else { // regular init container 522 if state := containerStatus.LastTerminationState; state.Terminated != nil && state.Terminated.ExitCode == 0 { 523 return true 524 } 525 if state := containerStatus.State; state.Terminated != nil && state.Terminated.ExitCode == 0 { 526 return true 527 } 528 } 529 } 530 // otherwise the pod has no record of being initialized 531 return false 532 } 533 534 // initializedContainers returns all status except for suffix of containers that are in Waiting 535 // state, which is the set of containers that have attempted to start at least once. If all containers 536 // are Waiting, the first container is always returned. 537 func initializedContainers(containers []v1.ContainerStatus) []v1.ContainerStatus { 538 for i := len(containers) - 1; i >= 0; i-- { 539 if containers[i].State.Waiting == nil || containers[i].LastTerminationState.Terminated != nil { 540 return containers[0 : i+1] 541 } 542 } 543 // always return at least one container 544 if len(containers) > 0 { 545 return containers[0:1] 546 } 547 return nil 548 } 549 550 // checkContainerStateTransition ensures that no container is trying to transition 551 // from a terminated to non-terminated state, which is illegal and indicates a 552 // logical error in the kubelet. 553 func checkContainerStateTransition(oldStatuses, newStatuses *v1.PodStatus, podSpec *v1.PodSpec) error { 554 // If we should always restart, containers are allowed to leave the terminated state 555 if podSpec.RestartPolicy == v1.RestartPolicyAlways { 556 return nil 557 } 558 for _, oldStatus := range oldStatuses.ContainerStatuses { 559 // Skip any container that wasn't terminated 560 if oldStatus.State.Terminated == nil { 561 continue 562 } 563 // Skip any container that failed but is allowed to restart 564 if oldStatus.State.Terminated.ExitCode != 0 && podSpec.RestartPolicy == v1.RestartPolicyOnFailure { 565 continue 566 } 567 for _, newStatus := range newStatuses.ContainerStatuses { 568 if oldStatus.Name == newStatus.Name && newStatus.State.Terminated == nil { 569 return fmt.Errorf("terminated container %v attempted illegal transition to non-terminated state", newStatus.Name) 570 } 571 } 572 } 573 574 for i, oldStatus := range oldStatuses.InitContainerStatuses { 575 initContainer, ok := kubeutil.GetContainerByIndex(podSpec.InitContainers, oldStatuses.InitContainerStatuses, i) 576 if !ok { 577 return fmt.Errorf("found mismatch between pod spec and status, container: %v", oldStatus.Name) 578 } 579 // Skip any restartable init container as it always is allowed to restart 580 if kubetypes.IsRestartableInitContainer(&initContainer) { 581 continue 582 } 583 // Skip any container that wasn't terminated 584 if oldStatus.State.Terminated == nil { 585 continue 586 } 587 // Skip any container that failed but is allowed to restart 588 if oldStatus.State.Terminated.ExitCode != 0 && podSpec.RestartPolicy == v1.RestartPolicyOnFailure { 589 continue 590 } 591 for _, newStatus := range newStatuses.InitContainerStatuses { 592 if oldStatus.Name == newStatus.Name && newStatus.State.Terminated == nil { 593 return fmt.Errorf("terminated init container %v attempted illegal transition to non-terminated state", newStatus.Name) 594 } 595 } 596 } 597 return nil 598 } 599 600 // updateStatusInternal updates the internal status cache, and queues an update to the api server if 601 // necessary. 602 // This method IS NOT THREAD SAFE and must be called from a locked function. 603 func (m *manager) updateStatusInternal(pod *v1.Pod, status v1.PodStatus, forceUpdate, podIsFinished bool) { 604 var oldStatus v1.PodStatus 605 cachedStatus, isCached := m.podStatuses[pod.UID] 606 if isCached { 607 oldStatus = cachedStatus.status 608 // TODO(#116484): Also assign terminal phase to static pods. 609 if !kubetypes.IsStaticPod(pod) { 610 if cachedStatus.podIsFinished && !podIsFinished { 611 klog.InfoS("Got unexpected podIsFinished=false, while podIsFinished=true in status cache, programmer error.", "pod", klog.KObj(pod)) 612 podIsFinished = true 613 } 614 } 615 } else if mirrorPod, ok := m.podManager.GetMirrorPodByPod(pod); ok { 616 oldStatus = mirrorPod.Status 617 } else { 618 oldStatus = pod.Status 619 } 620 621 // Check for illegal state transition in containers 622 if err := checkContainerStateTransition(&oldStatus, &status, &pod.Spec); err != nil { 623 klog.ErrorS(err, "Status update on pod aborted", "pod", klog.KObj(pod)) 624 return 625 } 626 627 // Set ContainersReadyCondition.LastTransitionTime. 628 updateLastTransitionTime(&status, &oldStatus, v1.ContainersReady) 629 630 // Set ReadyCondition.LastTransitionTime. 631 updateLastTransitionTime(&status, &oldStatus, v1.PodReady) 632 633 // Set InitializedCondition.LastTransitionTime. 634 updateLastTransitionTime(&status, &oldStatus, v1.PodInitialized) 635 636 // Set PodReadyToStartContainersCondition.LastTransitionTime. 637 updateLastTransitionTime(&status, &oldStatus, v1.PodReadyToStartContainers) 638 639 // Set PodScheduledCondition.LastTransitionTime. 640 updateLastTransitionTime(&status, &oldStatus, v1.PodScheduled) 641 642 if utilfeature.DefaultFeatureGate.Enabled(features.PodDisruptionConditions) { 643 // Set DisruptionTarget.LastTransitionTime. 644 updateLastTransitionTime(&status, &oldStatus, v1.DisruptionTarget) 645 } 646 647 // ensure that the start time does not change across updates. 648 if oldStatus.StartTime != nil && !oldStatus.StartTime.IsZero() { 649 status.StartTime = oldStatus.StartTime 650 } else if status.StartTime.IsZero() { 651 // if the status has no start time, we need to set an initial time 652 now := metav1.Now() 653 status.StartTime = &now 654 } 655 656 normalizeStatus(pod, &status) 657 658 // Perform some more extensive logging of container termination state to assist in 659 // debugging production races (generally not needed). 660 if klogV := klog.V(5); klogV.Enabled() { 661 var containers []string 662 for _, s := range append(append([]v1.ContainerStatus(nil), status.InitContainerStatuses...), status.ContainerStatuses...) { 663 var current, previous string 664 switch { 665 case s.State.Running != nil: 666 current = "running" 667 case s.State.Waiting != nil: 668 current = "waiting" 669 case s.State.Terminated != nil: 670 current = fmt.Sprintf("terminated=%d", s.State.Terminated.ExitCode) 671 default: 672 current = "unknown" 673 } 674 switch { 675 case s.LastTerminationState.Running != nil: 676 previous = "running" 677 case s.LastTerminationState.Waiting != nil: 678 previous = "waiting" 679 case s.LastTerminationState.Terminated != nil: 680 previous = fmt.Sprintf("terminated=%d", s.LastTerminationState.Terminated.ExitCode) 681 default: 682 previous = "<none>" 683 } 684 containers = append(containers, fmt.Sprintf("(%s state=%s previous=%s)", s.Name, current, previous)) 685 } 686 sort.Strings(containers) 687 klogV.InfoS("updateStatusInternal", "version", cachedStatus.version+1, "podIsFinished", podIsFinished, "pod", klog.KObj(pod), "podUID", pod.UID, "containers", strings.Join(containers, " ")) 688 } 689 690 // The intent here is to prevent concurrent updates to a pod's status from 691 // clobbering each other so the phase of a pod progresses monotonically. 692 if isCached && isPodStatusByKubeletEqual(&cachedStatus.status, &status) && !forceUpdate { 693 klog.V(3).InfoS("Ignoring same status for pod", "pod", klog.KObj(pod), "status", status) 694 return 695 } 696 697 newStatus := versionedPodStatus{ 698 status: status, 699 version: cachedStatus.version + 1, 700 podName: pod.Name, 701 podNamespace: pod.Namespace, 702 podIsFinished: podIsFinished, 703 } 704 705 // Multiple status updates can be generated before we update the API server, 706 // so we track the time from the first status update until we retire it to 707 // the API. 708 if cachedStatus.at.IsZero() { 709 newStatus.at = time.Now() 710 } else { 711 newStatus.at = cachedStatus.at 712 } 713 714 m.podStatuses[pod.UID] = newStatus 715 716 select { 717 case m.podStatusChannel <- struct{}{}: 718 default: 719 // there's already a status update pending 720 } 721 } 722 723 // updateLastTransitionTime updates the LastTransitionTime of a pod condition. 724 func updateLastTransitionTime(status, oldStatus *v1.PodStatus, conditionType v1.PodConditionType) { 725 _, condition := podutil.GetPodCondition(status, conditionType) 726 if condition == nil { 727 return 728 } 729 // Need to set LastTransitionTime. 730 lastTransitionTime := metav1.Now() 731 _, oldCondition := podutil.GetPodCondition(oldStatus, conditionType) 732 if oldCondition != nil && condition.Status == oldCondition.Status { 733 lastTransitionTime = oldCondition.LastTransitionTime 734 } 735 condition.LastTransitionTime = lastTransitionTime 736 } 737 738 // deletePodStatus simply removes the given pod from the status cache. 739 func (m *manager) deletePodStatus(uid types.UID) { 740 m.podStatusesLock.Lock() 741 defer m.podStatusesLock.Unlock() 742 delete(m.podStatuses, uid) 743 m.podStartupLatencyHelper.DeletePodStartupState(uid) 744 if utilfeature.DefaultFeatureGate.Enabled(features.InPlacePodVerticalScaling) { 745 m.state.Delete(string(uid), "") 746 } 747 } 748 749 // TODO(filipg): It'd be cleaner if we can do this without signal from user. 750 func (m *manager) RemoveOrphanedStatuses(podUIDs map[types.UID]bool) { 751 m.podStatusesLock.Lock() 752 defer m.podStatusesLock.Unlock() 753 for key := range m.podStatuses { 754 if _, ok := podUIDs[key]; !ok { 755 klog.V(5).InfoS("Removing pod from status map.", "podUID", key) 756 delete(m.podStatuses, key) 757 if utilfeature.DefaultFeatureGate.Enabled(features.InPlacePodVerticalScaling) { 758 m.state.Delete(string(key), "") 759 } 760 } 761 } 762 } 763 764 // syncBatch syncs pods statuses with the apiserver. Returns the number of syncs 765 // attempted for testing. 766 func (m *manager) syncBatch(all bool) int { 767 type podSync struct { 768 podUID types.UID 769 statusUID kubetypes.MirrorPodUID 770 status versionedPodStatus 771 } 772 773 var updatedStatuses []podSync 774 podToMirror, mirrorToPod := m.podManager.GetUIDTranslations() 775 func() { // Critical section 776 m.podStatusesLock.RLock() 777 defer m.podStatusesLock.RUnlock() 778 779 // Clean up orphaned versions. 780 if all { 781 for uid := range m.apiStatusVersions { 782 _, hasPod := m.podStatuses[types.UID(uid)] 783 _, hasMirror := mirrorToPod[uid] 784 if !hasPod && !hasMirror { 785 delete(m.apiStatusVersions, uid) 786 } 787 } 788 } 789 790 // Decide which pods need status updates. 791 for uid, status := range m.podStatuses { 792 // translate the pod UID (source) to the status UID (API pod) - 793 // static pods are identified in source by pod UID but tracked in the 794 // API via the uid of the mirror pod 795 uidOfStatus := kubetypes.MirrorPodUID(uid) 796 if mirrorUID, ok := podToMirror[kubetypes.ResolvedPodUID(uid)]; ok { 797 if mirrorUID == "" { 798 klog.V(5).InfoS("Static pod does not have a corresponding mirror pod; skipping", 799 "podUID", uid, 800 "pod", klog.KRef(status.podNamespace, status.podName)) 801 continue 802 } 803 uidOfStatus = mirrorUID 804 } 805 806 // if a new status update has been delivered, trigger an update, otherwise the 807 // pod can wait for the next bulk check (which performs reconciliation as well) 808 if !all { 809 if m.apiStatusVersions[uidOfStatus] >= status.version { 810 continue 811 } 812 updatedStatuses = append(updatedStatuses, podSync{uid, uidOfStatus, status}) 813 continue 814 } 815 816 // Ensure that any new status, or mismatched status, or pod that is ready for 817 // deletion gets updated. If a status update fails we retry the next time any 818 // other pod is updated. 819 if m.needsUpdate(types.UID(uidOfStatus), status) { 820 updatedStatuses = append(updatedStatuses, podSync{uid, uidOfStatus, status}) 821 } else if m.needsReconcile(uid, status.status) { 822 // Delete the apiStatusVersions here to force an update on the pod status 823 // In most cases the deleted apiStatusVersions here should be filled 824 // soon after the following syncPod() [If the syncPod() sync an update 825 // successfully]. 826 delete(m.apiStatusVersions, uidOfStatus) 827 updatedStatuses = append(updatedStatuses, podSync{uid, uidOfStatus, status}) 828 } 829 } 830 }() 831 832 for _, update := range updatedStatuses { 833 klog.V(5).InfoS("Sync pod status", "podUID", update.podUID, "statusUID", update.statusUID, "version", update.status.version) 834 m.syncPod(update.podUID, update.status) 835 } 836 837 return len(updatedStatuses) 838 } 839 840 // syncPod syncs the given status with the API server. The caller must not hold the status lock. 841 func (m *manager) syncPod(uid types.UID, status versionedPodStatus) { 842 // TODO: make me easier to express from client code 843 pod, err := m.kubeClient.CoreV1().Pods(status.podNamespace).Get(context.TODO(), status.podName, metav1.GetOptions{}) 844 if errors.IsNotFound(err) { 845 klog.V(3).InfoS("Pod does not exist on the server", 846 "podUID", uid, 847 "pod", klog.KRef(status.podNamespace, status.podName)) 848 // If the Pod is deleted the status will be cleared in 849 // RemoveOrphanedStatuses, so we just ignore the update here. 850 return 851 } 852 if err != nil { 853 klog.InfoS("Failed to get status for pod", 854 "podUID", uid, 855 "pod", klog.KRef(status.podNamespace, status.podName), 856 "err", err) 857 return 858 } 859 860 translatedUID := m.podManager.TranslatePodUID(pod.UID) 861 // Type convert original uid just for the purpose of comparison. 862 if len(translatedUID) > 0 && translatedUID != kubetypes.ResolvedPodUID(uid) { 863 klog.V(2).InfoS("Pod was deleted and then recreated, skipping status update", 864 "pod", klog.KObj(pod), 865 "oldPodUID", uid, 866 "podUID", translatedUID) 867 m.deletePodStatus(uid) 868 return 869 } 870 871 mergedStatus := mergePodStatus(pod.Status, status.status, m.podDeletionSafety.PodCouldHaveRunningContainers(pod)) 872 873 newPod, patchBytes, unchanged, err := statusutil.PatchPodStatus(context.TODO(), m.kubeClient, pod.Namespace, pod.Name, pod.UID, pod.Status, mergedStatus) 874 klog.V(3).InfoS("Patch status for pod", "pod", klog.KObj(pod), "podUID", uid, "patch", string(patchBytes)) 875 876 if err != nil { 877 klog.InfoS("Failed to update status for pod", "pod", klog.KObj(pod), "err", err) 878 return 879 } 880 if unchanged { 881 klog.V(3).InfoS("Status for pod is up-to-date", "pod", klog.KObj(pod), "statusVersion", status.version) 882 } else { 883 klog.V(3).InfoS("Status for pod updated successfully", "pod", klog.KObj(pod), "statusVersion", status.version, "status", mergedStatus) 884 pod = newPod 885 // We pass a new object (result of API call which contains updated ResourceVersion) 886 m.podStartupLatencyHelper.RecordStatusUpdated(pod) 887 } 888 889 // measure how long the status update took to propagate from generation to update on the server 890 if status.at.IsZero() { 891 klog.V(3).InfoS("Pod had no status time set", "pod", klog.KObj(pod), "podUID", uid, "version", status.version) 892 } else { 893 duration := time.Since(status.at).Truncate(time.Millisecond) 894 metrics.PodStatusSyncDuration.Observe(duration.Seconds()) 895 } 896 897 m.apiStatusVersions[kubetypes.MirrorPodUID(pod.UID)] = status.version 898 899 // We don't handle graceful deletion of mirror pods. 900 if m.canBeDeleted(pod, status.status, status.podIsFinished) { 901 deleteOptions := metav1.DeleteOptions{ 902 GracePeriodSeconds: new(int64), 903 // Use the pod UID as the precondition for deletion to prevent deleting a 904 // newly created pod with the same name and namespace. 905 Preconditions: metav1.NewUIDPreconditions(string(pod.UID)), 906 } 907 err = m.kubeClient.CoreV1().Pods(pod.Namespace).Delete(context.TODO(), pod.Name, deleteOptions) 908 if err != nil { 909 klog.InfoS("Failed to delete status for pod", "pod", klog.KObj(pod), "err", err) 910 return 911 } 912 klog.V(3).InfoS("Pod fully terminated and removed from etcd", "pod", klog.KObj(pod)) 913 m.deletePodStatus(uid) 914 } 915 } 916 917 // needsUpdate returns whether the status is stale for the given pod UID. 918 // This method is not thread safe, and must only be accessed by the sync thread. 919 func (m *manager) needsUpdate(uid types.UID, status versionedPodStatus) bool { 920 latest, ok := m.apiStatusVersions[kubetypes.MirrorPodUID(uid)] 921 if !ok || latest < status.version { 922 return true 923 } 924 pod, ok := m.podManager.GetPodByUID(uid) 925 if !ok { 926 return false 927 } 928 return m.canBeDeleted(pod, status.status, status.podIsFinished) 929 } 930 931 func (m *manager) canBeDeleted(pod *v1.Pod, status v1.PodStatus, podIsFinished bool) bool { 932 if pod.DeletionTimestamp == nil || kubetypes.IsMirrorPod(pod) { 933 return false 934 } 935 // Delay deletion of pods until the phase is terminal, based on pod.Status 936 // which comes from pod manager. 937 if !podutil.IsPodPhaseTerminal(pod.Status.Phase) { 938 // For debugging purposes we also log the kubelet's local phase, when the deletion is delayed. 939 klog.V(3).InfoS("Delaying pod deletion as the phase is non-terminal", "phase", pod.Status.Phase, "localPhase", status.Phase, "pod", klog.KObj(pod), "podUID", pod.UID) 940 return false 941 } 942 // If this is an update completing pod termination then we know the pod termination is finished. 943 if podIsFinished { 944 klog.V(3).InfoS("The pod termination is finished as SyncTerminatedPod completes its execution", "phase", pod.Status.Phase, "localPhase", status.Phase, "pod", klog.KObj(pod), "podUID", pod.UID) 945 return true 946 } 947 return false 948 } 949 950 // needsReconcile compares the given status with the status in the pod manager (which 951 // in fact comes from apiserver), returns whether the status needs to be reconciled with 952 // the apiserver. Now when pod status is inconsistent between apiserver and kubelet, 953 // kubelet should forcibly send an update to reconcile the inconsistence, because kubelet 954 // should be the source of truth of pod status. 955 // NOTE(random-liu): It's simpler to pass in mirror pod uid and get mirror pod by uid, but 956 // now the pod manager only supports getting mirror pod by static pod, so we have to pass 957 // static pod uid here. 958 // TODO(random-liu): Simplify the logic when mirror pod manager is added. 959 func (m *manager) needsReconcile(uid types.UID, status v1.PodStatus) bool { 960 // The pod could be a static pod, so we should translate first. 961 pod, ok := m.podManager.GetPodByUID(uid) 962 if !ok { 963 klog.V(4).InfoS("Pod has been deleted, no need to reconcile", "podUID", string(uid)) 964 return false 965 } 966 // If the pod is a static pod, we should check its mirror pod, because only status in mirror pod is meaningful to us. 967 if kubetypes.IsStaticPod(pod) { 968 mirrorPod, ok := m.podManager.GetMirrorPodByPod(pod) 969 if !ok { 970 klog.V(4).InfoS("Static pod has no corresponding mirror pod, no need to reconcile", "pod", klog.KObj(pod)) 971 return false 972 } 973 pod = mirrorPod 974 } 975 976 podStatus := pod.Status.DeepCopy() 977 normalizeStatus(pod, podStatus) 978 979 if isPodStatusByKubeletEqual(podStatus, &status) { 980 // If the status from the source is the same with the cached status, 981 // reconcile is not needed. Just return. 982 return false 983 } 984 klog.V(3).InfoS("Pod status is inconsistent with cached status for pod, a reconciliation should be triggered", 985 "pod", klog.KObj(pod), 986 "statusDiff", cmp.Diff(podStatus, &status)) 987 988 return true 989 } 990 991 // normalizeStatus normalizes nanosecond precision timestamps in podStatus 992 // down to second precision (*RFC339NANO* -> *RFC3339*). This must be done 993 // before comparing podStatus to the status returned by apiserver because 994 // apiserver does not support RFC339NANO. 995 // Related issue #15262/PR #15263 to move apiserver to RFC339NANO is closed. 996 func normalizeStatus(pod *v1.Pod, status *v1.PodStatus) *v1.PodStatus { 997 bytesPerStatus := kubecontainer.MaxPodTerminationMessageLogLength 998 if containers := len(pod.Spec.Containers) + len(pod.Spec.InitContainers); containers > 0 { 999 bytesPerStatus = bytesPerStatus / containers 1000 } 1001 normalizeTimeStamp := func(t *metav1.Time) { 1002 *t = t.Rfc3339Copy() 1003 } 1004 normalizeContainerState := func(c *v1.ContainerState) { 1005 if c.Running != nil { 1006 normalizeTimeStamp(&c.Running.StartedAt) 1007 } 1008 if c.Terminated != nil { 1009 normalizeTimeStamp(&c.Terminated.StartedAt) 1010 normalizeTimeStamp(&c.Terminated.FinishedAt) 1011 if len(c.Terminated.Message) > bytesPerStatus { 1012 c.Terminated.Message = c.Terminated.Message[:bytesPerStatus] 1013 } 1014 } 1015 } 1016 1017 if status.StartTime != nil { 1018 normalizeTimeStamp(status.StartTime) 1019 } 1020 for i := range status.Conditions { 1021 condition := &status.Conditions[i] 1022 normalizeTimeStamp(&condition.LastProbeTime) 1023 normalizeTimeStamp(&condition.LastTransitionTime) 1024 } 1025 1026 // update container statuses 1027 for i := range status.ContainerStatuses { 1028 cstatus := &status.ContainerStatuses[i] 1029 normalizeContainerState(&cstatus.State) 1030 normalizeContainerState(&cstatus.LastTerminationState) 1031 } 1032 // Sort the container statuses, so that the order won't affect the result of comparison 1033 sort.Sort(kubetypes.SortedContainerStatuses(status.ContainerStatuses)) 1034 1035 // update init container statuses 1036 for i := range status.InitContainerStatuses { 1037 cstatus := &status.InitContainerStatuses[i] 1038 normalizeContainerState(&cstatus.State) 1039 normalizeContainerState(&cstatus.LastTerminationState) 1040 } 1041 // Sort the container statuses, so that the order won't affect the result of comparison 1042 kubetypes.SortInitContainerStatuses(pod, status.InitContainerStatuses) 1043 return status 1044 } 1045 1046 // mergePodStatus merges oldPodStatus and newPodStatus to preserve where pod conditions 1047 // not owned by kubelet and to ensure terminal phase transition only happens after all 1048 // running containers have terminated. This method does not modify the old status. 1049 func mergePodStatus(oldPodStatus, newPodStatus v1.PodStatus, couldHaveRunningContainers bool) v1.PodStatus { 1050 podConditions := make([]v1.PodCondition, 0, len(oldPodStatus.Conditions)+len(newPodStatus.Conditions)) 1051 1052 for _, c := range oldPodStatus.Conditions { 1053 if !kubetypes.PodConditionByKubelet(c.Type) { 1054 podConditions = append(podConditions, c) 1055 } 1056 } 1057 1058 transitioningToTerminalPhase := !podutil.IsPodPhaseTerminal(oldPodStatus.Phase) && podutil.IsPodPhaseTerminal(newPodStatus.Phase) 1059 1060 for _, c := range newPodStatus.Conditions { 1061 if kubetypes.PodConditionByKubelet(c.Type) { 1062 podConditions = append(podConditions, c) 1063 } else if kubetypes.PodConditionSharedByKubelet(c.Type) { 1064 // we replace or append all the "shared by kubelet" conditions 1065 if c.Type == v1.DisruptionTarget { 1066 // guard the update of the DisruptionTarget condition with a check to ensure 1067 // it will only be sent once all containers have terminated and the phase 1068 // is terminal. This avoids sending an unnecessary patch request to add 1069 // the condition if the actual status phase transition is delayed. 1070 if transitioningToTerminalPhase && !couldHaveRunningContainers { 1071 // update the LastTransitionTime again here because the older transition 1072 // time set in updateStatusInternal is likely stale as sending of 1073 // the condition was delayed until all pod's containers have terminated. 1074 updateLastTransitionTime(&newPodStatus, &oldPodStatus, c.Type) 1075 if _, c := podutil.GetPodConditionFromList(newPodStatus.Conditions, c.Type); c != nil { 1076 // for shared conditions we update or append in podConditions 1077 podConditions = statusutil.ReplaceOrAppendPodCondition(podConditions, c) 1078 } 1079 } 1080 } 1081 } 1082 } 1083 newPodStatus.Conditions = podConditions 1084 1085 // ResourceClaimStatuses is not owned and not modified by kubelet. 1086 newPodStatus.ResourceClaimStatuses = oldPodStatus.ResourceClaimStatuses 1087 1088 // Delay transitioning a pod to a terminal status unless the pod is actually terminal. 1089 // The Kubelet should never transition a pod to terminal status that could have running 1090 // containers and thus actively be leveraging exclusive resources. Note that resources 1091 // like volumes are reconciled by a subsystem in the Kubelet and will converge if a new 1092 // pod reuses an exclusive resource (unmount -> free -> mount), which means we do not 1093 // need wait for those resources to be detached by the Kubelet. In general, resources 1094 // the Kubelet exclusively owns must be released prior to a pod being reported terminal, 1095 // while resources that have participanting components above the API use the pod's 1096 // transition to a terminal phase (or full deletion) to release those resources. 1097 if transitioningToTerminalPhase { 1098 if couldHaveRunningContainers { 1099 newPodStatus.Phase = oldPodStatus.Phase 1100 newPodStatus.Reason = oldPodStatus.Reason 1101 newPodStatus.Message = oldPodStatus.Message 1102 } 1103 } 1104 1105 // If the new phase is terminal, explicitly set the ready condition to false for v1.PodReady and v1.ContainersReady. 1106 // It may take some time for kubelet to reconcile the ready condition, so explicitly set ready conditions to false if the phase is terminal. 1107 // This is done to ensure kubelet does not report a status update with terminal pod phase and ready=true. 1108 // See https://issues.k8s.io/108594 for more details. 1109 if podutil.IsPodPhaseTerminal(newPodStatus.Phase) { 1110 if podutil.IsPodReadyConditionTrue(newPodStatus) || podutil.IsContainersReadyConditionTrue(newPodStatus) { 1111 containersReadyCondition := generateContainersReadyConditionForTerminalPhase(newPodStatus.Phase) 1112 podutil.UpdatePodCondition(&newPodStatus, &containersReadyCondition) 1113 1114 podReadyCondition := generatePodReadyConditionForTerminalPhase(newPodStatus.Phase) 1115 podutil.UpdatePodCondition(&newPodStatus, &podReadyCondition) 1116 } 1117 } 1118 1119 return newPodStatus 1120 } 1121 1122 // NeedToReconcilePodReadiness returns if the pod "Ready" condition need to be reconcile 1123 func NeedToReconcilePodReadiness(pod *v1.Pod) bool { 1124 if len(pod.Spec.ReadinessGates) == 0 { 1125 return false 1126 } 1127 podReadyCondition := GeneratePodReadyCondition(&pod.Spec, pod.Status.Conditions, pod.Status.ContainerStatuses, pod.Status.Phase) 1128 i, curCondition := podutil.GetPodConditionFromList(pod.Status.Conditions, v1.PodReady) 1129 // Only reconcile if "Ready" condition is present and Status or Message is not expected 1130 if i >= 0 && (curCondition.Status != podReadyCondition.Status || curCondition.Message != podReadyCondition.Message) { 1131 return true 1132 } 1133 return false 1134 }