Spark on Kubernetes Native Deployment Guide
前置环境
- 官方参考:Spark on Kubernetes
- 测试环境:Ubuntu 11.2.0, JDK 1.8.0_162, k8s server 1.15
- 需要搭载的 client 版本大概是 4.6,而 Spark 2.4.7 才更新这个包版本
- 如果 k8s server 是最新的 1.19 以上,需要 Spark 3.0 才支持
版本兼容性至关重要
- Kubernetes Client, Server, Minikube, Spark 之间的版本兼容性
Spark on Kubernetes 难点
- 版本兼容性
- 在 Kubernetes 中运行 Spark 最复杂的问题:权限、授信问题
下载 Spark
bash
wget https://dlcdn.apache.org/spark/spark-3.2.2/spark-3.2.2-bin-hadoop3.2.tgz
tar -xvzf spark-3.2.2-bin-hadoop3.2.tgz
构建镜像
- 构建镜像时,需要将 Spark/Hadoop 配置加进去
Dockerfile
dockerfile
## Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
ARG java_image_tag=11-jre-slim
FROM openjdk:${java_image_tag}
ARG spark_uid=185
# Before building the docker image, first build and make a Spark distribution following
# the instructions in http://spark.apache.org/docs/latest/building-spark.html.
# If this docker file is being used in the context of building your images from a Spark
# distribution, the docker build command should be invoked from the top level directory
# of the Spark distribution. E.g.:
# docker build -t spark:latest -f kubernetes/dockerfiles/spark/Dockerfile .
RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install --no-install-recommends -y sudo
RUN set -ex &&
sed -i 's/http://deb.(.*)/https://deb.1/g' /etc/apt/sources.list &&
apt-get update &&
ln -s /lib /lib64 &&
apt install -y bash tini libc6 libpam-modules krb5-user libnss3 procps &&
mkdir -p /opt/spark &&
mkdir -p /opt/spark/examples && mkdir -p /opt/spark/conf &&
mkdir -p /usr/local/jdk1.8.0_162 &&
mkdir -p /opt/spark/work-dir &&
touch /opt/spark/RELEASE &&
rm /bin/sh &&
ln -sv /bin/bash /bin/sh &&
echo "auth required pam_wheel.so use_uid" >> /etc/pam.d/su &&
chgrp root /etc/passwd && chmod ug+rw /etc/passwd &&
rm -rf /var/cache/apt/*
COPY jars /opt/spark/jars
COPY bin /opt/spark/bin
COPY sbin /opt/spark/sbin
COPY kubernetes/dockerfiles/spark/entrypoint.sh /opt/
COPY kubernetes/dockerfiles/spark/decom.sh /opt/
COPY examples /opt/spark/examples
COPY kubernetes/tests /opt/spark/tests
COPY data /opt/spark/data
COPY jdk1.8.0_162 /usr/local/jdk1.8.0_162/
COPY hosts /opt/spark/
COPY conf /opt/spark/conf
ENV SPARK_HOME /opt/spark
WORKDIR /opt/spark/work-dir
RUN chmod g+w /opt/spark/work-dir
RUN chmod a+x /opt/decom.sh
ENTRYPOINT [ "/opt/entrypoint.sh" ]
# Specify the User that the actual main process will run as
# USER ${spark_uid}
USER root
- entrypoint.sh 增加 hosts 配置,将集群各个节点 host 加入

Spark 提供 bin/docker-image-tool.sh 工具用于构建镜像。这个工具会找到 kubernetes/dockerfiles 下的 Docker 文件,根据 Dockerfile 会把需要的 Spark 命令、工具、库、jar 包、Java、example、entrypoint.sh 等构建进镜像。
从 Spark 2.3 开始只支持 Java/Scala,从 2.4 开始支持 Python 和 R,会有三个 Dockerfile,会构建出三个镜像,其中 Python 和 R 是基于 Java/Scala 版的。
bash
sudo ./bin/docker-image-tool.sh -r lfspace/thpub -t thspark3.2.2 build
如果报错:
bash
WARNING: Ignoring http://dl-cdn.alpinelinux.org/alpine/v3.9/main/x86_64/APKINDEX.tar.gz: temporary error (try again later)
WARNING: Ignoring http://dl-cdn.alpinelinux.org/alpine/v3.9/community/x86_64/APKINDEX.tar.gz: temporary error (try again later)
ERROR: unsatisfiable constraints: bash (missing):
required by: world[bash]
这是网络问题,可以修改 ./bin/docker-image-tool.sh,在里面的 docker build 命令加上 --network=host,使容器使用宿主机网络(要确保宿主机网络是 OK 的)。
镜像上传
bash
docker tag lfspace/thpub/spark:thspark3.2.2 lfspace/thpub:spark3.2.2
docker push lfspace/thpub:spark3.2.2
执行
bash
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
该操作默认使用 default 用户,在 default 用户没有足够权限的情况下,该任务最终会失败。
ServiceAccount 权限问题
Spark driver 需要获取 executor 的创建、运行、watch 等权限,需要配置对应用户权限。
1. 准备一个 role.yaml 文件
yaml
apiVersion: v1
kind: ServiceAccount
metadata:
name: spark
namespace: default
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
namespace: default
name: spark-role
rules:
- apiGroups: [""]
resources: ["pods"]
verbs: ["*"]
- apiGroups: [""]
resources: ["services"]
verbs: ["*"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: spark-role-binding
namespace: default
subjects:
- kind: ServiceAccount
name: spark
namespace: default
roleRef:
kind: Role
name: spark-role
apiGroup: rbac.authorization.k8s.io
可以参考:spark-rbac.yaml
执行命令:
bash
sudo kubectl apply -f role.yaml
查看配置:
bash
sudo kubectl get role
sudo kubectl get role spark-role -o yaml
sudo kubectl get rolebinding
sudo kubectl get rolebinding spark-role-binding -o yaml
2. 创建 ServiceAccount
bash
kubectl create serviceaccount spark
kubectl create clusterrolebinding spark-role --clusterrole=edit --serviceaccount=default:spark --namespace=default
容器里提交 Job
定义 deployment,注意指定 serviceAccountName 使用前面创建的 spark role。
yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: spark-client
spec:
replicas: 1
selector:
matchLabels:
app: spark
component: client
template:
metadata:
labels:
app: spark
component: client
spec:
containers:
- name: sparkclient
image: lfspace/thpub:spark3.2.2
workingDir: /opt/spark
command: ["/bin/bash", "-c", "while true;do echo hello;sleep 6000;done"]
serviceAccountName: spark
部署:
bash
sudo kubectl create -f client-deployment.yaml
查看并登录 pod:
bash
sudo kubectl exec -t -i spark-client-6479b76776-l5bzw /bin/bash
通过 env 命令可以看到容器里有定义 Kubernetes API Server 的地址。实际上容器上还有相应的 token 和证书,可以用来访问 API Server。
bash
TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)
curl --cacert /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
-H "Authorization: Bearer $TOKEN"
-s https://${KUBERNETES_SERVICE_HOST}:${KUBERNETES_SERVICE_PORT_HTTPS}/api/v1/namespaces/default/pods
但通过 spark-submit 提交 Job 报错了,说是没权限获取 configMap,看来要求的权限和在宿主机提交不一样。
改变 spark role 的配置,允许操作所有资源,然后重新执行 kubectl create。
yaml
- apiGroups: [""]
resources: ["*"]
verbs: ["*"]
重新提交 Job,可以看到成功启动运行了。
bash
# 第二个 wordcount.py 是作为参数用
bin/spark-submit
--master k8s://https://${KUBERNETES_SERVICE_HOST}:${KUBERNETES_SERVICE_PORT_HTTPS}
--deploy-mode cluster
--name spark-test
--conf spark.executor.instances=3
--conf spark.kubernetes.authenticate.driver.serviceAccountName=spark
--conf spark.kubernetes.container.image=lfspace/thpub:spark3.2.2
/opt/spark/examples/src/main/python/wordcount.py
/opt/spark/examples/src/main/python/wordcount.py
授信问题
最快捷的解决方案写在最下面
在 Kubernetes 中运行 Spark 最复杂的问题,我认为就是授信问题。关于授信问题,Spark 2.4 的官方文档写的很不好,官方文档地址:Spark on Kubernetes
在授信问题上提供了很多参数如:
spark.kubernetes.authenticate.submission.caCertFilespark.kubernetes.authenticate.submission.clientKeyFile
配置这些证书过程很复杂。
异常
bash
javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
这个是因为自签证书不受信任,需要将 ca.pem 导入 keystore。参考:自签证书导入 keystore
异常
bash
2020/10/15 11:09:49.147 WARN WatchConnectionManager : Exec Failure: HTTP 403, Status: 403 - pods "spark-pi-1602731387162-driver" is forbidden: User "system:anonymous" cannot watch resource "pods" in API group "" in the namespace "default"
这是客户端不受信任。解决方案是 spark-submit 添加参数:
bash
--conf spark.kubernetes.authenticate.submission.clientKeyFile=/root/admin-key.pem
--conf spark.kubernetes.authenticate.submission.clientCertFile=/root/admin.pem
或者执行:
bash
kubectl create clusterrolebinding test:anonymous --clusterrole=cluster-admin --user=system:anonymous
这里的证书有一个配置错误都会出问题。
所以最佳配置方案是:将 .kube 文件拷贝到 $HOME 目录下。
原理是:Spark 使用的是 io.fabric8 库,虽然 Spark 提供了一堆的参数,但是该库默认还是会寻找 ~/.kube/config 文件。
代码逻辑:
而这里的授信认证仅仅在 spark-submit 时生效。当命令提交后,生成 driver pod 之后,授信文件 pem 就失去职能了。
Driver 阶段授信
Spark on Kubernetes 大致的流程是 driver pod 去创建和销毁 executor pod,所以 driver pod 的权限需要很大才行。这里需要配置 RBAC,参考:Spark on Kubernetes RBAC
配置完成之后,需要告知 driver 以什么身份执行,因此还需要配置参数:
bash
--conf spark.kubernetes.authenticate.driver.serviceAccountName=spark
最后
Kubernetes client 和 server 版本兼容很烂。Kubernetes server 1.15 需要搭载的 client 版本大概是 4.6,而 Spark 2.4.7 才更新这个包版本。
参考:Setting up Apache Spark Kubernetes MicroK8s
因此测试时格外要注意 client 和 server 版本问题。
最后发布命令如下:
bash
/Users/zhouwenyang/Desktop/tmp/spark/bin/spark-submit
--master k8s://https://knode1:6443 --deploy-mode cluster
--name spark-pi --class org.apache.spark.examples.SparkPi
--conf spark.executor.instances=2
--conf spark.kubernetes.container.image=vm2173:5000/spark:2.4.7
--conf spark.kubernetes.authenticate.driver.serviceAccountName=spark
--conf spark.kubernetes.submission.waitAppCompletion=false
local:///opt/spark/examples/jars/spark-examples_2.11-2.4.7.jar
Spark 官方配置:Spark on Kubernetes RBAC
参考链接:
end
