Spark on Kubernetes 的 PodTemplate 配置

 阿里云安全     |      2020-04-10 00:00:00

vcg_VCG41157477466_RF.jpg
镜像下载、域名解析、时间同步请点击 阿里巴巴开源镜像站

一、概述

本文主要讲 Apache Spark on Kubernetes 的 PodTemplate 配置问题,其中涉及Spark Operator 里关于 PodTemplate 的问题,以及 Apache Spark 2.2 on Kubernetes 的 Fork 版本问题。

通常 Apache Spark on Kubernetes 在配置 Pod 的时候会有一些限制,比如针对 Pod 的调度,想加个 NodeSelector 或者 Tolerations。这在集群公用,或者有各种类型任务的集群里,是经常会遇到的情况,而在 Spark 2.x 里却是很难实现的。目前最新 Release 的版本 2.4.5 还没有支持通过 PodTemplate 来自定义 Pod 的配置,而社区的计划是在 Spark 3.0 的时候实现该功能,他支持的方式其实也比较简单,就是通过 PodTemplate 的一个文件,去描述 Driver/Executor 的 metadata/spec 字段,这样就可以在模板文件里加入调度所需要的一些字段。接下来,我们会为大家介绍配置 PodTemplate 的具体方法。

二、配置 PodTemplate

实际上,在  Spark Operator 里,本身就支持 Pod Template 的配置 SparkPodSpec,也就是说,像 NodeSelector, Tolerations 之类的,可以在创建 CRD 对象的时候在 YAML 上添加上,非常方便,比如下面的例子。

apiVersion: sparkoperator.k8s.io/v1beta2
kind: SparkApplication
metadata:
  name: spark-pi
  namespace: default
spec:
  type: Scala
  mode: cluster
  image: gcr.io/spark/spark:v2.4.5
  mainClass: org.apache.spark.examples.SparkPi
  mainApplicationFile: local:///opt/spark/examples/jars/spark-examples_2.11-2.4.5.jar
  nodeSelector: 
    key: value

可以看出,通过 Spark Operator 来配置,是比较灵活的。而在 Apache Spark 3.0 中,PodTemplate 是需要在 spark-submit 阶段将模板文件加到 spark.kubernetes.driver.podTemplateFile 或者 spark.kubernetes.executor.podTemplateFile 里的。
众所周知, Spark 在下载依赖文件的时候,可以通过  HTTP/HDFS/S3 等协议来下载需要的文件。但是读取 PodTemplate 的 API,目前只支持本地文件系统(当然要改成支持 http 也不是很复杂),SparkConf 的配置如下:

# template 在本地
spark.kubernetes.driver.podTemplateFile=/opt/spark/template.yaml
spark.kubernetes.executor.podTemplateFile=/opt/spark/template.yaml

关于 Apache Spark 3.0 是如何加载这些 PodTemplate 的文件,我们可以通过源码看出。在将 PodTemplate 文件加载到系统里的关键方法是是 KubernetesUtils.loadPodFromTemplate()

def loadPodFromTemplate(
  kubernetesClient: KubernetesClient,
  templateFile: File,
  containerName: Option[String]): SparkPod = {
  try {
    // 主要的还是利用 K8S 的客户端去 load 模板文件
    // load 模板文件目前只能支持本地文件系统,因为底层调用的是 File 接口
    val pod = kubernetesClient.pods().load(templateFile).get()
    // 这里需要注意会从模板里把指定 Container 捞出来
    // 目的主要是捞出来 Driver 和 Executor 容器
    // 否则就是以第一个容器作为 Driver/Executor 的容器
    selectSparkContainer(pod, containerName)
  } catch {
    case e: Exception =>
      logError(
        s"Encountered exception while attempting to load initial pod spec from file", e)
      throw new SparkException("Could not load pod from template file.", e)
  }
}

通过上述方法就可以利用 PodTemplate 来做一些 Pod 的定义了,避免了大量极其繁琐的 SparkConf 的配置。
由于通过 PodTemplate 来引导定义的操作相对来说是比较前置的,所以有些属性,可能会被后面针对 Pod 的其他配置给 overwrite,在 Spark 的最新文档的 running-on-kubernetes,可以找到那些属性可能会被后置配置覆盖掉。
关于 Spark Driver Pod 配置的具体步骤如下图所示:

1.png

val features = Seq(
  new BasicDriverFeatureStep(conf),
  new DriverKubernetesCredentialsFeatureStep(conf),
  new DriverServiceFeatureStep(conf),
  new MountSecretsFeatureStep(conf),
  new EnvSecretsFeatureStep(conf),
  new MountVolumesFeatureStep(conf),
  new DriverCommandFeatureStep(conf),
  new HadoopConfDriverFeatureStep(conf),
  new KerberosConfDriverFeatureStep(conf),
  new PodTemplateConfigMapStep(conf),
  new LocalDirsFeatureStep(conf))
