Spark 如何在Kubernetes运行官方权威资料点击这里看全文
Spark可以在由Kubernetes管理的集群上运行。这个特性利用了添加到Spark中的原生Kubernetes调度器。
安全性
默认情况下,未启用安全特性如身份验证。当部署一个对外开放或不受信任网络的集群时,重要的是保护集群的访问权限,以防止未经授权的应用程序在集群上运行。在运行Spark之前,请参考Spark安全性和本文档中的特定安全性部分。
用户身份
从提供的Dockerfile构建的镜像包含具有默认UID为185的默认USER指令。这意味着生成的镜像将在容器内作为该UID运行Spark进程。注重安全性的部署应该考虑提供带有USER指令的自定义镜像,指定所需的非特权UID和GID。生成的UID应该在其附加组中包括root组,以便能够运行Spark可执行文件。使用提供的docker-image-tool.sh脚本构建自己的镜像的用户可以使用-u <uid>
选项指定所需的UID。
另外,Pod模板功能可以用于为Spark提交的Pod添加Security Context,并设置runAsUser。这可以用于覆盖镜像本身中的USER指令。请注意,这需要用户的合作,因此可能不适合共享环境的解决方案。如果集群管理员希望限制Pod能够以何种用户身份运行,应使用Pod安全策略。
卷挂载
正如本文档后面的“在Kubernetes上使用卷”一节所述,Spark on K8S提供了配置选项,允许将某些卷类型挂载到驱动程序和执行器Pod中。特别是它允许使用hostPath卷,根据Kubernetes文档的描述,这种卷存在已知的安全漏洞。
集群管理员应使用Pod安全策略,适当限制挂载hostPath卷的能力。
先决条件
- 运行版本大于等于1.24的Kubernetes集群,并使用kubectl进行配置访问。如果您还没有一个可用的Kubernetes集群,可以使用minikube在本地机器上设置一个测试集群。
- 我们建议使用启用了DNS插件的最新版minikube。
- 请注意,默认的minikube配置不足以运行Spark应用程序。我们建议使用3个CPU和4G内存来启动一个简单的Spark应用程序,其中只有一个执行器。
- 检查kubernetes-client库的版本与您的Spark环境及其与Kubernetes集群版本的兼容性。
- 您必须具有适当的权限,以在集群中列出、创建、编辑和删除Pod。您可以通过运行
kubectl auth can-i <list|create|edit|delete> pods
来验证您是否能够列出这些资源。 - 驱动程序Pod使用的服务帐户凭据必须被允许创建Pod、服务和配置映射。
- 您必须在集群中配置了Kubernetes DNS。
工作原理
Spark集群组件
可以直接使用spark-submit将Spark应用程序提交到Kubernetes集群。提交机制的工作方式如下:
- Spark创建一个在Kubernetes pod中运行的Spark驱动程序。
- 驱动程序创建执行器,这些执行器也在Kubernetes pod中运行,并连接到它们并执行应用程序代码。
- 当应用程序完成时,执行器pod终止并进行清理,但是驱动程序pod保留日志,并在Kubernetes API中保持“已完成”状态,直到最终进行垃圾回收或手动清理。
请注意,在已完成的状态下,驱动程序pod不使用任何计算或内存资源。
驱动程序和执行器pod的调度由Kubernetes处理。通过fabric8进行与Kubernetes API的通信。可以使用节点选择器将驱动程序和执行器pod调度到可用节点的子集上,使用相应的配置属性。未来的版本将可能支持更高级的调度提示,如节点/容器亲和性。
提交应用到kubernetes
Docker镜像
Kubernetes要求用户提供可以部署到Pod中的容器镜像。这些镜像构建为在Kubernetes支持的容器运行时环境中运行。Docker是一个经常与Kubernetes一起使用的容器运行时环境。Spark(从版本2.3开始)附带了一个Dockerfile,可以用于此目的,或者根据个别应用程序的需求进行自定义。它可以在kubernetes/dockerfiles/目录中找到。
Spark还附带了一个bin/docker-image-tool.sh脚本,可用于构建和发布与Kubernetes后端一起使用的Docker镜像。
示例用法如下:
$ ./bin/docker-image-tool.sh -r <repo> -t my-tag build
$ ./bin/docker-image-tool.sh -r <repo> -t my-tag push
这将使用项目提供的默认Dockerfile进行构建。要查看更多可用于自定义此工具行为的选项,请使用-h标志运行。
默认情况下,bin/docker-image-tool.sh会构建用于运行JVM作业的Docker镜像。您需要选择构建其他语言绑定的Docker镜像。
示例用法如下:
# 构建额外的PySpark Docker镜像
$ ./bin/docker-image-tool.sh -r <repo> -t my-tag -p ./kubernetes/dockerfiles/spark/bindings/python/Dockerfile build
# 构建额外的SparkR Docker镜像
$ ./bin/docker-image-tool.sh -r <repo> -t my-tag -R ./kubernetes/dockerfiles/spark/bindings/R/Dockerfile build
您也可以直接使用Apache Spark的Docker镜像(例如apache/spark:)。
集群模式
要在集群模式下启动Spark Pi应用程序,请运行以下命令:
$ ./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
Spark master可以通过将–master命令行参数传递给spark-submit或在应用程序的配置中设置spark.master来指定,它必须是一个带有格式k8s://<api_server_host>:的URL。即使是HTTPS端口443,也必须始终指定端口。在master字符串前加上k8s://将导致Spark应用程序在Kubernetes集群上启动,并在api_server_url处与API服务器进行通信。如果URL中未指定HTTP协议,默认为https。例如,将master设置为k8s://example.com:443等同于将其设置为k8s://https://example.com:443,但要在不使用TLS连接的其他端口上连接,则master应设置为k8s://http://example.com:8080。
在Kubernetes模式下,由spark.app.name指定的Spark应用程序名称或通过spark-submit的–name参数指定的名称将默认用于命名创建的驱动程序和执行器等Kubernetes资源。因此,应用程序名称必须由小写字母数字字符、-和.组成,并且必须以字母数字字符开头和结尾。
如果您已经设置了一个Kubernetes集群,可以通过执行kubectl cluster-info来发现apiserver URL。
$ kubectl cluster-info
Kubernetes master is running at http://127.0.0.1:6443
在上面的示例中,可以通过将–master k8s://http://127.0.0.1:6443作为spark-submit的参数来使用特定的Kubernetes集群。此外,还可以使用身份验证代理kubectl proxy与Kubernetes API进行通信。
您可以通过以下命令启动本地代理:
$ kubectl proxy
如果本地代理正在localhost:8001上运行,则可以将–master k8s://http://127.0.0.1:8001作为spark-submit的参数。最后,请注意,在上面的示例中,我们指定了具有本地协议的特定URI的jar文件。该URI是已经在Docker镜像中的示例jar的位置。
客户端模式
从Spark 2.4.0开始,可以在客户端模式下在Kubernetes上运行Spark应用程序。当应用程序在客户端模式下运行时,驱动程序可以在一个Pod内或物理主机上运行。当以客户端模式运行应用程序时,建议考虑以下因素:
客户端模式网络
Spark执行器必须能够通过主机名和端口连接到Spark驱动程序。Spark在客户端模式下正常工作所需的特定网络配置会因设置而异。如果将驱动程序放在Kubernetes pod内,可以使用无头服务(headless service)来允许执行器通过稳定的主机名从执行器访问驱动程序。在部署无头服务时,请确保服务的标签选择器仅与驱动程序pod匹配,不与其他pod匹配;建议为驱动程序pod分配一个足够唯一的标签,并在无头服务的标签选择器中使用该标签。可以通过spark.driver.host指定驱动程序的主机名,并通过spark.driver.port指定Spark驱动程序的端口。
客户端模式执行器Pod垃圾回收
如果您将Spark驱动程序放在一个pod中,强烈建议将spark.kubernetes.driver.pod.name设置为该pod的名称。当设置了这个属性时,Spark调度程序将使用OwnerReference部署执行器pod,并确保一旦驱动程序pod从集群中删除,所有应用程序的执行器pod也将被删除。驱动程序将在由spark.kubernetes.namespace指定的命名空间中查找具有给定名称的pod,并将指向该pod的OwnerReference添加到每个执行器pod的OwnerReferences列表中。请注意,避免将OwnerReference设置为实际上不是驱动程序pod的pod,否则在删除错误的pod时,执行器可能会过早终止。
如果您的应用程序不在pod中运行,或者当您的应用程序实际上在一个pod中运行时未设置spark.kubernetes.driver.pod.name,则要注意,在应用程序退出时,执行器pod可能无法正确从集群中删除。Spark调度程序尝试删除这些pod,但是如果由于任何原因网络请求到API服务器失败,这些pod将保留在集群中。当执行器无法访问驱动程序时,执行器进程应该退出,因此执行器pod在应用程序退出后不应消耗计算资源(CPU和内存)。
您可以使用spark.kubernetes.executor.podNamePrefix来完全控制执行器pod的名称。设置此属性后,强烈建议在同一命名空间中的所有作业中使其唯一。
身份验证参数
在客户端模式下,使用精确的前缀spark.kubernetes.authenticate用于Kubernetes身份验证参数。
IPv4和IPv6
从3.4.0开始,Spark还支持通过IPv4/IPv6双栈网络功能在仅IPv6环境中使用。根据K8s集群的能力,spark.kubernetes.driver.service.ipFamilyPolicy和spark.kubernetes.driver.service.ipFamilies可以是SingleStack、PreferDualStack和RequireDualStack之一,以及IPv4、IPv6、IPv4,IPv6和IPv6,IPv4之一。默认情况下,Spark使用spark.kubernetes.driver.service.ipFamilyPolicy=SingleStack和spark.kubernetes.driver.service.ipFamilies=IPv4。
要仅使用IPv6,可以通过以下方式提交作业:
...
--conf spark.kubernetes.driver.service.ipFamilies=IPv6
在双栈环境中,您可能还需要使用java.net.preferIPv6Addresses=true配置JVM,并使用SPARK_PREFER_IPV6=true配置Python来使用IPv6。
依赖管理
如果应用程序的依赖项都托管在远程位置,如HDFS或HTTP服务器,可以使用相应的远程URI引用它们。还可以将应用程序依赖项预先挂载到自定义构建的Docker镜像中。可以通过使用local:// URI引用它们并/或在Dockerfiles中设置SPARK_EXTRA_CLASSPATH环境变量将这些依赖项添加到类路径中。客户端模式下,还支持从提交客户端的本地文件系统使用file://方案引用应用程序依赖项,或者在不使用方案(使用完整路径)的情况下引用它们,其中目标应该是一个Hadoop兼容的文件系统。使用S3的典型示例是通过传递以下选项:
...
--packages org.apache.hadoop:hadoop-aws:3.2.2
--conf spark.kubernetes.file.upload.path=s3a://<s3-bucket>/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
应用程序jar文件将上传到S3,并在启动驱动程序时下载到驱动程序pod并添加到其类路径中。Spark将在上传路径下生成一个具有随机名称的子目录,以避免与并行运行的spark应用程序冲突。用户可以根据自己的需求管理创建的子目录。
对于应用程序jar、以及由属性spark.jars、spark.files和spark.archives指定的依赖项,也支持client scheme。
重要提示:所有客户端依赖项将以扁平化的目录结构上传到给定路径,因此文件名必须唯一,否则文件将被覆盖。还请确保在派生的k8s镜像中默认的ivy目录具有所需的访问权限,或者按照上述修改设置。如果在集群模式下使用–packages,后者也很重要。
密钥管理
Kubernetes Secrets可用于提供Spark应用程序访问受保护服务的凭据。要将用户指定的密钥挂载到驱动程序容器中,请使用形式为spark.kubernetes.driver.secrets.[SecretName]=的配置属性。类似地,可以使用形式为spark.kubernetes.executor.secrets.[SecretName]=的配置属性将用户指定的密钥挂载到执行器容器中。请注意,假设要挂载的密钥与驱动程序和执行器pod在同一个命名空间中。例如,要在驱动程序和执行器容器中将名为spark-secret的密钥挂载到路径/etc/secrets,请将以下选项添加到spark-submit命令中:
--conf spark.kubernetes.driver.secrets.spark-secret=/etc/secrets
--conf spark.kubernetes.executor.secrets.spark-secret=/etc/secrets
要通过环境变量使用密钥,请在spark-submit命令中使用以下选项:
--conf spark.kubernetes.driver.secretKeyRef.ENV_NAME=name:key
--conf spark.kubernetes.executor.secretKeyRef.ENV_NAME=name:key
Pod模板
Kubernetes允许从模板文件定义Pod。Spark用户可以类似地使用模板文件来定义驱动程序或执行器pod的配置,这些配置不受Spark配置支持。为此,请指定spark属性spark.kubernetes.driver.podTemplateFile和spark.kubernetes.executor.podTemplateFile,以指向对spark-submit进程可访问的文件。
--conf spark.kubernetes.driver.podTemplateFile=s3a://bucket/driver.yml
--conf spark.kubernetes.executor.podTemplateFile=s3a://bucket/executor.yml
要允许驱动程序pod访问执行器pod模板文件,当创建驱动程序pod时,该文件将自动挂载到驱动程序pod中的一个卷上。Spark不会在取消编组这些模板文件后进行任何验证,并依赖于Kubernetes API服务器进行验证。
重要提示:Spark对某些pod配置持有意见,因此在模板文件中将始终被Spark覆盖的值。因此,使用此功能的用户应注意,指定模板pod文件只是让Spark在构建过程中使用模板pod而不是空白pod。有关将被spark覆盖的完整列表,请参阅spark覆盖的完整列表。
Pod模板文件还可以定义多个容器。在这种情况下,您可以使用spark属性spark.kubernetes.driver.podTemplateContainerName和spark.kubernetes.executor.podTemplateContainerName来指示哪个容器应作为驱动程序或执行器的基础。如果未指定或容器名称无效,或者容器名称无效,则Spark将假定列表中的第一个容器将是驱动程序或执行器容器。