设置SPARK_USER环境变量导致K8s上Spark的S3写入失败
问题描述
在Kubernetes环境运行Spark作业时,为了在Spark History Server里区分自己的作业,手动设置了SPARK_USER环境变量,结果出现S3写入异常:作业执行完后,S3路径下只生成_SUCCESS文件,没有实际数据输出;但移除SPARK_USER的设置后,S3写入就恢复正常,本地文件系统写入也不受影响,CSV和Parquet格式都存在这个问题。
用户使用的Spark配置如下:
spark.hadoop.fs.s3a.metadatastore.impl org.apache.hadoop.fs.s3a.s3guard.NullMetadataStore spark.hadoop.fs.defaultFS file:/// spark.sql.parquet.binaryAsString true spark.hadoop.fs.s3a.experimental.input.fadvise normal spark.hadoop.parquet.enable.summary-metadata false spark.sql.parquet.mergeSchema false spark.sql.parquet.filterPushdown true spark.sql.parquet.compression.codec snappy spark.speculation false spark.hadoop.mapreduce.fileoutputcommitter.algorithm.version 2 spark.hadoop.mapreduce.outputcommitter.factory.scheme.s3a org.apache.hadoop.fs.s3a.commit.S3ACommitterFactory spark.hadoop.fs.s3a.committer.name directory spark.hadoop.fs.s3a.committer.staging.conflict-mode append spark.sql.sources.commitProtocolClass org.apache.spark.internal.io.cloud.PathOutputCommitProtocol spark.sql.parquet.output.committer.class org.apache.spark.internal.io.cloud.BindingParquetOutputCommitter spark.kubernetes.driver.volumes.persistentVolumeClaim.datasets.options.claimName datasets spark.hadoop.fs.s3a.readahead.range 256K spark.hadoop.fs.s3a.committer.tmp.path file:///mnt/data/scratch/scratch/tmp spark.hadoop.fs.s3a.committer.staging.tmp.path /mnt/data/scratch/scratch/staging spark.hadoop.fs.s3a.buffer.dir /mnt/data/scratch/scratch/buffer spark.kubernetes.driver.volumes.persistentVolumeClaim.datasets.mount.path /mnt/data/scratch/scratch spark.serializer org.apache.spark.serializer.KryoSerializer spark.kryoserializer.buffer.max 2047m spark.kryoserializer.buffer 256m spark.hadoop.fs.s3a.multipart.size 256m
问题原因
核心问题出在S3A Directory Committer的本地临时目录权限上:
当设置SPARK_USER后,Spark作业会以该用户身份去创建和使用配置里的临时目录(spark.hadoop.fs.s3a.committer.tmp.path、staging.tmp.path等),但Kubernetes容器中该用户可能没有这些路径的读写权限,导致Task阶段生成的临时数据无法被提交器同步到S3,最后只完成了提交标记(_SUCCESS),实际数据却没传上去。
解决方案
1. 给SPARK_USER授权临时目录权限
在K8s Pod的启动脚本里,提前创建配置中的临时路径,并把权限赋给SPARK_USER:
mkdir -p /mnt/data/scratch/scratch/tmp /mnt/data/scratch/scratch/staging /mnt/data/scratch/scratch/buffer chown -R $SPARK_USER:$SPARK_USER /mnt/data/scratch/scratch
或者直接把临时目录改成容器内默认有读写权限的路径(比如/tmp),修改Spark配置:
spark.hadoop.fs.s3a.committer.tmp.path file:///tmp/s3a-committer-tmp spark.hadoop.fs.s3a.committer.staging.tmp.path /tmp/s3a-committer-staging spark.hadoop.fs.s3a.buffer.dir /tmp/s3a-buffer
2. 换成S3A Magic Committer
Magic Committer是专门为云存储设计的提交器,能避免本地临时目录的权限问题,修改以下配置即可切换:
spark.hadoop.fs.s3a.committer.name magic spark.hadoop.mapreduce.outputcommitter.factory.scheme.s3a org.apache.hadoop.fs.s3a.commit.S3ACommitterFactory
注意:需要确保你的Spark(2.4+)和Hadoop(3.1+)版本兼容Magic Committer。
3. 不用SPARK_USER,改用其他方式标记作业
如果只是为了在History Server区分作业,没必要硬改SPARK_USER,可以用这些方式:
- 提交作业时自定义应用名称,带上标识:
spark-submit --conf spark.app.name="MyJob_${USER}" ... - 或者通过Spark配置设置用户标识(部分版本支持):
spark.user=your-custom-user
验证
修改配置后重新提交作业,检查S3路径是否生成了实际的数据文件,同时确认Spark History Server里的作业标识是否正确显示。
内容的提问来源于stack exchange,提问作者Hutch3232

