Skip to content
Closed
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
a7edf6c
Support mounting hostPath volumes for executors
Apr 5, 2018
52f0d1c
Read mode for mounted volumes
Apr 5, 2018
70d050e
Refactor
Apr 5, 2018
463e9b5
Fix style
Apr 6, 2018
0a21a19
Add unit tests
Apr 6, 2018
87bcc9f
Update comment
Apr 6, 2018
c72cbc1
Fix unit tests
Apr 6, 2018
4f1e8b9
[SPARK-23871][ML][PYTHON] add python api for VectorAssembler handleIn…
huaxingao Apr 10, 2018
7c7570d
[SPARK-23944][ML] Add the set method for the two LSHModel
lu-wang-dl Apr 11, 2018
c7622be
[SPARK-23847][FOLLOWUP][PYTHON][SQL] Actually test [desc|acs]_nulls_[…
HyukjinKwon Apr 11, 2018
87611bb
[MINOR][DOCS] Fix R documentation generation instruction for roxygen2
HyukjinKwon Apr 11, 2018
c604d65
[SPARK-23951][SQL] Use actual java class instead of string representa…
hvanhovell Apr 11, 2018
271c891
[SPARK-23960][SQL][MINOR] Mark HashAggregateExec.bufVars as transient
Apr 11, 2018
653fe02
[SPARK-6951][CORE] Speed up parsing of event logs during listing.
Apr 11, 2018
3cb8204
[SPARK-22941][CORE] Do not exit JVM when submit fails with in-process…
Apr 11, 2018
75a1830
[SPARK-22883] ML test for StructuredStreaming: spark.ml.feature, I-M
jkbradley Apr 11, 2018
9d960de
typo rawPredicition changed to rawPrediction
JBauerKogentix Apr 11, 2018
e904dfa
Revert "[SPARK-23960][SQL][MINOR] Mark HashAggregateExec.bufVars as t…
gatorsmile Apr 12, 2018
6a2289e
[SPARK-23962][SQL][TEST] Fix race in currentExecutionIds().
squito Apr 12, 2018
0b19122
[SPARK-23762][SQL] UTF8StringBuffer uses MemoryBlock
kiszk Apr 12, 2018
0f93b91
[SPARK-23751][FOLLOW-UP] fix build for scala-2.12
WeichenXu123 Apr 12, 2018
682002b
[SPARK-23867][SCHEDULER] use droppedCount in logWarning
Apr 13, 2018
14291b0
[SPARK-23748][SS] Fix SS continuous process doesn't support SubqueryA…
jerryshao Apr 13, 2018
ab7b961
[SPARK-23942][PYTHON][SQL] Makes collect in PySpark as action for a q…
HyukjinKwon Apr 13, 2018
1018be4
[SPARK-23971] Should not leak Spark sessions across test suites
ericl Apr 13, 2018
4b07036
[SPARK-23815][CORE] Spark writer dynamic partition overwrite mode may…
Apr 13, 2018
0323e61
[SPARK-23905][SQL] Add UDF weekday
Apr 13, 2018
a83ae0d
[SPARK-22839][K8S] Refactor to unify driver and executor pod builder …
mccheah Apr 13, 2018
4dfd746
[SPARK-23896][SQL] Improve PartitioningAwareFileIndex
gengliangwang Apr 13, 2018
25892f3
[SPARK-23375][SQL] Eliminate unneeded Sort in Optimizer
mgaido91 Apr 13, 2018
558f31b
[SPARK-23963][SQL] Properly handle large number of columns in query o…
bersprockets Apr 13, 2018
cbb41a0
[SPARK-23966][SS] Refactoring all checkpoint file writing logic in a …
tdas Apr 13, 2018
73f2853
[SPARK-23979][SQL] MultiAlias should not be a CodegenFallback
viirya Apr 14, 2018
c096493
[SPARK-23956][YARN] Use effective RPC port in AM registration
gerashegalov Apr 16, 2018
6931022
[SPARK-23917][SQL] Add array_max function
mgaido91 Apr 16, 2018
083cf22
[SPARK-21033][CORE][FOLLOW-UP] Update Spillable
wangyum Apr 16, 2018
5003736
[SPARK-9312][ML] Add RawPrediction, numClasses, and numFeatures for O…
lu-wang-dl Apr 16, 2018
0461482
[SPARK-21088][ML] CrossValidator, TrainValidationSplit support collec…
WeichenXu123 Apr 16, 2018
c9bb4e1
Support mounting hostPath volumes for executors
Apr 5, 2018
1cdd9c8
Read mode for mounted volumes
Apr 5, 2018
ac25098
Refactor
Apr 5, 2018
a56e80f
Fix style
Apr 6, 2018
674a4df
Add unit tests
Apr 6, 2018
49c5b01
Update comment
Apr 6, 2018
79c30c4
Fix unit tests
Apr 6, 2018
86afec3
Merge
Apr 16, 2018
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,13 @@ private[spark] object Config extends Logging {
.stringConf
.createWithDefault("spark")

val KUBERNETES_EXECUTOR_VOLUMES =

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we are adding this option, it should be a more general API - allowing PVs, hostpath volumes and emptyDir volumes to be mounted. In the near future, hostpath volumes will be superseded by local PVs - kubernetes/kubernetes#7562

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, do we need a symmetric option for the drivers also?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. I'll add the driver option after I rebase on #20910 then.

ConfigBuilder("spark.kubernetes.executor.volumes")
.doc("List of volumes mounted into the executor container. The format of this property is " +
"a comma-separated list of mappings following the form hostPath:containerPath[:ro|rw]")
.stringConf
.createWithDefault("")

val KUBERNETES_ALLOCATION_BATCH_SIZE =
ConfigBuilder("spark.kubernetes.allocation.batch.size")
.doc("Number of pods to launch at once in each round of executor allocation.")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
*/
package org.apache.spark.deploy.k8s

import io.fabric8.kubernetes.api.model.LocalObjectReference
import io.fabric8.kubernetes.api.model._

import org.apache.spark.SparkConf
import org.apache.spark.util.Utils
Expand All @@ -38,7 +38,58 @@ private[spark] object KubernetesUtils {
}

/**
* Parses comma-separated list of imagePullSecrets into K8s-understandable format
* Parse a comma-delimited list of volume specs, each of which takes the form
* hostPath:containerPath[:ro|rw]; and add volume to pod and volume mount to container.
*
* @param pod original specification of the pod
* @param container original specification of the container
* @param volumes list of volume specs
* @return a tuple of (pod with the volume(s) added, container with mount(s) added)
*/
def addVolumes(pod: Pod, container : Container, volumes: String): (Pod, Container) = {
val podBuilder = new PodBuilder(pod).editOrNewSpec()
val containerBuilder = new ContainerBuilder(container)
var volumeCount = 0
volumes.split(",").map(_.split(":")).map { spec =>
var hostPath: Option[String] = None
var containerPath: Option[String] = None
var readOnly: Option[Boolean] = None
spec match {
case Array(hostPathV, containerPathV) =>
hostPath = Some(hostPathV)
containerPath = Some(containerPathV)
case Array(hostPathV, containerPathV, "ro") =>
hostPath = Some(hostPathV)
containerPath = Some(containerPathV)
readOnly = Some(true)
case Array(hostPathV, containerPathV, "rw") =>
hostPath = Some(hostPathV)
containerPath = Some(containerPathV)
readOnly = Some(false)
case spec =>
None
}
if (hostPath.isDefined && containerPath.isDefined) {
podBuilder.addToVolumes(new VolumeBuilder()
.withHostPath(new HostPathVolumeSource(hostPath.get))
.withName(s"hostPath-volume-$volumeCount")
.build())
val volumeBuilder = new VolumeMountBuilder()
.withMountPath(containerPath.get)
.withName(s"hostPath-volume-$volumeCount")
if (readOnly.isDefined) {
containerBuilder.addToVolumeMounts(volumeBuilder.withReadOnly(readOnly.get).build())
} else {
containerBuilder.addToVolumeMounts(volumeBuilder.build())
}
volumeCount += 1
}
}
(podBuilder.endSpec().build(), containerBuilder.build())
}

/**
* Parses comma-separated list of imagePullSecrets into K8s-understandable format
*/
def parseImagePullSecrets(imagePullSecrets: Option[String]): List[LocalObjectReference] = {
imagePullSecrets match {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -201,21 +201,27 @@ private[spark] class ExecutorPodFactory(
.endSpec()
.build()

val (executorPodWithVolumes, executorContainerWithVolumes) =
KubernetesUtils.addVolumes(executorPod,
executorContainer,
sparkConf.get(KUBERNETES_EXECUTOR_VOLUMES))

val containerWithLimitCores = executorLimitCores.map { limitCores =>
val executorCpuLimitQuantity = new QuantityBuilder(false)
.withAmount(limitCores)
.build()
new ContainerBuilder(executorContainer)
new ContainerBuilder(executorContainerWithVolumes)
.editResources()
.addToLimits("cpu", executorCpuLimitQuantity)
.endResources()
.build()
}.getOrElse(executorContainer)
}.getOrElse(executorContainerWithVolumes)

val (maybeSecretsMountedPod, maybeSecretsMountedContainer) =
mountSecretsBootstrap.map { bootstrap =>
(bootstrap.addSecretVolumes(executorPod), bootstrap.mountSecrets(containerWithLimitCores))
}.getOrElse((executorPod, containerWithLimitCores))
(bootstrap.addSecretVolumes(executorPodWithVolumes),
bootstrap.mountSecrets(containerWithLimitCores))
}.getOrElse((executorPodWithVolumes, containerWithLimitCores))


new PodBuilder(maybeSecretsMountedPod)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,53 @@ class ExecutorPodFactorySuite extends SparkFunSuite with BeforeAndAfter with Bef
checkOwnerReferences(executor, driverPodUid)
}

test("single executor hostPath volume gets mounted") {
val conf = baseConf.clone()
conf.set(KUBERNETES_EXECUTOR_VOLUMES, "/tmp/mount:/opt/mount")
val factory = new ExecutorPodFactory(conf, None)

val executor = factory.createExecutorPod(
"1", "dummy", "dummy", Seq[(String, String)](), driverPod, Map[String, Int]())

assert(executor.getSpec.getContainers.size() === 1)
assert(executor.getSpec.getContainers.get(0).getVolumeMounts.size() === 1)
assert(executor.getSpec.getContainers.get(0).getVolumeMounts.get(0).getName
=== "hostPath-volume-0")
assert(executor.getSpec.getContainers.get(0).getVolumeMounts.get(0)
.getMountPath === "/opt/mount")

assert(executor.getSpec.getVolumes.size() === 1)
assert(executor.getSpec.getVolumes.get(0).getHostPath.getPath === "/tmp/mount")

checkOwnerReferences(executor, driverPodUid)
}

test("multiple executor hostPath volumes get mounted") {
val conf = baseConf.clone()
conf.set(KUBERNETES_EXECUTOR_VOLUMES, "/tmp/mount1:/opt/mount1,/tmp/mount2:/opt/mount2")
val factory = new ExecutorPodFactory(conf, None)

val executor = factory.createExecutorPod(
"1", "dummy", "dummy", Seq[(String, String)](), driverPod, Map[String, Int]())

assert(executor.getSpec.getContainers.size() === 1)
assert(executor.getSpec.getContainers.get(0).getVolumeMounts.size() === 2)
assert(executor.getSpec.getContainers.get(0).getVolumeMounts.get(0).getName
=== "hostPath-volume-0")
assert(executor.getSpec.getContainers.get(0).getVolumeMounts.get(1).getName
=== "hostPath-volume-1")
assert(executor.getSpec.getContainers.get(0).getVolumeMounts.get(0)
.getMountPath === "/opt/mount1")
assert(executor.getSpec.getContainers.get(0).getVolumeMounts.get(1)
.getMountPath === "/opt/mount2")

assert(executor.getSpec.getVolumes.size() === 2)
assert(executor.getSpec.getVolumes.get(0).getHostPath.getPath === "/tmp/mount1")
assert(executor.getSpec.getVolumes.get(1).getHostPath.getPath === "/tmp/mount2")

checkOwnerReferences(executor, driverPodUid)
}

// There is always exactly one controller reference, and it points to the driver pod.
private def checkOwnerReferences(executor: Pod, driverPodUid: String): Unit = {
assert(executor.getMetadata.getOwnerReferences.size() === 1)
Expand Down