Skip to content

Fix KubernetesPodOperator 404 on pod preemption (#59626) - #60626

Closed
cruseakshay wants to merge 5 commits into
apache:mainfrom
cruseakshay:fix-pod-preemption-404
Closed

cruseakshay wants to merge 5 commits into
apache:mainfrom
cruseakshay:fix-pod-preemption-404

Conversation

@cruseakshay

Copy link
Copy Markdown
Contributor

Problem

When using KubernetesPodOperator with cluster autoscaling, task pods can be preempted by higher-priority daemonsets during node bootstrap. This results in a 404 error when Airflow tries to read the pod status, causing immediate task failure instead of allowing Kubernetes to reschedule the pod.

Fixes #59626

Solution

Introduce state-aware retry logic that tracks whether a pod ever reached the Running state:

  • Pod never reached Running → Raise PodPreemptedException (retriable)
  • Pod was Running → Raise PodNotFoundException (terminal failure)

This prevents duplicate execution of non-idempotent tasks while allowing safe retries for pods preempted before they started.

Changes

File Change
exceptions.py Add PodNotFoundException and PodPreemptedException
pod_manager.py Add PodPhaseTracker dataclass and 404 handling logic
hooks/kubernetes.py Add phase tracker support to async get_pod()
triggers/pod.py Integrate phase tracking in KubernetesPodTrigger
kubernetes_helper_functions.py Add PodPreemptedException to retry logic
test_pod_manager.py Add comprehensive tests for new functionality

Testing

  • Unit tests for PodPhaseTracker state transitions
  • Unit tests for 404 handling with different pod states
  • Backward compatibility tests (no tracker = existing behavior)

Was generative AI tooling used to co-author this PR?
  • Yes (please specify the tool below)

Generated-by: Claude (Cursor) following the guidelines

Add state-aware retry for 404 errors when pods are preempted by
daemonsets during node bootstrap. Only retry if pod never reached
Running state to prevent duplicate execution of non-idempotent tasks.
@boring-cyborg boring-cyborg Bot added area:providers provider:cncf-kubernetes Kubernetes (k8s) provider related issues labels Jan 16, 2026
@cruseakshay
cruseakshay marked this pull request as ready for review January 16, 2026 07:15

@jscheffl jscheffl left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are some questions and logic problems in this PR.

Not sure if I have a mis-understanding or what the aim of the fix is.

Comment on lines +228 to +229
if pod.status.phase == PodPhase.RUNNING:
self.ever_reached_running = True

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about the race condition if the Pod was so fast that it is already failed or completed when checking? I think it might be a bit dangerous checking just for running.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for pods running very quickly, (Pending → Running → Succeeded/Failed),

if pod.status.phase in (PodPhase.RUNNING, PodPhase.SUCCEEDED, PodPhase.FAILED):
            self.ever_started_execution = True

with this we can safely consider, any phase beyond Pending means pod was scheduled and not preempted.

except HTTPError as e:
raise KubernetesApiException(f"There was an error reading the kubernetes API: {e}")

def _handle_pod_not_found(self, pod: V1Pod, phase_tracker: PodPhaseTracker | None) -> None:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The code here is redundant (and some more) here in the utility and the hook. please consolidate to one place and copy only.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, will consolidate under PodPhaseTracker.

Comment on lines +964 to +969
raise PodNotFoundException(
f"Pod '{name}' in namespace '{namespace}' not found. "
f"The pod was previously observed in Running state (last phase: "
f"{phase_tracker.last_observed_phase}), so this is treated as a "
f"terminal failure to prevent duplicate execution of non-idempotent workloads."
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is not clear to me. There might be cases where a logic is only allowed to start only once and then a broken state must not retried - and there are cases where a failed execution still should retry.

I understand that you want to handle it differently but the exceptions are just different exceptions, but it is not changing anything in retry handling. Also the text is mis-leading because besides the exception types the "normal" retry would happen if configured?

For me this PR only makes sense if a configurable setting based on Exception type skips or makes the retry based on preemption.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jscheffl, thanks for the valuable feedback.
As task-level retries will, in turn, try pod re-creation, I am wondering if it is worth adding more complexity for internal retries. I will rely on your discretion here.

If I were to continue implementing - configurable part:

  • modify operator to support config: user can opt-in for this change in behaviour
KubernetesPodOperator(
    task_id="my_task",
    retry_on_preemption=True, # Default False
    ...
)
  • modify triggerer: PodPreemptedException is caught, yield status="preempted"
  • modify operator to distinguish preemption from other failures and decide whether to retry (based on config)

def _should_retry_api(exc: BaseException) -> bool:
"""Retry on selected ApiException status codes, plus plain HTTP/timeout errors."""
"""Retry on transient API errors, HTTP errors, and PodPreemptedException."""
if isinstance(exc, PodPreemptedException):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is wrong. The function is used to retry calling API if e.g. a HTTP 500 or 403 is returned - so on call level. I assume you wanted to make the Pod execution retryable - but this is not defined with this method.

@cruseakshay
cruseakshay marked this pull request as draft January 20, 2026 15:34
@cruseakshay cruseakshay closed this Feb 5, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:providers provider:cncf-kubernetes Kubernetes (k8s) provider related issues

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Kubernetes Pod Operator fails with 404 errors when pods are preempted by daemonsets

2 participants