Spark on Kubernetes PodTemplate 的配置

1 Overview

本文主要講 Apache Spark 在 on Kubernetes 的 PodTemplate 的問題,以及也會講到 Spark Operator 里關(guān)于 PodTemplate 的問題,當(dāng)然也會講到 Apache Spark 2.2 on Kubernetes 那個(gè) Fork 的版本,感興趣的同學(xué)可以往下看看。

之前講過 Apache Spark on Kubernetes 在配置 Pod 的時(shí)候的一些限制,比如針對 Pod 的調(diào)度,想加個(gè) NodeSelector 或者 Tolerations。這在集群公用,或者有各種類型任務(wù)的集群里,是經(jīng)常會遇到的情況,而在 Spark 2.x 里是很難做到的。

目前最新 Release 的版本 2.4.5 還沒有支持通過 PodTemplate 來自定義 Pod 的配置,而社區(qū)的計(jì)劃是在 Spark 3.0 的時(shí)候?qū)⑦@一 feature 完成,他支持的方式其實(shí)也比較簡單,就是可以有一個(gè) PodTemplate 的一個(gè)文件,去描述 Driver/Executor 的 metadata/spec 字段,這樣當(dāng)然就可以在模板文件里加入跟調(diào)度需要的一些字段了,

關(guān)于 PodTemplate 可以帶來什么呢?比如說其實(shí) Apache Spark 2.2 on Kubernetes 一開始是支持 initContainer 的,當(dāng)時(shí)可以通過 spark.kubernetes.initcontainer.docker.image 來配置 Pod 的 initContainer 但是隨著版本的演進(jìn),關(guān)于 initContainer 的代碼已經(jīng)去掉了,可以想象,如果只通過幾個(gè) SparkConf 來配置 initContainer 的話,這樣限制實(shí)現(xiàn)太多了,SparkConf 的表達(dá)能力有限,如果都通過 spark.kubernetes.driver.label.* 這樣的 SparkConf 來配置的話,既不靈活,也讓 SparkConf 的配置數(shù)量急劇膨脹。

那么現(xiàn)在如果用戶想通過 initContainer 做一些事情那可以怎么辦?在 Spark 2.x 的版本里,應(yīng)該是沒有辦法的,除非通過一些迂回的辦法來實(shí)現(xiàn)原先你想通過 intContainer 達(dá)到的目標(biāo),比如說將一個(gè)文件提交下載到 Volume 并進(jìn)行掛載這類操作,又或者直接去改下源碼。

具體可以參考在 SPARK-24434 Support user-specified driver and executor pod templates 的相關(guān)討論。不論 initContainer 的邏輯怎么樣了,至少現(xiàn)在用戶可以通過 PodTemplate 來自定義 Pod,當(dāng)然包括定義需要的 initContainer,以及跟調(diào)度相關(guān)的一些字段。

2 PodTemplate

實(shí)際上,如果是在 Spark Operator 里,本身就支持 Pod Template 的配置 SparkPodSpec,也就是說,像 NodeSelector, Tolerations 之類的,可以在創(chuàng)建 CRD 對象的時(shí)候在 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 在下載依賴文件的時(shí)候,可以通過 HTTP/HDFS/S3 等協(xié)議來下載需要的文件。但是讀取 PodTemplate 的 API,目前只支持本地文件系統(tǒng)(當(dāng)然要改成支持 http 也不是很復(fù)雜),SparkConf 的配置可能如下。

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

關(guān)于 Apache Spark 3.0 是如何加載這些 PodTemplate 的文件,我們可以看看源碼。在將 PodTemplate 文件加載到系統(tǒng)里的關(guān)鍵方法是是 KubernetesUtils.loadPodFromTemplate()。

