1. 为什么在 Ubuntu 22.04 上亲手搭建 Jenkins CI 流水线,比直接套 Docker 镜像更值得投入时间
Jenkins、Continuous Integration、Pipelines、Ubuntu 22.04——这四个词组合在一起,不是一句空泛的技术口号,而是一条真实可走、能立刻落地的工程化路径。我见过太多团队在项目初期图省事,用docker run -p 8080:8080 jenkins/jenkins:lts一键拉起一个 Jenkins 容器,结果两周后就卡在“插件装不上”“Maven 找不到 JDK”“Git 凭证死活不生效”“流水线跑一半报failed to resolve host name mirrors.tuna.tsinghua.edu.cn”这类问题上,最后不得不推倒重来。这不是 Jenkins 的问题,而是跳过了对底层运行环境的理解。Ubuntu 22.04 LTS(Jammy Jellyfish)作为当前主流的长期支持发行版,其默认的 systemd 服务管理机制、OpenJDK 11/17 的共存策略、APT 源的镜像配置逻辑、以及/var/lib/jenkins目录的权限继承规则,都和 Docker 容器里那个轻量、隔离、但高度抽象的文件系统存在本质差异。你不是在安装一个“工具”,而是在为整个持续集成流程构建一个可审计、可复现、可调试的基础设施基座。这个基座一旦建稳,后续添加 Java 编译、Vue 打包、Docker 镜像推送、Harbor 上传、甚至跨服务器部署,都只是往流水线里加几个 stage 而已;而如果基座松动,每一个新功能都会变成一次救火。所以本文不讲“三分钟启动 Jenkins”,而是带你从apt update开始,把每个依赖、每个用户、每个端口、每个证书链都亲手过一遍。这不是复古,是回归工程本质——当你清楚知道jenkins用户为什么必须属于docker组,/var/lib/jenkins/workspace/下的目录为什么不能被 root 直接 chown,你就已经避开了 80% 的线上故障。
2. 环境准备:绕开 Ubuntu 22.04 默认配置的三大“静默陷阱”
很多教程一上来就让你sudo apt install openjdk-11-jdk,然后wget下载 Jenkins.deb包。这在干净的最小化安装系统上可能成功,但在实际生产或开发环境中,Ubuntu 22.04 有三个默认配置会悄无声息地把你绊倒,它们不会报错,但会让你的 Jenkins 后续完全无法工作。
2.1 镜像源未切换导致的“网络超时幻觉”
Ubuntu 22.04 默认的sources.list指向的是archive.ubuntu.com和security.ubuntu.com。在国内直连这两个域名,延迟高、丢包率大,尤其当 Jenkins 启动时需要从updates.jenkins-ci.org拉取初始插件列表,或者你在插件管理界面点击“检查更新”时,页面会长时间转圈,最终显示“连接超时”。更隐蔽的是,它不会提示你“网络有问题”,而是表现为 Jenkins Web UI 响应缓慢、插件安装按钮灰掉、甚至systemctl status jenkins显示active (exited)却打不开网页。这不是 Jenkins 崩溃了,是它卡在网络握手阶段。解决方法必须在安装 Jenkins 前完成:
# 备份原配置 sudo cp /etc/apt/sources.list /etc/apt/sources.list.backup # 使用 sed 替换为清华源(国内最稳定) sudo sed -i 's/archive.ubuntu.com/mirrors.tuna.tsinghua.edu.cn/g' /etc/apt/sources.list sudo sed -i 's/security.ubuntu.com/mirrors.tuna.tsinghua.edu.cn/g' /etc/apt/sources.list # 更新索引(此时会明显感觉到速度提升) sudo apt update提示:不要用
apt upgrade全量升级系统,尤其是生产环境。Jenkins 对内核版本不敏感,但对libc6、libssl等基础库版本有强依赖。一次apt upgrade可能意外升级libtinfo.so.5到libtinfo.so.6,导致 Jenkins 启动时报错error while loading shared libraries: libtinfo.so.5: cannot open shared object file。这是 Ubuntu 22.04 中一个经典兼容性坑,根源在于 Jenkins LTS 版本(截至 2024 年中)仍链接旧版 ncurses 库。我们只做apt update,确保源可用即可。
2.2 OpenJDK 版本与 Jenkins 的“隐式绑定”
Jenkins 官方明确要求 Java 11 或 Java 17 运行时。Ubuntu 22.04 默认仓库提供openjdk-11-jdk和openjdk-17-jdk,但问题在于:Jenkins 的.deb包安装脚本会自动检测系统中第一个可用的 Java 11+ 环境,并将其硬编码进/etc/default/jenkins的JAVA_HOME变量中。如果你先装了openjdk-11-jdk,再装openjdk-17-jdk,Jenkins 依然会绑定到 JDK 11。这本身没问题,但当你后续想用 Maven 编译一个需要 JDK 17 的 Spring Boot 3.x 项目时,就会在流水线里看到java.lang.UnsupportedClassVersionError。更糟的是,你去修改/etc/default/jenkins,重启服务后发现又被覆盖了——因为.deb包的 postinst 脚本每次重启都会重新探测。正确做法是:在安装 Jenkins 前,先决定并固定你的 JDK 主版本。我推荐 JDK 17,因为它是当前 LTS,且 Spring Boot、Quarkus 等主流框架已全面适配。执行:
# 卸载所有已安装的 JDK(避免干扰) sudo apt remove --purge openjdk-* # 安装 JDK 17(Ubuntu 22.04 默认仓库即含) sudo apt install -y openjdk-17-jdk # 验证并设置系统级默认 sudo update-alternatives --config java # 在交互式菜单中选择 openjdk-17-jdk 的编号 # 手动导出 JAVA_HOME(关键!) echo "export JAVA_HOME=/usr/lib/jvm/java-17-openjdk-amd64" | sudo tee -a /etc/profile.d/java.sh sudo chmod +x /etc/profile.d/java.sh source /etc/profile.d/java.sh # 验证 java -version # 应输出 openjdk version "17.0.x" echo $JAVA_HOME # 应输出 /usr/lib/jvm/java-17-openjdk-amd64这样,Jenkins 安装时探测到的JAVA_HOME就是这个你手动设定的路径,后续不会被覆盖,也为你自己的 Maven、Gradle 构建提供了统一环境。
2.3 systemd 服务的“权限继承”误区
Jenkins 的.deb包会创建一个名为jenkins的系统用户,并将服务注册为systemd单元。很多人以为sudo systemctl start jenkins后,一切就归jenkins用户管了。但事实是:/var/lib/jenkins目录的属主虽然是jenkins:jenkins,其子目录如workspace/、plugins/、jobs/的权限却由umask和父目录继承规则共同决定。Ubuntu 22.04 的默认umask是002,这意味着新创建的目录权限是drwxrwxr-x(775),文件是-rw-rw-r--(664)。这看起来很安全,但问题出在“组写入”上。当你在流水线里用sh 'git clone ...'命令时,Jenkins 进程是以jenkins用户身份执行的,但它会尝试在workspace/下创建子目录。如果workspace/的组是jenkins,而umask允许组写入,那么新目录的组就是jenkins,一切正常。但如果某次手动操作(比如你用root用户cp了一个插件进去),不小心把plugins/目录的组改成了root,那么后续 Jenkins 就无法向其中写入新插件,报错Permission denied。这种错误极难排查,因为ls -l /var/lib/jenkins看起来一切正常。解决方案是:在 Jenkins 启动前,用chmod和chgrp强制规范整个家目录的权限模型:
# 停止 Jenkins(如果已启动) sudo systemctl stop jenkins # 递归设置属主和属组 sudo chown -R jenkins:jenkins /var/lib/jenkins # 设置目录权限为 755(所有者读写执行,组和其他人只读执行) sudo find /var/lib/jenkins -type d -exec chmod 755 {} \; # 设置文件权限为 644(所有者读写,组和其他人只读) sudo find /var/lib/jenkins -type f -exec chmod 644 {} \; # 关键:设置 setgid 位,确保新创建的子目录自动继承父目录的组 sudo chmod g+s /var/lib/jenkins sudo chmod g+s /var/lib/jenkins/workspace sudo chmod g+s /var/lib/jenkins/pluginssetgid(g+s)是 Linux 权限中一个常被忽略但极其关键的特性。它保证了无论谁(即使是jenkins用户)在/var/lib/jenkins/workspace下创建新目录,该目录的组都会自动设为jenkins,而不是创建者的主组。这从根本上杜绝了因权限继承混乱导致的流水线失败。
3. Jenkins 核心服务安装与首次初始化:从命令行到解锁页面的完整链路
现在,环境已经清理完毕,我们可以正式安装 Jenkins。这里强调“正式”,是因为我们要跳过所有图形化安装向导,全程使用命令行,确保每一步都可追溯、可审计。
3.1 下载与安装 Jenkins .deb 包(非 Docker)
Jenkins 官方提供两种主流安装方式:.deb包(适用于 Debian/Ubuntu)和 Docker 镜像。对于 Ubuntu 22.04,.deb包是更优解,因为它能与系统的systemd、apt、logrotate深度集成,日志自动归档到/var/log/jenkins/,磁盘空间自动轮转,服务状态与系统启动项完全同步。执行以下命令:
# 添加 Jenkins 官方 GPG 密钥(验证包签名) curl -fsSL https://pkg.jenkins.io/debian-stable/jenkins.io-2023.key | sudo tee \ /usr/share/keyrings/jenkins-stable-keyring.asc > /dev/null # 添加 Jenkins 官方 APT 仓库(注意:stable,非 weekly) echo deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/jenkins-stable-keyring.asc] \ https://pkg.jenkins.io/debian-stable binary/ | sudo tee \ /etc/apt/sources.list.d/jenkins-stable.list > /dev/null # 再次更新 APT 索引(这次会包含 Jenkins 仓库) sudo apt update # 安装 Jenkins(会自动解决依赖,包括 Java) sudo apt install -y jenkins这条命令链看似简单,但背后有深意。https://pkg.jenkins.io/debian-stable是 Jenkins 的稳定版仓库,它发布的版本经过充分测试,适合生产环境。而weekly仓库则包含最新功能,但稳定性风险更高。apt install jenkins不仅安装二进制文件,还会:
- 创建
jenkins系统用户和组; - 创建
/var/lib/jenkins主目录; - 注册
systemd服务单元/lib/systemd/system/jenkins.service; - 配置
/etc/default/jenkins,其中HTTP_PORT=8080、JENKINS_HOME=/var/lib/jenkins等关键变量已预设; - 设置
logrotate规则/etc/logrotate.d/jenkins,每天轮转日志。
3.2 启动服务并获取初始管理员密码
安装完成后,Jenkins 服务并不会自动启动。你需要手动启用并启动它:
# 启用开机自启 sudo systemctl enable jenkins # 启动服务 sudo systemctl start jenkins # 检查状态(关键!必须看到 active (running)) sudo systemctl status jenkinssystemctl status jenkins的输出是你判断安装是否成功的第一个黄金指标。如果看到active (running),并且Main PID后面跟着一个数字,说明 Java 进程已成功加载。如果卡在activating (start)或显示failed,请立即查看日志:
# 查看实时日志(按 Ctrl+C 退出) sudo journalctl -u jenkins -f # 或查看最近 100 行 sudo journalctl -u jenkins -n 100日志里最常见的错误就是前面提到的libtinfo.so.5缺失,或JAVA_HOME路径错误。一旦确认服务运行正常,下一步就是获取初始管理员密码。这个密码不是明文存储的,而是 Jenkins 在首次启动时,随机生成并写入一个临时文件:
# 读取初始密码(注意:路径是 /var/lib/jenkins/secrets/initialAdminPassword) sudo cat /var/lib/jenkins/secrets/initialAdminPassword你会看到一串 32 位的十六进制字符串,例如5a3b8c1d2e4f6a7b8c9d0e1f2a3b4c5d。复制它,打开浏览器,访问http://<你的服务器IP>:8080。你会看到 Jenkins 的经典蓝色欢迎页,提示“Unlock Jenkins”。粘贴密码,点击“Continue”。
3.3 插件安装策略:自定义 vs 推荐,一场关于“最小可行集”的博弈
Jenkins 会在此刻给你两个选项:“Install suggested plugins”(安装推荐插件)或 “Select plugins to install”(选择插件安装)。绝大多数新手会毫不犹豫点第一个。这没错,它会安装 Git、Pipeline、Credentials、Mailer 等约 30 个核心插件,足够你跑通第一个 Hello World 流水线。但作为一名资深运维,我强烈建议你选择第二个,手动勾选。原因有三:
- 启动时间与资源消耗:推荐插件集会下载并安装大量你短期内根本用不到的插件,如
kubernetes、docker-workflow、blueocean。这会让 Jenkins 首次启动时间从 2 分钟延长到 10 分钟以上,且占用更多内存。 - 安全攻击面:每个插件都是一个潜在的攻击入口。
blueocean插件曾多次曝出高危 RCE(远程代码执行)漏洞。在生产环境,你应该遵循“最小权限原则”,只安装必需的插件。 - 版本冲突风险:不同插件之间有严格的版本依赖关系。一次性安装太多,容易触发
PluginDependencyException,导致 Jenkins 启动失败。
因此,我的“最小可行集”清单如下(全部勾选):
- Git plugin:与 GitHub/GitLab 交互的基础。
- Pipeline:声明式流水线(Declarative Pipeline)的核心引擎。
- Credentials Plugin:安全地存储和管理 SSH Key、用户名密码等凭证。
- Mailer Plugin:发送构建结果邮件(可选,但强烈建议)。
- Matrix Project Plugin:支持多配置(Multi-configuration)项目,用于跨平台测试(可选,但非常实用)。
其他如Docker Pipeline、Kubernetes、Ansible等,留待你真正需要它们时,再通过 Jenkins Web UI 的“Manage Jenkins > Plugins > Available” 页面按需搜索安装。这样,你的 Jenkins 启动会快得多,也更干净。
4. 构建第一个 Pipeline:从“Hello World”到 Java 项目的完整编译与打包
解锁 Jenkins 后,你进入主界面。现在,我们要创建第一个真正的 CI 流水线。这里的关键是:不要用“Freestyle project”(自由风格项目),而要直接创建“Pipeline”项目。因为 Freestyle 是 Jenkins 1.x 的老范式,而 Pipeline(尤其是 Declarative Pipeline)是现代 CI/CD 的标准,它用 Groovy 语法编写,代码即配置(Code as Configuration),所有构建逻辑都以文本形式保存在Jenkinsfile中,可以和源码一起提交到 Git 仓库,实现版本控制和团队协作。
4.1 创建 Pipeline 项目与基础结构
在 Jenkins 主界面,点击左上角的“New Item”(新建任务)。
- 在“Enter an item name”框中输入
hello-world-pipeline。 - 选择下方的“Pipeline”类型,点击“OK”。
这会进入一个全新的配置页面。向下滚动,找到“Pipeline”部分。这里有两个关键选项:
- Definition: 选择 “Pipeline script”(脚本模式),这是最灵活的方式,适合学习和调试。
- Script: 在下方的大文本框中,输入以下最简化的 Declarative Pipeline 脚本:
pipeline { agent any stages { stage('Hello') { steps { echo 'Hello, Jenkins on Ubuntu 22.04!' sh 'uname -a' sh 'java -version' sh 'mvn -v' // 如果你后面要装 Maven,这行会报错,先注释掉 } } } }点击页面右下角的“Save”保存。回到项目主页,点击左侧的“Build Now”(立即构建)。你会看到一个构建队列,几秒后开始执行。点击刚生成的#1构建号,再点击“Console Output”,就能看到实时输出:
[Pipeline] Start of Pipeline [Pipeline] node Running on Jenkins in /var/lib/jenkins/workspace/hello-world-pipeline [Pipeline] { [Pipeline] stage [Pipeline] { (Hello) [Pipeline] echo Hello, Jenkins on Ubuntu 22.04! [Pipeline] sh + uname -a Linux ubuntu2204 5.15.0-xx-generic #xx-Ubuntu SMP ... [Pipeline] sh + java -version openjdk version "17.0.x" 2023-xx-xx ... [Pipeline] } [Pipeline] // stage [Pipeline] } [Pipeline] // node [Pipeline] End of Pipeline Finished: SUCCESS这个输出证明了三件事:Jenkins Agent(执行节点)能正常调度;sh步骤能在 Ubuntu 22.04 的 shell 环境中执行命令;Java 17 环境已正确加载。这就是一个完整的、可验证的 CI 流水线骨架。
4.2 为 Java 项目准备 Maven 环境
上面的hello-world-pipeline只是验证环境。真正的价值在于自动化构建 Java 项目。Ubuntu 22.04 默认不带 Maven,我们需要手动安装并将其注册为 Jenkins 的全局工具。
首先,在 Ubuntu 22.04 上安装 Maven:
# 下载 Apache Maven 3.9.x(最新稳定版) cd /tmp wget https://downloads.apache.org/maven/maven-3/3.9.6/binaries/apache-maven-3.9.6-bin.tar.gz # 解压到 /opt 目录 sudo tar -xzf apache-maven-3.9.6-bin.tar.gz -C /opt/ # 创建软链接,方便后续升级 sudo ln -sf /opt/apache-maven-3.9.6 /opt/maven # 配置环境变量 echo 'export MAVEN_HOME=/opt/maven' | sudo tee -a /etc/profile.d/maven.sh echo 'export PATH=$MAVEN_HOME/bin:$PATH' | sudo tee -a /etc/profile.d/maven.sh sudo chmod +x /etc/profile.d/maven.sh source /etc/profile.d/maven.sh # 验证 mvn -v接着,在 Jenkins Web UI 中注册这个 Maven:
- 点击左上角“Manage Jenkins” > “Tools” > “Maven installations...”。
- 点击“Add Maven”。
- 在 “Name” 中输入
maven-3.9.6。 - 在 “MAVEN_HOME” 中输入
/opt/maven。 - 点击“Save”。
现在,Maven 已成为 Jenkins 的一个“全局工具”,任何 Pipeline 都可以通过tools { maven 'maven-3.9.6' }来声明使用它。
4.3 构建一个真实的 Java Spring Boot 项目
假设你有一个托管在 GitHub 上的 Spring Boot 项目,地址是https://github.com/yourname/demo-springboot.git。我们将创建一个 Pipeline 来克隆它、编译、打包成 JAR,并验证其可运行性。
创建一个新的 Pipeline 项目,命名为demo-springboot-build。在 Pipeline 配置中,选择 “Pipeline script from SCM”(从源码管理中获取脚本),这是最佳实践,意味着Jenkinsfile就放在你的代码仓库根目录下,而不是写在 Jenkins 配置里。
- SCM: 选择 “Git”。
- Repository URL: 输入
https://github.com/yourname/demo-springboot.git。 - Credentials: 点击右侧的 “Add” 按钮,选择 “Jenkins”,在 “Kind” 中选择 “Username with password”,输入你的 GitHub 用户名和 Personal Access Token(PAT)。切勿使用密码!GitHub 已弃用密码认证,必须使用 PAT。
- Branches to build: 输入
*/main(或*/master,取决于你的默认分支)。 - Script Path: 保持默认
Jenkinsfile。
现在,你需要在你的demo-springboot仓库根目录下创建一个Jenkinsfile。内容如下:
pipeline { agent any tools { maven 'maven-3.9.6' jdk '17' // 这里需要先在 Jenkins 中配置 JDK 工具,见下文 } environment { // 定义环境变量,供后续步骤使用 APP_NAME = 'demo-springboot' BUILD_NUMBER = "${BUILD_NUMBER}" } stages { stage('Checkout') { steps { checkout scm sh 'ls -la' } } stage('Build') { steps { sh 'mvn clean compile -B' } } stage('Test') { steps { sh 'mvn test -B' } } stage('Package') { steps { sh 'mvn package -DskipTests -B' // 验证 JAR 文件是否生成 sh 'ls -la target/*.jar' } } stage('Verify') { steps { // 尝试启动 JAR(不阻塞,后台运行) sh 'nohup java -jar target/*.jar > /dev/null 2>&1 &' // 等待 10 秒让应用启动 sh 'sleep 10' // 检查进程是否存在 sh 'ps aux | grep java | grep demo-springboot' // 发送 HTTP 请求验证健康端点(假设应用有 /actuator/health) sh 'curl -f http://localhost:8080/actuator/health || exit 1' } } } post { success { echo "Build and verification succeeded for ${APP_NAME}!" } failure { mail to: 'admin@yourcompany.com', subject: "FAILED: ${APP_NAME} build #${BUILD_NUMBER}", body: "Check the console output at ${env.BUILD_URL}console" } } }这个Jenkinsfile展示了现代 CI 的核心思想:
agent any:在任意可用的 Agent 上执行(目前只有 Master)。tools:声明使用哪个 Maven 和 JDK 版本,Jenkins 会自动为你配置好PATH和JAVA_HOME。environment:定义流水线级别的环境变量。stages:清晰划分构建生命周期:检出、编译、测试、打包、验证。post:构建后的处理,成功发通知,失败发告警邮件。
要让jdk '17'生效,你还需要在 Jenkins 中配置 JDK 工具:
- “Manage Jenkins” > “Tools” > “JDK installations...” > “Add JDK”。
- Name:
17。 - JAVA_HOME:
/usr/lib/jvm/java-17-openjdk-amd64(与你之前设置的JAVA_HOME一致)。
保存这个Jenkinsfile并推送到 GitHub,然后在 Jenkins 中点击 “Build Now”。你会看到一个完整的、从零开始的 Java 项目构建流水线,它包含了编译、单元测试、打包、甚至简单的健康检查。这才是 Continuous Integration 的真实模样——每一次代码提交,都自动触发一套标准化的验证流程。
5. 进阶实战:将构建产物部署到远程服务器,打通 CI 到 CD 的最后一公里
CI(持续集成)的终点是生成一个可靠的、经过测试的构建产物(Artifact),比如一个.jar文件或一个.tar.gz包。而 CD(持续交付/部署)的起点,就是把这个产物安全、可靠地放到目标服务器上,并启动它。在 Ubuntu 22.04 环境下,最常用、最安全的方式是使用 SSH 和scp。但这背后有一系列必须解决的细节问题,否则你的流水线会在部署阶段功亏一篑。
5.1 在 Jenkins 中安全地管理远程服务器凭证
Jenkins 的 Credentials Plugin 是管理敏感信息的唯一正确方式。你绝不能在Jenkinsfile里硬编码用户名和密码,也不能把私钥文件直接cat进脚本。正确的流程是:
- 生成 SSH 密钥对:在 Jenkins Master 服务器上,为
jenkins用户生成一个专用的、无密码的密钥对(仅用于部署)。
# 切换到 jenkins 用户(注意:不是 sudo -u jenkins,因为 jenkins 用户默认没有 shell) sudo su -s /bin/bash jenkins # 生成密钥(-N '' 表示空密码,-f 指定文件名) ssh-keygen -t rsa -b 4096 -N '' -f ~/.ssh/id_rsa_jenkins_deploy # 查看公钥内容(需要复制到目标服务器) cat ~/.ssh/id_rsa_jenkins_deploy.pub将公钥部署到目标服务器:登录到你的目标服务器(例如
app-server),将上面cat出来的公钥内容,追加到~/.ssh/authorized_keys文件中。这一步确保了jenkins用户可以从 Master 无密码登录到目标服务器。在 Jenkins 中添加凭证:回到 Jenkins Web UI,“Manage Jenkins” > “Credentials” > “System” > “Global credentials (unrestricted)” > “Add Credentials”。
- Kind:
SSH Username with private key - Scope:
Global (Jenkins, nodes, items, all child items) - ID:
deploy-to-app-server(这个 ID 将在 Pipeline 中引用) - Username:
deploy-user(你在目标服务器上创建的、专门用于部署的普通用户,不要用 root) - Private Key: 选择 “From the Jenkins master ~/.ssh” 并输入路径
/var/lib/jenkins/.ssh/id_rsa_jenkins_deploy
- Kind:
注意:
/var/lib/jenkins/.ssh/id_rsa_jenkins_deploy是jenkins用户的私钥文件路径。Jenkins 的 Credentials Plugin 会安全地加密存储这个私钥,并在 Pipeline 执行时,将其注入到sh步骤的环境中,供ssh和scp命令使用。
5.2 编写部署阶段的 Pipeline 脚本
现在,我们来扩展之前的demo-springboot-buildPipeline,在Package阶段之后,增加一个Deploy阶段。修改Jenkinsfile的stages部分:
stage('Deploy') { steps { script { // 获取构建产物的绝对路径 def jarFile = sh( script: 'find target -name "*.jar" -type f | head -n 1', returnStdout: true ).trim() if (jarFile == '') { error "No JAR file found in target/ directory!" } // 使用 Credentials Plugin 中的凭证进行 SCP 传输 sh """ scp -o StrictHostKeyChecking=no \ -i /var/lib/jenkins/.ssh/id_rsa_jenkins_deploy \ ${jarFile} deploy-user@app-server:/home/deploy-user/app/ """ // 登录到目标服务器,停止旧进程,启动新 JAR sh """ ssh -o StrictHostKeyChecking=no \ -i /var/lib/jenkins/.ssh/id_rsa_jenkins_deploy \ deploy-user@app-server " cd /home/deploy-user/app && # 如果有旧进程,先 kill pkill -f demo-springboot.jar || echo 'No old process running.' && # 启动新 JAR,后台运行,并重定向日志 nohup java -jar $(basename ${jarFile}) > app.log 2>&1 & " """ } } }这段脚本的关键点在于:
script { ... }块允许你在 Pipeline 中执行复杂的 Groovy 逻辑,比如动态查找文件。sh步骤中的scp和ssh命令,都显式指定了-i参数,指向jenkins用户的私钥文件。这绕过了 Jenkins Credentials Plugin 的“自动注入”机制,因为我们已经将凭证作为文件安全地存放在了 Jenkins 的文件系统中,这种方式更可控、更透明。ssh命令内部是一个完整的 Bash 脚本,它在目标服务器上执行一系列操作:进入目录、杀死旧进程、启动新进程。pkill -f是一个安全的进程终止方式,它根据进程命令行参数(-f)来匹配,比killall更精准,避免误杀。
5.3 部署后的健康检查与回滚预案
一个健壮的 CD 流程,不能只关注“部署成功”,更要关注“部署后是否真的可用”。因此,Deploy阶段之后,应该紧接着一个Health Check阶段:
stage('Health Check') { steps { script { // 循环检查,最多等待 60 秒 for (int i = 0; i < 12; i++) { try { // 向目标服务器的应用健康端点发送请求 sh "curl -f http://app-server:8080/actuator/health" echo "Application is healthy!" break } catch (Exception e) { echo "Health check failed, waiting 5 seconds... (${i+1}/12)" sleep(5) } } } } }这个循环会每隔 5 秒向app-server:8080/actuator/health发送一次 HTTP GET 请求。如果返回 HTTP 200,说明应用已成功启动并响应。如果 60 秒后仍未成功,则整个 Pipeline 失败,你可以收到告警,并手动介入。
至于回滚,这是一个更高阶的话题。最简单的回滚方案,就是在Deploy阶段,先将旧的 JAR 文件备份,再覆盖新文件:
# 在 ssh 命令中加入备份逻辑 ssh ... " cd /home/deploy-user/app && mv demo-springboot.jar demo-springboot.jar.bak.$(date +%Y%m%d_%H%M%S) && nohup java -jar $(basename ${jarFile}) > app.log 2>&1 & "这样,如果新版本出问题,你只需登录服务器,mv demo-springboot.jar.bak.xxx demo-springboot.jar,然后pkill旧进程,再nohup java -jar ...启动备份版本即可。自动化回滚需要更复杂的版本管理和配置中心,那是另一个深度话题了。
6. 故障排查手册:Ubuntu 22.04 上 Jenkins 最常见的五个“拦路虎”及其根治方案
即使你严格按照上述步骤操作,也难免会遇到一些意料之外的问题。这些不是 Jenkins 的 Bug,而是 Ubuntu 22.04 环境与 Jenkins 运行时之间微妙的摩擦。我把它们总结为五大“拦路虎”,并给出每一种的根治方案,而非临时 workaround。
6.1 拦路虎一:Failed to resolve host name mirrors.tuna.tsinghua.edu.cn
这个错误信息非常具体,但它指向的不是一个单一问题,而是一个DNS 解析链路的全线崩溃。它可能发生在 Jenkins 启动时(拉取插件)、在 Pipeline 的sh步骤中(apt update)、甚至在git clone时。根本原因通常是 Ubuntu 22.04 的systemd-resolved服务与网络管理器(NetworkManager)之间的配置冲突。
根治方案:绕过systemd-resolved,直接配置/etc/resolv.conf。
# 停止并禁用 systemd-resolved sudo systemctl stop systemd-resolved sudo systemctl disable systemd-resolved # 删除符号链接,创建新的 resolv.conf sudo rm /etc/resolv.conf echo "nameserver 114.114.114.114" | sudo tee /etc/resolv.conf echo "nameserver 8.8.8.8" | sudo tee -a /etc/resolv.conf # 重启网络服务 sudo systemctl restart NetworkManager114.114.114.114是国内最快的公共 DNS 之一,8.8.8.8是 Google DNS 作为备用。这样配置后,所有系统进程(包括 Jenkins 的 Java 进程)都会使用这两个 DNS 服务器,mirrors.tuna.tsinghua.edu.cn的解析将瞬间变得稳定。
6.2 拦路虎二:Permission denied (publickey)在部署阶段
这个错误表明 Jenkins 的jenkins用户无法通过 SSH