
1. 环境搭建的整体思路与方案选型1.1 为什么选择 Docker 安装 Jenkins很多人在搭建持续集成环境时第一反应是在服务器上直接安装 Jenkins 的 war 包或者用系统包管理器安装。我早期也这么干过结果就是版本升级麻烦、依赖冲突频发、迁移环境时各种配置文件散落各处。后来全面转向 Docker 方式部署核心原因有三个环境隔离彻底、版本管理清晰、迁移复现简单。Docker 安装 Jenkins 的本质是把 Jenkins 及其运行所需的 Java 环境打包在一个容器里宿主机只需要有 Docker 引擎即可。这样做的好处是无论你的服务器是哪种 Linux 发行版只要 Docker 版本一致Jenkins 的运行表现就基本一致。对于团队协作来说这意味着新同事入职时不需要花半天时间折腾环境一条docker run命令就能拉起一套可用的 CI 服务。另一个容易被忽视的点是数据持久化。Jenkins 的所有任务配置、构建历史、插件信息都存放在/var/jenkins_home目录下。用 Docker 部署时我们会把这个目录挂载到宿主机的某个路径这样即使容器被删除重建数据依然完好。这个设计在后续升级 Jenkins 版本时特别有用——直接拉取新镜像、用同样的挂载参数重新启动即可。1.2 GitHub 与 SpringBoot 的集成逻辑整个流程的核心链路是这样的本地开发好 SpringBoot 项目后推送到 GitHub 仓库Jenkins 监听到代码变更或者手动触发构建从 GitHub 拉取最新代码然后用 Maven 打包成可执行的 jar 文件最后通过脚本把 jar 部署到目标服务器上运行。这里有一个关键决策点是让 Jenkins 直接部署到本机还是部署到远程服务器手动部署阶段我建议先把 Jenkins 和目标运行环境放在同一台机器上减少变量。等整个流程跑通之后再考虑拆分。因为初期涉及 SSH 免密、文件传输、远程命令执行等环节任何一环出问题都会让你排查到怀疑人生。GitHub 这边需要配置一个 Personal Access Token用于 Jenkins 拉取代码时的身份认证。虽然公开仓库可以匿名拉取但实际项目中基本都是私有仓库所以 Token 是必须的。Token 的权限范围建议只勾选repo相关项遵循最小权限原则。1.3 手动部署阶段的价值可能有人会问现在都有各种自动化部署工具了为什么还要手动部署我的看法是手动部署是理解整个 CI/CD 链路的最佳途径。当你亲手经历过从代码提交到服务运行的每一个环节你才能真正理解自动化工具到底帮你做了什么出了问题也知道从哪个环节入手排查。手动部署阶段我们需要自己写构建脚本、自己处理 jar 包的启动和停止、自己管理日志文件。这些看似繁琐的工作恰恰是后续实现全自动化部署的基础。我见过太多人直接上全自动方案结果构建失败时完全不知道从哪里查起因为中间的黑盒太多了。提示手动部署不是目的而是手段。建议至少完整走通两遍手动流程把每个环节的命令和参数都记录下来这些记录就是后续写自动化脚本的素材。2. 核心环境准备与关键细节解析2.1 Docker 环境的前置检查在开始之前先确认宿主机上的 Docker 是否就绪。执行docker version能看到 Client 和 Server 两部分信息说明 Docker 引擎正常运行。如果只显示 Client 信息那大概率是 Docker 服务没启动用systemctl start docker拉起即可。还有一个容易被忽略的点是 Docker 的镜像加速配置。默认情况下拉取 Docker Hub 的镜像速度可能很慢建议在/etc/docker/daemon.json中配置国内镜像源。这个文件如果不存在就新建内容格式如下{ registry-mirrors: [https://your-mirror.example.com] }配置完成后需要执行systemctl daemon-reload和systemctl restart docker让配置生效。这一步不是必须的但能显著提升后续拉取 Jenkins 镜像的速度。另外要检查宿主机的磁盘空间。Jenkins 镜像本身加上后续的构建产物、Maven 依赖缓存很容易占用几个 GB 的空间。用df -h看一下/var/lib/docker所在分区的剩余空间建议至少保留 20GB 以上。2.2 Jenkins 镜像的版本选择Jenkins 官方提供了两个主要的镜像标签jenkins/jenkins:lts和jenkins/jenkins:latest。生产环境强烈建议用lts版本也就是长期支持版稳定性经过更多验证。latest版本虽然功能最新但偶尔会有插件兼容性问题。还有一个细节是 JDK 版本。较新的 Jenkins LTS 版本默认基于 JDK 11 或 JDK 17而你的 SpringBoot 项目可能用的是 JDK 8。这本身不冲突因为 Jenkins 容器内的 JDK 只用于运行 Jenkins 本身构建 SpringBoot 项目时用的是 Maven 配置的 JDK。但如果你打算在 Jenkins 容器内直接执行构建就需要确保容器内的 JDK 版本能满足项目要求或者通过挂载宿主机的 JDK 来解决。我个人的做法是Jenkins 容器只负责调度和拉取代码实际的 Maven 构建放在宿主机上执行或者用独立的构建容器。这样职责清晰也避免了容器内环境与项目要求不匹配的问题。不过在手动部署的入门阶段为了简化流程先直接在 Jenkins 容器内完成构建也是可以接受的。2.3 目录挂载的规划启动 Jenkins 容器时需要规划好挂载目录。我通常会在宿主机上创建这样一个目录结构mkdir -p /data/jenkins/home chown -R 1000:1000 /data/jenkins/home这里把宿主机目录的属主改为 UID 1000是因为 Jenkins 官方镜像内的 jenkins 用户 UID 就是 1000。如果不改权限容器启动后会因为无法写入/var/jenkins_home而报错。这个坑我踩过不止一次表现是容器启动后立即退出查看日志显示权限拒绝。除了 Jenkins 主目录如果需要在 Jenkins 任务中执行 Docker 命令还需要把宿主机的 Docker 套接字挂载进容器-v /var/run/docker.sock:/var/run/docker.sock这样容器内的 Docker 客户端就能操作宿主机的 Docker 引擎。不过要注意这相当于给了容器很高的权限在生产环境中需要谨慎评估。入门阶段如果暂时不需要在 Jenkins 中构建 Docker 镜像可以先不挂载。2.4 GitHub 仓库的准备GitHub 这边需要准备一个 SpringBoot 项目仓库。如果还没有现成的项目可以用 Spring Initializr 快速生成一个。关键是确保项目能通过mvn clean package成功打包这是后续 Jenkins 构建的基础。仓库中建议包含一个.gitignore文件把target/目录排除掉。构建产物不应该提交到代码仓库这是基本规范。另外如果项目中有敏感配置比如数据库密码不要直接写在application.yml里提交应该用环境变量或者配置中心来管理。关于认证方式GitHub 从 2021 年开始就不再支持密码认证了必须用 Personal Access Token。创建路径在 GitHub 的 Settings - Developer settings - Personal access tokens。创建时勾选repo权限即可生成后把 Token 复制保存好因为页面关闭后就再也看不到了。3. 实操过程与核心环节实现3.1 Jenkins 容器的启动与初始化先拉取镜像docker pull jenkins/jenkins:lts然后启动容器完整的命令如下docker run -d \ --name jenkins \ -p 8080:8080 \ -p 50000:50000 \ -v /data/jenkins/home:/var/jenkins_home \ -v /etc/localtime:/etc/localtime:ro \ --restartalways \ jenkins/jenkins:lts逐项解释一下这些参数。-d是后台运行--name jenkins给容器起个名字方便管理-p 8080:8080把容器的 Web 端口映射到宿主机-p 50000:50000是 Jenkins 的代理通信端口暂时用不到但建议保留-v挂载数据目录和时区文件--restartalways确保容器在宿主机重启后自动拉起。启动后查看日志确认初始化完成docker logs -f jenkins日志中会出现一行初始管理员密码格式类似Please use the following password to proceed to installation下面跟着一串十六进制字符。把这个密码记下来首次访问 Web 界面时需要输入。浏览器访问http://你的服务器IP:8080进入解锁页面。输入初始密码后选择“安装推荐的插件”。这个过程会下载大量插件耗时取决于网络状况耐心等待即可。如果某些插件安装失败可以先跳过后续在插件管理中单独安装。插件安装完成后创建管理员账号。这里建议用真实的邮箱和容易记住的用户名密码因为后续配置通知、找回密码等都会用到。实例地址保持默认即可。3.2 全局工具配置与凭据管理进入 Jenkins 后第一件事是配置全局工具。路径是 Manage Jenkins - Global Tool Configuration。这里主要配置 JDK、Git 和 Maven。JDK 部分如果 Jenkins 容器内已有合适的 JDK可以填JAVA_HOME的路径。用docker exec jenkins echo $JAVA_HOME可以查看容器内的 Java 路径。如果需要特定版本的 JDK可以在这里配置自动安装但自动安装需要从外部下载速度可能较慢。Git 部分通常容器内已经安装了 Git填git即可。Maven 部分建议配置自动安装选择一个稳定的版本比如 3.8.x 或 3.9.x。Jenkins 会自动下载并配置好 Maven 环境。接下来配置 GitHub 凭据。路径是 Manage Jenkins - Manage Credentials - System - Global credentials - Add Credentials。类型选择“Username with password”Username 填你的 GitHub 用户名Password 填之前创建的 Personal Access Token。ID 可以填github-credentials方便后续引用。描述写清楚用途比如“GitHub 代码拉取凭据”。注意Token 一旦创建后无法再次查看如果丢失只能重新生成。建议把 Token 保存在安全的密码管理器中。3.3 创建 SpringBoot 构建任务回到 Jenkins 首页点击“新建任务”输入任务名称选择“构建一个自由风格的软件项目”。这种任务类型最灵活适合手动部署阶段。在“源码管理”部分选择 GitRepository URL 填你的 GitHub 仓库地址。凭据选择刚才创建的github-credentials。分支填*/main或*/master取决于你的仓库默认分支名。“构建触发器”部分手动部署阶段先不配置自动触发保持手动点击构建即可。等流程跑通后再考虑配置 Webhook 实现自动触发。“构建环境”部分可以勾选“Delete workspace before build starts”确保每次构建都是干净的环境。这个选项在排查构建问题时特别有用避免残留文件干扰。“构建”部分点击“增加构建步骤”选择“Invoke top-level Maven targets”。Maven 版本选择之前配置的Goals 填clean package -DskipTests。加-DskipTests是为了加快构建速度单元测试可以在后续单独配置一个任务来跑。3.4 构建后部署脚本的编写构建完成后需要把生成的 jar 包部署运行。在“构建后操作”中增加“Execute shell”步骤编写部署脚本。以下是我常用的脚本模板#!/bin/bash APP_NAMEmy-springboot-app JAR_PATHtarget/${APP_NAME}.jar DEPLOY_DIR/data/apps/${APP_NAME} LOG_DIR/data/logs/${APP_NAME} mkdir -p ${DEPLOY_DIR} ${LOG_DIR} # 停止旧进程 PID$(ps -ef | grep ${JAR_PATH} | grep -v grep | awk {print $2}) if [ -n ${PID} ]; then echo Stopping old process: ${PID} kill -15 ${PID} sleep 5 PID$(ps -ef | grep ${JAR_PATH} | grep -v grep | awk {print $2}) if [ -n ${PID} ]; then echo Force killing: ${PID} kill -9 ${PID} fi fi # 复制新包 cp ${JAR_PATH} ${DEPLOY_DIR}/ # 启动新进程 cd ${DEPLOY_DIR} nohup java -jar ${APP_NAME}.jar \ --spring.profiles.activeprod \ ${LOG_DIR}/app.log 21 echo Deployment completed. New PID: $!这个脚本的逻辑是先找到旧进程并优雅停止等待 5 秒后如果还没退出就强制杀掉然后复制新的 jar 包到部署目录最后用nohup后台启动新进程日志输出到指定文件。有几个细节值得说明。kill -15发送的是 SIGTERM 信号SpringBoot 应用会执行优雅关闭逻辑比如等待正在处理的请求完成。如果 5 秒后进程还在说明有线程卡住了这时候用kill -9强制终止。nohup配合让进程在后台运行即使 SSH 会话断开也不受影响。日志重定向到文件方便后续排查问题。3.5 完整构建流程的验证点击“立即构建”Jenkins 会开始执行任务。在构建历史中点击本次构建编号选择“Console Output”查看实时日志。日志会依次显示从 GitHub 拉取代码、Maven 下载依赖、编译打包、执行部署脚本。如果一切顺利最后会看到Deployment completed的输出。验证部署是否成功可以在服务器上执行ps -ef | grep my-springboot-app查看进程是否存在或者用curl http://localhost:8080/actuator/health检查应用健康状态前提是项目中引入了 SpringBoot Actuator。如果构建失败Console Output 中会有详细的错误信息。常见的失败原因包括Git 凭据错误导致拉取代码失败、Maven 依赖下载超时、编译错误、部署脚本权限不足等。根据错误信息逐一排查即可。4. 常见问题与排查技巧实录4.1 Jenkins 容器启动失败排查容器启动后立即退出用docker logs jenkins查看日志。如果看到Permission denied相关的错误基本可以确定是挂载目录的权限问题。解决方法是在宿主机上执行chown -R 1000:1000 /data/jenkins/home把目录属主改为 UID 1000。如果日志显示端口被占用用netstat -tlnp | grep 8080查看是哪个进程占用了 8080 端口。要么停掉那个进程要么在启动 Jenkins 容器时换一个宿主机端口比如-p 9090:8080。还有一种情况是磁盘空间不足导致容器无法启动。用df -h检查 Docker 数据目录所在分区的剩余空间清理一些无用的镜像和容器释放空间。4.2 插件安装失败的应对首次安装推荐插件时由于网络原因部分插件可能安装失败。这时候不要慌进入 Manage Jenkins - Manage Plugins - Available搜索缺失的插件单独安装。如果还是失败可以尝试在 Advanced 选项卡中把 Update Site 的 URL 换成国内镜像源。另一个常见问题是插件版本冲突。Jenkins 升级后某些旧插件可能不兼容。这时候需要进入插件管理页面查看是否有插件提示需要更新。通常把提示的插件更新到最新版本就能解决。如果插件问题导致 Jenkins 无法正常启动可以进入容器手动删除有问题的插件目录。插件存放在/var/jenkins_home/plugins下删除对应目录后重启容器即可。不过这种操作有风险建议先备份整个jenkins_home目录。4.3 构建过程中的典型错误Maven 构建失败最常见的原因是依赖下载超时。解决方法是在 Maven 的settings.xml中配置国内镜像仓库。这个文件通常位于/var/jenkins_home/tools/hudson.tasks.Maven_MavenInstallation/下的对应 Maven 版本目录中或者直接在 Jenkins 的 Global Tool Configuration 中配置。如果报错java.lang.OutOfMemoryError说明 Maven 构建时内存不足。可以在 Maven 的启动参数中增加MAVEN_OPTS-Xmx1024m -XX:MaxPermSize512m。在 Jenkins 任务配置的 Maven 步骤中有一个“MAVEN_OPTS”输入框可以设置。部署脚本执行失败常见原因是脚本中的路径不对或者权限不足。可以在脚本开头加上set -x开启调试模式这样 Console Output 中会打印每一行执行的命令方便定位问题。另外确保 Jenkins 用户对部署目录有写权限。4.4 应用启动后无法访问的排查jar 包部署后进程存在但无法访问首先检查应用是否真的启动完成了。查看日志文件tail -f /data/logs/my-springboot-app/app.log确认没有报错且看到了Started Application in X seconds的输出。如果日志显示端口被占用说明之前的老进程没有完全停止。用ps -ef | grep java找到残留进程手动杀掉然后重新部署。这也是为什么部署脚本中要有强制杀进程的逻辑。如果日志显示数据库连接失败检查application-prod.yml中的数据库配置是否正确。在手动部署阶段配置文件中的数据库地址、用户名密码都需要根据实际环境修改。建议把敏感配置通过环境变量传入而不是硬编码在配置文件中。防火墙问题也容易被忽略。如果应用监听的是 8080 端口但服务器防火墙没有放行这个端口外部就无法访问。用firewall-cmd --list-ports查看已放行的端口如果没有 8080 就用firewall-cmd --add-port8080/tcp --permanent添加然后firewall-cmd --reload生效。4.5 常见问题速查表问题现象可能原因排查方法解决方案容器启动后立即退出挂载目录权限不足docker logs jenkins查看日志chown -R 1000:1000修改目录属主Web 界面无法访问端口未映射或被占用netstat -tlnp检查端口更换映射端口或释放被占用端口拉取代码失败凭据错误或 Token 过期查看 Console Output 中的 Git 报错重新生成 Token 并更新凭据Maven 构建超时依赖下载慢观察日志中卡在哪个依赖配置国内 Maven 镜像源部署脚本报权限错误Jenkins 用户无写权限查看脚本执行日志给部署目录授权或调整脚本路径应用启动后无法访问端口未放行或配置错误查看应用日志和防火墙规则放行端口、修正配置文件构建成功但服务未更新旧进程未停止ps -ef查看进程启动时间检查部署脚本的停止逻辑提示每次遇到问题解决后把问题和解决方案记录到自己的知识库中。下次遇到类似问题时这份记录就是最高效的排查指南。5. 从手动部署到自动化的演进思路5.1 配置 GitHub Webhook 实现自动触发手动点击构建只能算是入门真正的持续集成需要代码推送后自动触发构建。GitHub 仓库的 Settings - Webhooks 中添加一个 WebhookPayload URL 填http://你的服务器IP:8080/github-webhook/Content type 选application/json触发事件选择“Just the push event”。然后在 Jenkins 任务的“构建触发器”中勾选“GitHub hook trigger for GITScm polling”。这样每次推送代码到 GitHubJenkins 就会自动开始构建。不过要注意如果 Jenkins 部署在内网环境GitHub 无法直接访问到你的 Webhook 地址这时候就需要其他方案来触发构建。5.2 构建通知的配置构建成功或失败后及时收到通知能大大缩短问题响应时间。Jenkins 支持邮件通知、即时通讯工具通知等多种方式。以邮件通知为例在 Manage Jenkins - Configure System 中配置 SMTP 服务器信息然后在任务配置的“构建后操作”中添加“E-mail Notification”设置收件人列表。邮件通知的模板可以自定义包含构建编号、构建状态、变更记录、Console Output 链接等信息。我通常会把 Console Output 的链接放在邮件正文中这样收到失败通知后可以直接点击链接查看详细日志不用再登录 Jenkins 界面。5.3 多环境部署的配置管理实际项目中通常有开发、测试、生产等多套环境。手动部署阶段可以通过 Jenkins 任务的参数化构建来实现环境切换。在任务配置中勾选“This project is parameterized”添加一个 Choice Parameter选项为dev、test、prod。然后在部署脚本中根据参数值选择不同的配置文件和部署目录。比如PROFILE${ENV} DEPLOY_DIR/data/apps/${APP_NAME}-${ENV}这样同一个任务就能部署到不同环境避免了为每个环境单独创建任务的繁琐。不过要注意生产环境的部署建议增加人工确认环节避免误操作。5.4 构建产物的版本管理每次构建生成的 jar 包建议保留历史版本方便回滚。可以在部署目录下创建一个releases子目录每次部署时把 jar 包按时间戳或构建编号命名保存TIMESTAMP$(date %Y%m%d%H%M%S) cp ${JAR_PATH} ${DEPLOY_DIR}/releases/${APP_NAME}-${TIMESTAMP}.jar同时在部署目录下维护一个软链接current指向当前运行的版本。回滚时只需要把软链接指向旧版本然后重启应用即可。这个机制在手动部署阶段就能建立起来后续迁移到自动化方案时也能直接复用。我在实际项目中发现构建产物的版本管理往往被忽视直到某次线上出问题需要紧急回滚时才发现根本没有旧版本可用。所以建议从手动部署阶段就养成保留历史版本的习惯这个投入在关键时刻能救命。5.5 后续可扩展的方向当手动部署流程完全跑通后可以考虑以下几个演进方向。一是引入 Jenkins Pipeline用 Jenkinsfile 把构建部署流程代码化这样流程配置也能纳入版本管理。二是把构建环境容器化用 Docker 容器来执行 Maven 构建确保构建环境的一致性。三是引入制品仓库把构建产物统一管理方便追溯和回滚。不过这些演进都应该建立在手动流程已经稳定的基础上。我见过太多团队在手动流程还没跑通的情况下就急着上 Pipeline结果出了问题连基本的排查能力都没有。先把基础打牢后续的演进才能水到渠成。