找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

1035

积分

0

好友

131

主题
发表于 昨天 06:26 | 查看: 1| 回复: 0

这不最近把 Kubernetes 集群给搭建起来了嘛,就想既然费了这么大劲,如果只用来跑 Flink 任务,是不是有点浪费。

于是琢磨着,是不是也可以把一部分 Spark 任务迁移过来试试水,毕竟 Spark 也明确支持在 K8s 上运行。顺便也能对比一下,同为 on K8s,Flink 和 Spark 的实际表现,看看哪个更给力一些。

先说结论:Spark on K8s 跑起来最大的坑,在于制作一个符合你当前环境需求的镜像。 当然,这也不代表其他地方就没坑了。我前前后后折腾了至少3台云服务器,才最终把这个难缠的镜像给搞定。
(PS:本次测试环境为 Spark 3.2 + Kubernetes 1.21)

0. 官方文档的“陷阱”

自 Spark 2.3 版本开始,官方就开始支持在 K8s 上运行了,所以我用的 3.2 版本肯定没问题。

但从官网说明来看,它和 Flink 的做法不太一样:Flink 有很多现成的 Docker 镜像,直接下载就能用。而 Spark 没有,它需要你基于一个基础 JDK 镜像,再结合你当前使用的 Spark 版本,去构建一个符合自己运行环境的目标镜像。

官方提供了一个构建脚本示例:

./bin/docker-image-tool.sh -r <repo> -t my-tag build
./bin/docker-image-tool.sh -r <repo> -t my-tag push

使用官方脚本构建和推送 Spark Docker 镜像

虽然官网给了模板配置,也提供了一个基础镜像,但我怀疑这是官方跟我们开的一个“小玩笑”。默认的Dockerfile配置是这样的:

ARG java_image_tag=11-jre-slim

官方Dockerfile中默认的Java镜像标签

它基于 openjdk:11-jre-slim 这个镜像,但我的 Spark 开发环境是基于 JDK 8 的。关键我去 Docker Hub 一查,官方压根就没提供这个 11-jre-slim 标签的镜像,硬拉肯定失败。
Docker Hub 搜索显示官方缺失的镜像标签

你说气不气人?所以不管出于兼容性还是实际可用性考虑,咱们都得自己换基础镜像。然而,就是这个换镜像的动作,开启了本次的“踩坑”之旅。

1. 事故一:镜像基础不对,文件缺失

既然官方默认的镜像不能用,那就只能去 Docker Hub 找一个既存在又符合我 Spark 开发环境(JDK8)的基础镜像。

我找来找去,找到了这个:8u342-jre
搜索到的 JDK 8 基础镜像

但因为网络问题,我部署 K8s 的物理机节点根本下载不下来这个基础镜像。没办法,换了一台云服务器才下载成功。

然后,我把这个基础镜像传到 K8s 节点上,准备用它来构建 Spark 镜像。但构建过程很快就失败了:
构建镜像失败,提示无法读取/etc/apt/sources.list

错误信息大概意思是:选择的镜像不对,Dockerfile 中期望存在的某些文件(比如 /etc/apt/sources.list)在基础镜像里没有。得,这个镜像不能用,继续换。

2. 事故二:网络问题,依赖下载失败

继续寻找,我又找到了一个看起来更合适的:8u342-jre-slim
另一个 JDK 8 slim 版本镜像

这次,上面那个文件缺失的问题没有了。但是,新的问题又来了——网络问题。在利用这个基础镜像构建 Spark 镜像的过程中,需要下载一些系统依赖包,结果连接超时。
镜像构建过程中 apt 更新连接超时

你猜我怎么解决的?对,我把构建镜像的任务,从 K8s 节点(物理机)迁移到了一台网络通畅的云服务器上。

但这可不是简单地换个地方执行命令。你需要把整个 Spark 的安装目录拷贝到那台云服务器上,因为构建脚本需要读取 Spark 的 jars、bin 等文件。

终于,在云服务器上,Spark 镜像可以顺利构建了:
在云服务器上成功构建 Spark 镜像的过程日志

构建完成后,镜像列表里可以看到它:
使用 docker images 命令查看构建成功的 Spark 镜像

你以为这就结束了吗?很抱歉,新一轮的“噩梦”才刚刚开始。

3. 事故三:Java 路径不对,启动报错

既然 Spark 的运行镜像做好了,接下来就该美滋滋地跑个 Demo 了对不对?

来,根据官网示例,我们这样提交一个 Spark Pi 任务:

./bin/spark-submit \
    --master k8s://https://192.168.xxx.xx:6443 \
    --deploy-mode cluster \
    --name spark-pi \
    --class org.apache.spark.examples.SparkPi \
    --conf spark.executor.instances=5 \
    --conf spark.kubernetes.container.image=apache/spark:3.2-scala_2.12-jre8 \
    local:///opt/spark/examples/jars/spark-examples_2.12-3.2.0.jar

任务确实能提交上去,但 Pod 很快进入了 Error 状态:
Spark driver pod 启动失败,状态为 Error

查看 Pod 日志,发现了问题:
Spark 启动时报错:bad array subscript,提示 Java 路径问题

日志显示 bad array subscript,这通常是因为 Spark 程序在启动时,会优先去 /usr/java/latest/bin 这个目录下寻找 Java 命令,但我们构建的镜像里,根本没有这个目录。

为了验证,我特意进入失败的容器内部查看:

ls /usr/java/latest/bin/java
ls /usr/java

在容器内验证,/usr/java 目录不存在

果然,目录不存在。但容器里其实是有 Java 的,路径在别处:

