You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@spark.apache.org by do...@apache.org on 2026/08/17 20:28:44 UTC

(spark) branch branch-4.3 updated: [SPARK-38794][K8S] Create executor ConfigMap before requesting executors

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

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


The following commit(s) were added to refs/heads/branch-4.3 by this push:
     new 8253c336b2a5 [SPARK-38794][K8S] Create executor ConfigMap before requesting executors
8253c336b2a5 is described below

commit 8253c336b2a58fb8b8d24716fa4a3b7593b717c1
Author: DongjunHwang <do...@hyundai.com>
AuthorDate: Mon Aug 17 13:28:14 2026 -0700

    [SPARK-38794][K8S] Create executor ConfigMap before requesting executors
    
    ### What changes were proposed in this pull request?
    Reorders two calls in `KubernetesClusterSchedulerBackend.start()` so that the executor
    ConfigMap (`setUpExecutorConfigMap`) is created *before* executor allocation is triggered
    (`podAllocator.setTotalExpectedExecutors`), instead of after. A regression test is added to
    `KubernetesClusterSchedulerBackendSuite` asserting this ordering.
    
    ### Why are the changes needed?
    `podAllocator.setTotalExpectedExecutors()` asynchronously submits executor pod creation
    requests via a background thread pool
    (`ExecutorPodsSnapshotsStoreImpl.notifySubscribers()` -> `subscribersExecutor.submit(...)`).
    Previously, this was called *before* the shared executor ConfigMap was created. This is a
    genuine race: if an executor pod is scheduled and its kubelet attempts to mount the shared
    ConfigMap volume before the driver has finished creating it, the pod fails to start with:
    
    MountVolume.SetUp failed for volume "spark-conf-volume-exec" :
    configmap "spark-exec-...-conf-map" not found
    
    Spark's executor retry logic (and the kubelet's own mount retry) normally recovers from this
    within a few seconds, so today this surfaces only as a transient `FailedMount` warning event
    rather than a hard failure. We reproduced this repeatedly on a production cluster during
    completely normal executor startup (no fault injection needed) and captured it via real-time
    Kubernetes event logs.
    
    This change closes the race window by giving the (synchronous) ConfigMap creation a head
    start over the async executor pod requests. It significantly narrows, but does not perfectly
    eliminate, the window, since a fully deterministic fix (e.g. blocking executor requests until
    the ConfigMap is confirmed synced to all nodes) would be a larger behavioral change.
    
    Note: this addresses a different failure mode than the "ConfigMap creation permanently fails
    (e.g. due to insufficient RBAC permissions) and the driver never retries" scenario also
    described in SPARK-38794. That scenario is not addressed by this change.
    
    ### Does this PR introduce _any_ user-facing change?
    No.
    
    ### How was this patch tested?
    - Added `KubernetesClusterSchedulerBackendSuite` test
      `SPARK-38794: executor ConfigMap is created before executors are requested`, using
      Mockito's `inOrder` to assert the ConfigMap is created before `setTotalExpectedExecutors`
      is called. Verified it fails without the fix and passes with it.
    - Ran the full `kubernetes/core` test module against current master: 381/381 passing.
    - Ran `./dev/scalafmt`; no reformatting was needed for the changed lines.
    - Reproduced and verified the fix against a real Kubernetes cluster across multiple driver
      restarts with dynamic allocation triggering executor creation, observing zero
      `FailedMount`/`configmap not found` events with the fix applied, versus intermittent
      occurrences without it.
    
    ### Was this patch authored or co-authored using generative AI tooling?
    Yes. Generated-by: Claude Sonnet 4.5 and Claude Sonnet 5 (via opencode CLI)
    
    Closes #58031 from HwangDongJun/fix/spark-38794-executor-configmap-race.
    
    Authored-by: DongjunHwang <do...@hyundai.com>
    Signed-off-by: Dongjoon Hyun <do...@apache.org>
    (cherry picked from commit 8b61d2017b4e3b04ae20210808728c716bbb55ec)
    Signed-off-by: Dongjoon Hyun <do...@apache.org>
---
 .../cluster/k8s/KubernetesClusterSchedulerBackend.scala          | 9 ++++++---
 .../cluster/k8s/KubernetesClusterSchedulerBackendSuite.scala     | 9 ++++++++-
 2 files changed, 14 insertions(+), 4 deletions(-)

