AIP-97 done-criterion #2: Kubernetes bridge, wired end-to-end (app-OOM excluded, infra refunded) - #7
AIP-97 done-criterion #2: Kubernetes bridge, wired end-to-end (app-OOM excluded, infra refunded)#71fanwang wants to merge 4 commits into
Conversation
…-criterion #2) to_task_failure_info() maps collect_pod_failure_details() output to a core TaskFailureInfo: node-level disruption (Evicted/Preempting/NodeShutdown/ DisruptionTarget) is source=infra; a container that ended on its own (app crash, or OOMKilled against its own limit) is source=user, so an app OOM earns no infra refund. The precise per-executor classification the scheduler signal cannot do. Verified on a real kind cluster: a pod OOMKilled against its 48Mi limit (exit 137) flows through the real collect_pod_failure_details -> to_task_failure_info and classifies source=user. 12 unit tests pin the reason->source table. Signed-off-by: 1fanwang <1fannnw@gmail.com>
The KubernetesExecutor now classifies a failed pod via to_task_failure_info() in _change_state and stashes the TaskFailureInfo on BaseExecutor.task_failure_info; the scheduler reads it once via get_task_failure_info() and prefers it over the generic infra tag when handling the failure. So the listener and the retry decision get the real pod reason: an Evicted/killed pod is infra (refunded), an OOMKilled container against its own limit is user (not refunded). Verified live on a real kind cluster (KubernetesExecutor, in-cluster): - k8s_oom OOMKilled -> source=user -> not refunded (try=2 max_tries=1, FAILED) - k8s_sleep pod force-deleted -> source=infra -> refunded (max_tries 1->2) Signed-off-by: 1fanwang <1fannnw@gmail.com>
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
…om leaking Two self-review fixes: - _maybe_refund_infra_attempt: task.retries can be None (unset) -> the cap math raised TypeError, and the scheduler call site isn't wrapped, so it aborted the whole executor-event batch. Treat retries as falsy like is_eligible_to_retry does; skip the refund when there's no user budget (it was an inert no-op anyway). - The scheduler now pops executor.get_task_failure_info(ti.key) when it processes the event, not only inside the killed-externally branch. A normally-failed (self-reporting) task's event skips that branch, so its stashed entry orphaned and the dict grew unbounded over the scheduler's lifetime. Now every processed event clears its entry. Signed-off-by: 1fanwang <1fannnw@gmail.com>
…nt (done-criterion #6) Signed-off-by: 1fanwang <1fannnw@gmail.com>
|
Superseded by #12 (Kubernetes classify) in the consolidated apache#66405. Closing; branch kept. |
Why
Stacked on #6 (Pillar 2). That PR classifies infra at the scheduler from the state-mismatch signal — enough to tell a self-reporting app failure from a silent death, but not the precise cause. Only the executor holds that: a pod
Evictedby the node vs a containerOOMKilledagainst its own limit. This is done-criterion #2 — the Kubernetes bridge, wired end to end.What
to_task_failure_info()maps the executor'scollect_pod_failure_details()output to a coreTaskFailureInfo: a node-level disruption (Evicted/Preempting/NodeShutdown/DisruptionTarget) isinfra; a container that ended on its own — an app crash or anOOMKilledagainst its own limit — isuser, so an app OOM earns no infra refund.KubernetesExecutorclassifies a failed pod in_change_stateand stashes theTaskFailureInfoonBaseExecutor.task_failure_info; the scheduler reads it once viaget_task_failure_info()and prefers it over the generic infra tag. So the listener and the retry decision get the real pod reason.Testing Done
Pre/post A/B on a real kind cluster — same deployment, flag off vs on, all 6 modes. Airflow deployed in-cluster (Postgres + api-server + dag-processor + scheduler as pods from this source),
KubernetesExecutorlaunching real task pods,max_infra_refunds=3, a recording listener in the scheduler pod. Every mode run twice; only[core] infra_failure_refund_retriesdiffers between the sweeps.k8s_appbugValueErrortry=2 max_tries=1try=2 max_tries=1k8s_oomtry=2 max_tries=1try=2 max_tries=1k8s_timeoutexecution_timeout→AirflowTaskTimeouttry=2 max_tries=1try=2 max_tries=1k8s_markfailtry=1 max_tries=0try=1 max_tries=0k8s_sleeptry=2 max_tries=1try=5 max_tries=4k8s_mixedValueErrortry=2 max_tries=1try=3 max_tries=2Two properties, both proven live:
failure_details— Pillar 1 is additive — butmax_triesnever moves.)k8s_sleepdied after 2 kills in RED (max_tries=1) and survived 5 in GREEN (refunded 1→2→3→4, then capped).k8s_mixedis the headline: the infra kill on attempt 1 is refunded, so the app code still gets its full two attempts (try 2 and 3 ran to the realValueError) — in RED that same kill left the app only one shot.Raw scheduler logs (verbatim)
Unit — 13 tests: the reason→source table (
Evicted/Preempting/NodeShutdown/... →infra;OOMKilled/Error→user; a nodeEvictedwith a container OOM →infra), an end-to-end pass throughcollect_pod_failure_detailswith an OOMKilledV1Pod, and the executor→scheduler seam (get_task_failure_infoclears after one read).Out of scope
A real node-pressure
Evicted(phase=Failed, reason=Evicted) needs controllable node memory, unsafe to force on a single-node kind; the external force-delete drives the samesource='infra'→ refund path and the precise pod-reason mapping is unit-pinned.