分布式计算引擎 Flink/Spark on k8s 的实现对比以及实践

本文对比了Flink和Spark在Kubernetes上的原生实现,探讨了它们的提交方式、资源清理、Pod Template、RBAC管理和依赖管理。Flink支持application和session模式,Spark支持cluster和client模式。在生产环境中,日志收集和监控是重要环节,可以使用DaemonSet、SideCar或自定义log appender。对于监控,Flink提供了PrometheusReporter和PrometheusPushGatewayReporter,而Spark推荐使用基于Kubernetes的服务发现机制。
摘要由CSDN通过智能技术生成

以 Flink 和 Spark 为代表的分布式流批计算框架的下层资源管理平台逐渐从 Hadoop 生态的 YARN 转向 Kubernetes 生态的 k8s 原生 scheduler 以及周边资源调度器,比如 Volcano 和 Yunikorn 等。这篇文章简单比较一下两种计算框架在 Native Kubernetes 的支持和实现上的异同,以及对于应用到生产环境我们还需要做些什么。

1. 什么是 Native

这里的 native 其实就是计算框架直接向 Kubernetes 申请资源。比如很多跑在 YARN 上面的计算框架,需要自己实现一个 AppMaster 来想 YARN 的 ResourceManager 来申请资源。Native K8s 相当于计算框架自己实现一个类似 AppMaster 的角色向 k8s 去申请资源,当然和 AppMaster 还是有差异的 (AppMaster 需要按 YARN 的标准进行实现)。

2. Spark on k8s 使用

提交作业

向 k8s 集群提交作业和往 YARN 上面提交很类似,命令如下,主要区别包括:

  • --master 参数指定 k8s 集群的 ApiServer
  • 需要通过参数 spark.kubernetes.container.image 指定在 k8s 运行作业的 image,
  • 指定 main jar,需要 driver 进程可访问:如果 driver 运行在 pod 中,jar 包需要包含在镜像中;如果 driver 运行在本地,那么 jar 需要在本地。
  • 通过 --name 或者 spark.app.name 指定 app 的名字,作业运行起来之后的 driver 命名会以 app 名字为前缀。当然也可以通过参数 spark.kubernetes.driver.pod.name 直接指定 dirver 的名字
$ ./bin/spark-submit \
    --master k8s://https://: \
    --deploy-mode cluster \
    --name spark-pi \
    --class org.apache.spark.examples.SparkPi \
    --conf spark.executor.instances=5 \
    --conf spark.kubernetes.container.image= \
    local:///path/to/examples.jar

提交完该命令之后,spark-submit 会创建一个 driver pod 和一个对应的 servcie,然后由 driver 创建 executor pod 并运行作业。

deploy-mode

和在 YARN 上面使用 Spark 一样,在 k8s 上面也支持 cluster 和 client 两种模式:

  • cluster mode: driver 在 k8s 集群上面以 pod 形式运行。
  • client mode: driver 运行在提交作业的地方,然后 driver 在 k8s 集群上面创建 executor。为了保证 executor 能够注册到 driver 上面,还需要提交作业的机器可以和 k8s 集群内部的 executor 网络连通(executor 可以访问到 driver,需要注册)。

资源清理

这里的资源指的主要是作业的 driver 和 executor pod。spark 通过 k8s 的 onwer reference 机制将作业的各种资源连接起来,这样当 driver pod 被删除的时候,关联的 executor pod 也会被连带删除。但是如果没有 driver pod,也就是以 client 模式运行作业的话,如下两种情况涉及到资源清理:

  • 作业运行完成,driver 进程退出,executor pod 运行完自动退出
  • driver 进程被杀掉,executor pod 连不上 driver 也会自行退出

可以参考:
https://kubernetes.io/docs/concepts/architecture/garbage-collection/

依赖管理

前面说到 main jar 包需要在 driver 进程可以访问到的地方,如果是 cluster 模式就需要将 main jar 打包到 spark 镜像中。但是在日常开发和调试中,每次重新 build 一个镜像的 effort 实在是太大了。spark 支持提交的时候使用本地的文件,然后使用 s3 等作为中转:先上传上去,然后作业运行的时候再从 s3 上面下载下来。下面是一个实例。

...
--packages org.apache.hadoop:hadoop-aws:3.2.0
--conf spark.kubernetes.file.upload.path=s3a:///path
--conf spark.hadoop.fs.s3a.access.key=...
--conf spark.hadoop.fs.s3a.impl=org.apache.hadoop.fs.s3a.S3AFileSystem
--conf spark.hadoop.fs.s3a.fast.upload=true
--conf spark.hadoop.fs.s3a.secret.key=....
--conf spark.driver.extraJavaOptions=-Divy.cache.dir=/tmp -Divy.home=/tmp
file:///full/path/to/app.jar