diff --git a/resource-managers/kubernetes/core/src/main/scala/org/apache/spark/scheduler/cluster/k8s/KubernetesClusterSchedulerBackend.scala b/resource-managers/kubernetes/core/src/main/scala/org/apache/spark/scheduler/cluster/k8s/KubernetesClusterSchedulerBackend.scala
index 0784b82a85de..255d76fece56 100644
--- a/resource-managers/kubernetes/core/src/main/scala/org/apache/spark/scheduler/cluster/k8s/KubernetesClusterSchedulerBackend.scala
+++ b/resource-managers/kubernetes/core/src/main/scala/org/apache/spark/scheduler/cluster/k8s/KubernetesClusterSchedulerBackend.scala
@@ -111,15 +111,18 @@ private[spark] class KubernetesClusterSchedulerBackend(
     super.start()
     // Must be called before setting the executors
     podAllocator.start(applicationId(), this)
+    // SPARK-38794: Create the executor ConfigMap before requesting executors. Executor
+    // allocation is asynchronous (background thread pool), so requesting executors first
+    // can race with ConfigMap creation, causing transient "configmap ... not found" mounts.
+    if (!conf.get(KUBERNETES_EXECUTOR_DISABLE_CONFIGMAP)) {
+      setUpExecutorConfigMap(podAllocator.driverPod)
+    }
     val defaultProfile = scheduler.sc.resourceProfileManager.defaultResourceProfile
     val initExecs = Map(defaultProfile -> initialExecutors)
     podAllocator.setTotalExpectedExecutors(initExecs)
     lifecycleManager.start(this)
     watchEvents.start(applicationId())
     pollEvents.start(applicationId())
-    if (!conf.get(KUBERNETES_EXECUTOR_DISABLE_CONFIGMAP)) {
-      setUpExecutorConfigMap(podAllocator.driverPod)
-    }
   }
 
   override def stop(): Unit = {
diff --git a/resource-managers/kubernetes/core/src/test/scala/org/apache/spark/scheduler/cluster/k8s/KubernetesClusterSchedulerBackendSuite.scala b/resource-managers/kubernetes/core/src/test/scala/org/apache/spark/scheduler/cluster/k8s/KubernetesClusterSchedulerBackendSuite.scala
index cf172eb096d4..5e4ec0ca6115 100644
--- a/resource-managers/kubernetes/core/src/test/scala/org/apache/spark/scheduler/cluster/k8s/KubernetesClusterSchedulerBackendSuite.scala
+++ b/resource-managers/kubernetes/core/src/test/scala/org/apache/spark/scheduler/cluster/k8s/KubernetesClusterSchedulerBackendSuite.scala
@@ -28,7 +28,7 @@ import io.fabric8.kubernetes.client.dsl.base.PatchContext
 import org.jmock.lib.concurrent.DeterministicScheduler
 import org.mockito.{ArgumentCaptor, Mock, MockitoAnnotations}
 import org.mockito.ArgumentMatchers.{any, eq => mockitoEq}
-import org.mockito.Mockito.{atLeastOnce, mock, never, spy, verify, when}
+import org.mockito.Mockito.{atLeastOnce, inOrder, mock, never, spy, verify, when}
 import org.scalatest.BeforeAndAfter
 
 import org.apache.spark.{SparkConf, SparkContext, SparkEnv, SparkFunSuite}
@@ -160,6 +160,13 @@ class KubernetesClusterSchedulerBackendSuite extends SparkFunSuite with BeforeAn
     verify(configMapResource).create()
   }
 
+  test("SPARK-38794: executor ConfigMap is created before executors are requested") {
+    schedulerBackendUnderTest.start()
+    val ordered = inOrder(configMapResource, podAllocator)
+    ordered.verify(configMapResource).create()
+    ordered.verify(podAllocator).setTotalExpectedExecutors(Map(defaultProfile -> 3))
+  }
+
   test("SPARK-56684: kubernetesClient is exposed within the k8s package") {
     assert(schedulerBackendUnderTest.kubernetesClient eq kubernetesClient)
   }


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@spark.apache.org
For additional commands, e-mail: commits-help@spark.apache.org