
你执行mvn clean package -DskipTests编译 Hadoop 源码前面几百个模块都顺利过了结果在hadoop-yarn-applications-catalog-webapp这里弹出一条触目惊心的错误[ERROR] Failed to run task: yarn install failed. org.apache.commons.exec.ExecuteException: Process exited with an error: 1是不是有点懵一个 Java 项目编译为什么会去执行yarn install这个报错看起来像是前端构建工具抛出来的怎么和 Hadoop 扯上关系了网上一搜一堆人遇到同样的问题但答案七零八落有的说换 Node 版本有的说改 registry有的说直接跳过到底该信谁我前前后后折腾过几轮踩了不少坑今天就把这个报错的完整链路、排查思路和最终解决方案一次性说清楚。这篇文章适合所有在编译 Hadoop 源码时遇到类似前端模块构建失败的人包括用 Maven 构建其他同时包含前端和后端模块的 Java 项目的人。1. 编译到一半卡住这个报错究竟发生在前端还是后端1.1 catalog webapp 模块是什么为什么出现在 Hadoop 源码里hadoop-yarn-applications-catalog-webapp是较新版本 Hadoop 源码里的一个子模块全路径一般在hadoop-yarn-applications/hadoop-yarn-applications-catalog/hadoop-yarn-applications-catalog-webapp。它的作用是给 YARN 的 Catalog Service资源目录服务提供一个 Web 管理界面用来登记、检索和管理数据集、表、函数这类元数据信息。说白了这是一个典型的“Java 后端 前端管理界面”的模块。后端负责 API 和元数据存取前端负责用户在浏览器里的操作页面。Apache 项目里这种结构不少见但问题就出在这里前端代码不是 Java不能直接交给 Maven 编译Maven 需要借助一个插件来“客串”前端构建工具。1.2 Maven 生命周期里为什么会跑 yarn install这里的关键角色是frontend-maven-plugincom.github.eirslett。它的作用是在 Maven 构建过程中自动下载 Node.js、Yarn/npm然后执行前端依赖安装和打包最终把生成的静态资源丢进 JAR 包。Hadoop 的 webapp 模块就是通过它把前端构建流程嵌入到了 Maven 生命周期里。看一段典型的配置就能明白plugin groupIdcom.github.eirslett/groupId artifactIdfrontend-maven-plugin/artifactId version1.12.0/version configuration nodeVersionv16.20.0/nodeVersion yarnVersionv1.22.19/yarnVersion /configuration executions execution idyarn install/id goals goalyarn/goal /goals phasegenerate-resources/phase configuration argumentsinstall/arguments /configuration /execution /executions /plugin这段配置绑定在generate-resources阶段而generate-resources是每个 Maven 模块编译早期就会执行的阶段。所以你会看到整个构建已经跑了好一会儿了到 webapp 模块时突然开始下载 Node、执行 yarn install任何一个环节失败整个 reactor 构建就中断了。注意一个关键点frontend-maven-plugin 会下载一个它自己指定的 Node.js 到项目目录里而不是使用你系统里node -v能看到的那个 Node。这就引出了很多人的第一个疑惑——“我机器上明明装了 Node 18怎么还报 Node 相关错误”因为插件用的是 pom 里指定的 v16.20.0这跟你系统里装的是什么完全没有关系。2. 先别急着改配置把报错信息拆开看2.1 “Failed to run task”只是外层包装大多数人看到Failed to run task: yarn install failed这行就慌了然后开始到处搜方案。但这行信息其实只是一个“包装”它只说明 frontend-maven-plugin 在底层执行 yarn 命令时子进程以非零状态退出了。org.apache.commons.exec.ExecuteException同样是 apache commons-exec 在执行外部进程时的标准异常包装。打个比方这就像你在终端里执行一个脚本脚本内部出错了但终端只告诉你“退出码是 1”至于脚本里哪一步挂的得看脚本自己打印的日志。yarn install 失败后真正有用的信息在它自己打印的日志里也就是上面的npm ERR!、yarn error这些行。2.2 从日志里找出真正的错误码要定位根因必须往前翻日志看 yarn 自己输出了什么。常见的输出形式有这几种npm ERR! code ETIMEDOUTnpm ERR! code EINTEGRITYyarn error There are no scenarios; must have at least onenpm ERR! unable to resolve dependency treeERR_PNPM_OUTDATED_LOCKFILE如果你不小心用了 pnpm 去解析 yarn.lock每种错误码对应完全不同的处理方式所以第一步要做的是定位真正的错误。如果日志刷得太快或者你执行的是整个 Hadoop 项目的全量编译建议单独编译 webapp 这个模块缩小排查范围mvn -pl hadoop-yarn-applications/hadoop-yarn-applications-catalog/hadoop-yarn-applications-catalog-webapp -am package -DskipTests -Dmaven.javadoc.skiptrue-pl指定要构建的模块路径-am表示同时构建它依赖的模块。这样就不用在几千行日志里捞错误信息了。2.3 同一个报错三种不同根因的识别方法我见过很多人在同一个报错上反复尝试不同的方案先清空 node_modules再换 npm 镜像又装个新版本 Node结果问题依旧最后才发现根因是网络根本访问不了默认源。识别根因其实可以用一个简单的对照表日志特征根因方向处理策略npm ERR! code ETIMEDOUT、ECONNREFUSED、请求 registry 超时网络问题访问 npm 默认源失败换 registry、配镜像、设置超时npm ERR! Unable to resolve dependency tree、peer 依赖冲突依赖版本冲突检查 package.json 与锁文件一致性yarn error There are no scenarios; must have at least oneyarn 版本与 yarn.lock 格式不匹配用项目内 yarn 版本重新 installError: certificate has expiredNode 版本太老内置 CA 证书过期升级 Node / 更新插件中的 nodeVersion磁盘明明有空间但报No space left on device分区 inode 耗尽清 inode或把构建目录换到空间充足的分区核心是先判断这是网络失败、版本不匹配、还是磁盘问题再动手。直接乱试方案经常会把问题越搞越复杂甚至把原本好的依赖目录弄坏。3. 逐项排查网络、Node 工具链、yarn.lock 谁在捣乱3.1 网络问题与 registry 超时这是国内用户最常见的坑。Hadoop 的 webapp 模块默认会去https://registry.npmjs.org拉取前端依赖这个源在不同网络环境下访问速度差异巨大一旦超时yarn install 就会整体失败然后被 Maven 包装成这行报错。判断其实很直观如果你在命令行里单独执行 yarn install同样卡住或者报网络错误那基本就是网络问题。验证方法很简单切到 webapp 项目目录看node_modules是否已经被创建、里面是否只有少量包或者直接看日志里有没有ETIMEDOUT。解决方案也直接给 yarn 换 registry。可以手动执行yarn config set registry https://registry.npmmirror.com也可以不落地到全局配置只针对这次安装生效yarn install --registryhttps://registry.npmmirror.com但如果问题出在 Maven 执行阶段光在终端手动设置还不够。因为 frontend-maven-plugin 执行 yarn install 时会按照 pom 里配置的 arguments 来跑你在命令行里的手工配置不一定能生效。这时可以通过环境变量把这个 registry 传下去export NPM_CONFIG_REGISTRYhttps://registry.npmmirror.com mvn clean package -DskipTestsNode 生态的工具链都会读取NPM_CONFIG_*开头的环境变量这个方式比手动改.npmrc更隐蔽也更适合在 CI 里统一配置。3.2 frontend-maven-plugin 自带的 Node 版本和系统 Node 不一致有人电脑上装了 Node 20代码里也用得好好的但 Hadoop 模块在构建时下载了 v16.20.0 的 Node这时候如果前端依赖里有某个包需要更高版本 Node 才能安装或运行就会出现你本地手工执行 yarn install 明明能过、Maven 一编译就挂的诡异现象。遇到这种情况先看 frontend-maven-plugin 下载的 Node 到底是多少版本。项目构建后Node 会被解压在模块目录下的node文件夹里可以直接查看cd hadoop-yarn-applications/hadoop-yarn-applications-catalog/hadoop-yarn-applications-catalog-webapp ./node/node -v ./node/yarn/dist/bin/yarn -v这就是“项目私有 Node”的位置。先确认它和你系统里用的是否一致再做版本决策。如果确实需要换 Node 版本可以修改 pom 里 frontend-maven-plugin 配置的nodeVersion参数。但要注意不要为了适配最新特性就把 Node 版本拉得太高。我试过一次把 nodeVersion 改成 v18结果 webpack 构建报 OpenSSL 相关的错误反而更麻烦。版本选择以项目原本配套的为准除非你明确知道某个依赖需要更高版本。3.3 yarn.lock 版本带来的兼容性陷阱这个坑比较隐蔽。Hadoop 源码里是带了yarn.lock的而这个锁文件是 Yarn 1.x 格式。如果你在本地用系统全局的 Yarn 3.x 或 4.xBerry手动跑过一次 install锁文件可能被重写成新版格式等你再用 Maven 时frontend-maven-plugin 用的是 pom 里指定的 Yarn 1.22.x读不懂新格式的锁文件就会报很奇怪的错。yarn.lock第一行会写__metadata:这种字段这是 Berry 的标志Yarn 1.x 的锁文件没有这个字段。看到__metadata基本可以确定锁文件被动过了。碰到这种情况先看 webapp 项目目录下是否有被改过的 yarn.lock如果有恢复成源码仓库里的原始版本或者直接删除后让 yarn 重新生成注意用对的 yarn 版本。我的建议是在涉及 Hadoop 这种项目建设时优先用项目内node/yarn/dist/bin/yarn而不是系统全局的 yarn从源头上避免锁文件格式错乱。3.4 排查顺序建议我踩了几次坑后总结出一套排查顺序推荐你照这个顺序来先看报错行里有没有ETIMEDOUT、ECONNREFUSED有则直接定位到网络问题。看是不是磁盘或 inode 满了执行df -h和df -i确认。在 webapp 目录下用项目内自带的 yarn 手工执行 install看能不能复现问题。检查 yarn.lock 是否被其他版本工具改写过。确认 pom 里指定的 nodeVersion、yarnVersion 与项目历史一致不要轻易改动。这套顺序能避免绝大多数“瞎试”的时间浪费。4. 落地的三套方案跳过、复用、换源4.1 方案A跳过前端构建只编后端模块如果你的目标只是编译 Hadoop 核心源码、做二次开发或者压根用不到 catalog 模块的 Web UI那么最省事的方案就是跳过前端构建。frontend-maven-plugin 支持通过系统属性跳过 Node 下载和 yarn 执行取决于 Hadoop 这个模块是否开放了对应参数。我测试下来比较有效的是在 Maven 命令行加这两个参数mvn clean package -DskipTests -Dmaven.javadoc.skiptrue -Dskip.installnodenpmtrue -Dskip.yarntrue-Dskip.installnodenpmtrue会跳过 frontend-maven-plugin 安装 Node 和 Yarn 的过程-Dskip.yarntrue会跳过 yarn 相关的 goal 执行。但要注意不是所有版本的插件都支持这两个参数Hadoop 具体版本的 pom 里未必定义了这两个属性的透传。如果加了参数发现还是照常执行 yarn install说明 pom 没有把系统属性绑定到插件配置上这时候就得直接修改 pom找到 webapp 模块的 pom.xml 中 frontend-maven-plugin 的配置把 yarn 相关的 execution 注释掉例如executions !-- execution idyarn install/id goals goalyarn/goal /goals phasegenerate-resources/phase configuration argumentsinstall/arguments /configuration /execution -- /executions跳过后后端 Java 代码能正常编译但最终 JAR 包里可能缺少前端静态资源。如果你只是编译、做后端逻辑开发问题不大但如果你要交付完整发行包、启动后要用到 Web UI就得老老实实把前端依赖装好。4.2 方案B手动构建前端依赖让 Maven 直接复用有的场景不能跳过前端构建但又不想在 Maven 里一次次地卡住这时候可以先手动把依赖装好让 Maven 的 yarn install 阶段变成一个“空操作”。操作方式cd hadoop-yarn-applications/hadoop-yarn-applications-catalog/hadoop-yarn-applications-catalog-webapp ./node/yarn/dist/bin/yarn install --registryhttps://registry.npmmirror.com --network-timeout 600000注意两点一定用项目内node/yarn/dist/bin/yarn不要用系统全局的 yarn原因前面讲过。加上--network-timeout 600000把网络超时拉到十分钟避免大依赖包下载到一半断开。手动执行完成后node_modules已经存在且完整。再次执行 Maven 构建时yarn install 会检测到依赖已就绪直接跳过实际安装过程构建就能顺利通过。如果 Maven 执行时还想进一步减少网络交互可以在 pom 的 yarn 参数里加--prefer-offline让 yarn 优先使用本地缓存。这是我实际使用中比较稳定的一个组合configuration argumentsinstall --prefer-offline --network-timeout 600000/arguments /configuration4.3 方案C给 frontend-maven-plugin 换下载源和 registry方案 A 是绕路方案 B 是手动搞定但如果想根治问题就得让插件本身的下载行为也走顺畅的源。frontend-maven-plugin 支持通过-Dfrontend.nodeDownloadRoot和-Dfrontend.yarnDownloadRoot覆盖 Node 和 Yarn 的下载地址同时配合 npm registry 环境变量可以在 Maven 层面一次性解决export NPM_CONFIG_REGISTRYhttps://registry.npmmirror.com mvn clean package -DskipTests -Dmaven.javadoc.skiptrue \ -Dfrontend.nodeDownloadRoothttps://npmmirror.com/mirrors/node/ \ -Dfrontend.yarnDownloadRoothttps://npmmirror.com/mirrors/yarn/注意npmmirror.com这里对应的 Node 镜像路径和 Yarn 镜像路径前者是 Node.js 二进制包的镜像后者是 Yarn 安装包的镜像。这样配置后插件下载 Node 和 Yarn 时走的是国内镜像速度会有明显提升随后 yarn install 时也会因为NPM_CONFIG_REGISTRY被设置而走镜像仓库。这套方案适合需要反复编译、经常清空构建目录、每次都要重新下载工具链的情况。而且这些参数都可以继续传给 CI 环境不用改源码。4.4 我最终用的组合我的场景是需要完整跑通 Hadoop 编译并且需要 webapp 模块的静态资源能正常打进包里所以不能Skip。最终采用的组合是先手动执行一遍项目内 yarn install配 registry 和超时让node_modules完整落地然后再用 Maven 全量构建并给 Maven 传了NPM_CONFIG_REGISTRY环境变量作为兜底同时给 frontend 插件配了 Node/Yarn 的国内下载源。这样一套下来构建稳定通过后续再编译就不会在那个模块上反复翻车了。如果你的场景是快速验证核心模块老老实实用方案 A 跳过就行没必要在 web UI 资源上死磕。5. 顺着这次构建摸到的其他坑5.1 磁盘空间和 inode 不足是个隐形炸弹很多人盯着 Node、yarn 排查了半天最后发现No space left on device。但更迷惑的是df -h明明显示还有空间yarn 却报磁盘满了。这时候要看 inodedf -h df -iyarn install 会解压大量小文件到node_modules每个文件都要消耗一个 inode。如果某个分区的 inode 已经满了即使可用空间还有几十 GB写入照样失败进程返回非零退出码被 Maven 包装成Failed to run task。我遇到过/tmp目录 inode 满导致 Maven 构建临时文件写不进去的情况清理完/tmp下旧的构建临时文件后问题立刻消失。如果系统分区比较紧张建议把 Maven 本地仓库和构建临时目录放到空间充足的分区mvn clean package -Dmaven.repo.local/data/m2 -Djava.io.tmpdir/data/tmp5.2 JDK 版本、Maven 版本与 protoc 的要求webapp 模块编译报错只是整个 Hadoop 构建中的一站。Hadoop 是个庞然大物不同分支对构建工具链要求非常严格。很多人一上来就遇到编译问题其实根本不是 webapp 相关而是环境版本不对。根目录下的BUILDING.txt把这一切都写清楚了。常见的要求包括JDK 版本不同分支要求不同有的要求 JDK 8有的要求 JDK 11 或 17JDK 版本和 Maven 插件不匹配时会出现各种奇怪的编译失败。Maven 版本过旧的 Maven 可能无法解析某些插件参数建议直接用项目推荐的稳定版本。protocProtocol Buffers 编译器版本Hadoop 源码编译依赖特定版本的 protoc版本不匹配会在编译 protobuf 相关的模块时报错。我建议在编译前先看BUILDING.txt并且按照官方推荐安装好对应版本的工具链。我见过不少人卡在 webapp 报错上但真实问题是本机 JDK 版本过高前面的模块侥幸编译通过到 webapp 这种涉及更多插件协同的模块时底层问题才暴露出来。环境本身一致了很多前端构建的报错也会跟着消失。5.3 依赖仓库慢的全局加速方式前端依赖可以通过 npmmirror 解决Maven 依赖同样有加速办法。在~/.m2/settings.xml里配置一个镜像源可以明显提升整个构建速度。注意只是加速 Maven 中央仓库下载不涉及任何安全问题mirrors mirror idaliyun/id mirrorOfcentral/mirrorOf namealiyun maven mirror/name urlhttps://maven.aliyun.com/repository/public/url /mirror /mirrors这条配置能让所有通过 Maven 中央仓库下载的依赖都走国内镜像。Hadoop 全量编译需要拉取上百个依赖这个优化能省下不少时间。6. 沉淀下来的排错习惯这次排错给我最大的收获不是某个具体的 registry 地址而是一套应对构建工具报错的方法论。遇到Failed to run task这类外层报错第一反应永远是找“子进程自己打印的错误信息”。frontend-maven-plugin 只是执行了 yarn install它不可能知道 yarn 内部因为网络、版本还是磁盘而失败。所以我会先翻日志锁定是npm ERR! code里的哪一类再决定下一步动作。这个思路不仅适用于 Hadoop也适用于任何 Maven 集成前端构建的项目甚至适用于所有通过 CI 执行外部命令的构建链。另外遇到这类问题别急着改版本、重装环境。先想清楚一个问题这个插件为什么要在这里执行这个命令它的下载源是什么它使用的是哪个版本的 Node如果这三个问题能答上来90% 的根因已经浮出水面了。我每次排这类问题真正花时间的地方不在于搜方案而在于把构建链路的每一环拆清楚。最后一个小建议如果你要长时间折腾 Hadoop 编译建议在BUILDING.txt之外额外记录一份自己环境的版本组合JDK 版本、Maven 版本、protoc 版本、Node 版本各是多少下次换机器或换分支直接照着自己的那份记录搭环境能省掉很多重复踩坑的时间。