-
Notifications
You must be signed in to change notification settings - Fork 29k
[SPARK-18278] [Scheduler] Spark on Kubernetes - Basic Scheduler Backend #19468
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
Changes from 1 commit
f6fdd6a
75e31a9
cf82b21
488c535
82b79a7
c052212
c565c9f
2fb596d
992acbe
b0a5839
a4f9797
2b5dcac
018f4d8
4b32134
6cf4ed7
1f271be
71a971f
0ab9ca7
7f14b71
7afce3f
b75b413
3b587b4
cb12fec
ae396cf
f8e3249
a44c29e
4bed817
c386186
b85cfc4
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
- Loading branch information
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -44,7 +44,6 @@ package object config extends Logging { | |
| .stringConf | ||
| .createWithDefault("IfNotPresent") | ||
|
|
||
|
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: duplicated empty lines.
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. Removed. |
||
|
|
||
| private[spark] val APISERVER_AUTH_DRIVER_CONF_PREFIX = | ||
| "spark.kubernetes.authenticate.driver" | ||
| private[spark] val APISERVER_AUTH_DRIVER_MOUNTED_CONF_PREFIX = | ||
|
|
@@ -95,12 +94,14 @@ package object config extends Logging { | |
| ConfigBuilder("spark.kubernetes.allocation.batch.size") | ||
| .doc("Number of pods to launch at once in each round of executor allocation.") | ||
| .intConf | ||
| .checkValue(value => value > 0, "Allocation batch size should be a positive integer") | ||
| .createWithDefault(5) | ||
|
|
||
| private[spark] val KUBERNETES_ALLOCATION_BATCH_DELAY = | ||
| ConfigBuilder("spark.kubernetes.allocation.batch.delay") | ||
| .doc("Number of seconds to wait between each round of executor allocation.") | ||
| .longConf | ||
| .checkValue(value => value > 0, s"Allocation batch delay should be a positive integer") | ||
| .createWithDefault(1) | ||
|
|
||
| private[spark] val KUBERNETES_EXECUTOR_LIMIT_CORES = | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -20,7 +20,7 @@ import scala.collection.JavaConverters._ | |
|
|
||
| import io.fabric8.kubernetes.api.model._ | ||
|
|
||
| import org.apache.spark.{SparkConf, SparkException} | ||
| import org.apache.spark.SparkConf | ||
| import org.apache.spark.deploy.k8s.ConfigurationUtils | ||
| import org.apache.spark.deploy.k8s.config._ | ||
| import org.apache.spark.deploy.k8s.constants._ | ||
|
|
@@ -77,11 +77,8 @@ private[spark] class ExecutorPodFactoryImpl(sparkConf: SparkConf) | |
| private val executorDockerImage = sparkConf.get(EXECUTOR_DOCKER_IMAGE) | ||
| private val dockerImagePullPolicy = sparkConf.get(DOCKER_IMAGE_PULL_POLICY) | ||
| private val executorPort = sparkConf.getInt("spark.executor.port", DEFAULT_STATIC_PORT) | ||
|
||
| private val blockmanagerPort = sparkConf | ||
| private val blockManagerPort = sparkConf | ||
| .getInt("spark.blockmanager.port", DEFAULT_BLOCKMANAGER_PORT) | ||
|
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. Are we assuming the port's will always be available (for executor and driver) to bind to ?
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. Yes - because we're deploying the driver and executors in a containerized setting, we're a lot more free to make looser assumptions about available ports. The exception will be if Spark applications are running sidecar containers along with the main driver/executor containers, but support for that is further out in the future when/if we expect Pod Presets to interact with our code.
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. Sounds good, I wanted to make sure I understood it right that we are making the assumption about port being unbound and available for spark. |
||
| private val kubernetesDriverPodName = sparkConf | ||
| .get(KUBERNETES_DRIVER_POD_NAME) | ||
| .getOrElse(throw new SparkException("Must specify the driver pod name")) | ||
|
|
||
| private val executorPodNamePrefix = sparkConf.get(KUBERNETES_EXECUTOR_POD_NAME_PREFIX) | ||
|
|
||
|
|
@@ -163,7 +160,7 @@ private[spark] class ExecutorPodFactoryImpl(sparkConf: SparkConf) | |
| ) ++ executorExtraJavaOptionsEnv ++ executorExtraClasspathEnv.toSeq | ||
| val requiredPorts = Seq( | ||
| (EXECUTOR_PORT_NAME, executorPort), | ||
| (BLOCK_MANAGER_PORT_NAME, blockmanagerPort)) | ||
| (BLOCK_MANAGER_PORT_NAME, blockManagerPort)) | ||
| .map(port => { | ||
|
||
| new ContainerPortBuilder() | ||
| .withName(port._1) | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -87,14 +87,8 @@ private[spark] class KubernetesClusterSchedulerBackend( | |
| private val initialExecutors = SchedulerBackendUtils.getInitialTargetExecutorNumber(conf) | ||
|
|
||
| private val podAllocationInterval = conf.get(KUBERNETES_ALLOCATION_BATCH_DELAY) | ||
| require(podAllocationInterval > 0, "Allocation batch delay " + | ||
| s"${KUBERNETES_ALLOCATION_BATCH_DELAY} " + | ||
| s"is ${podAllocationInterval}, should be a positive integer") | ||
|
|
||
| private val podAllocationSize = conf.get(KUBERNETES_ALLOCATION_BATCH_SIZE) | ||
| require(podAllocationSize > 0, "Allocation batch size " + | ||
| s"${KUBERNETES_ALLOCATION_BATCH_SIZE} " + | ||
| s"is ${podAllocationSize}, should be a positive integer") | ||
|
|
||
| private val allocatorRunnable = new Runnable { | ||
|
|
||
|
|
@@ -304,39 +298,40 @@ private[spark] class KubernetesClusterSchedulerBackend( | |
| private val DEFAULT_CONTAINER_FAILURE_EXIT_STATUS = -1 | ||
|
|
||
| override def eventReceived(action: Action, pod: Pod): Unit = { | ||
|
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: we can also consider to use case match for
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. Done. |
||
| if (action == Action.MODIFIED && pod.getStatus.getPhase == "Running" | ||
| && pod.getMetadata.getDeletionTimestamp == null) { | ||
| val podIP = pod.getStatus.getPodIP | ||
| val clusterNodeName = pod.getSpec.getNodeName | ||
| logInfo(s"Executor pod $pod ready, launched at $clusterNodeName as IP $podIP.") | ||
| executorPodsByIPs.put(podIP, pod) | ||
| } else if (action == Action.DELETED || action == Action.ERROR) { | ||
| val executorId = pod.getMetadata.getLabels.get(SPARK_EXECUTOR_ID_LABEL) | ||
| require(executorId != null, "Unexpected pod metadata; expected all executor pods" + | ||
| s" to have label $SPARK_EXECUTOR_ID_LABEL.") | ||
| val podName = pod.getMetadata.getName | ||
| val podIP = pod.getStatus.getPodIP | ||
| logDebug(s"Executor pod $podName at IP $podIP was at $action.") | ||
| if (podIP != null) { | ||
| executorPodsByIPs.remove(podIP) | ||
| } | ||
| val executorExitReason = if (action == Action.ERROR) { | ||
| logWarning(s"Received pod $podName exited event. Reason: " + pod.getStatus.getReason) | ||
| executorExitReasonOnError(pod) | ||
| } else if (action == Action.DELETED) { | ||
| logWarning(s"Received delete pod $podName event. Reason: " + pod.getStatus.getReason) | ||
| executorExitReasonOnDelete(pod) | ||
| } else { | ||
| throw new IllegalStateException( | ||
| s"Unknown action that should only be DELETED or ERROR: $action") | ||
| } | ||
| podsWithKnownExitReasons.put(pod.getMetadata.getName, executorExitReason) | ||
| if (!disconnectedPodsByExecutorIdPendingRemoval.containsKey(executorId)) { | ||
| log.warn(s"Executor with id $executorId was not marked as disconnected, but the" + | ||
| s" watch received an event of type $action for this executor. The executor may" + | ||
| s" have failed to start in the first place and never registered with the driver.") | ||
| } | ||
| disconnectedPodsByExecutorIdPendingRemoval.put(executorId, pod) | ||
| action match { | ||
|
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. Doesn't this block need a default match? Seems like you could get a
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. Added a default match. |
||
| case Action.MODIFIED if (pod.getStatus.getPhase == "Running" | ||
| && pod.getMetadata.getDeletionTimestamp == null) => | ||
|
||
| val podIP = pod.getStatus.getPodIP | ||
| val clusterNodeName = pod.getSpec.getNodeName | ||
| logInfo(s"Executor pod $pod ready, launched at $clusterNodeName as IP $podIP.") | ||
| executorPodsByIPs.put(podIP, pod) | ||
| case Action.DELETED | Action.ERROR => | ||
|
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. Add blank line before.
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. Done. |
||
| val executorId = getExecutorId(pod) | ||
| val podName = pod.getMetadata.getName | ||
| val podIP = pod.getStatus.getPodIP | ||
| logDebug(s"Executor pod $podName at IP $podIP was at $action.") | ||
| if (podIP != null) { | ||
| executorPodsByIPs.remove(podIP) | ||
| } | ||
|
|
||
| val executorExitReason = if (action == Action.ERROR) { | ||
| logWarning(s"Received pod $podName exited event. Reason: " + pod.getStatus.getReason) | ||
| executorExitReasonOnError(pod) | ||
| } else if (action == Action.DELETED) { | ||
| logWarning(s"Received delete pod $podName event. Reason: " + pod.getStatus.getReason) | ||
| executorExitReasonOnDelete(pod) | ||
| } else { | ||
| throw new IllegalStateException( | ||
| s"Unknown action that should only be DELETED or ERROR: $action") | ||
| } | ||
| podsWithKnownExitReasons.put(pod.getMetadata.getName, executorExitReason) | ||
|
|
||
| if (!disconnectedPodsByExecutorIdPendingRemoval.containsKey(executorId)) { | ||
| log.warn(s"Executor with id $executorId was not marked as disconnected, but the" + | ||
| s" watch received an event of type $action for this executor. The executor may" + | ||
| s" have failed to start in the first place and never registered with the driver.") | ||
|
||
| } | ||
| disconnectedPodsByExecutorIdPendingRemoval.put(executorId, pod) | ||
| } | ||
| } | ||
|
|
||
|
|
@@ -391,6 +386,13 @@ private[spark] class KubernetesClusterSchedulerBackend( | |
| ExecutorExited( | ||
| getExecutorExitStatus(pod), exitCausedByApp = false, exitMessage) | ||
|
||
| } | ||
|
|
||
| def getExecutorId(pod: Pod): String = { | ||
| val executorId = pod.getMetadata.getLabels.get(SPARK_EXECUTOR_ID_LABEL) | ||
| require(executorId != null, "Unexpected pod metadata; expected all executor pods " + | ||
| s"to have label $SPARK_EXECUTOR_ID_LABEL.") | ||
| executorId | ||
| } | ||
| } | ||
|
|
||
| override def createDriverEndpoint(properties: Seq[(String, String)]): DriverEndpoint = { | ||
|
|
||
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.
We should add comment to explain what does the function do, it not only return the configs, but also ensure no duplicate configs are set.
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.
Done.