k8s工作总结1:spark原生支持k8s

当前spark支持k8s有三种方式,第一种是以standalone的方式部署在k8s集群中,资源由spark自己管理,第二种是spark原生支持k8s,此时k8s相当于是yarn的作用,用户在使用上与standalone或者spark on yarn的方式一样,第三种是spark operator的方式支持k8s,通过k8s crd+operator创建任务运行资源。

目前我主要是基于第二种方式开展相关的实践。为了开展相关实践,首先,你得安装k8s集群,这里不再详细说明安装过程,有需要的读者可以在其它文章中寻找支持。我用以实践的k8s是3+3的部署形态。

实践过程中踩了一些坑,我下面的描述中都会如实描述整个过程,希望对自己和读者都有所启发,当然我会适当地拔高,跟当时实践的某个阶段的理解会有不一样,这样会更有逻辑和可理解。

我总结下来,会有两种使用的方式,第一种就是cluster,第二种是client。

我在实践的过程中,刚开始用的spark-submit的方式,deploy方式设置为cluster,比较顺利的执行,后面因为业务的原因,业务人员一直说需要在程序中使用spark,我理解为可能是跟spark-submit平行的另外一种方式,中间出现了不少问题,主要就是认证权限问题,后面又出现了exec找不到driver的问题,我还以为是spark原生 on k8s不支持这种使用方式。

后面我才理解到,其实这种所谓在程序中调用spark的使用方式,就是跟spark-submit的deploy为client一样的运行模式,而官方对此种方式已经有典型的部署案例,可以解决认证等问题。

cluster模式,说白了,就是driver运行在集群的一个Pod里面,而client模式,就是driver运行在本地,这里说的本地,有两种理解,如果是用spark-submit,那么本地就是此命令执行所在的机器,如果是直接采用python或者java运行任务程序,比如python脚本或者jar包,那么就是运行任务程序的那台机器。

1、cluster部署模式。

此部署模式下,driver或者executor都会运行于k8s集群的Pod中,用户使用spark-submit提交任务,将任务程序和数据源放置在指定的外部存储上,比如nfs或者hdfs。

下面是使用nfs作为存储的场景下主要参数设置方法。

bin/spark-submit \
--master k8s://https://172.16.98.100:6443 \
--deploy-mode cluster \
--conf spark.kubernetes.authenticate.driver.serviceAccountName=spark \
--conf spark.executor.instances=2 \
--conf spark.kubernetes.container.image=registry.gts.com.cn:5000/spark/spark-py:2.4.4-py \
--conf spark.kubernetes.driver.volumes.persistentVolumeClaim.spark-nfs-pv.mount.path=/root/ \
--conf spark.kubernetes.driver.volumes.persistentVolumeClaim.spark-nfs-pv.mount.readOnly=false \
--conf spark.kubernetes.driver.volumes.persistentVolumeClaim.spark-nfs-pv.options.claimName=spark-nfs-pvc \
--conf spark.kubernetes.executor.volumes.persistentVolumeClaim.spark-nfs-pv.mount.path=/root/ \
--conf spark.kubernetes.executor.volumes.persistentVolumeClaim.spark-nfs-pv.mount.readOnly=false \
--conf spark.kubernetes.executor.volumes.persistentVolumeClaim.spark-nfs-pv.options.claimName=spark-nfs-pvc \

--conf spark.kubernetes.authenticate.submission.caCertFile=/var/run/secrets/kubernetes.io/serviceaccount/ca.crt \
--conf spark.kubernetes.authenticate.submission.oauthToken=eyJhbGciOiJSUzI1NiIsImtpZCI6IjUyT0dMZm52d1JfNFNfcm5sSkt5aFBIZ0xSeTB6T0p3Yk5IdFo2(对应serviceAccountName的token)
/root/test.py

需要将对应的serviceAccountName的ca.crt文件放在/var/run/secrets/kubernetes.io/serviceaccount/目录下,同时将token如上通过参数传递给k8s。

 

python脚本test.py如下

