This is an automated email from the ASF dual-hosted git repository.

dongjoon-hyun pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/spark.git


The following commit(s) were added to refs/heads/master by this push:
     new 913175ed22f6 [SPARK-59918][K8S] Support `deployment` pods allocator in 
`ExecutorResizePlugin`
913175ed22f6 is described below

commit 913175ed22f616bb229d9f4533a398f832680cd5
Author: Dongjoon Hyun <[email protected]>
AuthorDate: Wed Sep 30 22:23:15 2026 -0700

    [SPARK-59918][K8S] Support `deployment` pods allocator in 
`ExecutorResizePlugin`
    
    ### What changes were proposed in this pull request?
    
    This PR aims to support `ExecutorResizePlugin` with the `deployment` pods 
allocator for Apache Spark 4.4.0 in addition to the default `direct` pods 
allocator.
    
    ### Why are the changes needed?
    
    [SPARK-56670](https://github.com/apache/spark/pull/55615) restricted 
`ExecutorResizePlugin` to the `direct` pods allocator because the higher-level 
controller's pod template is the source of truth for the other allocators with 
different mechanism.
    
    However, after SPARK-59914, Spark becomes capable of support `Deployment` 
because we never updates its pod template, and the `ReplicaSet` controller 
neither reverts in-place pod resizes nor rolls out a new revision for them.
    - https://github.com/apache/spark/pull/59175
    
    ### Does this PR introduce _any_ user-facing change?
    
    Yes. In Apache Spark 4.2.0, `ExecutorResizePlugin` logs a warning and does 
not start with the `deployment` pods allocator. After this PR, it works with 
the `deployment` pods allocator.
    
    ### How was this patch tested?
    
    Pass the CIs with the updated test case.
    
    I also verified manually on K8s v1.37.0 that the executor pods of the 
`Deployment` are resized in place up to 
`spark.kubernetes.executor.resizeMaxMemory` without restarts, while the 
`Deployment` keeps its pod template and a single `ReplicaSet`.
    
    ### Was this patch authored or co-authored using generative AI tooling?
    
    Generated-by: Claude Opus 5.5
    
    Closes #59180 from 
dongjoon-hyun/dongjoon/executor-resize-plugin-deployment-0e6321.
    
    Authored-by: Dongjoon Hyun <[email protected]>
    Signed-off-by: Dongjoon Hyun <[email protected]>
---
 docs/running-on-kubernetes.md                          |  3 ++-
 .../scheduler/cluster/k8s/ExecutorResizePlugin.scala   |  4 ++--
 .../cluster/k8s/ExecutorResizePluginSuite.scala        | 18 +++++++++++++++++-
 3 files changed, 21 insertions(+), 4 deletions(-)

diff --git a/docs/running-on-kubernetes.md b/docs/running-on-kubernetes.md
index dc83f056b70f..ace9686ba876 100644
--- a/docs/running-on-kubernetes.md
+++ b/docs/running-on-kubernetes.md
@@ -579,7 +579,8 @@ change to the application code. They apply to executor pods 
of every resource pr
 
 The resize plugins are registered via `spark.plugins` and run in the driver, 
while the recovery mode is
 built into the executor pod allocator and needs no plugin. All three features 
require the default
-`direct` pods allocator (`spark.kubernetes.allocation.pods.allocator`).
+`direct` pods allocator (`spark.kubernetes.allocation.pods.allocator`), except 
that the memory resize
+plugin also supports the `deployment` pods allocator.
 
 ### Executor Memory Resize
 
diff --git 
a/resource-managers/kubernetes/core/src/main/scala/org/apache/spark/scheduler/cluster/k8s/ExecutorResizePlugin.scala
 
b/resource-managers/kubernetes/core/src/main/scala/org/apache/spark/scheduler/cluster/k8s/ExecutorResizePlugin.scala
index 7c2dc44c525a..28d43b2d68b7 100644
--- 
a/resource-managers/kubernetes/core/src/main/scala/org/apache/spark/scheduler/cluster/k8s/ExecutorResizePlugin.scala
+++ 
b/resource-managers/kubernetes/core/src/main/scala/org/apache/spark/scheduler/cluster/k8s/ExecutorResizePlugin.scala
@@ -56,8 +56,8 @@ class ExecutorResizeDriverPlugin extends DriverPlugin with 
Logging {
 
   override def init(sc: SparkContext, ctx: PluginContext): JMap[String, 
String] = {
     val allocator = sc.conf.get(KUBERNETES_ALLOCATION_PODS_ALLOCATOR)
-    if (allocator != "direct") {
-      logWarning(log"ExecutorResizePlugin requires the 'direct' pods 
allocator; " +
+    if (allocator != "direct" && allocator != "deployment") {
+      logWarning(log"ExecutorResizePlugin requires the 'direct' or 
'deployment' pods allocator; " +
         log"${MDC(CONFIG, KUBERNETES_ALLOCATION_PODS_ALLOCATOR.key)} is " +
         log"${MDC(CONFIG2, allocator)}. Plugin will not start.")
       return Map.empty[String, String].asJava
diff --git 
a/resource-managers/kubernetes/core/src/test/scala/org/apache/spark/scheduler/cluster/k8s/ExecutorResizePluginSuite.scala
 
b/resource-managers/kubernetes/core/src/test/scala/org/apache/spark/scheduler/cluster/k8s/ExecutorResizePluginSuite.scala
index 32a8ff239e84..6b6e6bea029d 100644
--- 
a/resource-managers/kubernetes/core/src/test/scala/org/apache/spark/scheduler/cluster/k8s/ExecutorResizePluginSuite.scala
+++ 
b/resource-managers/kubernetes/core/src/test/scala/org/apache/spark/scheduler/cluster/k8s/ExecutorResizePluginSuite.scala
@@ -381,7 +381,7 @@ class ExecutorResizePluginSuite
     assert(countSkipLogs(2) === 1)
   }
 
-  Seq("statefulset", "deployment").foreach { allocator =>
+  Seq("statefulset").foreach { allocator =>
     test(s"init returns early when pods allocator is '$allocator'") {
       val plugin = new ExecutorResizeDriverPlugin()
       val sparkConf = new 
SparkConf().set(KUBERNETES_ALLOCATION_PODS_ALLOCATOR, allocator)
@@ -395,6 +395,22 @@ class ExecutorResizePluginSuite
     }
   }
 
+  test("SPARK-59918: init schedules the resize task when pods allocator is 
'deployment'") {
+    val plugin = new ExecutorResizeDriverPlugin()
+    val sparkConf = new SparkConf().set(KUBERNETES_ALLOCATION_PODS_ALLOCATOR, 
"deployment")
+    val sc = mock(classOf[SparkContext])
+    when(sc.conf).thenReturn(sparkConf)
+    val pluginCtx = mock(classOf[PluginContext])
+    try {
+      assert(plugin.init(sc, pluginCtx).isEmpty)
+      val field = plugin.getClass.getDeclaredField("periodicService")
+      field.setAccessible(true)
+      
assert(field.get(plugin).asInstanceOf[ScheduledThreadPoolExecutor].getQueue.size
 === 1)
+    } finally {
+      plugin.shutdown()
+    }
+  }
+
   Seq(
     (EXECUTOR_RESIZE_THRESHOLD, Seq("0", "1", "1.5"), "The threshold should be 
in (0, 1)"),
     (EXECUTOR_RESIZE_FACTOR, Seq("-0.1", "0", "1.5"), "The factor should be in 
(0, 1]")


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to