devops系列(四) Jenkins Pipeline:搭建你的第一条 CI/CD 流水线
Jenkins Pipeline搭建你的第一条 CI/CD 流水线最近有个读者私信我“哥我们公司发版还是手动mvn package完用 FTP 传服务器再 SSH 上去kill -9重启发一次版熬到凌晨两点还总出错。有没有救”我说“兄弟你这哪是发版这是渡劫啊。”今天咱们就来聊聊怎么用 Jenkins Pipeline 把这套人肉 CI/CD给自动化了。读完这篇文章你至少能搭出一条像模像样的流水线告别凌晨两点的人工操作。一、问题引入那些年我们手动发过的版先回忆一下传统发版的标准流程开发在本地打好包mvn package或npm run build打开 FTP/WinSCP把 jar 包或 dist 文件夹拖到服务器上SSH 登录服务器找到旧进程kill -9干掉启动新服务祈祷不要报错打开浏览器手动点两下验证应该没问题如果有问题手忙脚乱回滚…这套流程的问题咱们心里都门儿清容易出错人不是机器步骤一多就容易漏。jar 包传错了目录配置文件忘了覆盖服务启动参数写岔了随便一个失误就是生产事故。不可追溯谁发的版什么时候发的发了什么版本全凭微信群里的我发完了和运维小哥的记忆力。效率低下发一次版少则半小时多则几个小时。要是多环境测试、预发、生产各来一遍一晚上就没了。回滚困难一旦出问题想回滚到上一个版本不好意思你可能连上个版本的包都找不到。说白了发版这件事就不该靠人手动去做。机器能干的活为什么要让人熬夜干二、方案分析为什么选 Jenkins Pipeline解决这个问题的方案其实不少咱们简单盘一盘方案优点缺点GitLab CI/CD和代码仓库深度集成配置简单对 Jenkins 生态已有的插件和存量项目迁移成本高GitHub Actions云端即用社区 Action 丰富企业内网或私有部署场景下不太方便Jenkins Pipeline生态最成熟插件丰富自由度高企业用得最多界面有点复古配置稍显复杂如果你的公司已经在用 Jenkins或者你想学一套通用性最强、找工作最吃香的 CI/CD 方案那Jenkins Pipeline几乎是不二之选。而且 Pipeline 相比传统的 Jenkins Job在 Web 界面上点点点配置有个巨大的优势Pipeline as Code。整个流水线写在一个Jenkinsfile里跟代码一起放在 Git 仓库里。版本可控、可 review、可复用谁改了流水线一目了然。Jenkins Pipeline 有两种语法Scripted Pipeline基于 Groovy 脚本灵活度高但写起来像代码对新手不太友好。Declarative Pipeline声明式语法结构清晰像写 YAML 一样规整推荐新手和大多数场景使用。我的建议除非你有特别复杂的动态逻辑否则一律用 Declarative。好读、好维护、团队协作成本低。三、实现过程Step by Step 搭一条流水线好了说了这么多咱们开始动手。先看一下这条流水线的完整流程┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ 拉取 │ - │ 编译 │ - │ 单元测试│ - │ 构建镜像│ - │ 推送镜像│ - │ 部署应用│ │ 代码 │ │ 打包 │ │ │ │ │ │ 仓库 │ │ │ └─────────┘ └─────────┘ └─────────┘ └─────────┘ └─────────┘ └─────────┘这就是一条标准的 CI/CD 流水线。下面咱们逐个 stage 拆解。3.1 前置准备Jenkins 和核心插件Jenkins 的安装就不手把手教了网上教程一大把。不管你是用 Docker 起一台还是在服务器上直接装核心思路都一样。装完之后记得装这几个必备插件Pipeline没有它Jenkinsfile 跑不起来。Git从 Git 仓库拉代码。Docker Pipeline在 Pipeline 里执行 Docker 命令。Kubernetes CLI可选如果你要部署到 K8s这个插件能帮你方便地操作kubectl。3.2 编写 JenkinsfileDeclarative 语法下面是一份精简但可直接用的 Jenkinsfile 模板。我加了详细注释你可以直接复制到项目里改改参数就能跑。pipeline{// 指定在哪台 agent 上运行any 表示任意可用节点agent any// 定义环境变量方便后面复用environment{IMAGE_NAMEmyappIMAGE_TAG${BUILD_NUMBER}// 用 Jenkins 构建号做镜像标签REGISTRYregistry.example.comKUBE_CONFIGcredentials(kubeconfig-id)// 从 Jenkins 凭据里取 kubeconfig}stages{// Stage 1: 拉取代码stage(Checkout){steps{// 从 Git 仓库拉代码分支参数化默认 mastergit branch:${BRANCH_NAME},url:https://github.com/your-repo.gitecho代码拉取完成当前分支:${env.BRANCH_NAME}}}// Stage 2: 编译打包stage(Build){steps{// 以 Maven 项目为例执行编译并跳过测试测试单独跑shmvn clean package -DskipTestsecho编译完成产物在 target/ 目录下}}// Stage 3: 单元测试stage(Test){steps{shmvn test}post{always{// 收集测试报告Jenkins 界面上能看到junittarget/surefire-reports/*.xml}}}// Stage 4: 构建 Docker 镜像stage(Build Docker Image){steps{script{// 用当前目录的 Dockerfile 构建镜像docker.build(${REGISTRY}/${IMAGE_NAME}:${IMAGE_TAG})}}}// Stage 5: 推送镜像到仓库stage(Push Image){steps{script{// 登录镜像仓库并推送docker.withRegistry(https://${REGISTRY},registry-credentials-id){docker.image(${REGISTRY}/${IMAGE_NAME}:${IMAGE_TAG}).push()// 同时推一个 latest 标签方便快速拉取docker.image(${REGISTRY}/${IMAGE_NAME}:${IMAGE_TAG}).push(latest)}}}}// Stage 6: 部署到 K8sstage(Deploy){steps{// 用 sed 替换 deployment.yaml 里的镜像标签sh sed -i s|IMAGE_TAG|${IMAGE_TAG}|g k8s/deployment.yaml kubectl --kubeconfig${KUBE_CONFIG}apply -f k8s/ echo部署完成镜像版本:${IMAGE_TAG}}}}// 流水线跑完后的收尾post{success{echo 流水线执行成功可以去喝杯咖啡了。}failure{echo❌ 流水线挂了快去查日志吧...}always{// 清理工作区避免磁盘爆掉cleanWs()}}}关键点解读agent any让 Jenkins 自己挑一台空闲的节点跑任务。如果你的编译节点有标签比如label maven可以改成agent { label maven }。environment把镜像名、仓库地址等抽成变量后面改起来方便。post里的always不管成功失败都执行适合放清理、发通知这类操作。cleanWs()很多新手会漏掉这步结果 Jenkins 服务器磁盘被旧构建占满别问我怎么知道的。3.3 蓝绿部署 vs 滚动更新部署的时候怎么保证不断服代码编译完了、镜像也推了最后一步部署可不能简单粗暴地kill -9重启。生产环境讲究的是零停机发布。这里介绍两个最常见的思路滚动更新Rolling Update这是 K8s 的默认策略。简单说就是先起几个新 Pod等它们健康了再逐个干掉旧 Pod。整个过程中服务的总实例数基本保持不变用户无感知。优点是简单、省资源缺点是如果新版本有问题回滚需要时间而且更新期间新旧版本会共存一小段时间。蓝绿部署Blue-Green Deployment同时维护两套环境蓝色当前线上版本和绿色新版本。部署时先把绿色环境全部启动并验证通过然后把流量一次性从蓝色切到绿色。如果出问题秒切回蓝色。优点是回滚快、风险低缺点是需要双倍资源。对于刚入门 CI/CD 的同学我的建议是先用 K8s 自带的滚动更新配置简单、成本低。等团队成熟了再考虑上蓝绿部署或金丝雀发布。四、踩坑记录我踩过的那些 Jenkins 的坑理论很美好实操起来总有些意想不到的坑。这里分享两个我实实在在踩过的希望你别重蹈覆辙。坑 1Jenkins 容器里执行不了 Docker 命令很多同学习惯用 Docker 跑 Jenkins结果在 Pipeline 里一执行docker build报错docker: not found或者权限不足Got permission denied while trying to connect to the Docker daemon socket原因Jenkins 容器本身没有 Docker 环境或者容器里的jenkins用户没有权限访问宿主机的 Docker socket。解决方案启动 Jenkins 容器时把宿主机的 Docker socket 挂载进去dockerrun-d\-v/var/run/docker.sock:/var/run/docker.sock\-v/usr/bin/docker:/usr/bin/docker\jenkins/jenkins:lts同时给jenkins用户加到docker组# 进入 Jenkins 容器usermod-aGdockerjenkins注意挂载 Docker socket 有一定安全风险但在内网 CI 场景下基本够用。如果要求严格可以考虑用 Docker in DockerDinD配置会复杂一些。坑 2Pipeline 里的环境变量传不到 Shell 脚本里我在environment里定义了一个变量environment{MY_VARhello}然后在sh步骤里用$MY_VAR结果为空。原因Declarative Pipeline 的sh步骤里Groovy 变量和 Shell 环境变量的作用域是两套系统。直接用$MY_VAR有时候能行有时候不行取决于 Jenkins 版本和上下文。解决方案用双引号包裹sh并显式用${env.MY_VAR}或${MY_VAR}插值sh echo${env.MY_VAR}echo${MY_VAR}如果你用的是单引号sh ...Groovy 不会进行变量插值Shell 里自然拿不到值。这个细节坑了我整整一个下午。坑 3构建缓存导致依赖没更新 bonus 坑有时候明明改了pom.xml里的依赖版本Jenkins 构建出来的包还是老的。原因Maven 的本地仓库缓存~/.m2/repository或者 Docker 的 layer 缓存没清。解决方案Maven 构建加-U参数强制更新快照依赖mvn clean package -UDocker 构建加--no-cache参数或者在 Dockerfile 里把pom.xml和源码分层 COPY避免不必要的缓存五、验证与总结按照上面的 Jenkinsfile 配置好流水线之后你可以这样做验证在代码仓库里提交一个改动推送到 Git。打开 Jenkins点击立即构建或者配置 Webhook 让它自动触发。看着流水线一个 stage 一个 stage 地变绿最后显示 流水线执行成功。登录 K8s Dashboard 或服务器确认新版本已经跑起来了。这时候你会发现发版从原来的人工两小时变成了点一下按钮甚至自动触发的十分钟。而且全程有日志、有版本、可追溯出了问题也知道从哪查。六、写在最后CI/CD 这件事说起来高大上本质上就是把人重复做的事交给机器去做。Jenkins Pipeline 只是工具之一更重要的是流水线思维——代码提交后应该自动编译、自动测试、自动部署人只负责写代码和做决策。当然Jenkins 也不是完美的。界面老旧、插件兼容性偶尔抽风、Groovy 语法对 Java 程序员还算友好但对前端同学可能有点劝退。如果你已经在用 GitLab 或 GitHub也可以试试它们原生的 CI/CD。工具没有绝对的好坏适合团队现状的才是最好的。最后想问问你你们公司现在是怎么发版的还是手动 FTP 吗你在搭 Jenkins Pipeline 的时候踩过哪些坑有没有比 Jenkins 更好用的 CI/CD 工具推荐欢迎在评论区交流咱们一起进步如果这篇文章对你有帮助点个赞或者转发给还在手动发版的兄弟救救他吧。