# -*- coding: utf-8 -*
import os
from pyspark.sql import SparkSession
from pyspark import SparkConf
k8s_master = "k8s://https://172.16.98.100:6443"
conf = SparkConf().setAppName("test_pyspark").setMaster(k8s_master)
spark = SparkSession.builder.config(conf=conf).getOrCreate()
sc = spark.sparkContext
input_path = r"file:///root/*.csv"
df = spark.read.format("csv").option("encoding","utf-8").option("delimiter", "\t").load(input_path,header=True, schema=None)
df.show()
output_file = r"file:///root/out"

df.write.mode("overwrite").options(header="true").csv(output_file, sep='\t')
sc.stop()
spark.stop()

如果是hdfs的存储,spark-submit相关的参数设置如下

2、client部署模式。

client模式下,主要解决两个问题,第一调用serverapi的接入鉴权问题,第二是executor连接driver。

如果将client运行在k8s集群的Pod,则就能自然解决serverapi的接入鉴权,当然要在Pod中使用有相关权限的serviceAcount,否则,需要在client运行所在的机器的相关目录下放置ca.crt和token信息(对应的k8s账号),而且你会发现,目前的spark支持不太好,它总是会从文件中读取token,而不管你通过参数传递的token,而且让你奔溃的是,它每次读取的token都会多出一个回车符,从而导致你的token不对。

所以如果你想在集群外部去跑driver,基本是不可能完成的事情。现在只有两种办法,要么在集群中node节点上运行driver,要么是Pod中。

显然,Pod会更简单,而且使用者是没有办法搞坏你的集群的,最多把这个Pod搞挂到,显然安全性要高很多。

因此你剩下的事情就是,制作一个可以跑client的镜像,第一是java环境,第二有spark。

在Pod中跑spark任务的示例如下可供参考。

from pyspark.sql import SparkSession
from pyspark import SparkConf
import os
spark_master="k8s://https://172.16.98.100:6443"

spark_conf = SparkConf().setAppName("test_spark").setMaster(spark_master)
spark_conf.set("spark.kubernetes.authenticate.driver.serviceAccountName", "spark")
spark_conf.set("spark.kubernetes.authenticate.executor.serviceAccountName", "spark")
spark_conf.set("spark.executor.instances", "2")
spark_conf.set("spark.submit.deployMod", "client")
spark_conf.set("spark.kubernetes.container.image", "registry.gts.com.cn:5000/spark/spark-py:3.0.0")
spark_conf.set("spark.kubernetes.driver.volumes.persistentVolumeClaim.spark-nfs-pv.mount.path", "/root/")
spark_conf.set("spark.kubernetes.driver.volumes.persistentVolumeClaim.spark-nfs-pv.mount.readOnly", "false")
spark_conf.set("spark.kubernetes.driver.volumes.persistentVolumeClaim.spark-nfs-pv.options.claimName", "spark-nfs-pvc")
spark_conf.set("spark.kubernetes.executor.volumes.persistentVolumeClaim.spark-nfs-pv.mount.path", "/root/")
spark_conf.set("spark.kubernetes.executor.volumes.persistentVolumeClaim.spark-nfs-pv.mount.readOnly", "false")
spark_conf.set("spark.kubernetes.executor.volumes.persistentVolumeClaim.spark-nfs-pv.options.claimName", "spark-nfs-pvc")
spark_conf.set("spark.driver.host", "spark-client.default.svc.cluster.local")
spark_conf.set("spark.driver.port", "7321")

spark = SparkSession.builder.config(conf=spark_conf).getOrCreate()

这里有个重点需要关注的地方,那就是你的spark client运行的方式必须要是headless的,因为首先这里的driver.host参数必须要是真正的Pod地址,这样driver监听的时候才能起来server,如果你用clusterip,这是个虚拟的地址,driver无法在Pod中启动监听,然后,直接使用Pod IP的话,因为容器迁移时IP会变化,所以有可能会失效,所以最好用域名。为了让Pod拥有独立的域名,所以要使用headless的方式开放服务的访问。headless方式,只需要将服务的clusterip值修改为None即可。

你可能感兴趣的:(k8s,云原生)