0. 前情提要
在服务器上用standalone模式部署了Spark,使用本地文件系统(没有使用HDFS),在多用户提交Spark application计算任务并保存结果到本地文件系统时出现问题,保存时提示:
java.io.IOException: mkdirs failed to create file
或
java.io.IOException: Failed to rename DeprecatedRawLocalFileStatus
因为很少有像我这么“奇葩”的使用场景(单机Standalone+本地文件系统+多用户),所以花了两三个小时搜索研究才最终解决。
1. 解决方案
不想看完整问题分析的同学可以直接采用下面的解决方案:
- 关闭Spark
- 使
root
用户执行start-master.sh
和start-worker.sh
启动Spark - 在创建
SparkSession
时,将spark.hadoop.fs.permissions.umask-mode
设置为000
。可以在Spark安装目录的conf/spark-defaults.conf
文件内设置;以pySpark为例,也可以通过这种方式在运行时设置:
spark = SparkSession \
.builder \
.master("spark://youraddress:7077") \
.config("spark.hadoop.fs.permissions.umask-mode", "000") \
.appName("yourname") \
.getOrCreate()
- 如果你不存在多用户使用的场景,可以忽略第2、3步,使用相同的用户启动Spark和提交Spark任务即可
感兴趣的同学可以接着往下看前因后果,不感兴趣的可以右上角了
2. 系统环境
- 单台服务器:68 vCPUs + 600GB RAM
- CentOS 8
- Spark 3.1.2 (Standalone模式,使用本地文件系统,无HDFS)
- JupyterHub(支持多用户的Jupyter notebook)
- pySpark
3. 问题描述
用户可以正常执行Spark计算,而一旦将Spark结果输出到本地文件系统时,就会报错,比如执行df.write.csv('result')
。(注意: 这里的df
是Spark DataFrame,不是pandas DataFrame,如果Spark driver有足够内存将结果转换为pandas DataFrame再保存,是不会出现这个问题的)
报错分为两种情况:
- 启动Spark的为非root用户时:
java.io.IOException: mkdirs failed to create file
- 启动Spark的为root时:
java.io.IOException: Failed to rename DeprecatedRawLocalFileStatus
会导致结果保存过程中止,无法导出计算结果
4. 问题分析
问题的根本原因在于启动Spark的用户和使用Spark进行计算的用户不同,其他用户创建的文件和文件夹无法被当前用户修改:
- 在Spark保存结果时,首先会以提交计算任务的用户(driver)创建一个文件夹(比如上文提到的result),该文件夹的权限为
755
(rwxr-xr-x),其他非权限用户不具有写权限 - 然后,各个executor会以启动Spark的用户在上述文件夹中写入临时结果,如果启动Spark的用户不是root,就无法写入,报第一个错误
- 如果启动Spark的用户是root,那么临时结果可以被写入,最后需要由提交计算任务的用户(driver)来重新组织。由于这些临时文件的所有者是root,所以driver没有办法修改,报第二个错误
5. 解决思路
解决问题的关键在于,在启动和提交Spark计算用户不相同的前提下,让双方创建的文件、文件夹都能够被双方修改。问题似乎很简单,设置一下umask或者ACL不就好了吗?于是我开始了一些失败的尝试:
- 将保存位置的默认ACL文件夹权限设置为所有人可读写
- 将用户的umask设置为000(所有人可读写)
结果发现,在保存Spark结果的时候,这些设置都没有生效,生成的文件权限依然为755
这说明Spark有自己的一套配置覆盖了上面的默认配置,那么这个配置在哪呢?
答案是spark.hadoop.fs.permissions.umask-mode
,其默认值为022
,所以生成的文件权限依然为755
(感谢这位答主)。
然而,我以为我的使用环境(本地文件系统)不适用这个配置项,所以刚开始看到这个答案的时候不以为然直接忽略了…… 后来又折腾了几个小时,抱着试一试的心态,发现居然成功了
6. 仍存在的问题
- 首先肯定是安全性的问题,用户的文件理应只有用户自己能修改(或者至少是组成员)。因为这台服务器的用户都是可信的,所以图省事我把
umask-mode
改成了000
,稍微安全一点的做法是改成002
,然后建立一个(或多个)新组把Spark启动用户和计算用户加入进去 - 即使
umask-mode
改成了000
,如果启动Spark的用户不是root,依然会报第一个错误,原因不明 - 有条件、不怕麻烦的话大家还是上HDFS或者其他文件系统吧,起码出了问题后能搜到的相关内容都更多些
谨以我昨晚的搜索记录纪念一下这次debug:
参考资料
- https://spark.apache.org/docs/latest/configuration.html
- https://stackoverflow.com/questions/43077881/spark-how-to-write-files-with-a-given-permission
- https://stackoverflow.com/questions/51769375/how-to-force-spark-hive-to-create-task-directories-with-custom-permissions
- https://www.mail-archive.com/user@spark.apache.org/msg28820.html
- https://stackoverflow.com/a/35987436/6059213