HwangDongJun commented on code in PR #58187:
URL: https://github.com/apache/spark/pull/58187#discussion_r4163373265


##########
resource-managers/kubernetes/core/src/main/scala/org/apache/spark/deploy/k8s/submit/KubernetesClientApplication.scala:
##########
@@ -136,43 +188,75 @@ private[spark] class Client(
     val driverPodName = resolvedDriverPod.getMetadata.getName
 
     // setup resources before pod creation
-    val preKubernetesResources = 
resolvedDriverSpec.driverPreKubernetesResources
-    try {
-      kubernetesClient.resourceList(preKubernetesResources: 
_*).forceConflicts().serverSideApply()
-    } catch {
-      case NonFatal(e) =>
-        logError("Please check \"kubectl auth can-i create [resource]\" 
first." +
-          " It should be yes. And please also check your feature step 
implementation.")
-        kubernetesClient.resourceList(preKubernetesResources: _*).delete()
-        throw e
-    }
+    // SPARK-38079: the driver's own base config map (mounted as 
SPARK_CONF_VOLUME_DRIVER
+    // above) must also be created before the pod itself, to avoid a 
"configmap ... not
+    // found" mount race between the driver pod and the config map it depends 
on.
+    val preKubernetesResources = 
resolvedDriverSpec.driverPreKubernetesResources ++ Seq(configMap)
+
+    // SPARK-38079: some of the pre-resources above (e.g. the Kerberos 
keytab/delegation token
+    // secrets, the driver Kubernetes credentials secret) carry credentials. 
They are created
+    // here without an owner reference -- the owner reference is only added 
once the driver pod
+    // exists (see "Refresh all pre-resources' owner references" below), since 
Kubernetes owner
+    // references require the owner's UID, which does not exist before the pod 
is created. If
+    // this process is terminated abruptly in that window (e.g. Ctrl-C, 
SIGTERM, or a fatal JVM
+    // error), the pre-resources would otherwise be silently orphaned in the 
namespace forever,
+    // since Kubernetes only garbage-collects via owner references. This 
shutdown hook makes a
+    // best-effort attempt to delete them (and the driver pod, if this 
submission created it) in
+    // that case. It is a no-op -- and removed entirely, see the `finally` 
below -- once the
+    // owner-reference refresh has completed; from that point on the existing 
owner references
+    // make normal Kubernetes garbage collection sufficient, and e.g. Ctrl-C 
while waiting for
+    // the application to complete must keep today's behavior of just 
detaching.
+    val preResourcesApplied = new AtomicBoolean(false)
+    val podCreatedByUs = new AtomicBoolean(false)
+    val cleanupHookRef = shutdownHookOps.addHook(() => 
cleanupOrphanedPreResources(
+      preKubernetesResources, driverPodName, preResourcesApplied.get(), 
podCreatedByUs.get()))
 
     var watch: Watch = null
     var createdDriverPod: Pod = null
     try {
-      createdDriverPod =
-        
kubernetesClient.pods().inNamespace(conf.namespace).resource(resolvedDriverPod).create()
-    } catch {
-      case NonFatal(e) =>
-        kubernetesClient.resourceList(preKubernetesResources: _*).delete()
-        logError("Please check \"kubectl auth can-i create pod\" first. It 
should be yes.")
-        throw e
-    }
+      try {
+        kubernetesClient.resourceList(preKubernetesResources: 
_*).forceConflicts().serverSideApply()
+        preResourcesApplied.set(true)
+      } catch {
+        case NonFatal(e) =>
+          logError("Please check \"kubectl auth can-i create [resource]\" 
first." +
+            " It should be yes. And please also check your feature step 
implementation.")
+          kubernetesClient.resourceList(preKubernetesResources: _*).delete()
+          throw e
+      }
 
-    // Refresh all pre-resources' owner references
-    try {
-      addOwnerReference(createdDriverPod, preKubernetesResources)
-      kubernetesClient.resourceList(preKubernetesResources: 
_*).forceConflicts().serverSideApply()
-    } catch {
-      case NonFatal(e) =>
-        kubernetesClient.pods().resource(createdDriverPod).delete()
-        kubernetesClient.resourceList(preKubernetesResources: _*).delete()
-        throw e
+      try {
+        createdDriverPod =
+          
kubernetesClient.pods().inNamespace(conf.namespace).resource(resolvedDriverPod).create()
+        podCreatedByUs.set(true)
+      } catch {
+        case NonFatal(e) =>
+          kubernetesClient.resourceList(preKubernetesResources: _*).delete()

Review Comment:
   Good catch, and this is now fully guarded. Each cleanup `delete()` call in 
`deletePodAndPreResources` is wrapped in its own `Utils.tryLogNonFatalError`, 
so a failure there is only logged and never replaces the original exception 
that's about to be rethrown. The `logError` hint is also now called before 
cleanup runs (not after), so it's always printed regardless of whether cleanup 
succeeds.
   
   The same catch-block structure (hint first, then best-effort per-item 
cleanup via `tryLogNonFatalError`, then rethrow the original exception) is now 
used consistently at all three call sites (pod creation failure, pre-resource 
creation failure, gate-removal failure).



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


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

Reply via email to