为什么我放弃了传统方案,改用 Jenkins

devops进阶9 分钟阅读2026/9/1

为什么我放弃了传统方案,改用 Jenkins

一切始于一次凌晨两点的发布事故

先说说我是怎么走到这一步的。

去年我们团队大概十个人,项目是四个微服务加一个前端应用。当时的发布流程是典型的“传统方案”:开发在本地打包,通过 FTP 上传到跳板机,然后跑一个 shell 脚本重启服务。脚本是一个已经离职的同事写的,没人完全看得懂,出了问题就只能靠人肉排查。

有一天凌晨两点,一个紧急修复发布后服务起不来。我花了四十分钟才发现,是打包时本地 Node 版本和线上的不一致,导致构建产物有差异。那一刻我意识到:手工发布最大的问题不是慢,是不可重复。同一份代码,两个人打出来的包可能不一样。

当时我考虑过三个方案:

  1. 纯 shell 脚本 + Git hook:改造现有脚本,做成推代码自动部署
  2. GitLab CI:我们的代码已经在 GitLab 上,看起来顺理成章
  3. Jenkins:老牌工具,网上都说“重”,我一开始是抗拒的

我先试了方案一,花两天把脚本改成了支持回滚的版本,但每加一个项目就要复制粘贴一遍逻辑,参数硬编码,改一个地方容易漏另一个地方。方案二的 GitLab CI 是托管在内部 GitLab 的 runner 上,当时公司的 GitLab 版本太老,CI 功能受限,申请升级流程走了三周没下文。

于是我带着偏见去试了 Jenkins。三个月后的今天,我把剩下两个方案全部迁到了 Jenkins 上。下面说说具体怎么做的,以及我踩过的坑。

搭建:比我想象中简单,但有两个坑

我们用 Docker 跑 Jenkins,一台 4 核 8G 的虚拟机就够了:

docker run -d --name jenkins \
  -p 8080:8080 -p 50000:50000 \
  -v jenkins_home:/var/jenkins_home \
  -v /var/run/docker.sock:/var/run/docker.sock \
  jenkins/jenkins:lts

坑一:挂载 docker.sock 是为了让 Jenkins 容器里能调用宿主机的 Docker 构建镜像。 我一开始没挂,构建到 docker build 那一步直接报 docker: command not found,查了半小时才明白容器里根本没有 Docker。

坑二:容器里的时区默认是 UTC。 我们的构建历史显示的时间和实际差 8 小时,排查“这个包是什么时候打的”时特别混乱。解决方法是在启动时加环境变量:

-e TZ=Asia/Shanghai

装好之后,插件选择上我的建议是:不要装推荐的全部插件。首次进入的“安装推荐插件”会装一大堆你永远用不到的东西(各种 Subversion、TFS 之类的),拖慢启动。我最小化安装了这几样:

  • Git / GitLab(代码拉取和 webhook)
  • Pipeline(核心,后面细说)
  • Blue Ocean(可视化流水线,给团队看构建状态用)
  • Credentials Binding(管理密钥)

真正让我留下来的原因:Pipeline as Code

传统方案(包括我最初的 shell 脚本)的致命问题是流程只存在于某个人的脑子里或某台机器上。Jenkins 的 Pipeline 用 Jenkinsfile 把整个流程变成代码,进版本库,谁都能看、能改、能回溯。

这是我们一个后端服务的完整 Jenkinsfile(简化版):

pipeline {
    agent any
    environment {
        IMAGE = "registry.internal.com/order-service"
    }
    stages {
        stage('构建') {
            steps {
                sh 'mvn clean package -DskipTests'
            }
        }
        stage('测试') {
            steps {
                sh 'mvn test'
                junit 'target/surefire-reports/*.xml'
            }
        }
        stage('镜像') {
            steps {
                sh "docker build -t ${IMAGE}:${env.BUILD_NUMBER} ."
                sh "docker push ${IMAGE}:${env.BUILD_NUMBER}"
            }
        }
        stage('部署到测试环境') {
            steps {
                sh "ssh deploy@staging 'cd /app && ./deploy.sh ${IMAGE} ${env.BUILD_NUMBER}'"
            }
        }
    }
    post {
        failure {
            dingtalkNotify("构建失败: ${env.JOB_NAME} #${env.BUILD_NUMBER}")
        }
    }
}

