Spark on Kubernetes PodTemplate 的配置

1 Overview

本文主要講 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 的時候將這一 feature 完成,他支持的方式其實也比較簡單,就是可以有一個 PodTemplate 的一個文件,去描述 Driver/Executor 的 metadata/spec 字段,這樣當然就可以在模板文件里加入跟調度需要的一些字段了,

關於 PodTemplate 可以帶來什麼呢?比如說其實 Apache Spark 2.2 on Kubernetes 一開始是支持 initContainer 的,當時可以通過 spark.kubernetes.initcontainer.docker.image 來配置 Pod 的 initContainer 但是隨着版本的演進,關於 initContainer 的代碼已經去掉了,可以想象,如果只通過幾個 SparkConf 來配置 initContainer 的話,這樣限制實現太多了,SparkConf 的表達能力有限,如果都通過 spark.kubernetes.driver.label.* 這樣的 SparkConf 來配置的話,既不靈活,也讓 SparkConf 的配置數量急劇膨脹。

那麼現在如果用戶想通過 initContainer 做一些事情那可以怎麼辦?在 Spark 2.x 的版本里,應該是沒有辦法的,除非通過一些迂迴的辦法來實現原先你想通過 intContainer 達到的目標,比如說將一個文件提交下載到 Volume 並進行掛載這類操作,又或者直接去改下源碼。

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

2 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 的配置。如果想在 Apache Spark 3.0 之前的版本去實現 NodeSelector/Toleration 這些操作,直接通過 SparkConf 是不行的。後面 Driver/Executor 的 Pod 構建就分別交給 KubernetesDriverBuilderKubernetesExecutorBuilder 去做了。而在執行 spark-submit 的環境中,需要去讀取 PodTemplate 文件,然後通過 ConfigMap 來掛載到 Driver/Executor Pod。當然了,我覺得這樣還是不夠靈活,因爲 Executor 的 PodTemplate 也可以在 Spark 鏡像裏,不需要一定要在 spark-submit 的環境裏,目前的做法,如果是使用本地文件的話,就必須在 spark-submit 的本地環境了,而我覺得沒必要,不過我們還是可以改成通過 http 等方式,讓本地可以讀取到這些 PodTemplate 文件的,只是你還需要一個文件服務器去放這些 PodTemplate 的文件。

因爲通過 PodTemplate 來引導定義的操作相對來說是比較前置的,所以有些屬性,可能會被後面針對 Pod 的其他配置給 overwrite,在 Spark 的最新文檔的 running-on-kubernetes,可以找到那些屬性可能會被後置配置覆蓋掉。

下面是關於 Spark Driver Pod 是怎麼通過各種 Step 按順序最後給構建出來的示意圖。

Spark Driver Pod裝配過程.png-207.2kB

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

3 Example

下面給出一個例子,來給 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,否則是不生效的。

4 Summary

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章