天天看點

Spark on Kubernetes PodTemplate 的配置1 Overview2 PodTemplate3 Example4 Summary

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

實際上,如果是在

裡,本身就支援 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 建構就分别交給

KubernetesDriverBuilder

KubernetesExecutorBuilder

去做了。而在執行

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 on Kubernetes PodTemplate 的配置1 Overview2 PodTemplate3 Example4 Summary
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 中設定,倒是比較友善的。

繼續閱讀