yuAn8343头像
关注

Jenkins + Docker 构建 Spring Boot 项目踩坑全记录

从"连分支都拉不到"到 BUILD SUCCESS,一口气踩了 6 个大坑。记录环境、报错、原因与解决方法,供后来者参考。

环境一览

项目说明
宿主机VMware 虚拟机,Ubuntu(kernel 7.0.0-31)
JenkinsDocker 容器(docker-compose 启动),镜像 jenkins/jenkins
数据卷bind mount:~/jenkins/data → 容器内 /var/jenkins_home
代码仓库自建 GitLab:http://192.168.152.132:8929/root/my-project.git,默认分支 main
项目Spring Boot 2.7.18 + Maven,java.version=1.8
容器内 JavaTemurin 21.0.12.1(/opt/java/openjdk,Jenkins 镜像自带)
容器内 Maven3.9.6(/var/jenkins_home/maven,官方二进制包)

坑 1:Jenkins 找不到要构建的分支

报错:

ERROR: Couldn't find any revision to build. Verify the repository and branch configuration for this job.
Finished: FAILURE

原因: GitLab 新版本仓库默认分支是 main,而 Jenkins Job 的 Branch Specifier 默认写的是 master。日志中 git rev-parse refs/remotes/origin/master^{commit} 失败,说明远程根本没有 master 分支。

排查命令:

git ls-remote http://192.168.152.132:8929/root/my-project.git
# 输出只有 refs/heads/main,没有 master

解决: Job → Configure → Source Code Management → Git → Branches to build,把 master 改成 */main,保存重新构建即可。

经验: 遇到 “Couldn’t find any revision to build”,第一反应是分支名不匹配,用 git ls-remote 看远程真实存在的分支。


坑 2:Maven 本体是坏的——手工拷贝的"残缺品"

报错:

[my-project] $ /var/jenkins_home/maven/bin/mvn clean package -DskipTests
Error: Could not find or load main class org.codehaus.plexus.classworlds.launcher.Launcher
Build step 'Invoke top-level Maven targets' marked build as failure

原因: 容器里的 Maven 是从宿主机 apt 安装的 /usr/share/maven 整个目录(连同软链接)拷进数据卷的。apt 版 Maven 的启动 jar 是个软链接:

$ ls -l /var/jenkins_home/maven/boot/
lrwxrwxrwx ... plexus-classworlds-2.x.jar -> ../../java/plexus-classworlds.jar

链接目标 /usr/share/java/plexus-classworlds.jar 在容器里不存在——悬空软链接mvn 脚本自然找不到启动类。连目录结构都不对劲:多了 man 目录、少了 LICENSE/NOTICE/README.txt

排查命令:

# 找出整个 Maven 目录下所有断掉的软链接
find /var/jenkins_home/maven -xtype l

解决: 删掉手拼的 Maven,装官方二进制包(装进数据卷,容器重建不丢):

cd ~/jenkins/data
sudo rm -rf maven
wget https://archive.apache.org/dist/maven/maven-3/3.9.6/binaries/apache-maven-3.9.6-bin.tar.gz
tar -zxvf apache-maven-3.9.6-bin.tar.gz
mv apache-maven-3.9.6 maven
sudo chown -R 1000:1000 maven

装完验收四件套(别凭感觉,逐条过):

M=/var/jenkins_home/maven
$M/bin/mvn -v                                   # ① 能打印版本号 = 能点火
ls -l $M/boot/                                  # ② plexus-classworlds-x.y.z.jar 是实体文件
find $M -type l | wc -l                         # ③ 官方包软链接数为 0
unzip -l $M/boot/plexus-classworlds-*.jar | grep Launcher   # ④ jar 里真有 Launcher 类

经验: 错误 Could not find or load main class ...Launcher 99% 是 Maven 安装目录有问题(路径配错一层、或文件残缺/软链接断裂)。装任何 Maven 后第一件事就是 mvn -v,这一步省了,后面全是坑。


坑 3:settings.xml 的三连事故

3.1 两个过期的 mirror

