Skip to content

feat(kubernetes): retry disrupted Job pods - #327

Open
steve-bako wants to merge 1 commit into
TangleML:masterfrom
steve-bako:sbako/retry-disrupted-kubernetes-jobs
Open

feat(kubernetes): retry disrupted Job pods#327
steve-bako wants to merge 1 commit into
TangleML:masterfrom
steve-bako:sbako/retry-disrupted-kubernetes-jobs

Conversation

@steve-bako

Copy link
Copy Markdown

Problem

Tangle renders every Kubernetes container execution as an Indexed Job with backoffLimitPerIndex: 0 and maxFailedIndexes: 0. A pod deleted by scheduler preemption, the eviction API, or node loss therefore fails its index immediately and the Job never creates a replacement.

This is distinct from a component returning a non-zero exit code: infrastructure disruptions often have no useful application exit code and carry the Kubernetes DisruptionTarget=True condition.

Observed production examples:

  • scheduler preemption: DisruptionTarget=True, reason PreemptionByScheduler; a higher-priority CoreDNS pod displaced a running Tangle task
  • autoscaler eviction: DisruptionTarget=True, reason EvictionByEvictionAPI

See Shopify/oasis-backend#491.

Change

Add an opt-in task annotation:

annotations:
  tangleml.com/launchers/kubernetes/job/disruption_retries: "1"

For opted-in Kubernetes Jobs:

  • set backoffLimitPerIndex to the requested bounded retry count (0–10)
  • Count a pod with DisruptionTarget=True, allowing the Job controller to replace it until that budget is exhausted
  • FailIndex immediately when the main container exits non-zero, so deterministic user-code failures are not rerun
  • set podReplacementPolicy: Failed, as required with podFailurePolicy

The default remains exactly zero retries with no pod failure policy, preserving existing behavior for every task that does not opt in.

Safety

  • Opt-in only; no global rerun behavior.
  • Values are validated as canonical integers in [0, 10] and fail closed.
  • Application failures remain fail-fast.
  • This is a pod replacement within the same Kubernetes Job and Tangle execution, not a sibling pipeline run.
  • Components opting in must be restart-safe because a disrupted attempt may have produced partial external side effects.

Validation

  • PYTHONPATH=. uv run --frozen pytest -q — 475 passed
  • Black check passed
  • Focused tests verify default behavior, disruption classification, application fail-fast behavior, and invalid inputs

Remaining validation

Draft pending a real-GKE canary that deletes/evicts an opted-in test pod and confirms one replacement is created under the same Job before this is enabled on production workloads.

@steve-bako

Copy link
Copy Markdown
Author

@Ark-kun When you have a chance, could you review the API/Job-policy shape? This is deliberately draft and opt-in pending a real-GKE disruption canary. The exact production DisruptionTarget evidence and safety constraints are in the PR body; full suite is green (475 tests).

Signed-off-by: Steve Bako <steve.bako@shopify.com>
Assisted-By: devx/3e534c34-53a1-4bfa-9aee-cc649ee10009
Signed-off-by: Steve Bako <steve.bako@shopify.com>
@steve-bako
steve-bako force-pushed the sbako/retry-disrupted-kubernetes-jobs branch from c438c67 to e800927 Compare August 13, 2026 14:28
@steve-bako

Copy link
Copy Markdown
Author

Follow-up pushed: added the user-facing annotation/restart-safety contract to README and an assertion over the exact serialized Kubernetes Job spec (backoffLimitPerIndex, podFailurePolicy, podReplacementPolicy, and maxFailedIndexes). Full suite remains green: 475 passed.

@steve-bako
steve-bako marked this pull request as ready for review August 14, 2026 11:47
@steve-bako
steve-bako requested a review from a team August 14, 2026 11:47
@steve-bako

Copy link
Copy Markdown
Author

Marking ready for maintainer review. The feature is opt-in and defaults to the existing zero-retry behavior, so upstream merge alone changes no workload. Proposed validation sequence: merge upstream -> bump Oasis staging submodule -> run a real GKE DisruptionTarget replacement canary -> only then add the annotation to restart-safe production tasks.

@steve-bako
steve-bako requested a review from Ark-kun as a code owner August 14, 2026 11:47
@steve-bako

Copy link
Copy Markdown
Author

New real-GKE evidence from staging run https://oasis.shopify.io/runs/01a000936be1d56d65cc: the Embed pod ran successfully for 10h15m, then its node became NotReady. Kubernetes set DisruptionTarget=True, reason DeletionByTaintManager, message Taint manager: deleting due to NoExecute taint. The Job had backoffLimitPerIndex: 0, immediately set failedIndexes: "0", and failed MaxFailedIndexesExceeded; there was no application or BigQuery error. The pod also had cluster-autoscaler.kubernetes.io/safe-to-evict: "false", confirming anti-scale-down annotation cannot cover node failure. The opt-in policy in this PR matches this exact condition and would create the bounded replacement. The checkpointed candidate will not be retried.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant