Docker部署Maven Spark应用报SparkSession类找不到异常排查
抛出java.lang.NoClassDefFoundError: org/apache/spark/sql/SparkSession的核心原因是你打进Docker镜像的Jar包未包含Spark等第三方依赖,和Docker环境本身无关,本地直接执行java -jar target/my.script.spark.jar也会出现完全相同的错误。本地IDE运行无异常是因为Eclipse会自动把Maven依赖加入运行类路径,和打包出的Jar内容无关。
具体配置错误有3个:
- 所有Maven插件配置都放在
<pluginManagement>标签下,该标签仅用于锁定插件版本、给子模块提供继承配置,当前项目不会实际执行这部分插件逻辑,你配置的打包规则完全没生效 - 重复定义了两次
maven-jar-plugin,且jar-with-dependencies(包含依赖的胖包)是maven-assembly-plugin的配置项,maven-jar-plugin本身不支持把依赖打入Jar包,配置配错了插件 - 生成的默认Jar包仅包含你自己写的业务代码,所有第三方依赖都在本地Maven仓库里,没有被打包进Jar,容器里没有本地Maven仓库的依赖,自然找不到类。
1. 修正pom.xml的打包配置
删除原<build>块内的全部内容,替换为如下配置,移除无效的<pluginManagement>标签,改用正确的maven-assembly-plugin打包含所有依赖的胖包:
<build> <finalName>my.script.spark</finalName> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.8.0</version> <configuration> <source>1.8</source> <target>1.8</target> <encoding>UTF-8</encoding> </configuration> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-assembly-plugin</artifactId> <version>3.3.0</version> <configuration> <appendAssemblyId>false</appendAssemblyId> <archive> <manifest> <mainClass>my.script.spark.App</mainClass> </manifest> </archive> <descriptorRefs> <descriptorRef>jar-with-dependencies</descriptorRef> </descriptorRefs> </configuration> <executions> <execution> <id>make-full-jar</id> <phase>package</phase> <goals> <goal>single</goal> </goals> </execution> </executions> </plugin> </plugins> </build>
注意:如果后续你需要把Jar提交到独立Spark集群运行,可以把Spark相关依赖的scope设置为
provided,集群会自带Spark依赖不需要打入包内;但你当前是直接通过java -jar在容器内启动,必须保持Spark依赖为默认的compilescope,否则依赖不会被打入胖包。
2. 重新打包验证
在项目根目录执行mvn clean package,或者在Eclipse中右键项目 -> Run As -> Maven Build,Goals输入clean package执行打包。
打包完成后检查target目录下的my.script.spark.jar,大小应该在200M以上,本地先执行java -jar target/my.script.spark.jar验证,确认本地能正常启动不报错,再进行镜像构建。
3. 重新构建镜像启动服务
你的Dockerfile和docker-compose.yml配置没有问题,直接重新构建镜像即可:
docker build -t user/my_spark .
构建完成后执行docker-compose up启动容器,就不会再报类找不到的错误,不需要在镜像内单独安装Spark。
- 你当前pom中引入的
hadoop-hdfs 2.2.0版本和Spark 2.4.0内置的Hadoop 2.7.x版本不兼容,后续运行大概率会出现HDFS相关报错,建议直接删除该依赖,Spark自带的Hadoop依赖足够支撑常规运行;如果必须指定Hadoop版本,需要和Spark编译使用的Hadoop版本保持一致。 - 如果不想打几百兆的胖包,可以通过
maven-dependency-plugin把依赖拷贝到单独目录,在Dockerfile中把依赖目录复制进镜像,启动时通过-cp参数指定类路径,但打胖包是配置最少、出错概率最低的方式,适合简单Spark脚本场景。
内容的提问来源于stack exchange,提问作者Pedro Alves

