Skip to content

fix(controller): cancel in-flight spark-submit on controller shutdown#2934

Open
a7i wants to merge 3 commits into
kubeflow:masterfrom
a7i:fix/controller-shutdown-grace-window
Open

fix(controller): cancel in-flight spark-submit on controller shutdown#2934
a7i wants to merge 3 commits into
kubeflow:masterfrom
a7i:fix/controller-shutdown-grace-window

Conversation

@a7i

@a7i a7i commented May 8, 2026

Copy link
Copy Markdown

Purpose of this PR

Reduce duplicate driver submissions when the controller restarts mid-submit (refs #2788). Complementary to adoption in #2932: adoption recovers races this cannot avoid; this PR narrows the window by making in-flight spark-submit cancellable on shutdown.

On SIGTERM, controller-runtime cancels the reconcile context immediately. The post-submit status write uses that same context, so the real lever is interrupting the spark-submit JVM before it creates a driver pod the controller will never record. Previously runSparkSubmit used exec.Command without context propagation.

Proposed changes:

  • SparkSubmitter.runSparkSubmit: use exec.CommandContext, send SIGTERM on cancellation (not SIGKILL), and wait up to ShutdownGracePeriod before force-killing the child.
  • Reuse --driver-pod-creation-grace-period for SparkSubmitter.ShutdownGracePeriod (WaitDelay after SIGTERM). Leave Manager.GracefulShutdownTimeout at controller-runtime's 30s default (not overridden). No separate --graceful-shutdown-timeout flag.
  • Helm: controller.terminationGracePeriodSeconds (default 60), must exceed the 30s manager shutdown window.
  • Startup validation: reject driver-pod-creation-grace-period >= 30s so WaitDelay stays below GracefulShutdownTimeout.
  • Unit tests for runSparkSubmit (SIGTERM on cancel, WaitDelay before force-kill) and chart deployment rendering.

How the defaults were chosen

Setting Default Role
driverPodCreationGracePeriod 10s Retry "driver pod not found" after submit; cmd.WaitDelay after SIGTERM to spark-submit
GracefulShutdownTimeout 30s (controller-runtime default) Manager cleanup window after SIGTERM; unchanged from today
terminationGracePeriodSeconds 60 Kubelet ceiling; must stay above GracefulShutdownTimeout so the manager can reap in-flight JVMs before SIGKILL

Ordering: driverPodCreationGracePeriod < GracefulShutdownTimeout (30s) < terminationGracePeriodSeconds.

Typical spark-submit in kubernetes mode with submission-wait-app-completion=false takes ~5-10s; worst case on a busy cluster ~15-20s. Tune driverPodCreationGracePeriod up if your cluster needs more headroom (must stay below 30s).

Change Category

  • Bugfix (non-breaking change which fixes an issue)
  • Feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that could affect existing functionality)
  • Documentation update

Rationale

Refs #2788. Complementary to #2932.

Checklist

  • I have conducted a self-review of my own code.
  • I have updated documentation accordingly.
  • I have added tests that prove my changes are effective or that my feature works.
  • Existing unit tests pass locally with my changes.

Additional Notes

Locally validated:

  • go test ./internal/controller/sparkapplication/ -run TestRunSparkSubmit -timeout 120s
  • helm unittest charts/spark-operator-chart -f 'tests/controller/deployment_test.yaml'

@google-oss-prow

Copy link
Copy Markdown
Contributor

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by:
Once this PR has been reviewed and has the lgtm label, please assign chenyi015 for approval. For more information see the Kubernetes Code Review Process.

The full list of commands accepted by this bot can be found here.

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@github-actions

github-actions Bot commented May 8, 2026

Copy link
Copy Markdown

🎉 Welcome to the Kubeflow Spark Operator! 🎉

Thanks for opening your first PR! We're happy to have you as part of our community 🚀

Here's what happens next:

Join the community:

Feel free to ask questions in the comments if you need any help or clarification!
Thanks again for contributing to Kubeflow! 🙏

@a7i a7i force-pushed the fix/controller-shutdown-grace-window branch from ae91caf to 71f74fc Compare May 8, 2026 01:45
@a7i a7i marked this pull request as ready for review May 8, 2026 01:50
Copilot AI review requested due to automatic review settings May 8, 2026 01:50

Copilot AI 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.

Pull request overview

This PR reduces the likelihood of losing the post-spark-submit status update during controller shutdown/restarts by (1) increasing the controller-runtime graceful shutdown window and (2) propagating reconcile cancellation into the spark-submit subprocess.

