-
Notifications
You must be signed in to change notification settings - Fork 1.5k
feat: garbage-collect terminated SparkApplications via operator default TTL #3042
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from 6 commits
f62e891
b22ea35
a09851b
e970edd
c1a6742
41f0c42
998d1b1
8c2b854
f5a607e
80263ac
0117162
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -85,6 +85,7 @@ var ( | |
| controllerThreads int | ||
| cacheSyncTimeout time.Duration | ||
| maxTrackedExecutorPerApp int | ||
| defaultTimeToLiveSeconds int64 | ||
|
|
||
| // Driver PDB feature gate. When enabled, the controller creates a | ||
| // PodDisruptionBudget for each SparkApplication that sets | ||
|
|
@@ -205,6 +206,10 @@ func NewStartCommand() *cobra.Command { | |
| command.Flags().StringVar(&namespaceSelector, "namespace-selector", "", "Label selector for namespaces to watch (e.g., 'spark-operator=enabled,env in (prod,staging)'). Namespaces matching this selector will be watched in addition to those specified via --namespaces. Requires ClusterRole permission to list and watch namespaces.") | ||
| command.Flags().DurationVar(&cacheSyncTimeout, "cache-sync-timeout", 30*time.Second, "Informer cache sync timeout.") | ||
| command.Flags().IntVar(&maxTrackedExecutorPerApp, "max-tracked-executor-per-app", 1000, "The maximum number of tracked executors per SparkApplication.") | ||
| command.Flags().Int64Var(&defaultTimeToLiveSeconds, "default-time-to-live-seconds", 0, | ||
| "Default Time-To-Live in seconds applied to terminated SparkApplications that do "+ | ||
| "not set spec.timeToLiveSeconds. Requires the DefaultTimeToLive feature gate. "+ | ||
| "0 (default) or negative disables it.") | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit: the
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Updated the helper,
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. func EffectiveTimeToLiveSeconds(app *v1beta2.SparkApplication, defaultSeconds int64) (*int64, bool) {
if app.Spec.TimeToLiveSeconds != nil {
return app.Spec.TimeToLiveSeconds, false
}
if features.Enabled(features.DefaultTimeToLive) && defaultSeconds > 0 {
seconds := defaultSeconds
return &seconds, true
}
return nil, false
}Zero results in backward compatibility. |
||
| command.Flags().BoolVar(&enableDriverPDB, "enable-driver-pdb", false, | ||
| "Enable creation of a PodDisruptionBudget for Spark driver pods. "+ | ||
| "Each SparkApplication must additionally opt in via "+ | ||
|
|
@@ -528,6 +533,7 @@ func newSparkApplicationReconcilerOptions() sparkapplication.Options { | |
| SparkExecutorMetrics: sparkExecutorMetrics, | ||
| MaxTrackedExecutorPerApp: maxTrackedExecutorPerApp, | ||
| EnableDriverPDB: enableDriverPDB, | ||
| DefaultTimeToLiveSeconds: defaultTimeToLiveSeconds, | ||
| } | ||
| if enableBatchScheduler { | ||
| options.KubeSchedulerNames = kubeSchedulerNames | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -32,6 +32,7 @@ import ( | |||||||||||||||||
|
|
||||||||||||||||||
| "github.com/kubeflow/spark-operator/v2/api/v1beta2" | ||||||||||||||||||
| "github.com/kubeflow/spark-operator/v2/pkg/common" | ||||||||||||||||||
| "github.com/kubeflow/spark-operator/v2/pkg/features" | ||||||||||||||||||
| ) | ||||||||||||||||||
|
|
||||||||||||||||||
| // GetDriverPodName returns name of the driver pod of the given spark application. | ||||||||||||||||||
|
|
@@ -60,14 +61,26 @@ func IsTerminated(app *v1beta2.SparkApplication) bool { | |||||||||||||||||
| app.Status.AppState.State == v1beta2.ApplicationStateFailed | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| // IsExpired returns whether the given SparkApplication is expired. | ||||||||||||||||||
| // IsExpired returns whether the given SparkApplication is expired according to its | ||||||||||||||||||
| // own spec.timeToLiveSeconds. | ||||||||||||||||||
| func IsExpired(app *v1beta2.SparkApplication) bool { | ||||||||||||||||||
| return IsExpiredWithTTL(app, app.Spec.TimeToLiveSeconds) | ||||||||||||||||||
| } | ||||||||||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm wondering if we need Rather maybe we just remove This would also require removing these tests.
Suggested change
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This make sense.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. done.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I removed the IsExpiredWithTTL and updated the signature of IsExpired added defaulttimetolive as second arg. |
||||||||||||||||||
|
|
||||||||||||||||||
| // IsExpiredWithTTL returns whether the given terminated SparkApplication has outlived | ||||||||||||||||||
| // the provided TTL. The TTL may originate from the user's spec or from an | ||||||||||||||||||
| // operator-configured default (see EffectiveTimeToLiveSeconds). | ||||||||||||||||||
| // | ||||||||||||||||||
| // - A nil ttlSeconds means no TTL is defined: the application never expires. | ||||||||||||||||||
| // - A non-nil ttlSeconds that is <= 0 means the application expires immediately | ||||||||||||||||||
| // once it has a termination time. | ||||||||||||||||||
| func IsExpiredWithTTL(app *v1beta2.SparkApplication, ttlSeconds *int64) bool { | ||||||||||||||||||
| // The application has no TTL defined and will never expire. | ||||||||||||||||||
| if app.Spec.TimeToLiveSeconds == nil { | ||||||||||||||||||
| if ttlSeconds == nil { | ||||||||||||||||||
| return false | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| ttl := time.Duration(*app.Spec.TimeToLiveSeconds) * time.Second | ||||||||||||||||||
| ttl := time.Duration(*ttlSeconds) * time.Second | ||||||||||||||||||
| now := time.Now() | ||||||||||||||||||
| if !app.Status.TerminationTime.IsZero() && now.Sub(app.Status.TerminationTime.Time) > ttl { | ||||||||||||||||||
| return true | ||||||||||||||||||
|
|
@@ -76,6 +89,29 @@ func IsExpired(app *v1beta2.SparkApplication) bool { | |||||||||||||||||
| return false | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| // EffectiveTimeToLiveSeconds returns the TTL (in seconds) that should govern cleanup | ||||||||||||||||||
|
dineshkumar181094 marked this conversation as resolved.
|
||||||||||||||||||
| // of the given SparkApplication, and whether the operator default (rather than the | ||||||||||||||||||
| // user spec) was the source. | ||||||||||||||||||
| // | ||||||||||||||||||
| // Resolution order: | ||||||||||||||||||
| // - the user's spec.timeToLiveSeconds whenever it is set (a value <= 0 is an | ||||||||||||||||||
| // explicit request to expire immediately and still wins), else | ||||||||||||||||||
| // - the operator default when the DefaultTimeToLive feature gate is enabled and | ||||||||||||||||||
| // defaultSeconds > 0, else | ||||||||||||||||||
| // - nil, meaning "never expire". | ||||||||||||||||||
| // | ||||||||||||||||||
| // It never mutates the SparkApplication. | ||||||||||||||||||
| func EffectiveTimeToLiveSeconds(app *v1beta2.SparkApplication, defaultSeconds int64) (*int64, bool) { | ||||||||||||||||||
| if app.Spec.TimeToLiveSeconds != nil { | ||||||||||||||||||
| return app.Spec.TimeToLiveSeconds, false | ||||||||||||||||||
| } | ||||||||||||||||||
| if features.Enabled(features.DefaultTimeToLive) && defaultSeconds > 0 { | ||||||||||||||||||
| seconds := defaultSeconds | ||||||||||||||||||
| return &seconds, true | ||||||||||||||||||
| } | ||||||||||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm thinking it might be better to use something like the following in That way, we avoid dependency on the global feature gate within what are effectively helper functions.
Suggested change
Ideally we'd want helper functions in a utility package to be pure functions and for these to not have side effects from global external dependencies. The above approach could help avoid a form of code smell where we couple components that are best left decoupled.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. addressed this. |
||||||||||||||||||
| return nil, false | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| // IsDriverRunning returns whether the driver pod of the given SparkApplication is running. | ||||||||||||||||||
| func IsDriverRunning(app *v1beta2.SparkApplication) bool { | ||||||||||||||||||
| return app.Status.AppState.State == v1beta2.ApplicationStateRunning | ||||||||||||||||||
|
|
||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Worth adding a note that when the
DefaultTimeToLivegate is enabled, this value must be set to a positive integer or the controller will fail to start. Something like:There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Now I have update the 0 means default disabled.