val spec = KubernetesDriverSpec(
  initialPod,
  driverKubernetesResources = Seq.empty,
  conf.sparkConf.getAll.toMap)
features.foldLeft(spec) { case (spec, feature) =>
  val configuredPod = feature.configurePod(spec.pod)
  val addedSystemProperties = feature.getAdditionalPodSystemProperties()
  val addedResources = feature.getAdditionalKubernetesResources()
  KubernetesDriverSpec(
    configuredPod,
    spec.driverKubernetesResources ++ addedResources,
    spec.systemProperties ++ addedSystemProperties)
}

看完整个过程,可以发现,装配 Driver Pod 的步骤非常复杂。主要是延续了 Spark 2.2 on K8S 那个 Fork 的思路。具体如下:

  1. 通过自定义镜像,将 PodTemplate 文件置入镜像的某个目录中,如 /opt/spark/template.yaml
  2. 在 SparkConf 填入参数 spark.kubernetes.driver.podTemplateFile=/opt/spark/template/driver.yaml
  3. 如果 Pod 里准备用其他容器,则需要在 SparkConf 指定 Driver Container 的名字,例如:spark.kubernetes.driver.podTemplateContainerName=driver-container

三、运行

下面给出一个例子,来给 Spark 的 Drvier/Executor 都加一个 initContainer,将 PodTemplate 文件 template-init.yaml 放在 /opt/spark 目录下,下面是 PodTemplate 的具体内容,即添加一个会 sleep 1s 的 initContainer。

apiversion: v1
kind: Pod
spec:
  initContainers:
  - name: init-s3
    image: hub.oa.com/runzhliu/busybox:latest
    command: ['sh', '-c', 'sleep 1']

SparkConf 里添加以下内容:

spark.kubernetes.driver.podTemplateFile=/opt/spark/template-init.yaml
spark.kubernetes.executor.podTemplateFile=/opt/spark/template-init.yaml

配置完成后,运行一个 SparkPi 的例子,spark-submit 命令如下:

/opt/spark/bin/spark-submit
--deploy-mode=cluster
--class org.apache.spark.examples.SparkPi
--master=k8s://https://172.17.0.1:443
--conf spark.kubernetes.namespace=demo
--conf spark.kubernetes.driver.container.image=hub.oa.com/public/spark:v3.0.0-template
--conf spark.kubernetes.executor.container.image=hub.oa.com/public/spark:v3.0.0-template
--conf=spark.driver.cores=1
--conf=spark.driver.memory=4096M
--conf=spark.executor.cores=1
--conf=spark.executor.memory=4096M
--conf=spark.executor.instances=2
--conf spark.kubernetes.driver.podTemplateFile=/opt/spark/template-init.yaml
--conf spark.kubernetes.executor.podTemplateFile=/opt/spark/template-init.yaml
--conf=spark.kubernetes.executor.deleteOnTermination=false
local:///opt/spark/examples/jars/spark-examples_2.12-3.0.0-SNAPSHOT.jar
100

运行结束,查看一下 Driver Pod 的 YAML 文件,发现 initContainer 已经加上,并且运行正常。

...
  initContainers:
  - command:
    - sh
    - -c
    - sleep 1
    image: hub.oa.com/runzhliu/busybox:latest
    imagePullPolicy: Always
    name: init
    resources: {}
...
  initContainerStatuses:
  - containerID: docker://526049a9a78c4b29d4e4f7b5fcc89935d44c0605bcbf427456c7d7bdf39a6172
    image: hub.oa.com/runzhliu/busybox:latest
    lastState: {}
    name: init
    ready: true
    restartCount: 0
    state:
      terminated:
        containerID: docker://526049a9a78c4b29d4e4f7b5fcc89935d44c0605bcbf427456c7d7bdf39a6172
        exitCode: 0
        finishedAt: "2020-04-02T00:03:35Z"
        reason: Completed
        startedAt: "2020-04-02T00:03:34Z"

注意:PodTemplate 文件里大小写要符合 Kubernetes 的规范,比如 Pod 不能写成 pod,initContainer 不能写成 initcontainer,否则是不生效的。

四、总结

Apache Spark 3.0 支持 PodTemplate,所以用户在配置 Driver/Executor 的 Pod 的时候,会更加灵活,但是 Spark 本身是不会校验 PodTemplate 的正确性的,所以这也给调试带来了很多麻烦。关于 NodeSelector, Taints, Tolerations 等,这些字段在 Spark Operator 中设置,倒是比较方便的。

阿里巴巴开源镜像站 提供全面,高效和稳定的系统镜像、应用软件下载、域名解析和时间同步服务。”