Pod Template

k8s 的 controller (比如 Deployment,Job)创建 Pod 的时候根据 spec 中的 pod template 来创建。下面是一个 Job 的示例。

apiVersion: batch/v1
kind: Job
metadata:
  name: hello
spec:
  template:
    # 下面的是一个 pod template
    spec:
      containers:
      - name: hello
        image: busybox
        command: ['sh', '-c', 'echo "Hello, Kubernetes!" && sleep 3600']
      restartPolicy: OnFailure
    # The pod template ends here

由于我们通过 spark-submit 提交 spark 作业的时候,最终的 k8s 资源(driver/executor pod)是由 spark 内部逻辑构建出来的。但是有的时候我们想要在 driver/executor pod 上做一些额外的工作,比如增加 sidecar 容器做一些日志收集的工作。这种场景下 PodTemplate 就是一个比较好的选择,同时 PodTemplate 也将 spark 和底层基础设施(k8s)解耦开。比如 k8s 发布新版本支持一些新的特性,那么我们只要修改我们的 PodTemplate 即可,而不涉及到 spark 的内部改动。

RBAC

RBAC 全称是 Role-based access control,是 k8s 中的一套权限控制机制。通俗来说:

  • RBAC 中包含了一系列的权限设置,比如 create/delete/watch/list pod 等,这些权限集合的实体叫 Role 或者 ClusterRole
  • 同时 RBAC 还包含了角色绑定关系(Role Binding),用于将 Role/ClusterRole 赋予一个或者一组用户,比如 Service Account 或者 UserAccount

为了将 Spark 作业在 k8s 集群中运行起来,我们还需要一套 RBAC 资源:

  • 指定 namespace 下的 serviceaccount
  • 定义了权限规则的 Role 或者 ClusterRole,我们可以使用常见的 ClusterRole "edit"(对几乎所有资源具有操作权限,比如 create/delete/watch 等)
  • 绑定关系

下面命令在 spark namespace 下为 serviceaccount spark 赋予了操作同 namespace 下其他资源的权限,那么只要 spark 的 driver pod 挂载了该 serviceaccount,它就可以创建 executor pod 了。

$ kubectl create serviceaccount spark
$ kubectl create clusterrolebinding spark-role --clusterrole=edit --serviceaccount=spark:spark --namespace=spark

下面做一个简单的演示:

通过如下命令提交作业 SparkPiSleep 到 k8s 集群中。

$ spark-submit --master k8s://https://: --deploy-mode cluster --class org.apache.spark.examples.SparkPiSleep --conf spark.executor.memory=2g --conf spark.driver.memory=2g --conf spark.driver.core=1 --conf spark.app.name=test12 --conf spark.kubernetes.submission.waitAppCompletion=false --conf spark.executor.core=1 --conf spark.kubernetes.container.image= --conf spark.eventLog.enabled=false --conf spark.shuffle.service.enabled=false --conf spark.executor.instances=1 --conf spark.dynamicAllocation.enabled=false --conf spark.kubernetes.namespace=spark --conf spark.kubernetes.authenticate.driver.serviceAccountName=spark --conf spark.executor.core=1  local:///path/to/main/jar

查看 k8s 集群中的资源

$ kubectl get po -n spark
NAME                               READY   STATUS              RESTARTS   AGE
spark-pi-5b88a27b576050dd-exec-1   0/1     ContainerCreating   0          2s
test12-9fd3c27b576039ae-driver     1/1     Running             0          8s

其中第一个就是 executor pod,第二个是 driver 的 pod。除此之外还创建了一个 service,可以通过该 service 访问到 driver pod,比如 Spark UI 都可以这样访问到。

$ kubectl get svc -n spark
NAME                                 TYPE           CLUSTER-IP     EXTERNAL-IP     PORT(S)                                       AGE
test12-9fd3c27b576039ae-driver-svc   ClusterIP      None                     7078/TCP,7079/TCP,4040/TCP                    110s

下面再看一下 service owner reference,executor pod 也是类似的。

$ kubectl get svc test12-9fd3c27b576039ae-driver-svc -n spark -oyaml
apiVersion: v1
kind: Service
metadata:
  creationTimestamp: "2021-08-18T03:48:50Z"
  name: test12-9fd3c27b576039ae-driver-svc
  namesp
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值