Spark on Kubernetes 的現(xiàn)狀與挑戰(zhàn)
云原生時代,Kubernetes 的重要性日益凸顯,這篇文章以 Spark 為例來看一下大數(shù)據(jù)生態(tài) on Kubernetes 生態(tài)的現(xiàn)狀與挑戰(zhàn)。
1. Standalone 模式
Spark 運行在 Kubernetes 集群上的第一種可行方式是將 Spark 以 Standalone 模式運行,但是很快社區(qū)就提出使用 Kubernetes 原生 Scheduler 的運行模式,也就是 Native 的模式。關于 Standalone 模式這里就沒有繼續(xù)討論的必要了。
2. Kubernetes Native 模式
Native 模式簡而言之就是將 Driver 和 Executor Pod 化,用戶將之前向 YARN 提交 Spark 作業(yè)的方式提交給 Kubernetes 的 apiserver,提交命令如下:
- $ bin/spark-submit \
- --master k8s://https://<k8s-apiserver-host>:<k8s-apiserver-port> \
- --deploy-mode cluster \
- --name spark-pi \
- --class org.apache.spark.examples.SparkPi \
- --conf spark.executor.instances=5 \
- --conf spark.kubernetes.container.image=<spark-image> \
- local:///path/to/examples.jar
其中 master 就是 kubernetes 的 apiserver 地址。提交之后整個作業(yè)的運行方式如下,先將 Driver 通過 Pod 啟動起來,然后 Driver 會啟動 Executor 的 Pod。這些方式很多人應該都了解了,就不贅述了,詳細信息可以參考:https://spark.apache.org/docs/latest/running-on-kubernetes.html 。
3. Spark Operator
除了這種直接想 Kubernetes Scheduler 提交作業(yè)的方式,還可以通過 Spark Operator 的方式來提交。Operator 在 Kubernetes 中是一個非常重要的里程碑。在 Kubernetes 剛面世的時候,關于有狀態(tài)的應用如何部署在 Kubernetes 上一直都是官方不愿意談論的話題,直到 StatefulSet 出現(xiàn)。StatefulSet 為有狀態(tài)應用的部署實現(xiàn)了一種抽象,簡單來說就是保證網(wǎng)絡拓撲和存儲拓撲。但是狀態(tài)應用千差萬別,并不是所有應用都能抽象成 StatefulSet,強行適配反正加重了開發(fā)者的心智負擔。
然后 Operator 出現(xiàn)了。我們知道 Kubernetes 給開發(fā)者提供了非常開放的一種生態(tài),你可以自定義 CRD,Controller 甚至 Scheduler。而 Operator 就是 CRD + Controller 的組合形式。開發(fā)者可以定義自己的 CRD,比如我定義一種 CRD 叫 EtcdCluster 如下:
- apiVersion: "etcd.database.coreos.com/v1beta2"
- kind: "EtcdCluster"
- metadata:
- name: "example-etcd-cluster"
- spec:
- size: 3
- version: "3.1.10"
- repository: "quay.io/coreos/etcd"
提交到 Kubernetes 之后 Etcd 的 Operator 就針對這個 yaml 中的各個字段進行處理,最后部署出來一個節(jié)點規(guī)模為 3 個節(jié)點的 etcd 集群。你可以在 github 的這個 repo:https://github.com/operator-framework/awesome-operators 中查看目前實現(xiàn)了 Operator 部署的分布式應用。
Google 云平臺,也就是 GCP 在 github 上面開源了 Spark 的 Operator,repo 地址:。Operator 部署起來也是非常的方便,使用 Helm Chart 方式部署如下,你可以簡單認為就是部署一個 Kubernetes 的 API Object (Deployment)。
- $ helm repo add incubator http://storage.googleapis.com/kubernetes-charts-incubator
- $ helm install incubator/sparkoperator --namespace spark-operator
這個 Operator 涉及到的 CRD 如下:
- ScheduledSparkApplication
- |__ ScheduledSparkApplicationSpec
- |__ SparkApplication
- |__ ScheduledSparkApplicationStatus
- |__ SparkApplication
- |__ SparkApplicationSpec
- |__ DriverSpec
- |__ SparkPodSpec
- |__ ExecutorSpec
- |__ SparkPodSpec
- |__ Dependencies
- |__ MonitoringSpec
- |__ PrometheusSpec
- |__ SparkApplicationStatus
- |__ DriverInfo
如果我要提交一個作業(yè),那么我就可以定義如下一個 SparkApplication 的 yaml,關于 yaml 里面的字段含義,可以參考上面的 CRD 文檔。
- apiVersion: sparkoperator.k8s.io/v1beta1
- kind: SparkApplication
- metadata:
- ...
- spec:
- deps: {}
- driver:
- coreLimit: 200m
- cores: 0.1
- labels:
- version: 2.3.0
- memory: 512m
- serviceAccount: spark
- executor:
- cores: 1
- instances: 1
- labels:
- version: 2.3.0
- memory: 512m
- image: gcr.io/ynli-k8s/spark:v2.4.0
- mainApplicationFile: local:///opt/spark/examples/jars/spark-examples_2.11-2.3.0.jar
- mainClass: org.apache.spark.examples.SparkPi
- mode: cluster
- restartPolicy:
- type: OnFailure
- onFailureRetries: 3
- onFailureRetryInterval: 10
- onSubmissionFailureRetries: 5
- onSubmissionFailureRetryInterval: 20
- type: Scala
- status:
- sparkApplicationId: spark-5f4ba921c85ff3f1cb04bef324f9154c9
- applicationState:
- state: COMPLETED
- completionTime: 2018-02-20T23:33:55Z
- driverInfo:
- podName: spark-pi-83ba921c85ff3f1cb04bef324f9154c9-driver
- webUIAddress: 35.192.234.248:31064
- webUIPort: 31064
- webUIServiceName: spark-pi-2402118027-ui-svc
- webUIIngressName: spark-pi-ui-ingress
- webUIIngressAddress: spark-pi.ingress.cluster.com
- executorState:
- spark-pi-83ba921c85ff3f1cb04bef324f9154c9-exec-1: COMPLETED
- LastSubmissionAttemptTime: 2018-02-20T23:32:27Z
提交作業(yè)。
- $ kubectl apply -f spark-pi.yaml
對比來看 Operator 的作業(yè)提交方式似乎顯得更加的冗長復雜,但是這也是一種更 kubernetes 化的 api 部署方式,也就是 Declarative API,聲明式 API。
4. 挑戰(zhàn)
基本上,目前市面的大部門公司都是使用上面兩種方式來做 Spark on Kubernetes 的,但是我們也知道在 Spark Core 里面對 Kubernetes 的這種 Native 方式支持其實并不是特別成熟,還有很多可以改善的地方:
1.scheduler 差異。
資源調(diào)度器可以簡單分類成集中式資源調(diào)度器和兩級資源調(diào)度器。兩級資源調(diào)度器有一個中央調(diào)度器負責宏觀資源調(diào)度,對于某個應用的調(diào)度則由下面分區(qū)資源調(diào)度器來做。兩級資源調(diào)度器對于大規(guī)模應用的管理調(diào)度往往能有一個良好的支持,比如性能方面,缺點也很明顯,實現(xiàn)復雜。其實這種設計思想在很多地方都有應用,比如內(nèi)存管理里面的 tcmalloc 算法,Go 語言的內(nèi)存管理實現(xiàn)。大數(shù)據(jù)的資源調(diào)度器 Mesos/Yarn,某種程度上都可以歸類為兩級資源調(diào)度器。
集中式資源調(diào)度器對于所有的資源請求進行響應和決策,這在集群規(guī)模大了之后難免會導致一個單點瓶頸,毋庸置疑。但是 Kubernetes 的 scheduler 還有一點不同的是,它是一種升級版,一種基于共享狀態(tài)的集中式資源調(diào)度器。Kubernetes 通過將整個集群的資源緩存到 scheduler 本地,在進行資源調(diào)度的時候在根據(jù)緩存的資源狀態(tài)來做一個 “樂觀” 分配(assume + commit)來實現(xiàn)調(diào)度器的高性能。
Kubernetes 的默認調(diào)度器在某種程度上并不能很好的 match Spark 的 job 調(diào)度需求,對此一種可行的技術方案是再提供一種 custom scheduler,比如 Spark on Kubernetes Native 方式的參與者之一的大數(shù)據(jù)公司 Palantir 就開源了他們的 custom scheduler,github repo: https://github.com/palantir/k8s-spark-scheduler。
2.集群規(guī)模瓶頸。
基本上現(xiàn)在可以確定的是 Kubernetes 會在集群規(guī)模達到五千臺的時候出現(xiàn)瓶頸,但是在很早期的時候 Spark 發(fā)表論文的時候就聲稱 Spark Standalone 模式可以支持一萬臺規(guī)模。Kubernetes 的瓶頸主要體現(xiàn)在 master 上,比如用來做元數(shù)據(jù)存儲的基于 raft 一致性協(xié)議的 etcd 和 apiserver 等。對此在剛過去的 2019 上海 KubeCon 大會上,阿里巴巴做了一個關于提高 master 性能的 session: 了解 Kubernetes Master 的可擴展性和性能,感興趣的可以自行了解。
3.Pod 驅(qū)逐(Eviction)問題。
在 Kubernetes 中,資源分為可壓縮資源(比如 CPU)和不可壓縮資源(比如內(nèi)存),當不可壓縮資源不足的時候就會將一些 Pod 驅(qū)逐出當前 Node 節(jié)點。國內(nèi)某個大廠在使用 Spark on kubernetes 的時候就遇到因為磁盤 IO 不足導致 Spark 作業(yè)失敗,從而間接導致整個測試集都沒有跑出來結(jié)果。如何保證 Spark 的作業(yè) Pod (Driver/Executor) 不被驅(qū)逐呢?這就涉及到優(yōu)先級的問題,1.10 之后開始支持。但是說到優(yōu)先級,有一個不可避免的問題就是如何設置我們的應用的優(yōu)先級?常規(guī)來說,在線應用或者 long-running 應用優(yōu)先級要高于 batch job,但是顯然對于 Spark 作業(yè)來說這并不是一種好的方式。
4.作業(yè)日志。
Spark on Yarn 的模式下,我們可以將日志進行 aggregation 然后查看,但是在 Kubernetes 中暫時還是只能通過 Pod 的日志查看,這塊如果要對接 Kubernetes 生態(tài)的話可以考慮使用 fluentd 或者 filebeat 將 Driver 和 Executor Pod 的日志匯總到 ELK 中進行查看。
5.Prometheus 生態(tài)。
Prometheus 作為 CNCF 畢業(yè)的第二個項目,基本是 Kubernetes 監(jiān)控的標配,目前 Spark 并沒有提供 Prometheus Sink。而且 Prometheus 的數(shù)據(jù)讀取方式是 pull 的方式,對于 Spark 中 batch job 并不適合使用 pull 的方式,可能需要引入 Prometheus 的 pushgateway。
5. 結(jié)語
被稱為云上 OS 的 Kubernetes 是 Cloud Native 理念的一種技術承載與體現(xiàn),但是如何通過 Kubernetes 來助力大數(shù)據(jù)應用還是有很多可以探索的地方。歡迎交流。