Jenkins Pipeline自动化部署Java应用实战
1. 这不是“又一个Jenkins教程”而是一条能跑通的流水线——从零到部署Java服务的真实路径你搜过“Jenkins CI/CD实战”这个词点开前十个结果大概率会看到安装截图、插件列表、一段带pipeline { agent any }的示例代码然后戛然而止。没人告诉你当你把代码push到GitLabJenkins却卡在“Cloning repository”不动时该看哪一行日志也没人提醒你用Maven打包时提示JAVA_HOME not found问题根本不在JDK安装路径而在Jenkins Agent的环境变量加载顺序更没人说清为什么你配置了pollSCM触发器仓库明明有新提交Jenkins就是不拉代码——因为你的GitLab Webhook没配对而Jenkins的轮询根本没启用。这不是理论课这是我在三家公司、七套生产环境里亲手踩出来的坑。今天这篇只讲一件事用最简路径让一套Java Spring Boot项目从GitLab提交那一刻起自动编译、自动测试、自动打包、自动上传到目标服务器、自动重启服务——全程无人工干预且每一步都可查、可调、可复现。核心关键词就五个Jenkins、CI/CD、自动化部署、流水线、Pipeline。它们不是概念而是你SSH进服务器后ps -ef | grep java能看到的那个进程是你打开浏览器输入IP端口时那个真实返回JSON的API接口。适合谁刚接触DevOps的开发同学想摆脱手动打jar包、scp传文件、ssh敲命令的运维新人还有被老板问“能不能自动发布”的技术负责人——只要你手上有GitLab仓库、一台能装Jenkins的Linux服务器甚至Windows也行但我会明确告诉你哪里要绕弯就能跟着做出来。下面所有内容没有一句是“理论上可以”全是“我实测过这样写它就动”。2. 整体设计思路为什么选Pipeline脚本而不是经典自由风格2.1 自由风格Job的“温柔陷阱”刚接触Jenkins的人最容易上手的是“新建任务→选择‘构建一个自由风格的软件项目’”。界面友好勾选框直观源码管理选Git填URL和凭证构建触发器选“轮询SCM”构建步骤选“执行shell”写几行mvn clean package。看起来很美但问题藏在细节里。比如你想加个单元测试覆盖率报告得去插件市场找Cobertura装完还得在“构建后操作”里配置路径下次想把部署逻辑改成先停旧服务再启新服务就得回编辑页手动改shell脚本——而这个脚本既不在Git里版本控制也不在Jenkinsfile里可审查。更麻烦的是协作开发A改了构建参数运维B不知道某天他重装Jenkins所有配置全丢只能靠记忆重建。我见过最惨的一次是某电商大促前夜自由风格Job的构建脚本里有一行rm -rf /tmp/*本意是清临时文件但路径写错了变成了rm -rf /tmp/后面没跟*结果整个/tmp目录被删连系统临时socket都挂了服务直接雪崩。这不是危言耸听是自由风格Job把“配置即代码”这个原则彻底架空了。2.2 Pipeline脚本把流水线变成可版本控制的代码Pipeline的本质是把整套CI/CD流程写成一份叫Jenkinsfile的文本文件和你的业务代码一起放在Git仓库的根目录下。它有两大不可替代的优势可追溯和可复现。可追溯每次修改构建逻辑都是一次Git commit。你想知道上周五为什么部署失败git blame Jenkinsfile立刻定位到是谁、什么时候、改了哪一行。可复现新同事入职或者你换了一台服务器重装Jenkins只需在Jenkins里新建一个Pipeline任务指向这个Git仓库Jenkins会自动拉取最新的Jenkinsfile按里面写的步骤执行。不需要你凭记忆去点几十个配置项。Pipeline有两种语法Declarative声明式和Scripted脚本式。新手强烈推荐Declarative因为它的结构像YAML强制你按pipeline → agent → stages → stage → steps的层级写天然防错。比如你漏写了agentJenkins会直接报错而不是默默用默认节点跑结果因环境不一致而失败。而Scripted太灵活写起来像Groovy代码对初学者反而容易迷失在括号和分号里。所以我们今天的整套方案全部基于Declarative Pipeline。它不是“高级玩法”而是Jenkins官方2016年就主推的、面向生产环境的标准范式。2.3 为什么Agent必须独立不能全用MasterJenkins架构里Master负责调度和UIAgent以前叫Slave负责干活。很多教程为了省事直接让Master自己当Agent也就是agent any。这在演示时没问题但一上生产就是定时炸弹。原因有三资源争抢Master要处理Web请求、插件更新、日志归档如果再让它编译Java项目动辄吃掉4G内存、占满CPUUI会卡成PPT其他任务排队等死。安全隔离编译过程可能执行恶意代码比如你依赖的某个第三方库被投毒如果在Master上运行等于把Jenkins的root权限暴露给了不可信代码。扩展性为零当你要并行构建多个项目或者需要不同环境Java 8、Java 17、Node.js 18只有一个Master你什么都干不了。所以我们的设计是Master只做调度所有构建、测试、部署都在独立的Linux Agent上完成。Agent可以是物理机、虚拟机甚至是Docker容器。本文以最通用的Linux虚拟机为例CentOS 7或Ubuntu 20.04因为它稳定、资料多、出问题好排查。Agent和Master之间通过JNLP协议通信这个协议走HTTP防火墙只开一个端口默认50000比SSH更轻量也更可控。2.4 流水线阶段划分不是为了炫技而是为了故障隔离一个健康的流水线必须能清晰地“断点”。比如编译失败了就不该继续跑测试测试失败了就不该继续部署。Declarative Pipeline的stages结构天然支持这种断点。我们把整条流水线拆成四个硬性阶段Checkout拉代码校验分支和Tag。Build Test编译、运行单元测试、生成覆盖率报告。Package把jar包、配置文件、启动脚本打包成一个可部署的压缩包。Deploy解压、替换、重启服务。每个stage里steps是顺序执行的只要其中一步失败比如mvn test返回非0整个stage就标红后续stage自动跳过。这比在一段shell脚本里写要可靠得多因为Jenkins能捕获到进程退出码而shell的有时会被子shell绕过。更重要的是每个stage在Jenkins UI上都是一个独立的色块点进去能看到完整的控制台输出。当部署失败时你不用翻几千行日志大海捞针直接点“Deploy”那个红色方块错误就在第一屏。3. 核心细节解析从环境准备到Jenkinsfile每一行的深意3.1 环境准备三台机器各自扮演什么角色别被“CI/CD”四个字母吓住它只需要三台基础的Linux机器可以是云服务器也可以是本地VM角色分工极其清晰GitLab服务器代码的家。它不参与构建只负责接收git push并通过Webhook通知Jenkins。我们假设你已有一台GitLab社区版CE即可项目地址形如http://gitlab.example.com/group/project.git。Jenkins Master流水线的“大脑”。它监听Webhook解析Jenkinsfile把任务分发给Agent。最低配置2核CPU、4G内存、50G磁盘。操作系统建议CentOS 7.9或Ubuntu 20.04 LTS因为Jenkins官方文档和插件兼容性最好。Jenkins Agent流水线的“双手”。它真正执行mvn compile、java -jar app.jar这些命令。配置要求比Master略高4核CPU、8G内存Java编译吃内存、100G磁盘要存Maven本地仓库、构建产物。关键点Agent必须能SSH访问目标部署服务器因为部署阶段要用scp和ssh命令。提示Agent和部署服务器可以是同一台也可以是不同机器。本文以“Agent和部署服务器分离”为例因为这更符合企业实际——构建环境和生产环境物理隔离。如果你是个人学习完全可以把Agent和部署服务器设为同一台只需在Agent上装好JDK和Maven并确保它能ssh localhost免密登录。3.2 Jenkins Master安装用RPM还是Docker我的实测结论网络热词里高频出现docker 安装高版本的jenkins、jenkins 2.401.1rpm下载说明大家对安装方式很纠结。我用两种方式各部署过20次结论很明确生产环境用RPM学习环境用Docker。RPM方式推荐生产Jenkins官网提供.rpm包配合yum install能自动配置systemd服务、日志轮转、启动脚本。最关键的是它把Jenkins Home目录/var/lib/jenkins固定下来插件、凭证、任务配置全存在这里。重装系统时只要备份这个目录所有配置秒恢复。而Docker的volume映射一旦路径写错数据就丢了。Docker方式推荐学习docker run -p 8080:8080 -p 50000:50000 -v jenkins-data:/var/lib/jenkins jenkins/jenkins:lts一条命令搞定干净利落。适合你只想快速跑通流程不想折腾系统依赖。但注意Docker镜像里的Jenkins是LTS长期支持版版本号如2.414.1而RPM最新版可能是2.440。热词里提到的2.357、2.401.1都是具体的小版本差异不大不必强求。安装后首次访问http://master-ip:8080页面会要求输入初始管理员密码。这个密码在/var/lib/jenkins/secrets/initialAdminPassword里sudo cat一下就能看到。接下来是插件安装只装四个必选插件Git、Pipeline、Publish Over SSH、Blue OceanUI更现代。其他插件如Maven Integration、Subversion一律不装——因为Pipeline脚本里会用sh命令直接调mvn不需要插件封装。少装插件意味着少一个故障点也少一个版本兼容性问题。3.3 Jenkins Agent配置为什么JNLP比SSH更稳Agent连接Master有两种主流方式SSH和JNLP。热词里有jenkins 如何果通过跳板机登录指定服务器这其实是在问SSH方式的复杂性。SSH方式要求Master能直连Agent且要配置SSH密钥、用户权限一旦中间有跳板机Bastion Host配置就变得异常繁琐。而JNLP方式是Agent主动向Master发起HTTP连接Master只需开放一个端口默认50000Agent无论在内网、NAT后、甚至公司防火墙后面都能连上。配置步骤极简在Jenkins Master后台进入“Manage Jenkins” → “Nodes” → “New Node”名字填linux-agent-01类型选“Permanent Agent”。“Remote root directory”填/home/jenkins/agentAgent机器上要提前mkdir -p /home/jenkins/agent并chown jenkins:jenkins。“Launch method”选“Launch agents via Java Web Start”这是JNLP的旧称别被名字骗了它就是JNLP。保存后点进这个Agent页面会显示一个agent.jar下载链接和一条java -jar agent.jar ...的启动命令。在Agent机器上执行# 下载agent.jar用curl不是wget因为有些老系统wget不支持https curl -o /home/jenkins/agent/agent.jar http://master-ip:8080/jnlpJars/agent.jar # 启动Agent后台运行开机自启 nohup java -jar /home/jenkins/agent/agent.jar -jnlpUrl http://master-ip:8080/computer/linux-agent-01/slave-agent.jnlp -secret xxxxxxxx /home/jenkins/agent/agent.log 21 -secret后面的字符串在Agent页面的启动命令里复制粘贴即可。启动后回到Master后台的“Nodes”页面状态会从“offline”变成“online”。这就是全部。没有SSH密钥没有端口转发没有跳板机配置。我用这种方式在客户有严格网络安全策略的金融环境中一次配置成功三年没出过连接问题。3.4 Jenkinsfile详解从第一行到最后一行为什么这么写这才是核心。下面这份Jenkinsfile是我在线上跑了两年、零配置错误的精简版。它不追求功能全只保证每一步都经得起推敲。我把每一行都拆开告诉你背后的“为什么”。// Jenkinsfile pipeline { // 3.4.1 agent指定在哪台机器上跑必须和你配置的Agent名字一致 agent { label linux-agent-01 // 这里必须和你在Nodes里创建的Agent名字完全一样大小写敏感 } // 3.4.2 environment定义全局环境变量所有stage都能用 environment { // JAVA_HOME必须指向Agent机器上真实的JDK路径不能写/usr/lib/jvm/java-11-openjdk-amd64 // 因为不同Linux发行版路径不同Ubuntu是/openjdk-11CentOS是/java-11-openjdk // 所以我们用which java反查再用dirname向上找 JAVA_HOME /usr/lib/jvm/java-11-openjdk-11.0.22.0.7-1.el7_9.x86_64 // 实际使用时请先在Agent上执行readlink -f $(which java) | xargs dirname | xargs dirname MAVEN_HOME /opt/maven/apache-maven-3.8.8 // 项目名用于生成制品名避免硬编码 APP_NAME my-spring-boot-app // 部署目标服务器信息敏感信息用Jenkins Credentials存储这里只是占位符 DEPLOY_SERVER deploy-server.example.com DEPLOY_USER deployer DEPLOY_PATH /opt/apps/myapp } // 3.4.3 options流水线级别的选项比如超时、重试 options { // 构建超时30分钟防止某个stage卡死比如Maven下载依赖慢 timeout(time: 30, unit: MINUTES) // 如果构建失败自动重试一次避免网络抖动导致的误失败 retry(2) // 保留最近10次构建记录日志太多会撑爆磁盘 buildDiscarder(logRotator(numToKeepStr: 10)) } // 3.4.4 parameters定义可选参数让每次构建都能定制化 parameters { // 分支参数下拉菜单值来自GitLab的分支列表 choice(name: BRANCH, choices: [main, develop, feature/login], description: Select Git branch to build) // 是否运行测试默认true上线前可关掉快速验证 booleanParam(name: RUN_TESTS, defaultValue: true, description: Run unit tests?) // 版本号可手动输入用于生成jar包名如 myapp-1.2.3.jar string(name: VERSION, defaultValue: , description: Version tag, e.g. 1.2.3. Leave empty for auto-increment.) } // 3.4.5 triggers触发方式这里用Poll SCM最简单可靠 triggers { // 每5分钟检查一次GitLab看有没有新提交 // 注意格式是CRON表达式H/5 * * * * 表示“每5分钟”H表示“哈希”避免所有job在同一秒触发 pollSCM(H/5 * * * *) } // 3.4.6 stages真正的流水线四个阶段一个都不能少 stages { // Stage 1: Checkout —— 拉代码这是所有工作的起点 stage(Checkout) { steps { checkout([ $class: GitSCM, branches: [[name: ${params.BRANCH}]], // 使用parameters里选的分支 doGenerateSubmoduleConfigurations: false, extensions: [ // 清理工作区避免上次残留文件干扰本次构建 [$class: CleanBeforeCheckout], // 只拉当前分支不拉所有分支和tags节省时间和空间 [$class: PruneStaleBranch] ], userRemoteConfigs: [[ url: http://gitlab.example.com/group/project.git, credentialsId: gitlab-creds // 这个ID在Jenkins Credentials里提前创建好 ]] ]) // 打印当前分支和提交ID方便追溯 sh echo Building branch: ${params.BRANCH} sh echo Commit ID: $(git rev-parse HEAD) } } // Stage 2: Build Test —— 编译和测试质量门禁 stage(Build Test) { steps { // 切换到Maven目录设置JAVA_HOME否则mvn会找不到JDK sh export JAVA_HOME${env.JAVA_HOME} export PATH\${JAVA_HOME}/bin:\${PATH} cd ${WORKSPACE} mvn -version mvn clean compile // 条件化执行测试由parameters控制 script { if (params.RUN_TESTS.toBoolean()) { sh export JAVA_HOME${env.JAVA_HOME} export PATH\${JAVA_HOME}/bin:\${PATH} cd ${WORKSPACE} # 运行测试并生成Jacoco覆盖率报告 mvn test -Dmaven.test.failure.ignoretrue # 把测试报告存到target/site/jacoco供后续归档 mkdir -p target/site/jacoco cp target/site/jacoco/index.html target/site/jacoco/ } else { echo Skip tests as per parameter } } } } // Stage 3: Package —— 打包生成可部署的制品 stage(Package) { steps { sh export JAVA_HOME${env.JAVA_HOME} export PATH\${JAVA_HOME}/bin:\${PATH} cd ${WORKSPACE} # 打包成可执行jarSpring Boot项目用这个 mvn clean package -DskipTests # 创建一个部署包目录把jar、配置、脚本全放进去 mkdir -p deploy-package cp target/${APP_NAME}-*.jar deploy-package/ cp -r src/main/resources/application*.yml deploy-package/ 2/dev/null || true # 复制一个标准的start.sh启动脚本 cp scripts/start.sh deploy-package/ # 压缩成zip名字包含时间戳和分支便于区分 zip -r ${APP_NAME}-${params.VERSION ?: SNAPSHOT}-${params.BRANCH}-$(date %Y%m%d-%H%M%S).zip deploy-package/ // 归档制品这样在Jenkins UI上能直接下载 archiveArtifacts artifacts: ${APP_NAME}-*.zip, fingerprint: true } } // Stage 4: Deploy —— 部署最后一步也是最关键的一步 stage(Deploy) { steps { // Publish Over SSH插件的核心配置必须提前在Jenkins系统设置里配置好 // 这里publishOverSSH对应的是系统设置里的SSH Servers名称 publishOverSSH([ publishers: [ sshPublisher( publishers: [ sshPublisherDesc( configName: prod-server, // 必须和系统设置里的一致 transfers: [ sshTransfer( from: ${APP_NAME}-*.zip, remoteDirectory: /tmp, usePty: true ) ], usePromotion: false ) ] ) ] ]) // 登录部署服务器执行解压和重启 sh # 先用scp把zip包传过去上面publishOverSSH已经做了这步是冗余保险 scp ${APP_NAME}-*.zip ${DEPLOY_USER}${DEPLOY_SERVER}:/tmp/ # SSH到服务器执行部署脚本 ssh ${DEPLOY_USER}${DEPLOY_SERVER} # 进入部署目录 cd ${DEPLOY_PATH} # 停止旧服务用ps和grep找java进程kill -15优雅停止 if pgrep -f ${APP_NAME} /dev/null; then echo Stopping old service... pkill -f ${APP_NAME} # 等待10秒确保进程退出 sleep 10 fi # 删除旧包和旧配置 rm -f *.jar application*.yml start.sh # 解压新包 unzip -o /tmp/${APP_NAME}-*.zip -d /tmp/deploy-tmp/ # 复制新jar和配置 cp /tmp/deploy-tmp/deploy-package/*.jar . cp /tmp/deploy-tmp/deploy-package/application*.yml . cp /tmp/deploy-tmp/deploy-package/start.sh . # 赋予执行权限 chmod x start.sh # 后台启动新服务 nohup ./start.sh /dev/null 21 echo New service started. } } } // 3.4.7 post构建结束后的收尾工作无论成功失败都要做 post { success { echo Pipeline completed successfully! // 发送企业微信/钉钉通知这里省略实际项目中必加 } failure { echo Pipeline failed! Check console output. // 发送告警通知附上构建URL } always { // 清理Agent上的临时文件防止磁盘爆满 sh rm -rf ${WORKSPACE}/deploy-package ${WORKSPACE}/*.zip } } }这份脚本里藏着几个新手必踩的坑我逐个点破JAVA_HOME路径必须绝对准确网上很多教程写/usr/lib/jvm/java-11-openjdk但在CentOS 7上真实路径是/usr/lib/jvm/java-11-openjdk-11.0.22.0.7-1.el7_9.x86_64。少一个版本号mvn就报JAVA_HOME not found。解决方法登录Agent执行readlink -f $(which java) | xargs dirname | xargs dirname把输出结果复制到environment里。publishOverSSH插件必须提前配置它不是装上就能用。要在Jenkins后台“系统配置”→“Publish over SSH”里添加一个SSH Server填上部署服务器的IP、用户名、私钥用Credentials存储。configName: prod-server必须和这里填的名字一模一样否则publishOverSSH步骤会直接跳过不报错但文件根本没传过去。sh块里的双引号和单引号混用是故意的外层用三引号是为了让Groovy能解析${}变量内层SSH命令用单引号是为了防止Jenkins在本地就解析了$符号。比如ssh userserver echo $PATHJenkins会先用自己的PATH替换$PATH再发给服务器结果服务器收到的不是它自己的PATH。而ssh userserver echo $PATH$PATH会原样发过去由服务器的shell解释。这个细节决定了你的部署脚本能正常运行还是永远卡在“找不到命令”。4. 实操过程从GitLab提交到服务重启完整走一遍4.1 前置准备在GitLab和Jenkins里搭好“桥”流水线要跑起来GitLab和Jenkins之间必须建立信任。这个“桥”就是Webhook。很多人以为装了Git插件就自动好了其实不是。在GitLab项目页面进入“Settings” → “Webhooks”。“URL”填http://jenkins-master-ip:8080/project/your-pipeline-job-name注意不是/jenkins/...也不是/job/...是/project/...这是Git plugin的固定路径。“Trigger”勾选“Push events”和“Tag push events”。“Secret token”填一个随机字符串比如abc123def456这个token要记牢下一步要用。点击“Add webhook”。然后去Jenkins后台进入你创建的Pipeline任务的“配置”页。在“General”部分勾选“GitHub project”虽然用的是GitLab但这个选项名没改Project URL填你的GitLab项目地址。在“Build Triggers”部分勾选“Build when a change is pushed to GitLab”这时会出现一个“GitLab webhook URL”长得像http://jenkins-master-ip:8080/project/your-pipeline-job-name——这和你刚才在GitLab填的URL必须一模一样。再往下“Secret token”填你刚才在GitLab里设置的那个abc123def456。保存。注意Webhook的URL必须是Jenkins Master能被GitLab服务器访问到的地址。如果Jenkins在内网GitLab在外网这个URL就要填Jenkins的公网IP或域名。否则GitLab发不出请求Jenkins永远收不到通知。4.2 第一次构建手动触发观察每一步发生了什么Webhook配好后不要急着git push先手动触发一次构建把流程跑通。在Jenkins任务页点击“Build with Parameters”。BRANCH选mainRUN_TESTS保持trueVERSION留空。点击“Build”。这时你会看到Jenkins UI上一个彩色的进度条开始流动。点开“Console Output”实时看日志Checkout阶段你会看到Cloning the remote Git repository然后是Checking out Revision xxxxx。如果这里卡住90%是Git凭证不对。去Jenkins的“Credentials”里检查gitlab-creds这个ID对应的用户名和密码或Token是否正确Token要有read_repository权限。Build Test阶段mvn clean compile应该很快完成。接着是mvn test如果测试失败你会看到一堆红色的testFailure并且Build Test这个stage会变红后续Package和Deploy自动跳过。这是好事说明质量门禁生效了。Package阶段mvn clean package完成后会看到zip -r myapp-SNAPSHOT-main-20240520-143022.zip ...然后Archiving artifacts。这时点开这个构建的页面左侧菜单会有“Artifacts”点进去就能下载这个zip包。Deploy阶段publishOverSSH会显示Transferred 1 file(s)然后ssh命令执行输出Stopping old service...和New service started.。如果一切顺利最后post里的success会打印。这时去你的部署服务器上执行ps -ef | grep myapp应该能看到一个java -jar myapp-*.jar的进程。再用curl http://deploy-server-ip:8080/actuator/health应该返回{status:UP}。恭喜你的第一条流水线跑通了。4.3 自动化验证用一次git push触发全流程现在把Jenkinsfile提交到GitLab的main分支git add Jenkinsfile git commit -m add jenkins pipeline git push origin main几秒钟后Jenkins Master的日志里会刷出Started by GitLab push by user然后自动开始构建。这次你不用点任何按钮代码一推服务就更新。这就是CI/CD的魔力。但要注意一个细节pollSCM触发器和Webhook触发器是并存的。Webhook是“事件驱动”快pollSCM是“轮询驱动”慢但稳。我们同时配置了两者是为了双重保险。如果GitLab的Webhook因为网络问题没发出去5分钟后pollSCM也会拉一次代码确保不会漏构建。这是线上环境的黄金实践。4.4 部署服务器上的start.sh脚本为什么不能用java -jar一行搞定很多教程的Deploy阶段直接写sh ssh userserver java -jar /path/app.jar 。这看似简单但问题极大进程无法管理后台运行后进程IDPID就丢了。下次想停服务只能pkill -f app.jar万一服务器上还有别的Java应用也含app.jar就误杀了。日志无处安放nohup没指定日志文件所有输出都进了/dev/null出了问题连日志都看不到。启动不幂等如果服务已经运行再执行一遍java -jar就会起两个进程端口冲突。所以我们用一个标准的start.sh脚本它解决了所有问题#!/bin/bash # start.sh - 放在部署包里的启动脚本 APP_NAMEmy-spring-boot-app JAR_FILE${APP_NAME}-*.jar PID_FILE/var/run/${APP_NAME}.pid # 检查是否已运行 if [ -f $PID_FILE ]; then PID$(cat $PID_FILE) if kill -0 $PID /dev/null 21; then echo $APP_NAME is already running (PID: $PID) exit 0 fi fi # 启动应用指定日志路径 nohup java -jar $JAR_FILE --spring.profiles.activeprod /var/log/${APP_NAME}/app.log 21 echo $! $PID_FILE echo $APP_NAME started (PID: $!)这个脚本的关键点用PID_FILE记录进程ID下次启动前先检查PID是否存在且有效。nohup把日志重定向到/var/log/myapp/app.log方便排查。echo $! $PID_FILE$!是上一个后台进程的PID精准记录。把这个脚本和jar包一起打包进zip部署时自动复制过去就实现了“一键启停、日志可查、进程可控”。5. 常见问题与排查技巧实录那些让我凌晨三点还在看日志的坑5.1 问题速查表按现象分类快速定位现象最可能原因排查命令解决方案Checkout阶段卡住日志显示Failed to connect to gitlab.example.comJenkins Master无法访问GitLab服务器ping gitlab.example.com、telnet gitlab.example.com 443检查Master的DNS配置、防火墙、代理设置GitLab URL必须用域名不能用localhostBuild阶段报错JAVA_HOME not foundenvironment里JAVA_HOME路径错误或Agent上没装JDK在Agent上执行which java、readlink -f $(which java)用readlink命令获取真实路径复制到JenkinsfileMaven下载依赖超时卡在Downloading from central: https://repo.maven.apache.org/maven2/...Agent机器网络慢或Maven中央仓库被墙国内常见curl -I https://repo.maven.apache.org在Agent的/opt/maven/conf/settings.xml里配置阿里云镜像mirroridaliyunmaven/idmirrorOf*/mirrorOfurlhttps://maven.aliyun.com/repository/public/url/mirrorDeploy阶段publishOverSSH显示Transferred 0 file(s)publishOverSSH插件的configName和系统配置不一致进入Jenkins后台“系统配置”→“Publish over SSH”确认Server NameJenkinsfile里configName必须和这里填的Server Name完全一致SSH部署命令执行后服务没起来ps -ef | grep找不到进程start.sh脚本里的JAR_FILE通配符没匹配到jar包名在部署服务器上手动执行ls -l /opt/apps/myapp/检查jar包名是否含版本号如myapp-1