从老教程抄来的配置:两个 <mirror>id 重复(都叫 alimaven)、mirrorOf 相同、URL 全是废弃老地址:

<url>http://maven.aliyun.com/nexus/content/groups/public/</url>      <!-- 已废弃 -->
<url>http://maven.aliyun.com/nexus/content/repositories/central/</url> <!-- 已废弃 -->

阿里云早就迁到 https://maven.aliyun.com/repository/... 新路径。mirror id 重复是非法配置,mirrorOf 相同则第二个永远不生效。

3.2 文件开头混入乱码

报错:

[FATAL] Non-parseable settings /var/jenkins_home/maven/conf/settings.xml:
only whitespace content allowed before start tag and not a (position: START_DOCUMENT seen a... @1:2)

cat -A 查看文件头:

ae.ae/ad/aO5<?xml version="1.0" encoding="UTF-8"?>$

文件开头被误敲/粘贴进了 12 个垃圾字符。XML 规定 <?xml ?> 声明必须是文件第一个字符。

3.3 标签内混入乱码(仅警告,但很碍事)

[WARNING] expected START_TAG or END_TAG not TEXT (position: TEXT seen ...</mirror>\n     -->\n    <mirror>aG2\n      <i... @159:9)

旧文件内容没删干净,残留的注释下面又粘了一个新的 <mirror> 块,里面混进了 aG2 这种杂字符。

解决(三种毛病的统一解药):整份替换,别修修补补

cat > ~/jenkins/data/maven/conf/settings.xml <<'EOF'
<?xml version="1.0" encoding="UTF-8"?>
<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"
          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0 https://maven.apache.org/xsd/settings-1.0.0.xsd">
  <localRepository>/var/jenkins_home/.m2/repository</localRepository>
  <mirrors>
    <mirror>
      <id>aliyunmaven</id>
      <mirrorOf>*</mirrorOf>
      <name>阿里云公共仓库</name>
      <url>https://maven.aliyun.com/repository/public</url>
    </mirror>
  </mirrors>
</settings>
EOF

保存后先验证再构建:

head -2 ~/jenkins/data/maven/conf/settings.xml        # 第一行必须干干净净是 <?xml ...?>
xmllint --noout ~/jenkins/data/maven/conf/settings.xml && echo "XML OK"
docker exec jenkins head -2 /var/jenkins_home/maven/conf/settings.xml   # 容器侧确认是同一份

经验: 手动编辑 XML 容易粘进隐藏字符,改完必验证;镜像配置只留一个、用新地址、mirrorOf*(阿里云官方推荐写法,接管全部仓库请求)。


坑 4:连不上 Maven Central——TLS 握手失败

报错:

[FATAL] Non-resolvable parent POM ... The following artifacts could not be resolved:
org.springframework.boot:spring-boot-starter-parent:pom:2.7.18 (absent):
Could not transfer artifact ... from/to central (https://repo.maven.apache.org/maven2):
Received fatal alert: handshake_failure

原因: 国内网络访问 repo.maven.apache.org 被干扰,TLS 握手直接被掐断。Maven 本身和 JDK 都没问题。

排查对比:

docker exec jenkins curl -sI -m 10 https://repo.maven.apache.org/maven2/ | head -3     # 失败/超时
docker exec jenkins curl -sI -m 10 https://maven.aliyun.com/repository/public/ | head -3  # 200

解决: 配置阿里云镜像(见坑 3 的 settings.xml)。判据:构建日志里出现

Downloading from aliyunmaven: https://maven.aliyun.com/repository/public/...

而不是 Downloading from central: https://repo.maven.apache.org/...,说明镜像生效。

经验: 国内环境玩 Maven,第一时间配镜像,别跟 central 死磕。


坑 5(本篇最重):No trusted certificate found——Java 信任库之谜

修完镜像后,错误变成了:

Could not transfer artifact ... from/to aliyunmaven (https://maven.aliyun.com/repository/public):
No trusted certificate found

curl 访问同一个地址完全正常,Java 却死活不信任证书。围绕这个错误展开了一场完整侦查,过程比结果更值得记录。

5.1 第一嫌疑人"JDK 太老"被排除

No trusted certificate found 最常见原因是 JDK 太旧、信任库没有新根证书。但容器里 mvn -v 显示的是 Temurin 21.0.12.1——很新。注意:这是 docker exec 新开 shell 测出来的,是容器默认 Java,未必是构建用的 Java(伏笔)。

5.2 锁定构建真正用的 JDK

Jenkins 全局工具配置(Global Tool Configuration)里发现定义了一个 JDK:

Name: jdk8    JAVA_HOME: /var/jenkins_home/jdk

由于全局只定义了它一个,Freestyle 任务默认就选中了它。容器里没有 Job 级的 JDK 下拉框(它只在"靠上的独立 JDK 区域",且全局有定义才显示),所以一开始完全没意识到构建用的不是容器默认 JDK。

验证手段(推荐):把 Job 的 Goals 临时改成 -version,构建一次,控制台会直接打印构建环境的 Java versionJava home。看完记得改回来。

5.3 真凶:悬空软链接的信任库(和坑 2 同款病因)

/var/jenkins_home/jdk 也是照教程从 Ubuntu 宿主机拷来的 JDK 8(其实是新的 8u502,并不老)。Ubuntu 系 JDK 的信任库不是实体文件,是指向 /etc/ssl/certs/java/cacerts 的软链接(由 ca-certificates-java 包生成):

$ ls -l /var/jenkins_home/jdk/jre/lib/security/cacerts
lrwxrwxrwx ... cacerts -> /etc/ssl/certs/java/cacerts
$ docker exec jenkins ls /etc/ssl/certs/java/
ls: cannot access '/etc/ssl/certs/java/': No such file or directory

链接目标在 Jenkins 容器(Debian 系)里不存在——Java 手里一份可信证书都没有,对任何 HTTPS 站点都会报 No trusted certificate found

为什么 curl 正常? 因为两者用的是完全不同的信任体系:curl 读 /etc/ssl/certs 下的 PEM 证书包(系统维护、完好);Java 只认自己 cacerts 文件。curl 通、Java 挂,一点都不矛盾。

5.4 失败的修复尝试 A:apt 补装 ca-certificates-java

docker exec -u root jenkins apt-get install -y ca-certificates-java

包装上了,但安装脚本打印:

No JRE found. Skipping Java certificates setup.

Debian 这个包只给"系统注册的 JRE"(alternatives 体系)生成信任库,而容器的 Java 是放在 /opt/java/openjdk 的 Temurin,不在 Debian 的体系里,于是直接跳过——目标文件依然不存在。

5.5 失败的修复尝试 B:拷贝宿主机信任库

宿主机是 Ubuntu,/etc/ssl/certs/java/cacerts 存在(137KB JKS、121 个证书条目),把它拷进数据卷替换掉悬空软链接:

cd ~/jenkins/data/jdk/jre/lib/security
rm cacerts
sudo cp /etc/ssl/certs/java/cacerts .
sudo chown 1000:1000 cacerts && chmod 644 cacerts

keytool -list 确认 121 个条目都在,但构建依旧 No trusted certificate found——这份库里缺阿里云证书链所需的 GlobalSign 新根(阿里云服务器证书由 2025 年新 intermediate GlobalSign GCC R46 OV TLS CA 2025 签发)。

5.6 决定性实验:两个 JDK 分别直连验证

用一段脚本让指定 JDK 亲自发起 HTTPS 请求,秒级定案:

# JDK 8 —— 完美复现构建报错(No trusted certificate found,连栈都一样)
docker exec jenkins /var/jenkins_home/jdk/bin/jrunscript -e 'var c=new java.net.URL("https://maven.aliyun.com/...").openConnection(); print(c.getResponseCode())'

# Temurin 21(JDK 15+ 没有 jrunscript,改用 jshell;注意 getResponseCode 在 HttpURLConnection 上,要强转)
docker exec -i jenkins /opt/java/openjdk/bin/jshell <<'EOF'
var c = (java.net.HttpURLConnection) new java.net.URL("https://maven.aliyun.com/...").openConnection();
System.out.println(c.getResponseCode());
/exit
EOF
# 输出 200 ✅

5.7 最终解决:构建改用容器自带的 Temurin 21

Manage Jenkins → Global Tool Configuration → JDK → 把 JAVA_HOME 改为:

/opt/java/openjdk

然后清缓存重建:

rm -rf ~/jenkins/data/.m2/repository     # 失败记录会被 Maven 缓存,不清重跑还是报错

构建 Goals 加 -U 强制刷新:

clean package -DskipTests -U

日志一路 Downloading from aliyunmaven: ...,最终 BUILD SUCCESS

备选方案(如果必须用 JDK 8):把 Temurin 21 的完整信任库转成 JKS 塞进 JDK 8(JDK 8 默认不认 PKCS12 格式的信任库,需要 -deststoretype JKS 转换):

docker exec -u root jenkins bash -c '
rm -f /var/jenkins_home/jdk/jre/lib/security/cacerts
/opt/java/openjdk/bin/keytool -importkeystore \
  -srckeystore /opt/java/openjdk/lib/security/cacerts \
  -srcstoretype PKCS12 -srcstorepass changeit \
  -destkeystore /var/jenkins_home/jdk/jre/lib/security/cacerts \
  -deststoretype JKS -deststorepass changeit -noprompt'

5.8 经验总结

  1. curl 通 ≠ Java 通:两套信任体系,排查 TLS 问题时必须分别验证。
  2. docker exec 的环境 ≠ 构建环境:Jenkins 全局工具配置、docker-compose 的 environment 都会改变构建时用的 JDK/Maven,而新开的 exec shell 完全感知不到。定案前必须用 -version goal 之类手段确认构建实际环境。
  3. 从宿主机拷软件目录进容器要谨慎:apt 版软件大量依赖软链接指回系统目录(/usr/share/java/etc/ssl/certs/java),拷贝=埋雷。坑 2 的 Maven 和坑 5 的 JDK 是同一种死法。
  4. 官方二进制包 + 数据卷是 Docker 里装 Maven/JDK 的正确姿势,装完各跑一次版本命令验收。

坑 6(彩蛋):那些不起眼的小坑

-DskipTest 不跳过测试: 正确参数是 -DskipTests(带 s)。写错了不报错、不生效,测试悄悄照跑。更彻底的跳过是 -Dmaven.test.skip=true(连测试代码都不编译)。

Maven 缓存失败记录: 依赖下载失败后,Maven 会把"absent"状态写进本地仓库缓存,不处理的话重跑构建直接失败、连重试都没有。解法:构建加 -U,或删掉本地仓库:

rm -rf ~/.m2/repository        # 容器场景:rm -rf ~/jenkins/data/.m2/repository

apt 装进容器的改动会丢: ca-certificates-java 这类 apt 安装在容器层,容器一重建就没了。要持久的软件就放数据卷,或干脆写进镜像 Dockerfile。


排查方法论复盘

这场排查能走到底,靠的是"一次只验证一个假设":

假设验证手段结论
分支不存在git ls-remote✅ 实锤,秒修
Maven 安装损坏ls -l boot/-xtype lmvn -v✅ 悬空软链接
settings.xml 不合法报错行号 + cat -A 看隐藏字符✅ 三种毛病
网络到 central 不通容器内 curl 对比官方仓/阿里云✅ 配镜像
JDK 太旧mvn -v❌ 排除(但注意测的是 exec 环境)
构建 JDK 被偷换Global Tool Configuration + -version goal✅ 实锤
Java 信任库损坏jrunscript/jshell 让指定 JDK 直连✅ 悬空软链接 + 缺根证书

一句话精髓: 报错信息要读完整(里面藏着"from/to 谁"、“在哪个文件第几行”),每个环节都有对应的"最小验证命令",别猜,测。

转载自 CSDN-专业IT技术社区

原文链接:https://blog.csdn.net/cui_hao_nan/article/details/164750702

文章来源转载

评论

赞0

评论列表

微信小程序
QQ小程序

关于作者

点赞数:0
关注数:0
粉丝:0
文章:0
关注标签:0
加入于:--