which java

使用 which 命令查找 Java 的实际安装路径

看到路径是 /usr/local/openjdk-8/bin/java。那怎么办?难道又换基础镜像吗?我替你试过了,没用!

这里的解决方案是:修改 Spark 的 Dockerfile,在构建时创建所需的目录,并将实际的 Java 可执行文件链接过去。 具体来说,就是在 Dockerfile 里加上这几行:

mkdir -p /usr/java/latest/bin &&
ln -sv /usr/local/openjdk-8/bin/java /usr/java/latest/bin/java &&
java -version

在 Dockerfile 中添加创建目录和软链接的命令

它没有这个目录?我们建一个。它没有这个文件?我们创建一个软链接过去。这样一来,问题就解决了。

4. 事故四:服务账号权限不足

这回,任务提交后,Driver 和 Executor Pod 都能成功启动了。但是,好景不长,很快又出现了新的错误:
Pod 因权限问题再次失败,状态为 Error 和 Terminating

查看日志,发现是 Kubernetes API 权限问题:

Caused by: io.fabric8.kubernetes.client.KubernetesClientException: Failure executing: GET at: https://kubernetes.default.svc/api/v1/namespaces/default/pods/spark-pi-xxx-driver. Message: Forbidden!Configured service account doesn‘t have access. Service account may have been revoked. pods “spark-pi-xxx-driver” is forbidden: User “system:serviceaccount:default:default” cannot get resource “pods” in API group ““ in the namespace “default”

好家伙,这个问题我熟!之前玩 Flink on K8s 时就遇到过一模一样的问题。解决办法也类似,需要为 Spark 任务创建一个专属的 ServiceAccount 并绑定权限。

我们照猫画虎,执行以下命令:

# 创建namespace
kubectl create ns spark
# 创建serviceaccount
kubectl create serviceaccount spark-account -n spark
# 用户授权 (这里授予了 edit 角色,生产环境请根据需要细化权限)
kubectl create clusterrolebinding spark-role-binding-spark --clusterrole=edit --serviceaccount=spark:spark-account

然后,修改 Spark 任务提交命令,指定使用我们创建的 namespace 和 serviceaccount:

./bin/spark-submit \
    --master k8s://https://192.168.xxxx.xx:6443 \
    --deploy-mode cluster \
    --name spark-pi \
    --class org.apache.spark.examples.SparkPi \
    --conf spark.executor.instances=5 \
    --conf spark.kubernetes.container.image=apache/spark:3.2-scala_2.12-jre8 \
    --conf spark.kubernetes.namespace=spark \
    --conf spark.kubernetes.authenticate.driver.serviceAccountName=spark-account \
    local:///opt/spark/examples/jars/spark-examples_2.12-3.2.0.jar

这样,权限问题就解决了。

5. 事故五:Hostname 解析失败

再次提交任务,没跑一会儿,又双叒出错了!这次的报错是:

java.lang.IllegalArgumentException: java.net.UnknownHostException: hdp01.xxx.xx
        at org.apache.hadoop.security.SecurityUtil.buildTokenService(SecurityUtil.java:466)

这次是主机名(hostname)解析问题。这个问题在裸机部署时可能不起眼,但在 K8s 的 Pod 里,常规的修改 /etc/hosts 方法往往行不通。

瞬间感觉脑瓜子嗡嗡的,真是防不胜防。我试过在构建镜像时修改 hosts 文件,没用;也试过一些AI给的偏方,在提交命令里加参数,也没用。

此时此刻,我真想放弃,要知道 Flink on K8s 可没这么多破事儿。生气归生气,问题还得解决。你猜最后怎么搞定的?

我直接把提交 Spark 任务的那台机器上,Hadoop 配置文件(如 core-site.xml 等)里面,所有涉及到这个无法解析的 hostname(hdp01.xxx.xx)的地方,全部替换成了对应的 IP 地址。

然后,任务就正常了!看吧,关键时刻,还得靠经验。

最终,任务顺利提交到集群:
Spark Executor Pod 全部成功运行

配置的 5 个 Executor 都争气地跑起来了。最后,计算 Pi 的任务也顺利完成:
Spark Driver Pod 任务完成,状态为 Completed

至此,请允许我长舒一口气。

总结

同样是跑通一个简单的 Demo,一番折腾下来发现,Spark on K8s 的初始配置复杂度,确实比 Flink 要高不少。官方文档给出的信息,对于应对实际环境中的各种“坑”来说,有点不够看。

  • 没有现成的、开箱即用的生产级镜像;
  • 官方给的模板镜像参数可能不适用或镜像不存在;
  • 自己找的基础镜像可能缺少必要的目录结构;
  • 关键文件路径不对,还需要手动识别并修正;
  • 镜像搞定后,还要处理 K8s 权限和网络解析等问题。

叠了这么多层“Buff”,新手很容易被劝退。要不是之前部署 Flink on K8s 积攒了一些经验,按我平时的性子,可能早就放弃了。

这次我们只是跑通了测试任务,至于在 K8s 上运行真实的生产级 Spark 任务表现如何,性能怎样,资源管理是否方便,那就是下一次要测试的话题了。如果你也在探索 Spark on K8s,希望这篇踩坑记录能帮你少走一些弯路。欢迎到 云栈社区 分享你的经验和遇到的问题。




上一篇:开源单点登录系统选型指南:企业级统一身份认证平台MaxKey解析
下一篇:深入剖析Linux pkeys内存保护机制与CTF漏洞利用实战
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-2-7 06:33 , Processed in 0.381652 second(s), 38 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表