
前阵子有朋友问我写好的Java代码每次都要自己打包、传到服务器、手动重启有没有什么办法让代码一提交就自动完成构建部署我当时的回答很简单——你缺的是一套持续集成环境而绝大多数团队接触到的第一个工具就是Jenkins。这篇教程不是从官方文档翻译过来的那种而是把我实际搭 Jenkins 的经验、踩过的坑、以及推荐的做法整理出来。适合刚接触持续集成的开发、测试、运维也适合那些已经装好了 Jenkins 但一直停留在创建任务 - 手动点构建阶段的人。看完你能明白 Jenkins 到底解决什么问题、怎么装、怎么配、怎么用以及怎么避掉最常见的坑。1. 为什么团队迟早会需要一个叫 Jenkins 的东西1.1 从一次手动构建翻车说起我见过很多小团队的开发流程是这样的代码在 GitLab 上每个人在自己电脑上写代码写完推到主干分支然后由某个人手工登录服务器执行编译打包、上传部署包、重启应用。刚开始项目小这套流程勉强能跑通。但一旦人多起来问题就来了。有次周五下午同事改完代码推上去结果忘了把打包好的文件传到生产服务器等到周一线上才发现还是旧版本。还有一次两个后端同时改了同一个模块直接在构建环境里互相覆盖了配置部署上去之后接口报错排查了大半天。这类问题本质上不是谁忘了操作而是整个流程依赖人工记忆和手动操作必然会在某个环节出漏子。持续集成要解决的核心问题就是把这些人为容易忘记、容易做错的动作变成一条自动化的流水线代码提交到仓库构建系统自动拉取最新代码编译、跑测试、出包甚至自动部署到目标环境然后把结果反馈给相关的人。1.2 持续集成的最小闭环持续集成Continuous IntegrationCI的完整闭环可以拆成四步触发有人推代码、合并分支、打标签或者定时执行系统感知到变化并启动一次构建构建把源码编译成可运行的产物比如 jar、war、前端静态文件同时执行单元测试和静态检查反馈构建成功或失败通过邮件、钉钉、企业微信等渠道通知到相关人部署可选构建产物自动发布到测试环境或生产环境这四步里每一步都有可以做文章的地方。但最基础、也是大多数团队最迫切的需求是触发 构建 反馈这三步。部署可以先从手动点按钮开始等跑顺了再追求全自动。1.3 Jenkins 在这个体系里的位置做持续集成的工具很多有 GitLab CI、GitHub Actions、Travis CI 等但 Jenkins 在私有化部署场景下依然是使用率最高的那一档。原因有三个插件生态非常庞大Git、GitLab、Maven、Docker、SSH 部署基本上都有现成插件部署环境要求低一台 2G 内存的虚拟机就能跑起来不需要额外依赖云服务灵活度高从最简单的图形界面任务到 Pipeline 流水线再到分布式构建都能一步步平滑升级一个小项目和一个大型技术团队可以共用同一套技能栈Jenkins 本身不代替你写代码也不代替你部署它更像是调度中心把代码仓库、构建工具、测试工具、服务器连接起来按你编排好的顺序执行。理解了这个定位后面学起来会顺很多。2. CentOS 7.9 装 Jenkins先处理 JDK 版本兼容问题2.1 版本对应关系不是越新越好很多人在安装这一步就卡住了。最常见的问题服务器上用的是 JDK 1.8项目也规定只能用 JDK 1.8结果一装最新版 Jenkins 就启动失败。这不是你操作有问题是 Jenkins 新版本对 Java 运行时有硬性要求。Jenkins 本身是 Java 写的它的 war 包需要跑在 JVM 上。我自己用下来版本选择和 Java 版本的对应关系大致是这样的Jenkins 版本需要的 Java 运行版本说明2.346.x 及更早的 LTSJava 8 / 11 / 17老项目的首选也是 JDK 1.8 环境能跑的最后一个大版本系列2.357 及之后Java 11 或 17官方不再支持 Java 8 运行 Jenkins2.361.1 之后的 LTSJava 11 或 17当前主流的长期支持版本线如果你的服务器业务代码必须用 JDK 1.8那最省事的方式是装2.346.3这个版本。它虽然是 2022 年左右的版本但作为 LTS 版本社区支持周期很长插件兼容性也验证得很充分对学习和小团队使用完全够。2.2 指定版本 RPM 安装的完整过程我这里拿 CentOS 7.9 举例因为这是生产环境里非常常见的一个系统版本。整个过程分三步。第一步装 JDK 1.8yum install -y java-1.8.0-openjdk-devel java -version第二步下载指定版本的 Jenkins RPM 包并安装cd /opt wget https://get.jenkins.io/redhat-stable/jenkins-2.346.3-1.1.noarch.rpm rpm -ivh jenkins-2.346.3-1.1.noarch.rpm如果你用的是新服务器也可以直接用 yum 方式安装最新版sudo wget -O /etc/yum.repos.d/jenkins.repo https://pkg.jenkins.io/redhat-stable/jenkins.repo sudo rpm --import https://pkg.jenkins.io/redhat-stable/jenkins.io-2023.key yum install -y jenkins但注意这条命令拉下来的默认是最新的 LTS要求 Java 11 以上。所以如果你必须用 JDK 1.8别偷懒请走上面指定 RPM 包的方式。第三步启动服务并设置开机自启systemctl daemon-reload systemctl start jenkins systemctl enable jenkins systemctl status jenkins看到active (running)就说明 Jenkins 已经跑起来了。默认端口是 8080用浏览器访问http://服务器IP:8080会进入一个解锁页面需要输入初始管理员密码。2.3 一次启动失败的排查示例有次帮朋友排查一台机器安装步骤完全一样但systemctl status jenkins显示启动失败。我第一反应是看日志journalctl -u jenkins --no-pager | tail -50日志里明确写着Unsupported Java version。原来那台机器虽然java -version显示的是 JDK 1.8但是JAVA_HOME没设置Jenkins 的启动脚本自己去找找到了系统自带的 OpenJDK 11。解决办法是在/etc/init.d/jenkins里手动指定 Java 路径vi /etc/init.d/jenkins # 找到 candidates 这个变量把完整 JDK 路径加进去 # 例如/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.362.b08-0.el7_9.x86_64所以提醒一句安装 Jenkins 之前先确认$JAVA_HOME和which java指向的是同一个版本。这是新手最容易忽略、但排查起来最费时间的一个点。初始密码的位置cat /var/lib/jenkins/secrets/initialAdminPassword登录进去之后建议立刻在 Manage Jenkins - Users 里创建自己的管理员账号然后在全局安全配置里把匿名用户可读关掉避免服务器暴露在公网时被陌生人看到任务和构建日志。3. 装完先别急着建任务插件、凭据、全局工具配置3.1 插件最小集合够用比全家桶省钱Jenkins 安装完成后首页会提示你安装推荐插件列表。很多人图省事直接全选结果插件装了一大堆页面卡、启动慢而且大部分根本用不上。我更建议按自己的实际需求装最小集合大概是这几个插件作用是否必装Git Plugin从 Git 仓库拉取源码必装GitLab Plugin与 GitLab 集成支持 Webhook 触发和 MR 状态回写建议装Credentials Plugin管理账号密码、SSH Key 等凭据通常自带Maven Integration Plugin支持在任务里直接调用 Maven 构建Maven 项目必装Pipeline支持声明式流水线新版自带Publish Over SSH通过 SSH 把构建产物传到远程服务器并执行命令需要远程部署时必装DingTalk / Email Extension构建结果通知按团队习惯选装安装路径是Manage Jenkins - Plugins - Available plugins搜索名字勾选后点安装。插件之间可能会有版本依赖Jenkins 会自己处理你不用太操心。3.2 凭据管理把 GitLab 的访问权安全交给 JenkinsJenkins 要拉取 GitLab 上的私有仓库必须有访问权限。很多新手直接在任务里填 GitLab 账号密码这样做有两个问题一是密码明文存在任务配置里团队里谁有点权限都能看到二是如果账号密码改了所有引用了这个账号的任务全部要重新改。正确做法是在Manage Jenkins - Credentials - Global里添加一条凭据。常见两种类型Username with password填 GitLab 用户名和密码或者个人访问令牌Personal Access TokenSSH Username with private key生成一对 SSH Key公钥加到 GitLab 账号的 SSH Keys 里私钥填到 Jenkins我实际使用中更推荐 SSH Key 方式尤其是 Jenkins 和 GitLab 走内网通信时SSH 方式少了一层 HTTP 层面的鉴权干扰稳定性更好。创建凭据的时候不要忘记把ID填成一个自己能看懂的名字比如gitlab-ssh-key后面在任务和 Pipeline 里要用这个 ID 去引用凭据。3.3 全局工具配置JDK、Git、Maven 的路径怎么填在Manage Jenkins - Tools里可以配置各种构建工具。核心是 JDK、Git、Maven 这三个。JDK 配置建议选本地安装把JAVA_HOME填进去。比如/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.362.b08-0.el7_9.x86_64不推荐用 Jenkins 的自动安装因为自动安装默认从 Oracle 或 Adoptium 下载国内网络环境下下载慢而且 Oracle 的下载还需要账号很容易卡住。本地装什么就配什么简单直接。Git 的配置更简单如果 Git 已经在系统 PATH 里填git可执行文件的绝对路径即可比如/usr/bin/git。Maven 同理填/usr/local/apache-maven-3.8.8这种路径。注意还要配一个 Maven 的 settings.xml里面加上国内镜像仓库不然打包时下载依赖会慢到你怀疑人生。这一步的配置直接影响后面的构建任务配错了通常会在拉代码或者执行 Maven 构建时报错信息很明确按错误提示回到 Tools 里改路径就行。4. 跑通第一个自动化部署任务4.1 用 Freestyle 任务先跑通一条完整链路对刚入门的人来说不要一上来就学 Pipeline先用 Freestyle project 把整条链路跑通理解各个配置项的含义后面再看 Pipeline 就会很容易。我拿一个典型的 Java Maven 项目举例。在 Jenkins 首页点 新建任务输入任务名选 Freestyle project进入配置页。左边是源码管理选 Git填上仓库地址。比如http://gitlab.local/dev/demo-api.git然后选凭据指定分支可以是*/main或者*/develop。如果你希望每次构建时能临时指定分支可以先写成*/main后面再加参数化构建。再往下是构建区域点增加构建步骤选 Invoke top-level Maven targetsMaven 版本选系统里配置好的那个Goals 填clean package -DskipTests这样可以跳过单元测试先保证一键出包。等你熟悉之后建议把-DskipTests去掉让测试作为构建的一部分跑起来这才是持续集成的意义。最后是构建后操作大多数人的第一个需求是把构建产物归档起来。点击增加构建后操作步骤选 Archive the artifacts填写target/*.jar保存之后回到任务页面点立即构建第一次构建需要下载依赖可能比较慢。等构建结束后点击构建历史里的那个构建号能看到完整控制台日志包括 Maven 的输出、Git 的提交信息、构建产物的归档记录。4.2 参数化构建与触发方式在任务配置页勾选参数化构建过程可以添加一个String Parameter比如名字叫BRANCH默认值main。这样在点击立即构建时会弹出一个输入框你可以临时指定要构建的分支。参数化构建的价值在于同一套任务可以服务于多个分支的验证不需要为每个分支单独创建任务。这个能力在多人协作、多分支并行开发时非常实用。再进一步如果你想实现代码一 pushJenkins 自动构建需要两步在构建触发器里勾选Build when a change is pushed to GitLab把生成的 GitLab Webhook URL 和 Secret Token 填到 GitLab 项目的Settings - Webhooks里这样当有代码提交到指定分支时GitLab 会主动通知 Jenkins 触发构建真正实现自动化。4.3 用同样的思路做一个 Python 项目的持续集成很多团队后端是 Python不是 Java。Python 项目的持续集成和 Java 的差别主要在构建命令上。在 Freestyle 任务的构建区域选择 Execute shell填写python3 -m venv venv source venv/bin/activate pip install -r requirements.txt -i https://mirrors.aliyun.com/pypi/simple/ pytest tests/ -q构建后操作可以选择归档测试报告或者通过 SSH 把代码同步到目标服务器并执行重启命令。整体逻辑和 Java 项目完全一致只是把 Maven 命令换成了 Python 命令。热词里经常有人搜 python持续集成部署其实核心套路就是这四行建虚拟环境、装依赖、跑测试、执行部署脚本。如果你用到的是 Django 项目部署步骤通常是在服务器上拉代码、执行 migrate、重启 gunicorn 或 uwsgi。这些命令都可以写进构建后操作里或者写成服务器上的一个 shell 脚本Jenkins 只负责远程触发它。5. Pipeline 才是长期可维护的玩法5.1 Freestyle 任务到后期为什么让人抓狂Freestyle 任务有几个明显的短板。首先所有配置都保存在 Jenkins 的 Web 界面里任务复杂之后想通过代码审查去追踪配置变更几乎不可能。其次当你有多个环境、多个项目时复制任务改配置很容易漏改一处导致生产部署用了测试环境的参数。Pipeline 的思路是把构建流程写成一个Jenkinsfile文件放在项目仓库里和代码一起维护。这样构建逻辑是代码化的可以走 MR 审查、可以看历史 diff、可以在不同任务之间复用。虽然入门成本比 Freestyle 高一点但一旦你有了多个项目这个成本回本非常快。5.2 一个最小声明式 Pipeline 逐段拆解声明式 Pipeline 是当前官方推荐的方式语法好理解结构也清晰。下面这个例子是一个 Java 项目的完整 pipeline要求项目根目录下存在Jenkinsfilepipeline { agent any options { timestamps() disableConcurrentBuilds() } environment { APP_NAME demo-api BRANCH ${params.BRANCH} } parameters { string(name: BRANCH, defaultValue: main, description: 要构建的分支) } stages { stage(拉取代码) { steps { checkout([$class: GitSCM, branches: [[name: ${params.BRANCH}]], userRemoteConfigs: [[url: http://gitlab.local/dev/demo-api.git, credentialsId: gitlab-ssh-key]] ]) } } stage(Maven打包) { steps { sh mvn clean package -DskipTests } } stage(远程部署) { steps { sshPublisher(publishers: [ sshPublisherDesc(configName: prod-server, transfers: [ sshTransfer(sourceFiles: target/*.jar, removePrefix: target/, execCommand: sh /opt/deploy/restart.sh) ]) ]) } } } post { success { echo ${APP_NAME} 构建成功 } failure { echo ${APP_NAME} 构建失败请检查日志 } } }逐段解释一下agent any让这个流水线在任意一个可用的构建节点上执行optionstimestamps()给日志加时间戳disableConcurrentBuilds()防止同一个任务同时并发构建避免互相覆盖 workspaceenvironment定义流水线内可见的环境变量注意这里的BRANCH引用了构建参数parameters定义这条流水线支持的构建参数构建时手动指定分支stages核心阶段列表每个stage是一个逻辑步骤post构建结束后的回调成功和失败分别输出不同的提示把这些内容写进项目仓库根目录的Jenkinsfile然后在 Jenkins 里创建一个 Pipeline 类型的任务源码管理里填上仓库地址和凭据流水线定义选Pipeline script from SCM脚本路径填Jenkinsfile保存后点击构建效果和 Freestyle 任务一样但所有的流程逻辑都在仓库里可审查、可追溯。5.3 环境变量与构建参数的传递实践Jenkins 自带一批环境变量比如BUILD_NUMBER构建号、JOB_NAME任务名、WORKSPACE工作目录、GIT_COMMIT当前构建的 commit hash。这些变量可以在 Pipeline 里通过${env.BUILD_NUMBER}访问也可以在 Freestyle 的 shell 里通过$BUILD_NUMBER直接用。常用的几个我整理了一张表环境变量含义使用场景WORKSPACE当前构建的工作目录定位源码路径、构建产物位置BUILD_NUMBER当前构建的编号生成版本号、归档名JOB_NAME当前任务名称日志展示、通知文案GIT_COMMIT当前构建对应的 commit hash回滚、版本记录BRANCH_NAME当前分支名多分支流水线里做环境区分在 Pipeline 里如果你定义了一个构建参数ENV在 stage 中引用时要注意写法。比如你想执行mvn package -P${ENV}直接写sh mvn package -P${ENV}是不行的因为 shell 会把ENV当作 shell 变量。正确写法是sh mvn package -P${params.ENV}用双引号把整个命令包起来让 Groovy 先解析${params.ENV}再交给 shell 执行。这个细节我第一次写流水线时踩过日志里报的是Bad substitution排查了半天才反应过来是引号问题。6. 部署后的高频问题与排查思路6.1 Jenkins 和 GitLab 能不能跑在同一台主机的 Docker 里可以而且很多预算不多的小团队就是这么干的。两个服务都在同一台物理机或虚拟机上的 Docker 容器里只要端口不冲突、网络能互通完全没有问题。实际部署时要关注三件事。第一端口映射。GitLab 默认占 80、443Jenkins 默认 8080如果这台机器上已经跑了 Nginx 或其他 Web 服务需要调整宿主机的端口映射比如把 GitLab 的 HTTP 端口映射成8929:80把 Jenkins 映射成18080:8080。第二容器网络。建议创建一个自定义 Docker 网络GitLab 和 Jenkins 都加入这个网络容器之间可以用服务名互相访问比如 Jenkins 拉代码时填http://gitlab:8929/dev/demo-api.git。第三资源。GitLab 官方建议内存至少 4GJenkins 最低 1G再加上系统本身的消耗这台机器建议至少 8G 内存否则跑一阵子就会卡到没法用。我见过一个典型的问题是Jenkins 容器里通过 SSH 方式访问 GitLab 容器的 22 端口失败。原因是 GitLab 默认把 SSH 端口也做了映射比如映射成宿主机2222:22但在同一个 Docker 网络里Jenkins 访问的应该是 GitLab 容器的 22 端口本身这个链路和外部访问不一样所以要在 GitLab 的仓库 URL 配置里区分内网地址和外网地址或者干脆 Jenkins 拉代码走 HTTP 方式通过凭据认证能绕开很多 SSH 端口的烦恼。6.2 只读权限、匿名访问和权限锁死怎么破很多人搜 jenkins只读权限这个关键词多半是遇到了这两类情况。第一类是以匿名用户打开 Jenkins 页面只能看任务列表不能看构建日志更不能构建。这是因为全局安全配置里的授权策略默认是登录用户可以做任何事匿名用户只有读权限。如果你希望团队成员登录后能构建而没登录的人什么都看不到可以在Manage Jenkins - Security - Authorization里选择Logged-in users can do anything然后取消Allow anonymous read access。第二类是用角色权限插件如 Role-based Strategy后给用户分配了Overall/Read和Job/Read但没有给Job/Build权限导致登录后任务一行字都改不了、构建按钮也点不动。这种情况不是 Jenkins 坏了是权限矩阵缺少对应项去权限配置里加上Job/Build、Job/Configure就行。我自己还遇到过更尴尬的场景在全局安全配置里手滑把自己唯一的管理员账号也锁了所有人登录后都变成只读。恢复的办法是停止 Jenkins编辑/var/lib/jenkins/config.xml把authorizationStrategy节点改回默认配置再重启。具体步骤如下systemctl stop jenkins vi /var/lib/jenkins/config.xml # 找到类似这样的内容 # authorizationStrategy classhudson.security.FullControlOnceLoggedInAuthorizationStrategy # 确认 class 是这个然后保存退出 systemctl start jenkins这个修法的核心是让 Jenkins 回到只要登录就有全部权限的初始状态。改完以后重新配置权限别再把自己锁外面。6.3 磁盘膨胀、时区显示和中文乱码这些小病这三个问题看起来小但几乎每个 Jenkins 实例都会遇到。磁盘膨胀的本质是 Jenkins 默认不会清理历史构建的工作空间和归档产物。一个 Java 项目每次构建产生几十上百 MB 的 jar跑一年下来轻轻松松占满几十 G。解决办法是在 Freestyle 任务配置里勾选丢弃旧的构建保留最近 10 次、最近 30 天在 Pipeline 里可以这样写options { buildDiscarder(logRotator(numToKeepStr: 10, daysToKeepStr: 30)) cleanWs() }buildDiscarder控制保留数量和天数cleanWs()在每次构建后清空工作目录不给磁盘埋雷。时区显示问题通常出现在 Docker 部署的 Jenkins 上日志时间比北京时间慢 8 小时。这是因为容器默认时区是 UTC。解决办法是在启动 Jenkins 容器时设置环境变量TZAsia/Shanghai或者在 Jenkins 的启动参数里加上-Duser.timezoneAsia/Shanghai。中文乱码问题多在 Maven 构建或 shell 输出中出现。原因是系统默认编码不是 UTF-8。可以在Manage Jenkins - System里设置全局属性JAVA_TOOL_OPTIONS-Dfile.encodingUTF-8同时在执行 shell 时检查/etc/locale.conf里的LANG是否包含UTF-8。关于这套基础流程我最后想多说两句这套东西我前前后后帮好几个团队搭过。每次搭完都会发现真正让团队效率提升的不是 Jenkins 本身装得多花哨而是把谁手动做了什么这件事彻底自动化并且让每次构建的结果都有迹可循。学 Jenkins 有一个比较顺的路径先手动装一次再用 Freestyle 任务跑通一个项目然后把任务升级成 Pipeline最后再研究多分支、分布式构建这些进阶功能。你现在看到的那一堆复杂配置都是从这个最简单的链路长出来的。如果你在实践过程中卡在哪一步建议先看构建日志日志里的报错通常比界面上的提示更有价值。另外刚搭好环境的头一周别忘了配好构建结果通知否则自动化构建失败了没人知道那才是真正麻烦的开始。