Skip to content

Kubernetes Infrastructure failures consume retry attempts. #73238

Open

Description

@aru-trackunit

Under which category would you file this issue?

Providers

Apache Airflow version

3.3.1

What happened and how to reproduce it?

Executor KubernetesExecutor(parallelism=60) reported that the task instance <TaskInstance: xxxx scheduled__2026-09-16T07:00:00+00:00 map_index=5 [queued] ti_id=01a0a95a-37ed-7177-b212-4e25272f5c2a> finished with state failed, but the task instance's state attribute is queued. Learn more: https://airflow.apache.org/docs/apache-airflow/stable/troubleshooting.html#task-state-changed-externally Extra info: Pod failed because of None [airflow.task] loc=taskinstance.py:1841"

On the screenshot below it says that first attempt is not visible, and it succeeded performing only 2nd attempt.

Image

What you think should happen instead?

It would be valuable to support different retry policies based on the cause of a failure, particularly for application failures versus infrastructure or connectivity failures.

Task authors may want deterministic application errors to fail immediately, since rerunning the same code with the same input is unlikely to succeed. They may also need to disable retries when an operation is non-idempotent or otherwise unsafe to repeat.

However, disabling retries entirely is risky. A task can fail before the operation even begins—for example, because its worker pod cannot start—or because of a transient network problem or temporary third-party service outage. These failures may be safe and worthwhile to retry.

Conversely, applying one retry policy to every failure can cause deterministic application errors to run repeatedly when they should fail immediately.

Ideally, Airflow would allow different behavior for at least the following categories:

  • Application failures that should fail immediately
  • Infrastructure or pre-execution failures that should be retried without consuming the application retry budget
  • Transient connectivity or third-party failures that should follow a configurable retry policy

I am not sure what the ideal implementation would look like, but representing application errors, infrastructure failures, and third-party timeouts with the same generic failed state makes it difficult to choose a safe and effective retry strategy.

Apologies for covering several concerns in one ticket, but I believe they are closely related: without knowing why a task failed, Airflow cannot determine—or allow users to configure—the appropriate retry behavior.

Related:
#73164
#69052
#66405

Operating System

Debian GNU/Linux 12 (bookworm)

Deployment

Official Apache Airflow Helm Chart

Apache Airflow Provider(s)

cncf-kubernetes

Versions of Apache Airflow Providers

apache-airflow-providers-amazon==9.34.0
apache-airflow-providers-cncf-kubernetes==10.21.0
apache-airflow-providers-common-compat==1.18.0
apache-airflow-providers-common-io==1.8.0
apache-airflow-providers-common-sql==2.1.0
apache-airflow-providers-databricks==7.18.1
apache-airflow-providers-fab==3.8.0
apache-airflow-providers-github==2.11.3
apache-airflow-providers-hashicorp==4.8.0
apache-airflow-providers-http==6.0.5
apache-airflow-providers-microsoft-mssql==4.7.0
apache-airflow-providers-mysql==6.6.1
apache-airflow-providers-postgres==7.0.1
apache-airflow-providers-sftp==6.0.1
apache-airflow-providers-slack==9.10.2
apache-airflow-providers-smtp==3.0.3
apache-airflow-providers-ssh==6.0.1
apache-airflow-providers-standard==1.17.0

Official Helm Chart version

1.22.0 (latest released)

Kubernetes Version

1.34.9

Helm Chart configuration

No response

Docker Image customizations

No response

Anything else?

No response

Are you willing to submit PR?

  • Yes I am willing to submit a PR!

Code of Conduct

