改版通知

巨人肩膀网站已全新改版。若您仍依赖旧站功能或数据,欢迎联系我们,我们会协助处理。联系我们

Spark on Kubernetes Native Deployment Guide

ckckck2025年1月10日3 浏览

前置环境

  • 官方参考: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 才支持

版本兼容性至关重要

  1. Kubernetes Client, Server, Minikube, Spark 之间的版本兼容性

Spark on Kubernetes 难点

  1. 版本兼容性
  2. 在 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

构建镜像

  1. 构建镜像时,需要将 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
  1. 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.caCertFile
  • spark.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