ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

CentOS 9 + Gitee + Jenkins 自动化部署与回滚实战

CentOS 9 + Gitee + Jenkins 自动化部署与回滚实战 关于自动化部署很多团队上来就推 GitLab CI、GitHub Actions但在国内网络环境、内网隔离、代码托管合规等现实约束下Gitee Jenkins CentOS 这套组合反而更贴近大多数中小团队的实际情况。我前前后后帮几个团队搭过类似的本地化部署方案也踩了不少坑这次基于 CentOS Stream 9 重新梳理了一套把 Gitee 私有仓库、Jenkins 流水线、脚本化部署、异常回滚全串起来目标是做到“代码一推、几分钟后线上更新”同时尽量降低出问题时的恢复成本。这套方案不依赖云厂商的托管服务全部跑在自己机房或内网机器上适合那种对数据敏感、不想把代码和构建流程放到第三方平台的团队也适合个人开发者在一台闲置服务器上捣鼓一套完整的 DevOps 基础链路。整篇文章会从环境初始化、密钥配置、Jenkins 部署、任务编排、部署脚本、回滚机制、常见坑位排查这几个纬度完整展开只要跟着做基本能拿到一套可以长期稳定的自动化部署底座。1. 方案整体设计为什么是 CentOS Stream 9 Gitee Jenkins1.1 三个核心组件的选型逻辑先说操作系统。CentOS Stream 9 是 Red Hat 生态里滚动更新的发行版比 CentOS 7/8 老版本内核新很多默认带了 Podman、更现代的 GCC 版本、更新的 systemd 和 Python 3.9这意味着你在装 Jenkins、Node.js、OpenJDK 这些依赖的时候可以少走很多“版本太老装不上”的弯路。有人纠结 Stream 不是纯稳定版但对部署服务器来说不是内核天天升的实验特性就完全够用而且它能拿到后续安全补丁比停在 EOL 版本强太多。Gitee 这边的选择逻辑更直接——国内访问速度快私有仓库免费额度够用Webhook 回调机制和 Jenkins 兼容性好还自带 Gitee 专属插件可以在 Jenkins 里直接配任务触发。如果你的团队代码本来就在 Gitee 上那这套链路是零额外成本。Jenkins 则是自动化部署里最稳妥的调度中控。它不要求你写复杂的 YAML 配置界面式任务配置配合 Shell 脚本团队里没人懂 DevOps 也能快速上手。更关键的一点是 Jenkins 生态里插件非常多从 Gitee 集成、SSH Agent、Publish Over SSH 到参数化构建、凭据管理都有成熟实现可以避免我们从头造轮子。1.2 整套方案的数据流向与职责划分这套架构里有三个核心角色职责可以分得很清楚Gitee代码托管中心只负责存储源码、管理分支和发放 Webhook 触发信号。它不参与构建也不碰服务器上的部署目录。Jenkins调度大脑监听 Gitee 发来的 Push 事件拉取最新代码执行编译、打包、单元测试然后把产物分发到指定的目标服务器并触发远程部署脚本。部署目标机运行你真实业务的服务器也可以和 Jenkins 同一台接收 Jenkins 传输过来的构建产物执行停服、备份、替换、启动、健康检查这一套动作。有人喜欢把 Jenkins 和目标部署放一台机器形成 All-in-One 搭建也有人把 Jenkins 和业务服务器分开。我这里更推荐至少逻辑上用不同目录隔离如果资源紧张物理共用一台也行但服务之间涉及权限操作时尽量分独立用户去跑防止 Jenkins 权限过大导致误删线上数据。部署流程我用一句话来概括代码推送到 Gitee 特定分支 → Gitee 发送 Webhook 给 Jenkins → Jenkins 触发构建任务 → 拉代码 构建打包 → 传包到部署机 → 远程执行部署脚本 → 脚本自动备份旧版本并切换新版本 → 健康检查通过则完成失败自动回滚。这样一套走下来基本杜绝了“人肉 FTP 上传覆盖文件”这种危险操作。2. 基础环境准备CentOS Stream 9 初始化与部署机配置2.1 系统初始化与基础依赖安装很多人刚拿到一台 CentOS Stream 9 就往上面装 Jenkins结果后面发现问题一大堆防火墙没放端口、SELinux 拦截了脚本执行、中文字符集导致日志乱码、时区不对导致构建时间错乱。所以系统初始化这一步不要跳过我一般会按下面的顺序处理。先更新系统软件包把内核和基础库拉到最新sudo dnf update -y sudo dnf install -y vim wget curl git lrzsz unzip zip然后同步时区并校准时间Jenkins 构建记录里的时间戳如果不对排查线上问题时会对不上日志非常别扭sudo timedatectl set-timezone Asia/Shanghai sudo systemctl enable --now chronyd chronyc sources接下来调整字符集避免后续脚本里中文路径或日志输出出现乱码sudo localectl set-locale LANGzh_CN.UTF-8 source /etc/locale.conf echo export LANGzh_CN.UTF-8 ~/.bashrc再装一个常用工具集合后面传输构建包、排查端口、看进程状态都用得上sudo dnf install -y net-tools lsof tree jq telnet nc到这里系统的底子基本干净了。注意 CentOS Stream 9 默认的防火墙是 firewalld如果你不想因为端口问题排查得太痛苦先把它配置清楚。2.2 防火墙与 SELinux 策略调整防火墙这块Jenkins 默认跑在 8080 端口如果打算开放给团队访问需要提前放行。我习惯限制来源 IP不让 8080 对全网暴露sudo firewall-cmd --permanent --add-rich-rulerule familyipv4 source address192.168.1.0/24 port port8080 protocoltcp accept sudo firewall-cmd --reload如果 Jenkins 和业务部署在同一台机器业务端口也要相应放行。这里有个小提醒真正执行部署脚本时不要依赖防火墙放行来实现安全端口该关还是要关只对需要的来源开放。再说 SELinux。CentOS Stream 9 默认 SELinux 是 enforcing 模式Jenkins 作为服务拉取代码、执行脚本、写文件很容易被 SELinux 策略挡住。网上很多人图省事直接setenforce 0我不建议这么做生产环境这么搞风险太大。安全且省心的做法是只对 Jenkins 相关的进程域放开必要权限。如果只是测试环境图省事临时切到 permissive 也能跑通但正式环境请务必保留 enforcing 并做策略适配# 临时查看当前状态 getenforce # 测试时切到宽松模式 —— 只能在测试环境这么干 sudo setenforce 0部署时容易被忽略的是 SSH 远程执行脚本会被 SELinux 的ssh_exec域拦截如果你遇到“远程连接正常但远程命令执行没反应”八九不离十是 SELinux 策略在作祟。2.3 部署用户与目录规划我强烈建议不要用 root 直接跑 Jenkins 和部署脚本。安全是一方面更现实的问题是 root 身份下的误操作没有后悔药。我通常单独建一个 devops 用户归属到wheel组里同时给它部署目录的读写权限sudo useradd -m -G wheel devops sudo passwd devops sudo mkdir -p /data/{backup,releases,deploy,packages} sudo chown -R devops:devops /data目录规划按我多年习惯分成四块/data/backup旧版本备份区域每次部署时在这里沉淀历史版本留最近 N 份。/data/releases新版本解压落地区域按时间和构建序号命名。/data/deploy当前线上生效的版本通常是软链接指向 releases 下的具体某个版本。/data/packagesJenkins 传过来的临时安装包暂存区。这样设计和业务“发布目录”分离是为了后面实现原子切换和快速回滚做铺垫。你可以理解为永远不直接在当前运行目录上覆盖文件而是先准备一套新的然后通过改软链整体切过去。3. Gitee 仓库与 SSH 密钥配置3.1 SSH 密钥生成与 Gitee 端配置Gitee 和 Jenkins 之间拉代码我一般不推荐直接用账号密码方式。账号密码存在 Jenkins 凭据里虽然是加密的但一旦库泄露就是整个代码托管账号沦陷。相比之下 SSH 密钥能在 Gitee 后台单独管理、随时吊销安全边界清晰得多。在 Jenkins 服务器上生成部署专用密钥注意是 Jenkins 服务运行的用户不是 rootsudo -u jenkins ssh-keygen -t ed25519 -C jenkins-deploy -f /var/lib/jenkins/.ssh/id_ed25519如果没有jenkins用户等装完 Jenkins 之后再执行。生成之后把公钥内容复制下来cat /var/lib/jenkins/.ssh/id_ed25519.pub然后登录 Gitee进入目标仓库的【管理】→【部署公钥管理】→【添加公钥】把上面内容粘贴进去。这里有个细节如果是私有仓库记得勾选“允许公钥访问”否则 Jenkins 只推送不拉取或者拉取被拒绝。在 Jenkins 服务器上先手动拉一次仓库目的是把 Gitee 主机指纹写入 known_hosts否则 Jenkins 构建时首次连接会卡在指纹确认上sudo -u jenkins ssh -T gitgitee.com sudo -u jenkins git clone gitgitee.com:yourgroup/yourrepo.git /tmp/yourrepo --depth1首次执行时 SSH 会提示确认主机真实性输入 yes 回车即可。3.2 Jenkins 服务器上的 Git 全局配置Jenkins 用户拉取和提交代码时Git 会校验 user.name 和 user.email如果没有配置部分 Git 操作会报错。虽然拉代码场景不强制但如果你在流水线里需要自动打 tag或者后续接了自动生成提交信息那这个配置缺不了sudo -u jenkins git config --global user.name jenkins-bot sudo -u jenkins git config --global user.email jenkinsyourcompany.com sudo -u jenkins git config --global core.autocrlf inputcore.autocrlf input是为了避免 Windows 和 Linux 混合开发时换行符导致的 diff 错乱。团队里只要有 Windows 开发人员这条配置几乎必加。3.3 Gitee 私有仓库的分支保护与合并规范Gitee 仓库建立之后建议先做好分支保护把main或master分支设为受保护分支禁止直接 push只允许通过 Pull Request 合并。这样 Jenkins 触发构建的时机才能集中在 dev/staging 分支而不是每个成员的临时提交都触发一次构建把 Jenkins 搞成流水线轰炸区。我习惯的分支策略是main生产环境对应分支受保护只有提测通过才能合并。dev开发自测分支合并后触发 Jenkins 测试环境部署。release/x.y.z按版本号拉出的预发布分支合并后触发预发布环境部署。Gitee 的 Webhook 支持按分支过滤可以精确控制触发条件后面 Jenkins 配置任务时也会用分支名做参数。4. Jenkins 安装、初始化与插件选型4.1 Jenkins 安装与初始化CentOS Stream 9 上安装 Jenkins最省心的是用官方 RPM 源。先导入 Jenkins 仓库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 sudo dnf install -y jenkins注意 Jenkins 本身是 Java 应用需要先确认 JDK 版本。Jenkins 新版2.420要求 Java 11 或 17CentOS Stream 9 自带 OpenJDK 17直接安装即可sudo dnf install -y java-17-openjdk-devel装好后启动并设为开机自启sudo systemctl enable --now jenkins sudo systemctl status jenkins首次初始化时浏览器打开http://你的服务器IP:8080会要求输入初始密码。初始密码在/var/lib/jenkins/secrets/initialAdminPassword查看方式sudo cat /var/lib/jenkins/secrets/initialAdminPassword进入界面后建议选“选择插件来安装”不要一键安装全部推荐插件里面有一堆用不上的白白增加启动负担和潜在的安全漏洞面。4.2 必装插件清单与加速配置根据这套方案的实际需求插件不用多但一个都不能少Gitee PluginGitee 官方出品的 Webhook 集成插件没有它Gitee 推送事件根本没法和 Jenkins 联动。Credentials Binding凭据绑定用来把 Gitee SSH 私钥从 Jenkins 凭据库动态注入到构建环境。SSH Agent Plugin在流水线里临时挂载 SSH 私钥用于 Git 拉取和推送操作。Publish Over SSH通过 SSH 把构建产物传送到部署机并执行远程命令省去在每台机器上装 Agent。Timestamper给构建日志加时间戳排查问题看日志先后顺序会直观很多。Build Timeout构建超时保护防止某个步骤永久阻塞。Plain Credentials管理 Gitee 用户名密码或其他敏感信息虽不常用但备用。Gitee 插件安装可能要绕一下因为 Gitee 官方插件不在 Jenkins 默认更新中心里。配置方法是进入【系统管理】→【插件管理】→【高级设置】在“升级站点”填上 Gitee 的镜像地址https://jenkins-update.gitee.io/update-center.json保存后再去搜 Gitee 插件就能搜到了。同样国内服务器安装 Jenkins 插件普遍慢用这个镜像地址能快几倍到几十倍。4.3 全局工具配置JDK、Git、Maven进入【系统管理】→【全局工具配置】把 JDK、Git、Maven 指到你想要的位置。如果之前系统里已经装好了就选“Install automatically”让它自动装或者直接填系统路径。我个人推荐如果是内网环境且网络不稳直接用系统安装的工具填绝对路径更可控。比如JDK/usr/lib/jvm/java-17-openjdkGit/usr/bin/gitMaven手动解压到/opt/maven填路径即可这里有一个容易踩的坑Jenkins 默认以 jenkins 系统用户运行而 Maven 仓库和 npm 缓存默认放在用户目录下。如果你在构建命令里用了 root 之前装的依赖就会遇到权限不足报错。解决办法是把/opt/maven目录 owner 改给 jenkins或者统一用sudo -u jenkins预装依赖到 jenkins 用户目录。5. 自动化部署流水线设计与高可靠保障5.1 设计一个带备份与回滚的部署脚本部署脚本是整个链路的核心中的核心前面所有组件都是在为这个脚本服务的。它要解决的不是“把代码复制过去”而是“如何让新版本平稳上线同时保证出问题能快速回到旧版本”。我长期使用的部署脚本思路是全新发布 → 备份当前版本 → 切换软链 → 重启服务 → 健康检查 → 失败自动回滚。下面是一个通用化的部署脚本模板以 Java 服务为例假设我们通过 systemd 管理服务。#!/bin/bash # deploy.sh —— 通用部署脚本入参包名、部署批次号 set -euo pipefail APP_NAMEyour-service BACKUP_DIR/data/backup RELEASES_DIR/data/releases DEPLOY_DIR/data/deploy PKG_NAME${1} RELEASE_NO${2} RELEASE_DIR${RELEASES_DIR}/${APP_NAME}-${RELEASE_NO} CURRENT_LINK${DEPLOY_DIR}/${APP_NAME} # 1. 准备新版本目录 mkdir -p ${RELEASE_DIR} tar -zxf /data/packages/${PKG_NAME} -C ${RELEASE_DIR} # 2. 停服避免替换时文件被占用 sudo systemctl stop ${APP_NAME} # 3. 备份当前版本 if [ -L ${CURRENT_LINK} ] [ -d $(readlink -f ${CURRENT_LINK}) ]; then CUR_VERSION$(basename $(readlink -f ${CURRENT_LINK})) cp -a $(readlink -f ${CURRENT_LINK}) ${BACKUP_DIR}/${APP_NAME}-${CUR_VERSION}-$(date %Y%m%d%H%M%S) fi # 4. 切换软链 ln -sfn ${RELEASE_DIR} ${CURRENT_LINK} # 5. 启动服务 sudo systemctl start ${APP_NAME} # 6. 健康检查最多等30秒 for i in $(seq 1 30); do if curl -sf http://127.0.0.1:8080/health /dev/null; then echo 健康检查通过 exit 0 fi sleep 1 done # 7. 部署失败自动回滚到上一个版本 echo 健康检查失败执行回滚 sudo systemctl stop ${APP_NAME} ln -sfn ${BACKUP_DIR}/$(ls -t ${BACKUP_DIR} | head -1) ${CURRENT_LINK} sudo systemctl start ${APP_NAME} exit 1这个脚本的核心逻辑是软链切换。新版本永远不覆盖当前版本目录而是部署成一个新目录然后通过替换软链实现生效。这样即使新版本完全没法起旧版本目录还原封不动躺在那里回滚只是改一条软链加一次重启一分钟内能完成。5.2 Jenkins 流水线任务配置与 Gitee Webhook 联动Jenkins 任务类型我用的是 Pipeline 任务模板化程度高代码存在 Jenkinsfile 里跟着仓库走团队成员能直接看 Git 历史了解部署逻辑变动比在界面上点出来的 Freestyle 任务靠谱得多。核心的 Jenkinsfile 大致长这样pipeline { agent any environment { // 读取 Gitee 上的分支名称由 Webhook 传递 BRANCH_NAME ${params.BRANCH_NAME} RELEASE_NO ${BUILD_NUMBER}-${BRANCH_NAME.replaceAll(/, -)} } stages { stage(拉取代码) { steps { checkout([$class: GitSCM, branches: [[name: ${BRANCH_NAME}]], userRemoteConfigs: [[url: gitgitee.com:yourgroup/yourrepo.git, credentialsId: gitee-ssh-key]] ]) } } stage(构建) { steps { sh mvn clean package -DskipTests } } stage(打包) { steps { sh mkdir -p output cp target/${APP_NAME}.jar output/ tar -zcf ${APP_NAME}-${RELEASE_NO}.tar.gz output/ } } stage(传输与部署) { steps { sshPublisher( publishers: [ sshPublisherDesc( configName: deploy-server, transfers: [ sshTransfer( sourceFiles: ${APP_NAME}-${RELEASE_NO}.tar.gz, remoteDirectory: /data/packages, execCommand: bash /data/deploy/deploy.sh ${APP_NAME}-${RELEASE_NO}.tar.gz ${RELEASE_NO} ) ] ) ] ) } } } post { failure { // 构建失败时通知 emailext( subject: 构建失败: ${env.JOB_NAME} - ${env.BUILD_NUMBER}, body: 请检查构建日志: ${env.BUILD_URL}, to: devyourcompany.com ) } } }配置完任务后还要在 Gitee 仓库后台添加 Webhook。进入 Gitee 仓库的【管理】→【WebHooks】→【添加 WebHook】URL 填http://你的Jenkins地址:8080/gitee-project/你的任务名勾选 “Push”分支过滤填你要触发部署的分支这样每当你向 Gitee 的对应分支推送代码Gitee 就会自动打 Jenkins 的接口触发流水线。5.3 高可靠细节构建清理、并发锁、超时保护自动化部署不是“能跑就行”要长期稳定运行必须考虑资源回收和竞态控制。构建产物清理Jenkins 跑多了/var/lib/jenkins/workspace里全是历史构建的临时文件磁盘不知不觉就满了。我通常是两条策略并行一是 Jenkins 的“丢弃旧构建”策略配置为保留 30 天或最近 20 次构建二是在流水线的post阶段用cleanWs()清理工作区。并发锁防冲突如果团队多个人同时推代码Jenkins 默认会同时跑多个构建新版本和旧版本部署可能在同一台机器上互相踩脚。我用一个简单粗暴的办法在流水线开头加一个 locklock(deploy-lock) { // 整个部署过程在锁内执行 }lock 是 Lockable Resources Plugin 提供的能力加了之后同一时刻只有一个部署任务在执行避免部署过程乱套。超时保护Pipeline 里给长时间执行的阶段加超时控制stage(构建) { options { timeout(time: 20, unit: MINUTES) } steps { sh mvn clean package -DskipTests } }如果 Maven 依赖下载卡死20 分钟后自动中断不会把 Jenkins 执行队列堵死。6. 高可靠部署实战从推代码到上线全流程6.1 一次完整部署的触发与执行链路当开发人员执行git push origin dev之后整个链路就启动了我来逐步拆解一次真实部署中后台发生的事第一步Gitee 接收 push 事件校验 Webhook 配置里的分支过滤规则匹配到 dev 分支后向 Jenkins 对应的任务地址发送 POST 请求。第二步Jenkins 收到请求Gitee Plugin 会校验请求来源真实性和项目匹配关系匹配通过后创建一个新的构建记录把分支名、提交信息等变量注入到这次构建环境中。第三步Pipeline 开始执行。checkout阶段以 Jenkins 用户的身份走 SSH 拉取刚推送的最新代码这里用的就是前面配好的部署公钥。第四步Maven 构建阶段。代码拉下来后执行mvn clean package跳过程序测试产物是同一个 Jar 包。构建过程中如果有单元测试失败或者编译报错流水线直接标记为失败后面的步骤不会执行线上环境完全不受影响。第五步打包和传输。构建成功后脚本把 Jar 包打成tar.gz通过 Publish Over SSH 推到目标服务器的/data/packages目录。第六步目标机执行部署脚本。脚本按前面设计的流程完成备份、软链切换、服务重启、健康检查。整个流程从 push 到健康检查通过一台配置还行的机器上 3-5 分钟能走完。6.2 部署过程日志分析要点部署出问题时第一件事就是看 Jenkins 构建日志。日志里面有几个标志性节点值得关注Started by user xxx触发者是谁。Checking out Revision本次构建拉取的精确 commit。[INFO] BUILD SUCCESSMaven 构建是否成功。SSH: Transferred包是否传输成功。SSH: Exec completed远端脚本是否完整执行。Finished: SUCCESS/FAILURE整体构建结果。如果构建失败发生在SSH: Exec completed之后那问题大概率出在部署机本身而不是构建过程。这时候需要到部署机上查 systemd 服务的日志sudo journalctl -u your-service -n 200 --no-pager大多数情况下的部署失败都能在这条命令的输出里找到原因——端口被占用、配置文件引用了不存在的路径、数据库连不上、依赖的服务还没起等等。6.3 回滚操作与误报处理即使部署脚本里已经带了自动回滚人肉操作回滚的能力还是要具备。手动回滚的命令其实非常简单# 查看当前软链指向 ls -l /data/deploy/your-service # 找到最近一次备份 ls -t /data/backup | head -5 # 停服并切到指定备份 sudo systemctl stop your-service ln -sfn /data/backup/your-service-20240101120000 /data/deploy/your-service sudo systemctl start your-service有一个容易误判的情况健康检查用的是 HTTP 状态码但有些应用端口虽然起来了内部的定时任务或缓存预热还在准备中。我处理这个问题的方法是在脚本里加一个“后置确认等待”健康检查通过后再等 10 秒然后额外跑一个接口验证业务关键数据能否正常读取curl -sf http://127.0.0.1:8080/api/health/db || exit 1这样能在部署脚本阶段就发现大多数“表面正常、实际异常”的问题。7. 常见问题与排查技巧实录7.1 Gitee Webhook 不触发构建这个问题出现的频率排第一。排查顺序是这样的先在 Gitee 后台 Webhook 管理页面点击“测试”看返回码是多少。如果是 302 或 404说明 Jenkins URL 没填对或者任务名大小写不匹配。确认 Jenkins 任务名和 Gitee 插件要求的一致性。Gitee Webhook 路径通常是/gitee-project/{jobName}如果任务 URL 被 Jenkins 的 “Quiet Period” 或者反代路径改写过也会造成 404。确认 Jenkins 系统设置里的“Jenkins URL”填的是外网可访问的地址内网环境填 localhost 的话 Gitee 的服务器访问不到。最气人的一种情况是 Webhook 显示发送成功但 Jenkins 不识别签名。这时候去 Jenkins 系统日志里搜Gitee webhook关键字看看有没有签名验证失败的提示。通常是因为配置 Webhook 时设置了签名密钥而 Jenkins 插件里没填对应的密钥。7.2 SSH 连接失败和权限拒绝Jenkins 拉代码失败时日志里最常见的错误是Host key verification failed或者Permission denied (publickey)。Host key 的问题就按前面说的先手动跑一次 SSH 连接把 known_hosts 写好。Permission denied 则优先检查 Gitee 后台的公钥是否配置到了正确仓库以及 Jenkins 服务运行的当前用户是谁。很多人会犯一个错在 root 下生成了密钥配到了 Gitee但 Jenkins 服务是用 jenkins 用户跑的Jenkins 根本读不到 root 的私钥。解决办法是确认生成密钥和执行操作的统一用户最好就固定在 jenkins 用户上sudo -u jenkins ssh-keyscan gitee.com /var/lib/jenkins/.ssh/known_hosts sudo -u jenkins chmod 600 /var/lib/jenkins/.ssh/id_ed255197.3 构建环境磁盘被占满自动化部署跑久了Jenkins 工作区、Maven 本地仓库、历史备份目录三处地方最容易占满磁盘。尤其是备份目录如果不设上限一次部署几 GB几个月下来磁盘就爆了。我常用的磁盘告警脚本会定时检查 df 输出df -h / | awk NR2 {if ($50 80) system(echo 磁盘使用率超过80%)}在部署脚本里备份目录保留数量要加限制# 保留最近5个备份更老的自动清理 ls -1t /data/backup | tail -n 6 | xargs -I {} rm -rf /data/backup/{}Maven 本地仓库缓存的清理则用find ~/.m2/repository -name *.lastUpdated -type f -delete7.4 构建超时和依赖下载慢内网环境拉 Maven 依赖是老大难问题。我们团队的实际做法是配置阿里云镜像效果立竿见影。在~/.m2/settings.xml里加mirrors mirror idaliyun/id namealiyun maven mirror/name urlhttps://maven.aliyun.com/repository/public/url mirrorOfcentral/mirrorOf /mirror /mirrors如果是 npm 项目同理配置淘宝镜像npm config set registry https://registry.npmmirror.com把镜像配好之后构建时间能压缩一大半Webhook 触发到部署完成的间隔会明显缩短。7.5 服务启动成功但健康检查失败这类问题在部署脚本判断上最难缠。常见原因是服务监听绑定地址不对比如应用默认监听127.0.0.1而健康检查脚本里 curl 的是http://服务器IP:8080/health必然失败。排查思路如下先确认监听地址和端口ss -lntp | grep java在部署机上手动 curl 一次复现失败现场看应用日志确认启动过程走到了哪一步如果应用本身没问题只是健康检查 URL 写得不对把脚本里的 URL 改成实际健康检查路径即可。8. 方案扩展从单机部署走向多机集群8.1 使用 Publish Over SSH 扩展到多台部署机单机部署是基础生产环境中更常见的是多台机器同时部署。这时候不需要改太多逻辑把 Jenkins 的 SSH Publishers 里增加多个目标节点即可。Publish Over SSH 支持一次任务推送到多台机器stage(传输与部署) { steps { sshPublisher( publishers: [ sshPublisherDesc( configName: deploy-server-01, transfers: [...] ), sshPublisherDesc( configName: deploy-server-02, transfers: [...] ) ] ) } }但这里要考虑部署策略同步推送所有机器还是灰度推送一部分机器确认没问题再推剩下部分。我实践中更推荐拆成两个阶段先推一台跑完健康检查再推剩下的。这样可以把故障爆炸半径控制在最小。8.2 接入钉钉/企业微信通知自动化部署链路如果不配通知那就是黑盒构建失败要等用户反馈才知道。Jenkins 生态里有现成的 DingTalk 插件配置起来是插件市场搜索 DingTalk 并安装。在钉钉群里创建自定义机器人拿到 Webhook 地址。Jenkins 全局配置里填入机器人的 Webhook 和加签密钥。Pipeline 的 post 阶段发送构建状态消息。一个简单的通知片段post { success { dingtalk( robot: default, type: TEXT, text: [部署成功: ${env.JOB_NAME} #${BUILD_NUMBER}] ) } failure { dingtalk( robot: default, type: TEXT, text: [部署失败请关注: ${env.JOB_NAME} #${BUILD_NUMBER}] ) } }收到通知的闭环价值很大开发人员不用反复刷 Jenkins 页面推送代码后等着钉钉消息即可。8.3 数据库自动迁移与初始化很多真实项目部署不只是替换代码还涉及数据库结构变更。自动化部署里不应该跳过这一步但也不能盲目执行。我采取的方式十分保守——在部署包里附带 SQL 目录部署脚本里加入“迁移检查”阶段# 对比已执行迁移记录和新脚本 MIGRATE_DIR${RELEASE_DIR}/migrations if [ -d ${MIGRATE_DIR} ]; then for sql_file in $(ls ${MIGRATE_DIR}/*.sql | sort); do if ! grep -q ${sql_file} ${DEPLOY_DIR}/.migration_history 2/dev/null; then mysql -u deploy -p${DB_PWD} app_db ${sql_file} echo ${sql_file} ${DEPLOY_DIR}/.migration_history fi done fi这套“历史记录比对 只执行增量”的策略可以防止重复执行同一个 SQL 导致数据异常。8.4 保留现场构建产物归档出问题时经常需要回查“当时构建出来的包到底是什么样子”。我养成的习惯是在 Jenkins 里开启“归档构建产物”把每次构建的 tar 包保留下来。这样即使部署机上的文件被清掉了Jenkins 上还能找到原始包。post { success { archiveArtifacts artifacts: *.tar.gz } }如果团队要求“每个版本都要能追朔代码对应关系”还可以让构建脚本自动打 Git tagstage(打版本标签) { steps { sh git tag v${RELEASE_NO} git push origin v${RELEASE_NO} } }以后任何一次部署都能通过 tag 精确对应到一次代码提交排障和审计都方便很多。9. 写在最后的经验复盘这套方案断断续续折腾了小半年中间经历过好几次“半夜被叫起来回滚”的教训现在基本稳定下来。最深的体会有三点第一自动化部署真正难的不是写脚本而是想清楚回滚策略。只要回滚链路是流畅的上线就没什么不敢做的。我后来在脚本上花精力最多的地方不是怎么把新版本发上去而是怎么在 10 秒内把旧版本拿回来。第二所有环境和配置都要代码化能写进仓库的就不要只在 Jenkins 界面上点。团队成员的 Jenkins 任务配置如果只存在于那个实例上哪天服务器挂了所有流水线都没了重建的成本会让人崩溃。所以我后来把 Jenkinsfile、部署脚本、系统初始化脚本都沉淀到了 Gitee 仓库里新机器拉起部署环境的时间从一天缩到两小时。第三自动化的核心是给人留有余地。即便整个链路已经全自动了我仍然保留了手动执行部署脚本的路径并且确认团队成员都熟练。自动化不是把人的操作权限拿掉而是把重复劳动替掉真正关键的时刻人还是要能接管场面。这套 CentOS Stream 9 Gitee Jenkins 的组合在中小团队里算得上性价比极高的方案。如果读完这篇对你有帮助建议不要照着抄一遍就完而是先拿一台测试机完整走通一次部署、回滚、通知的闭环把每一条日志看明白再往生产环境上搬。只有自己亲手踩过一轮坑这套系统在你手里才是真正可靠的。
返回列表