Changes:

  • Add a --graceful-shutdown-timeout CLI flag (default 60s) and wire it to controller-runtime GracefulShutdownTimeout.
  • Make spark-submit execution cancellable by using exec.CommandContext(ctx, ...) and threading the reconcile context into submission.
  • Extend the Helm chart to configure controller.gracefulShutdownTimeout (default 60s) and controller.terminationGracePeriodSeconds (default 90), with unit tests and README updates.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
internal/controller/sparkapplication/submission.go Pass reconcile context into spark-submit via exec.CommandContext to support cancellation.
cmd/operator/controller/start.go Add --graceful-shutdown-timeout flag and configure controller-runtime manager shutdown timeout.
charts/spark-operator-chart/values.yaml Introduce default values for graceful shutdown timeout and pod termination grace period.
charts/spark-operator-chart/templates/controller/deployment.yaml Render the new controller arg and set terminationGracePeriodSeconds on the controller Pod spec.
charts/spark-operator-chart/tests/controller/deployment_test.yaml Add Helm unit tests for the new arg and pod termination grace period settings.
charts/spark-operator-chart/README.md Document the new Helm values.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +64 to +65
// cancellation) terminates the child process instead of letting it run to completion
// and create a driver pod whose status update will never be persisted.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Reworded in b41b544 to drop the implication that cancellation prevents a driver pod from being created; the comment now says cancellation only narrows the window for losing the post-submit status update.

Comment thread internal/controller/sparkapplication/submission.go

@tariq-hasan tariq-hasan left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Hi @a7i! I have left a few questions. Thanks!

/cc @nabuskey @ChenYi015

Comment thread internal/controller/sparkapplication/submission.go
Comment thread internal/controller/sparkapplication/submission.go
# has time to drain in-flight reconciles, including any that are still writing status
# after a successful spark-submit.
terminationGracePeriodSeconds: 90

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I am wondering if in-flight reconciles can really finish if a termination signal is already propagated down the controller-runtime chain to the reconcile loop before it hits the spark-submit.

My understanding is that the real crux of the code changes here is that JVM processes are being terminated or killed off through signal propagation so we have a better chance of avoiding driver pod creation altogether. Reconciles seem to be cancelled immediately and are simply given a grace period for cleanup rather than being allowed to finish.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Good point, you are right. The controller-runtime cancels the reconcile context as soon as SIGTERM arrives, and updateSparkApplicationStatus uses that same context, so the window does not let a normal reconcile finish writing status. The real value is interrupting the in-flight spark-submit JVM before it creates a driver pod the controller will never get to record. I reworded both gracefulShutdownTimeout and terminationGracePeriodSeconds in b41b544 to reflect that (cleanup/reap window, not "let reconciles finish"). Regenerated the chart README as well.

@a7i

a7i commented Jun 4, 2026

Copy link
Copy Markdown
Author

Production context

