我们知道现在已经release的spark版本中,spark on K8s的实现,是将driver和executor作为pod跑在K8s中,但是目前spark已经实现了将executor作为statefulsets跑在K8s中,只不过还处于开发阶段,有一些细节还在完善中。但是博主已经在自己的环境中尝试过了,已经可以成功的运行一个spark application,相信这个功能应该能很快上线。本文这里就针对这个功能的实现做一些介绍。
主要参考链接:[SPARK-36058][K8S]: https://github.com/apache/spark/pull/33508
测试样例
拉取最新的spark master branch的code,制作一个docker image, 这些步骤都比较简单,参考官网就可以完成:
1. ./build/mvn -Pkubernetes -DskipTests clean package
2. $ ./bin/docker-image-tool.sh -t my-tag build
submit command:
提交一个spark-pi job
.\bin\spark-submit.cmd --master k8s://<master-url> --deploy-mode cluster --name spark-pi --class org.apache.spark.examples.SparkPi --conf spark.executor.instance=2 --conf spark.kubernetes.container.image=<docker image> --conf spark.kubernetes.allocation.pods.allocator=statefulset --conf spark.kubernetes.authenticate.driver.serviceAccountName=spark local:///opt/spark/examples/jars/spark-examples_2.12-3.3.0-SNAPSHOT.jar 5000
通过执行kubectl 的一些命令,可以观察到,最终运行了一个driver pod和一个statefulset, 这个statefulset中有两个pod作为了executor。当job结束,statefulset就会被删除。
实现分析
这里我给出一个基础的UML图,很多东西都有省略,具体大家可以对照源码看。
上图是已经release的spark版本的code,也就是将executor作为Pod直接跑在K8s上。为了实现statefulset, 开源社区做了一些扩展和改动。
从entrypoint.sh文件里我们可以看到,之前launch一个executor的class是CoarseGrainedExecutorBackend,现在改为了KubernetesExecutorBackend,这个作为在K8s上运行在executor上的backend。
executor)
shift 1
CMD=(
${JAVA_HOME}/bin/java
"${SPARK_EXECUTOR_JAVA_OPTS[@]}"
-Xms$SPARK_EXECUTOR_MEMORY
-Xmx$SPARK_EXECUTOR_MEMORY
-cp "$SPARK_CLASSPATH:$SPARK_DIST_CLASSPATH"
org.apache.spark.scheduler.cluster.k8s.KubernetesExecutorBackend
--driver-url $SPARK_DRIVER_URL
--executor-id $SPARK_EXECUTOR_ID
--cores $SPARK_EXECUTOR_CORES
--app-id $SPARK_APPLICATION_ID
--hostname $SPARK_EXECUTOR_POD_IP
--resourceProfileId $SPARK_RESOURCE_PROFILE_ID
--podName $SPARK_EXECUTOR_POD_NAME
)
;;
增加了一个abstract class AbstractPodsAllocator,这样就将来不管是以什么方式去allocate executor,只需要实现该类即可。
@DeveloperApi
abstract class AbstractPodsAllocator {
/*
* Set the total expected executors for an application
*/
def setTotalExpectedExecutors(resourceProfileToTotalExecs: Map[ResourceProfile, Int]): Unit
/*
* Reference to driver pod.
*/
def driverPod: Option[Pod]
/*
* If the pod for a given exec id is deleted.
*/
def isDeleted(executorId: String): Boolean
/*
* Start hook.
*/
def start(applicationId: String, schedulerBackend: KubernetesCluster