def loadPodFromTemplate(
  kubernetesClient: KubernetesClient,
  templateFile: File,
  containerName: Option[String]): SparkPod = {
  try {
    // 主要的還是利用 K8S 的客戶端去 load 模板文件
    // load 模板文件目前只能支持本地文件系統(tǒng),因?yàn)榈讓诱{(diào)用的是 File 接口
    val pod = kubernetesClient.pods().load(templateFile).get()
    // 這里需要注意會從模板里把指定 Container 撈出來
    // 目的主要是撈出來 Driver 和 Executor 容器
    // 否則就是以第一個(gè)容器作為 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 的配置。如果想在 Apache Spark 3.0 之前的版本去實(shí)現(xiàn) NodeSelector/Toleration 這些操作,直接通過 SparkConf 是不行的。后面 Driver/Executor 的 Pod 構(gòu)建就分別交給 KubernetesDriverBuilderKubernetesExecutorBuilder 去做了。而在執(zhí)行 spark-submit 的環(huán)境中,需要去讀取 PodTemplate 文件,然后通過 ConfigMap 來掛載到 Driver/Executor Pod。當(dāng)然了,我覺得這樣還是不夠靈活,因?yàn)?Executor 的 PodTemplate 也可以在 Spark 鏡像里,不需要一定要在 spark-submit 的環(huán)境里,目前的做法,如果是使用本地文件的話,就必須在 spark-submit 的本地環(huán)境了,而我覺得沒必要,不過我們還是可以改成通過 http 等方式,讓本地可以讀取到這些 PodTemplate 文件的,只是你還需要一個(gè)文件服務(wù)器去放這些 PodTemplate 的文件。

因?yàn)橥ㄟ^ PodTemplate 來引導(dǎo)定義的操作相對來說是比較前置的,所以有些屬性,可能會被后面針對 Pod 的其他配置給 overwrite,在 Spark 的最新文檔的 running-on-kubernetes,可以找到那些屬性可能會被后置配置覆蓋掉。

下面是關(guān)于 Spark Driver Pod 是怎么通過各種 Step 按順序最后給構(gòu)建出來的示意圖。

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)
}

看完整個(gè)過程,可以發(fā)現(xiàn),裝配 Driver Pod 的步驟竟然如此復(fù)雜。這個(gè)設(shè)計(jì)也是延續(xù)了 Spark 2.2 on K8S 那個(gè) Fork 的思路。

  1. 通過自定義鏡像,將 PodTemplate 文件置入鏡像的某個(gè)目錄中,如 /opt/spark/template.yaml
  2. 然后在 SparkConf 填入?yún)?shù) spark.kubernetes.driver.podTemplateFile=/opt/spark/template/driver.yaml
  3. 如果 Pod 里準(zhǔn)備起其他容器,則需要在 SparkConf 指定 Driver Container 的名字,例如 spark.kubernetes.driver.podTemplateContainerName=driver-container

3 Example

下面給出一個(gè)例子,來給 Spark 的 Drvier/Executor 都加一個(gè) initContainer,將 PodTemplate 文件 template-init.yaml 放在 /opt/spark 目錄下,下面是 PodTemplate 的具體內(nèi)容,就是加一個(gè)會 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

運(yùn)行一個(gè) 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

運(yùn)行結(jié)束,查看一下 Driver Pod 的 YAML 文件,發(fā)現(xiàn) initContainer 已經(jīng)加上,并且運(yùn)行正常。

...
  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 文件里,有幾個(gè)事情需要注意一下的,就是大小寫要符合 Kubernetes 的規(guī)范,比如 Pod 不能寫成 pod,initContainer 不能寫成 initcontainer,否則是不生效的。

4 Summary

Apache Spark 3.0 支持 PodTemplate,所以用戶在配置 Driver/Executor 的 Pod 的時(shí)候,會更加靈活,但是 Spark 本身是不會校驗(yàn) PodTemplate 的正確性的,所以這也給調(diào)試帶來了很多麻煩。關(guān)于 NodeSelector, Taints, Tolerations 等,這些字段在 Spark Operator 中設(shè)置,倒是比較方便的。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容

  • 背景介紹 相信凡是關(guān)注容器生態(tài)圈的人都不會否認(rèn),Kubernetes 已經(jīng)成為容器編排調(diào)度的實(shí)際標(biāo)準(zhǔn),不論 Doc...
    Kyligence閱讀 1,500評論 0 1
  • 后來聽惠林解釋道,繞過熙熙攘攘的人群,才發(fā)現(xiàn)已經(jīng)偏離了酒肆很遠(yuǎn)了。葉玖十分懊惱,來了此地進(jìn)門坐一會,怎地不說一句就...
    曾羽茉閱讀 273評論 0 0
  • 今天和老公的關(guān)系有所緩和,感覺老公現(xiàn)在對我比以前更珍惜和重視了。謝謝老公的包容和陪伴。 謝謝娟娟組織的閱讀群,謝謝...
    秀艷的美好生活閱讀 223評論 0 0
  • 我以語音朗讀的方式向您推薦簡書作者南飛雨燕的散文作品:《光明與黑暗》 光明與黑暗(點(diǎn)擊收聽) 以下為作者原文圖片
    揚(yáng)飏閱讀 507評論 2 2
  • 當(dāng)您在應(yīng)用程序的HTML文件中將模板寫為<template name =“foo”> ... </ templat...
    與時(shí)間賽跑_閱讀 224評論 0 0