Activity

  1. added and removed
    needs-triagelabel for new issues that we didn't triage yet
    on Sep 16, 2026
  2. added theissue type on Sep 16, 2026
  3. kartik00052 commented on Sep 22, 2026

    @kartik00052
    Contributor

    Reading through the retry/failure paths after seeing this issue and #73395 — a couple of things worth pointing at directly with code references, plus a possible way to close the remaining gap.

    A bounded, separately-counted infra retry already exists for one case.

    KubernetesExecutor._is_pre_execution_failure() (in kubernetes_executor.py) already distinguishes a pod that failed before the task instance reached RUNNING from one that failed after — it checks ti_state == QUEUED at failure time, and if that holds, the pod is requeued on its own counter (pod_launch_failure_max_retries) without ever touching task.retries. That's exactly the "infra failure shouldn't burn the application's retry budget" behavior this issue is asking for — it just only covers pod-launch failures.

    The mid-execution case — the one in this issue's screenshot — doesn't get that treatment.

    Once a task instance is RUNNING and gets killed externally (pod eviction, node drain, SIGTERM), that's the case #73395 is fixing: right now on main, _on_term in the task runner doesn't raise anything, so execute() silently resumes and the task can end up reported SUCCESS even though the work was interrupted. #73395 fixes that by raising AirflowTaskTerminated.

    But looking at how task_runner.py handles that exception (the except-chain around except AirflowTaskTerminated), it goes straight to state = TaskInstanceState.FAILED — it never calls _handle_current_task_failed, which is the path that checks TaskInstance.is_eligible_to_retry(). So after #73395 merges, a mid-execution infra kill won't silently succeed anymore, but it will permanently fail with zero retries even if the task has retries=5 set. #73395's own PR description flags this directly: "Whether an infrastructure kill should instead be retry-eligible is a real question — it is what #73238 is about... changing that classification also changes the UI 'mark failed' path, so it is left alone here."

    So the gap this issue is really about, narrowed down: AirflowTaskTerminated needs the same kind of separate, bounded retry budget that pod-launch failures already get — not the application's task.retries, and not zero.

    A possible shape for that, modeled on the existing pattern rather than inventing something new:

    • A new config, e.g. infrastructure_failure_max_retries, parallel to pod_launch_failure_max_retries.
    • The real complication: pod_launch_failure_max_retries is tracked in KubernetesExecutor.pod_launch_attempts, an in-memory counter on the scheduler process. AirflowTaskTerminated is raised inside the task runner process itself — it doesn't have access to that. So this can't just reuse the same storage; the counter would need to live somewhere both sides can see it (task instance / task instance history row seems like the natural place, but I haven't traced whether there's already a suitable field, or whether this needs a new column/attempt-tracking concept).
    • Rather than making AirflowTaskTerminated unconditionally terminal, it would need to go through something like _handle_current_task_failed's retry-eligibility check, but against the new infra-budget instead of (or in addition to) task.retries.
    • I'd explicitly leave the UI "mark failed" path alone, same as Fail a task whose execute is interrupted by SIGTERM #73395 did — that's a separate behavior change and probably deserves its own discussion if anyone wants to touch it.

    Given this touches task-sdk (where AirflowTaskTerminated is raised) and core retry semantics (not just the K8s executor), I'd guess this needs at least a short AIP rather than going straight to a PR, but I'm not certain where the line is for a change this size — happy to be corrected.

    Open questions I don't have good answers to yet:

    • Where should the infra-retry counter actually live given the cross-process constraint above?
    • Should this budget be K8s-executor-specific (like today's pod-launch one) or executor-agnostic, since AirflowTaskTerminated itself is raised in executor-agnostic code?
    • Does this want to be one unified "infrastructure failure retry budget" covering both pre-execution and mid-execution cases, or should they stay as separate mechanisms since they're detected completely differently?

    Happy to take a pass at drafting this out further (or an AIP if that's the right next step) if this direction seems reasonable — wanted to sanity-check the approach here first rather than show up with a PR that reopens a decision #73395 intentionally deferred.

  4. namanjain24-sudo commented on Sep 22, 2026

    @namanjain24-sudo
    Contributor

    Thanks for the detailed breakdown, @kartik00052 — matches what I ran into while working on #73395. Once that lands, I'd like to pick up the infra-retry-budget piece you described (a separate, bounded retry for AirflowTaskTerminated, not the application's task.retries).

    Since it touches task-sdk and core retry semantics across processes, would a short AIP be the right next step here, or is a design write-up in the PR description enough for something this size? Wanted to check before writing code.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions