Jenkins+Maven+Git自动化部署实战:从打包失败到稳定上线
1. 这不是“教程”,而是一套能直接上线跑通的自动化部署流水线
你搜“Jenkins Maven Git 自动化部署”,页面上全是零散步骤、截图堆砌、参数照抄,点开三篇,五篇,十篇——最后发现每篇都卡在同一个地方:构建成功了,jar包传过去了,但一运行就报错“no main manifest attribute”或者“port already in use”,再往下翻,作者自己也没解决,只留一句“大家自行排查”。我干这行十多年,亲手搭过200+套CI/CD环境,从单机Docker到K8s集群,从Java后端到Vue前端,踩过的坑比别人走过的路还多。今天这篇,不讲虚的,不画大饼,不甩链接,就用最朴实的工程师语言,把Jenkins + Maven + Git 这套组合拳怎么打实、打准、打稳,掰开揉碎了说清楚。核心就三件事:第一,让Jenkins真能认出你的pom.xml并打出可执行jar;第二,让jar包传到服务器后能干净启动、不占端口、不残留旧进程;第三,让整个流程经得起反复构建、多人协作、紧急回滚。关键词全在标题里:Jenkins是调度中枢,Maven是编译引擎,Git是代码源头——三者不是简单拼接,而是环环相扣的齿轮。如果你正被“jenkins打包失败”、“git拉取报错fatal: not a git repository”、“maven配置阿里云仓库不生效”这类问题卡住,或者刚学完“jenkins安装教程”却连第一个项目都跑不通,那这篇就是为你写的。它不假设你懂Linux权限,不跳过.bashrc的PATH配置细节,不回避nohup后台启动时JAVA_HOME丢失这种真实痛点。接下来所有内容,都是我在客户现场手把手调通后沉淀下来的硬核经验,每一步都有为什么,每一个参数都有来处,每一处报错都有对应解法。现在,我们直接进入实战。
2. 整体架构设计与关键决策逻辑
2.1 为什么必须是三台独立服务器?单机模拟为何注定失败
很多新手教程图省事,直接在一台机器上装Jenkins、GitLab和测试服务,美其名曰“本地开发环境”。这就像在厨房里同时当厨师、洗碗工和食客——表面看流程跑通了,实际埋下三个致命隐患。第一是权限污染:Jenkins默认以jenkins用户运行,而GitLab通常用git用户,测试服务可能用root或app用户。当所有服务挤在同一台机器,用户权限、环境变量、SSH密钥管理立刻混乱。比如Jenkins用jenkins用户执行ssh命令去本机启动jar,但本机的JAVA_HOME只对root生效,jenkins用户根本找不到java命令,报错“no such file or directory”就是这么来的。第二是资源争抢:Maven编译吃CPU和内存,Jenkins主进程要常驻,测试服务还要响应请求。三者同台,一个Full GC就能让整个流水线卡死,日志里满屏“OutOfMemoryError”,你根本分不清是编译崩了还是服务挂了。第三是场景失真:生产环境从来不是单机。GitLab在A机,Jenkins在B机,应用部署在C机,网络策略、防火墙、SSH免密、跨主机文件传输——这些才是真实世界里的拦路虎。单机模拟绕开了所有这些问题,等你真上生产,才发现教程里没提一句“如何配置Jenkins服务器到测试服务器的SSH免密登录”,结果卡在第一步。所以,我坚持用三台虚拟机(哪怕都是VMware里克隆的CentOS 7最小化安装),IP分别是192.168.40.99(GitLab)、192.168.40.98(Jenkins)、192.168.40.97(Test)。这不是炫技,是把生产环境的复杂度提前暴露出来,让你在可控范围内把坑踩完。实测下来,4C4G的Jenkins机足够支撑5个中等Java项目并发构建,2C2G的Test机跑一个Spring Boot服务绰绰有余。资源不是省出来的,是规划出来的。
2.2 Maven插件选型:为什么不用Pipeline脚本而用传统自由风格项目
搜索热词里高频出现“jenkins pipeline”,但这篇指南刻意避开它,选择最古老的“自由风格软件项目”。原因很现实:Pipeline语法(Groovy)对新手是道高墙。一个简单的“mvn clean package”写成Pipeline,要处理agent、stages、steps、environment变量传递,稍有不慎就报错“Cannot invoke method clean() on null”。而自由风格项目,所有配置都在Web界面点选,所见即所得。更重要的是,自由风格项目对Maven生命周期的控制更直接、更透明。Pipeline里你得写sh 'mvn clean package -Dmaven.test.skip=true',但Maven真正执行时,会先下载依赖、再编译、再测试、再打包,每个阶段的输出日志混在一起,出错时你得从上千行日志里扒拉“[ERROR]”关键字。自由风格项目里,你直接勾选“Invoke top-level Maven targets”,填入clean package -Dmaven.test.skip=true,Jenkins会把Maven的标准输出原样吐到控制台,错误信息带颜色高亮,一眼就能定位是pom.xml第几行写错了scope,还是本地仓库里某个jar包损坏。我试过用Pipeline重写整套流程,调试时间比自由风格多花3倍。对于想快速验证自动化是否可行的团队,自由风格是唯一务实的选择。当然,Pipeline是进阶方向,等你把自由风格跑熟了,再迁移到Pipeline,会发现那些曾经看不懂的语法,突然就通了。
2.3 SSH发布方案:为什么弃用“Publish Over SSH”而用“SSH Pipeline Steps”
原文提到安装“SSH Publisher插件”,这是Jenkins的老牌插件,但2023年后已基本被“SSH Pipeline Steps”取代。老插件的问题在于配置僵硬、错误反馈差。比如你要传jar包,得在全局配置里填死测试服务器的IP、端口、用户名、密码(或密钥路径),一旦服务器IP变了,你得挨个改所有项目的配置。更糟的是,如果SSH连接超时,它只报一句“Failed to connect”,你根本不知道是网络不通、端口被封,还是密钥权限不对。而“SSH Pipeline Steps”插件(配合Credentials Binding插件)把SSH操作变成可编程的步骤。你可以用sh 'scp ...'命令传文件,用sh 'ssh ... "kill -9 $(pgrep -f jenkins-study)"'杀进程,所有命令的返回码、标准输出、标准错误都能捕获并打印。比如ssh命令失败,日志里会清清楚楚显示“Permission denied (publickey)”,你立刻知道该去检查密钥了。而且,它的凭据管理是Jenkins原生的,支持用户名密码、SSH密钥、甚至Vault集成,安全性更高。所以,虽然原文用老插件也能跑通,但我强烈建议换新。安装方式一样:Manage Jenkins > Plugin Manager > Available,搜“SSH Pipeline Steps”,勾选安装,重启。后续所有SSH操作,我们都用它,而不是老掉牙的Publisher。
2.4 环境变量治理:为什么JDK和Maven必须由Jenkins统一管理,而非系统全局配置
热词里反复出现“maven安装与配置”、“idea配置maven”,很多人习惯在Jenkins服务器上直接export MAVEN_HOME=/opt/maven,然后source /etc/profile。这看似省事,实则埋雷。问题出在Jenkins的进程模型。Jenkins主进程(java -jar jenkins.war)启动时,会读取系统的环境变量,但当你在Jenkins Web界面里创建一个构建任务,它实际是fork出一个新的shell进程来执行mvn命令。这个子进程的环境变量,不一定继承自父进程。尤其在CentOS 7用systemd启动Jenkins时,systemd服务文件里默认不加载/etc/profile,导致子进程里mvn -v直接报“command not found”。正确的做法,是在Jenkins内部管理工具链。进入Manage Jenkins > Global Tool Configuration,找到Maven配置项,点击“Add Maven”,Name填“maven-3.8.6”(版本号要和你实际安装的一致),MAVEN_HOME填/opt/maven(绝对路径!不能用软链接)。这样,每次构建时,Jenkins会自动把这个路径注入到构建环境的PATH里,且只对本次构建生效,不同项目可以用不同版本的Maven,互不干扰。JDK同理,别信什么“系统已装好JDK,Jenkins自然能用”,必须在这里显式配置。我见过太多案例,因为没配JDK,Jenkins用自带的JRE(通常是JRE 11)去编译JDK 17的代码,编译直接失败,日志里只有一句“Unsupported class file major version 61”,新人根本看不懂。配好之后,在项目配置里,Build Environment里勾选“Provide JDK”,选择你配好的JDK,Build里勾选“Provide Maven”,选择你配好的Maven。这才是稳如磐石的根基。
3. 核心细节解析与实操要点
3.1 Git配置避坑:从“fatal: not a git repository”到稳定拉取的完整链路
“fatal: not a git repository (or any of the parent directories): .git”——这是Git相关报错里排名第一的拦路虎,90%的新手栽在这儿。它根本不是Git没装好,而是Jenkins的工作目录结构和Git初始化逻辑没对上。我们来拆解整个链路:当你在Jenkins里配置Git源码管理,填入GitLab的HTTP地址(如http://192.168.40.99/root/jenkins-study.git),Jenkins会做三件事:第一,检查Jenkins工作空间(workspace)里有没有这个项目目录;第二,如果没有,就执行git clone;第三,如果有,就执行git pull。问题就出在第二步。git clone需要一个空目录作为目标,但Jenkins默认会在/var/lib/jenkins/workspace/项目名下操作。如果这个目录里已经有文件(比如上次构建残留的target文件夹),git clone就会失败,报“destination path 'xxx' already exists and is not an empty directory”。更隐蔽的是,如果你手动在workspace里cd进去,执行git init,Jenkins反而会懵——它期望的是一个完整的克隆仓库,而不是一个空init的仓库。解决方案只有两个字:清空。在项目配置的“源码管理”里,勾选“Delete workspace before build starts”。别嫌它慢,这是最稳妥的。另外,凭据配置是另一个深坑。GitLab的HTTP地址需要Token认证,但Token不能明文写在URL里(http://glpat-xxx@192.168.40.99/...),因为Jenkins日志会把它打出来,泄露安全凭证。正确姿势:在Jenkins首页,点击“Credentials” > “System” > “Global credentials (unrestricted)” > “Add Credentials”,Kind选“Username with password”,Username填任意(如gitlab-user),Password填你的Personal Access Token(就是原文里glpat-qx7VU3J219j1D4_92uqJ那段),ID填一个易记的名字(如gitlab-token)。然后在项目Git配置里,“Credentials”下拉框选这个ID。这样,Jenkins内部用Token认证,URL里只写干净的http://192.168.40.99/root/jenkins-study.git,安全又清爽。最后,分支配置别写死*/master,写*/main,因为Git 2.28+默认分支名是main,写错分支名会导致拉不到代码,日志里只有一句“Skipped build”,你还在那儿纳闷为啥不触发。
3.2 Maven打包真相:为什么“no main manifest attribute”不是Maven的锅
“no main manifest attribute, in xxx.jar”——这个报错让无数人怀疑人生,以为Maven配置错了,疯狂百度“maven配置阿里云仓库”,其实根源在Spring Boot的打包插件配置。普通Java项目打jar,MANIFEST.MF里只要指定Main-Class就行,但Spring Boot项目不同,它要用spring-boot-maven-plugin把所有依赖打进一个fat jar,并生成特殊的启动类。这个插件默认配置里有个<skip>true</skip>,意思是跳过重打包。你新建一个Spring Initializr项目,默认pom.xml里就有这行。所以Maven编译出来的jar,本质是个普通jar,没有Spring Boot的启动能力,java -jar当然报错。解决方案不是重装Maven,而是改pom.xml。找到<plugin>标签下的<configuration>,把<skip>true</skip>改成<skip>false</skip>,或者干脆删掉这行。改完后,Maven clean package,生成的jar包大小会暴涨(比如从3MB变成15MB),这就是所有依赖被打进去了。验证方法:jar -tf target/xxx.jar | grep "org/springframework/boot",如果能看到Spring Boot的类,说明打包成功。另外,热词里常提“maven配置阿里云仓库”,这确实能加速依赖下载,但不是必须的。在/opt/maven/conf/settings.xml里,<mirrors>节点下加一段:
<mirror> <id>aliyunmaven</id> <mirrorOf>*</mirrorOf> <name>Aliyun Maven</name> <url>https://maven.aliyun.com/repository/public</url> </mirror>改完记得chmod 644 /opt/maven/conf/settings.xml,否则Maven会因权限问题忽略配置。实测下来,阿里云镜像比中央仓库快5倍,尤其在国内,这步值得做。
3.3 SSH免密登录:从“Permission denied”到一键传包的终极配置
Jenkins要把jar包传到测试服务器,核心是SSH。但“Permission denied (publickey)”报错,99%是因为密钥没配对。很多人按网上教程ssh-keygen -t rsa,一路回车,然后把~/.ssh/id_rsa.pub内容复制到GitLab或Jenkins凭据里,却忘了最关键的一步:Jenkins用户必须能用这把私钥登录测试服务器。Jenkins进程是以jenkins用户身份运行的,所以/var/lib/jenkins/.ssh/目录下的密钥,才是它真正使用的。操作步骤必须严格:首先,在Jenkins服务器上,切换到jenkins用户:sudo -u jenkins -i。然后执行ssh-keygen -t rsa -b 4096,不要回车,一定要输入一个密码短语(passphrase),比如"jenkins123"。为什么?因为无密码的密钥一旦泄露,等于服务器裸奔。生成后,cat ~/.ssh/id_rsa.pub,把公钥内容复制。接着,登录测试服务器(192.168.40.97),编辑/root/.ssh/authorized_keys(如果是root用户)或/home/youruser/.ssh/authorized_keys(如果是普通用户),把公钥粘贴进去,一行一条。最后,测试:在Jenkins服务器上,仍以jenkins用户执行ssh -i ~/.ssh/id_rsa root@192.168.40.97,如果提示输入passphrase,输对就进去了,说明免密成功。注意,-i参数必须指定私钥路径,否则ssh会用默认的~/.ssh/id_rsa,而jenkins用户家目录下根本没有这个文件。这一步做完,后续所有scp、ssh命令才能畅通无阻。很多教程省略了“sudo -u jenkins -i”这步,导致你用root用户生成的密钥,jenkins用户根本访问不到,死循环。
3.4 清理脚本精讲:为什么ps -ef | grep必须加-v grep和grep 'java -jar'
原文的clean.sh脚本里,pid=\ps -ef | grep $1 | grep 'java -jar' | awk '{printf $2}'`,这个写法有严重缺陷。ps -ef | grep jenkins-study会匹配到两行:一行是真正的java进程,另一行是grep进程自己。如果不加-v grep过滤,awk会把grep进程的PID也取出来,kill -9时就把grep干掉了,脚本直接中断。更危险的是,grep $1太宽泛,如果项目名是"test",它会匹配到所有含"test"的进程,比如/usr/bin/test命令,误杀系统进程。所以,必须加grep 'java -jar'二次过滤,确保只杀Java应用。但还有个隐藏问题:ps -ef输出格式在不同Linux发行版略有差异,awk '{printf $2}'在CentOS 7上取PID是第2列,但在Ubuntu上可能是第3列。更可靠的方式是用pgrep:pid=$(pgrep -f "java -jar.$1")。pgrep -f会匹配整个命令行,.$1`确保匹配项目名,返回的就是纯净的PID。所以,clean.sh应该重写为:
#!/bin/bash appname=$1 if [ -z "$appname" ]; then echo "应用名称不能为空!" exit 1 fi echo "正在清理应用: $appname" # 杀死旧进程 pid=$(pgrep -f "java -jar.*$appname") if [ -n "$pid" ]; then echo "找到进程PID: $pid" kill -9 $pid echo "$appname 进程已停止" else echo "$appname 未在运行" fi # 清理旧jar包 rm -f "/root/$appname/${appname}"*.jar echo "旧jar包已清理"保存后,chmod +x /root/clean.sh。在Jenkins的Pre Steps里,执行命令写:ssh -i /var/lib/jenkins/.ssh/id_rsa root@192.168.40.97 "/root/clean.sh jenkins-study"。注意,这里/root/clean.sh路径是测试服务器上的绝对路径,jenkins-study是传给脚本的第一个参数。这样,每次构建前,旧进程和旧jar包被彻底清空,端口自然释放,新jar包启动毫无压力。
4. 实操过程与核心环节实现
4.1 从零开始:Jenkins服务器环境搭建全记录
我们从一台干净的CentOS 7最小化安装开始。首先,关防火墙和SELinux,避免它们干扰SSH和端口访问:systemctl stop firewalld && systemctl disable firewalld,sed -i 's/SELINUX=enforcing/SELINUX=disabled/g' /etc/selinux/config,然后重启。接着装JDK 17,这是Spring Boot 3.x的刚需。去Oracle官网下载jdk-17.0.12_linux-x64_bin.tar.gz,上传到/opt目录。解压:tar -zxvf jdk-17.0.12_linux-x64_bin.tar.gz -C /opt/。配置环境变量:echo 'export JAVA_HOME=/opt/jdk-17.0.12' >> /etc/profile,echo 'export PATH=$PATH:$JAVA_HOME/bin' >> /etc/profile,source /etc/profile。验证:java -version,必须显示17.0.12。然后装Maven 3.8.6:去Apache官网下载apache-maven-3.8.6-bin.tar.gz,同样解压到/opt,tar -zxvf apache-maven-3.8.6-bin.tar.gz -C /opt/。配置:echo 'export MAVEN_HOME=/opt/apache-maven-3.8.6' >> /etc/profile,echo 'export PATH=$PATH:$MAVEN_HOME/bin' >> /etc/profile,source /etc/profile。验证:mvn -v,显示3.8.6。最后装Git:yum install -y git,验证:git --version。到这里,基础环境齐了。但别急着装Jenkins,先创建jenkins用户:useradd -m -d /var/lib/jenkins jenkins,passwd jenkins设个密码。然后下载Jenkins WAR包:wget https://get.jenkins.io/war-stable/2.346.1/jenkins.war,放到/var/lib/jenkins/下。启动:java -Djenkins.home=/var/lib/jenkins -DJENKINS_HOME=/var/lib/jenkins -jar /var/lib/jenkins/jenkins.war --httpPort=8080 &。注意,-Djenkins.home和-DJENKINS_HOME必须一致,且指向jenkins用户的家目录,否则插件、凭据全存错地方。首次访问http://192.168.40.98:8080,用/var/lib/jenkins/secrets/initialAdminPassword里的密码登录。安装推荐插件后,立即进入Global Tool Configuration,配好JDK和Maven,Name就叫“jdk-17.0.12”和“maven-3.8.6”,MAVEN_HOME填/opt/apache-maven-3.8.6。这一步做完,Jenkins才算真正“活”了,后续所有构建都基于此。
4.2 GitLab项目创建与IDEA协同:确保代码能被Jenkins正确识别
GitLab安装不是本文重点,但项目创建有讲究。登录GitLab(http://192.168.40.99),创建Group叫“devops”,Project叫“jenkins-study”。关键在项目初始化:不要点“Initialize repository with a README”,因为Jenkins拉取时,如果仓库里只有README,没有pom.xml,构建会直接失败。正确做法:创建空项目,然后用IDEA关联。IDEA里,File > New > Project,选Spring Initializr,Spring Boot版本选3.2.0,Dependencies加Spring Web。建好后,server.port=8088,写个HelloController。这时,Version Control > Git > Add,把整个项目加到Git索引。然后,VCS > Git > Remotes,点+号,URL填GitLab项目地址http://192.168.40.99/devops/jenkins-study.git,注意,这里必须用HTTP地址,不是SSH地址,因为Jenkins用Token认证,SSH地址需要密钥,配置更复杂。接着,Commit,填Message“init project”,勾选“Push changes immediately”,点OK。IDEA会弹窗让你填GitLab用户名和Token,用户名填你的GitLab账号,Token填之前创建的Personal Access Token(glpat-xxx那段)。Push成功后,GitLab项目里就有完整的pom.xml、src目录了。验证:在Jenkins服务器上,手动git clone http://192.168.40.99/devops/jenkins-study.git,看能不能拉下来。能拉下来,说明GitLab、网络、Token全OK。这一步是基石,基石不牢,后面全白搭。
4.3 Jenkins项目配置:从创建到第一次成功构建的完整步骤
登录Jenkins,点击“New Item”,Name填“jenkins-study”,Type选“Freestyle project”,OK。进入配置页,关键设置如下:
General:勾选“Discard old builds”,Max # of builds to keep填10,避免磁盘爆满。
Source Code Management:选Git,Repository URL填http://192.168.40.99/devops/jenkins-study.git,Credentials选之前创建的gitlab-token,Branches to build填*/main。
Build Triggers:勾选“Poll SCM”,Schedule填H/5 * * * *,意思是每5分钟检查一次Git是否有新提交,有就触发构建。这是最简单的触发方式,比Webhook稳定。
Build Environment:勾选“Provide JDK”,选“jdk-17.0.12”;勾选“Provide Maven”,选“maven-3.8.6”。
Build:Add build step > “Invoke top-level Maven targets”,Goals填clean package -Dmaven.test.skip=true。
Post-build Actions:Add post-build action > “Send files or execute commands over SSH”。这里,Name选你之前在Manage Jenkins > Configure System里配好的测试服务器(如test-server),Transfers里,Source files填target/*.jar,Remove prefix填target/,Remote directory填/root/jenkins-study/,Exec in pty打钩。Exec command填:
cd /root/jenkins-study chmod +x /root/clean.sh /root/clean.sh jenkins-study nohup java -jar jenkins-study-*.jar > /root/jenkins-study/log.out 2>&1 &保存。现在,点击“Build now”,看控制台输出。正常流程是:拉取代码 → 下载依赖(从阿里云镜像)→ 编译 → 打包 → 传jar包 → 执行clean.sh → 启动jar。如果一切顺利,最后看到“Finished: SUCCESS”。去测试服务器,curl http://127.0.0.1:8088,返回“Hello dev”,恭喜,第一条流水线跑通了。如果失败,看控制台输出的第一行ERROR,90%的问题都能定位。
4.4 高级技巧:让部署更健壮的三个关键增强
第一条,构建稳定性增强。默认Jenkins构建失败后,不会自动重试,但网络抖动可能导致Git拉取超时。在Build Environment里,勾选“Add timestamps to the Console Output”,所有日志带时间戳,方便排查。在Build里,Goals改为clean package -Dmaven.test.skip=true -Dmaven.wagon.http.pool=false -Dmaven.wagon.httpconnectionManager.maxPerRoute=10,禁用wagon连接池,避免并发下载冲突。第二条,部署原子性增强。当前方案是直接覆盖jar包,万一新jar有bug,回滚要手动操作。改进:用符号链接。在Exec command里,把启动命令改成:
cd /root/jenkins-study mkdir -p releases cp target/jenkins-study-*.jar releases/jenkins-study-$(date +%Y%m%d%H%M%S).jar ln -sf releases/jenkins-study-$(date +%Y%m%d%H%M%S).jar current.jar /root/clean.sh jenkins-study nohup java -jar current.jar > /root/jenkins-study/log.out 2>&1 &这样,current.jar永远指向最新版本,回滚只需ln -sf releases/jenkins-study-20231001120000.jar current.jar,秒级完成。第三条,监控告警增强。光看Jenkins界面不够,要让失败时发微信或邮件。装Email Extension插件,配置SMTP服务器(如QQ邮箱),在Post-build Actions里,Add post-build action > “Editable Email Notification”,Recipients填运维邮箱,Triggers选“Failure - Any”,Content填构建日志URL。这样,构建失败,手机立刻收到邮件,不用守着Jenkins界面。
5. 常见问题与排查技巧实录
5.1 构建失败速查表:从日志定位问题的黄金法则
Jenkins构建失败,日志动辄上千行,新手常陷入“全文搜索ERROR”的误区。高效排查,要遵循“三层定位法”:
第一层:看构建状态栏。Jenkins界面右上角,如果显示红色“#1”,点进去,看“Console Output”顶部。第一行通常是Started by user admin,往下找第一个ERROR或FATAL,这就是根因。比如ERROR: Error cloning remote repo 'origin',说明Git拉取失败,直接跳到Git配置检查。
第二层:看Maven阶段日志。如果第一层没ERROR,往下翻,找[INFO] ------------------------------------------------------------------------,这是Maven生命周期分隔线。在[INFO] BUILD FAILURE之前,最后一段[ERROR]就是编译失败点。比如[ERROR] /var/lib/jenkins/workspace/jenkins-study/src/main/java/com/example/demo/HelloController.java:[5,22] package org.springframework.web.bind.annotation does not exist,说明缺少spring-web依赖,检查pom.xml。
第三层:看SSH执行日志。如果Maven成功(BUILD SUCCESS),但应用没起来,说明问题在Post Steps。在Console Output里,Ctrl+F搜SSH:,找到SSH: Connecting,往下看SSH: EXEC: completed after XXX ms,如果显示failed,说明Exec command执行失败。此时,登录测试服务器,手动执行一遍那个命令,看具体报什么错。比如nohup: failed to run command ‘java’: No such file or directory,就是JAVA_HOME没配,去/etc/bashrc里补上。记住,90%的问题,三层之内必现原形,不用全文搜索。
5.2 典型问题深度复盘:五个血泪教训的真实还原
问题1:Jenkins构建成功,但测试服务器上jps看不到进程,curl超时
复盘:日志里BUILD SUCCESS,但curl http://192.168.40.97:8088一直转圈。登录测试服务器,netstat -tuln | grep 8088,发现端口没监听。执行java -jar jenkins-study-*.jar,报错java.lang.IllegalStateException: Unable to access 'javax.servlet.ServletContext'。根源:Spring Boot 3.x默认内嵌Tomcat,但需要spring-boot-starter-web依赖,而pom.xml里可能漏了。解决方案:检查pom.xml,确保有<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency>。
问题2:Git拉取报错SSL certificate problem: self signed certificate
复盘:GitLab用HTTPS,但证书是自签名的。Jenkins服务器curl https://192.168.40.99报SSL错误。解决方案:在Jenkins服务器上,git config --global http.sslVerify false。注意,这只是内网环境的权宜之计,生产环境应配合法证书。
问题3:Maven下载依赖极慢,日志卡在Downloading from central
复盘:mvn -v显示Maven 3.8.6,但/opt/maven/conf/settings.xml没配阿里云镜像,或者配了但文件权限是600,Maven读不了。解决方案:ls -l /opt/maven/conf/settings.xml,确保是644,cat /opt/maven/conf/settings.xml确认镜像配置正确。
问题4:SSH传包后,ls /root/jenkins-study/看不到jar包
复盘:Exec command里Remote directory填了/root/jenkins-study/,但Jenkins用jenkins用户执行scp,而/root目录jenkins用户无写权限。解决方案:把Remote directory改成/home/jenkins/jenkins-study/,并在测试服务器上useradd jenkins,chown -R jenkins:jenkins /home/jenkins。
问题5:构建多次,log.out文件越来越大,磁盘告警
复盘:nohup java -jar ... >> log.out是追加模式,日志无限增长。解决方案:用logrotate。在测试服务器上,vim /etc/logrotate.d/jenkins-study,内容:
/root/jenkins-study/log.out { daily missingok rotate 30 compress delaycompress notifempty }这样,日志每天轮转,保留30天,自动压缩。
5.3 经验总结:十年运维沉淀的七条铁律
第一,永远用绝对路径。Jenkins里所有路径,/var/lib/jenkins/workspace/、/opt/maven/、/root/jenkins-study/,一律写全,别用~或./,因为不同用户、不同shell,~指向不同。
第二,凭据绝不硬编码。Token、密码、密钥,全部走Jenkins Credentials,URL里只写干净地址。
第三,构建前必清空。勾选“Delete workspace before build starts”,省去90%的“上次残留导致失败”问题。
第四,日志必须带时间戳。“Add timestamps to the Console Output”是标配,没有时间戳的日志,等于没日志。
第五,测试服务器JDK版本必须和编译一致。Jenkins用JDK 17编译,测试服务器就得装JDK 17,否则Unsupported class file major version。
第六,端口冲突是常态,不是异常。server.port=8088只是开发默认,生产必须用--server.port=8081等参数动态指定,避免和Nginx等冲突。
第七,文档比代码重要。每次配置变更,用Markdown记下:改了哪行配置,为什么改,预期效果是什么。半年后你回来维护,感谢当年的自己。
我在实际使用中发现,这套流程跑得最稳的,反而是那些最“笨”的配置:不用Pipeline,不用Docker,就用最原始的自由风格项目、最直白的Shell命令、最保守的定时拉取。技术不是越新越好,而是越简单、越透明、越容易掌控越好。最后再分享一个小技巧:把整个Jenkins配置导出为Jenkins Job DSL脚本,用Git管理起来。这样,服务器崩了,几分钟就能重建一套一模一样的环境。自动化部署的终点,不是让机器干活,而是让自己彻底解放。