几个月下来,这个文件带来三个实实在在的好处:

  1. 每次构建产物都有唯一版本号(用 BUILD_NUMBER),回滚就是部署上一个版本号,一条命令的事
  2. 测试强制通过才能构建镜像,再也没人能跳过测试发布
  3. 新项目接入只要复制一个 Jenkinsfile,改几个参数,十分钟搞定

凭据管理:告别散落在脚本里的密码

以前我们的部署脚本里有这样一行:

sshpass -p 'MyP@ssw0rd123' ssh deploy@prod-server

密码明文写在脚本里,还在 Git 仓库里躺了两年。Jenkins 的 Credentials 功能把这个彻底解决了:

Manage Jenkins → Credentials 里添加一个 "SSH Username with private key" 类型的凭据,ID 设为 deploy-key,然后在 Pipeline 里:

withCredentials([sshUserPrivateKey(credentialsId: 'deploy-key', keyFileVariable: 'SSH_KEY')]) {
    sh 'ssh -i $SSH_KEY deploy@prod-server "cd /app && ./deploy.sh"'
}

日志里密码会被自动打码成 ****。我把仓库里所有带明文密码的历史记录都清理掉了,这件事拖了太久,Jenkins 逼着我做完了。

踩坑记录

说几个文档里不会告诉你、但我实打实撞上的问题:

1. 不要把 Jenkins master 当构建机器用。 项目多了以后,构建并发会让 master 卡死。后来我加了两台 agent 节点,master 上设置 executor 数为 0,只负责调度。这一步我拖太久了,期间 master 至少挂了三次。

2. workspace 不清理会撑爆磁盘。 每次 mvn package 留下的 target 目录累积起来,三个月后磁盘满了,所有构建诡异失败。解决:在 pipeline 里加 deleteDir() 或者用 Workspace Cleanup 插件。

3. webhook 配置后不触发构建。 我以为是 GitLab 配置问题,排查半天,其实是 Jenkins 任务的 URL 填错了,且防火墙没放行 GitLab 到 Jenkins 的 8080 端口。防火墙——这个最朴素的原因,我最后才查。

4. 插件升级要谨慎。 有一次我批量升级了所有插件,某个插件新版本和 GitLab 插件不兼容,webhook 全挂了。教训是:升级一次只升一个,升级前用 ThinBackup 插件备份

实际效果对比

指标 传统方案 Jenkins 之后
单次发布时间 20-40 分钟,手工 6 分钟,自动
发布频率 每周 1-2 次 每天 3-5 次
回滚 手工找旧包,靠运气 一条命令指定版本号
排查构建问题 翻聊天记录问人 看构建日志,全部留痕

诚实的评价:Jenkins 不是完美的

说了这么多好话,也得讲讲代价:

  • UI 确实老旧,Blue Ocean 好一些但很久没大更新了,和 GitLab CI、GitHub Actions 的界面没法比
  • Groovy 语法学习成本不低,团队里两个人至今只会改不会写
  • 维护成本真实存在:插件升级、磁盘清理、节点管理,这些活儿每个月大概要花我半天时间
  • 如果你是新项目、云端部署,GitHub Actions 或云厂商自带的 CI 可能更省心,没必要上来就自建 Jenkins

我的结论是:Jenkins 最适合的场景是自建机房、多项目复用、需要灵活控制部署流程的团队。我们这种内网环境加十几个人规模的团队,它就是当下最务实的选择。

如果你也在被手工发布折磨,我的建议是:先挑一个最痛的服务,用 Docker 起个 Jenkins,写一个五阶段的流水线跑通全流程。一旦体会到“点一下按钮、看着日志滚动、六分钟后服务上线”的感觉,你就回不去了。

相关 Agent

D

Docker

Docker 是一个容器化平台,允许开发者将应用及其依赖项打包成轻量级、可移植的容器。它简化了跨环境的开发、测试和部署过程。该描述强调它最适合容器化和开发环境,提供免费方案和团队方案,团队方案从每用户每月5美元起。

了解更多 →