We hit the same failure mode in production on 2026-06-03 (details in #2788). Two SparkApplication resources were created while the controller was rolling to a new version; both followed the same sequence.

How this PR helps: SIGTERM during rollout interrupted an in-flight submit after the driver pod often already existed. Making spark-submit cancellable and widening the shutdown window should reduce orphan submissions and lost status writes on normal shutdown. That aligns with the clarified intent in review (cleanup/reap, not letting reconciles run to completion).

What still needs #2932: In our trace the driver was already created before cancel, and the next leader logged driver pod already exist and marked the CR FAILED. Adoption/idempotent submit (#2932) is what recovers that case; this PR and #2932 are complementary.

We are also planning maxUnavailable: 0 / maxSurge: 1 on the controller Deployment and chart wiring for --graceful-shutdown-timeout / terminationGracePeriodSeconds once this lands.

@a7i

a7i commented Jun 17, 2026

Copy link
Copy Markdown
Author

Hi @tariq-hasan @nabuskey @ChenYi015 - this is a pretty low risk PR, would you consider reviewing this?

We continue to have issues in production when the operator pods are rotated or we do a new operator rollout. This causes pipelines that are mid-flight (in particular driver pod creation via spark-submit) to be cancelled.

@a7i

a7i commented Jun 17, 2026

Copy link
Copy Markdown
Author

If you prefer, I can remove the graceful-shutdown-timeout" flag and instead ensure that the context is not cancelled with the --driver-pod-creation-grace-period as the wait delay

@a7i a7i force-pushed the fix/controller-shutdown-grace-window branch from b41b544 to 5524fb2 Compare June 25, 2026 13:36
@a7i a7i changed the title fix(controller): widen shutdown grace window for in-flight submissions fix(controller): cancel in-flight spark-submit on controller shutdown Jun 25, 2026
@a7i

a7i commented Jun 25, 2026

Copy link
Copy Markdown
Author

If you prefer, I can remove the graceful-shutdown-timeout" flag and instead ensure that the context is not cancelled with the --driver-pod-creation-grace-period as the wait delay

ended up doing this to reduce the changes and flags

@a7i a7i force-pushed the fix/controller-shutdown-grace-window branch from 5524fb2 to 4cd9268 Compare June 25, 2026 15:24
Comment thread cmd/operator/controller/start.go Outdated
LeaseDuration: &leaderElectionLeaseDuration,
RenewDeadline: &leaderElectionRenewDeadline,
RetryPeriod: &leaderElectionRetryPeriod,
GracefulShutdownTimeout: &driverPodCreationGracePeriod,

@nabuskey nabuskey Jun 25, 2026

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 changes the current (implicit) behaviour of GracefulShutdownTimeout. It currently sets to 30 seconds viasetOptionsDefaults in controller manager.

So creation grace period setting is applied to all controller runtime managed context.

Is this needed? As far as I can tell, it should be: driverPodCreationGracePeriod < GracefulShutdownTimeout < terminationGracePeriodSeconds

If this must be in exactly this order, we could either derive it from a value or validate it at startup to protect end users.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

yeah initially I added a new flag called gracefulShutdownTimeout but removed it thinking that it would simplify the implementation. let me take another look

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Addressed in 8a7a6a2: GracefulShutdownTimeout is now driverPodCreationGracePeriod + 1s, so the ordering is driverPodCreationGracePeriod < GracefulShutdownTimeout < terminationGracePeriodSeconds with defaults 10s / 11s / 60s. Chart values and flag help text document the constraint.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Correction in 22469f5: we no longer override GracefulShutdownTimeout. It stays at controller-runtime's 30s default; driverPodCreationGracePeriod only sets runSparkSubmit WaitDelay. Startup rejects grace period >= 30s to preserve ordering.

cmd.Cancel = func() error {
return cmd.Process.Signal(syscall.SIGTERM)
}
cmd.WaitDelay = s.ShutdownGracePeriod

@nabuskey nabuskey Jun 25, 2026

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.

Is this a race condition? We wait for spark-submit command to exit and ShutdownGracePeriod for exactly the same time. So there's no guarantee that one happens before the other?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Good catch. WaitDelay and GracefulShutdownTimeout were both 10s before, so the manager could exit while runSparkSubmit was still in its WaitDelay window. GracefulShutdownTimeout is now driverPodCreationGracePeriod + 1s, and a test asserts we wait the full ShutdownGracePeriod before force-kill when SIGTERM is ignored.

a7i and others added 2 commits July 14, 2026 08:45
When the controller leader receives SIGTERM during spark-submit, the reconcile
context is cancelled immediately and the JVM subprocess may keep running until
the process exits or the kubelet sends SIGKILL. That can create a driver pod
the controller never records, contributing to duplicate submissions on restart.

- Add SparkSubmitter.runSparkSubmit using exec.CommandContext; send SIGTERM
  on cancellation and wait up to ShutdownGracePeriod before force-killing.
- Reuse --driver-pod-creation-grace-period for ShutdownGracePeriod and
  Manager.GracefulShutdownTimeout (no separate graceful-shutdown flag).
- Helm: controller pod terminationGracePeriodSeconds default 60.
- Add runSparkSubmit unit tests and chart deployment tests.

Refs kubeflow#2788

Signed-off-by: Amir Alavi <amiralavi7@gmail.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Set GracefulShutdownTimeout to driverPodCreationGracePeriod plus one
second so the manager outlives runSparkSubmit's WaitDelay window.
Document the ordering constraint and add a test for force-kill after
WaitDelay.

Co-authored-by: Cursor <cursoragent@cursor.com>
@a7i a7i force-pushed the fix/controller-shutdown-grace-window branch from 4cd9268 to 8a7a6a2 Compare July 14, 2026 12:47
Do not override GracefulShutdownTimeout with driverPodCreationGracePeriod.
The manager default stays 30s; driverPodCreationGracePeriod only caps
runSparkSubmit WaitDelay. Reject grace periods at or above 30s at startup.

Co-authored-by: Cursor <cursoragent@cursor.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants