1. 从手动到自动为什么你的团队需要一个CI/CD流水线我见过不少团队项目初期都是手动部署。开发同学吭哧吭哧写完代码本地打个包然后登录服务器上传、解压、重启服务。运气好的时候一次成功运气不好各种环境问题、依赖冲突能折腾一晚上。这种“人肉运维”模式在项目规模小、迭代慢的时候还能凑合一旦项目复杂起来多模块、多环境、多分支手动部署简直就是灾难现场效率低下不说还极易出错。所以搭建一个自动化的CI/CD流水线本质上不是炫技而是为了“救火”和“提效”。它能帮你把那些重复、枯燥、容易出错的部署工作交给机器去完成。你只需要一次配置后续每次代码提交流水线就会自动帮你完成编译、测试、打包、构建镜像、部署上线这一整套流程。想象一下下午提交的代码下班前就已经自动部署到测试环境测试同学立刻就能验证这种顺畅感是手动部署给不了的。这次我们要聊的就是基于Jenkins和阿里云Kubernetes (k8s)来搭建这样一条流水线。为什么是这两个组合Jenkins是老牌的自动化服务器插件生态极其丰富几乎你能想到的集成它都能搞定像个万能的“胶水”。而阿里云k8s则提供了稳定、弹性、易于管理的容器化部署平台。用Jenkins做“流水线引擎”用k8s做“运行底座”这个组合拳打好了能解决绝大多数中小型团队的自动化部署需求。特别是面对那些有多个子模块、依赖关系复杂的项目如何高效地管理构建和镜像正是这套方案要解决的核心痛点。2. 基石搭建Jenkins的安装与“避坑”指南工欲善其事必先利其器。第一步就是把Jenkins这个“大脑”给装好。原始文章里提到了两种方式RPM包安装和Docker安装。这里我强烈建议除非你对Docker和Linux权限管理非常熟悉否则老老实实用系统包管理器安装。我踩过这个坑用Docker跑Jenkins看起来清爽但实际用起来想在Jenkins的Pipeline里执行docker build命令会碰到各种权限问题比如经典的docker: not found错误。这主要是因为容器内的Jenkins进程无法直接调用宿主机的Docker守护进程虽然网上有各种解决方案比如挂载Docker Socket但都涉及敏感的系统权限配置复杂且有一定安全风险。所以咱们走最稳妥的路。以CentOS/RHEL系统为例安装过程其实很简单# 1. 下载Jenkins的RPM包可以去官网找对应版本 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.key # 2. 安装 sudo yum install jenkins # 3. 调整Java路径关键 # 编辑Jenkins的启动配置文件 sudo vim /etc/sysconfig/jenkins # 找到 JAVA_HOME 这一行取消注释并设置为你服务器上JDK的实际路径 # 例如JAVA_HOME/usr/lib/jvm/java-11-openjdk这里有个细节很容易被忽略Jenkins默认会用自己的内置JRE但如果你服务器上已经装了Maven等项目构建工具它们对JDK版本可能有要求。所以最好在/etc/sysconfig/jenkins里显式指定JAVA_HOME让它使用我们统一管理的JDK避免后续构建时出现版本兼容问题。配置好后启动服务sudo systemctl start jenkins。用浏览器访问http://你的服务器IP:8080就能看到解锁页面了。初始密码在哪里执行sudo cat /var/lib/jenkins/secrets/initialAdminPassword就能看到。登录后我建议选择“安装推荐的插件”这会装好Git、Pipeline等最常用的插件省心。3. 打通任督二脉关键配置详解Jenkins装好了但它还是个“光杆司令”需要和你的代码仓库Git、容器仓库Harbor/Docker Registry、以及最终的部署平台阿里云k8s打通。这一步配置对了后面就顺了。3.1 让Jenkins和Gitlab“握手”自动化流水线的起点是代码。我们需要让Jenkins能从Gitlab上拉取代码而且最好是无密码的SSH方式这样最安全方便。注意一定要用jenkins系统用户来操作因为Jenkins服务默认就是以jenkins用户运行的。# 切换到jenkins用户 sudo su -s /bin/bash jenkins # 生成SSH密钥对 ssh-keygen -t rsa -C jenkinsyour-server # 一路回车不用设密码生成后cat ~/.ssh/id_rsa.pub把公钥内容复制到Gitlab账号的SSH Keys设置里。这步是让Gitlab认识这台Jenkins服务器。接着回到Jenkins网页界面。进入“Manage Jenkins” - “Manage Credentials” - “全局凭据” - “添加凭据”。类型选择“SSH Username with private key”。ID可以自己起个名比如gitlab-ssh-key。用户名填git这是Gitlab SSH协议默认用户。私钥选择“Enter directly”然后点“Add”把刚才生成的私钥内容sudo cat /var/lib/jenkins/.ssh/id_rsa整个复制进去包括-----BEGIN RSA PRIVATE KEY-----和-----END RSA PRIVATE KEY-----这两行。配置完后最好测试一下。可以在Jenkins服务器上用jenkins用户手动执行一次git clone命令首次连接时会询问是否信任主机指纹输入yes即可。这样就把“信任关系”建立好了。3.2 赋予Jenkins操控k8s集群的权限接下来是重头戏让Jenkins能操作阿里云k8s集群去部署应用。这里我们采用kubeconfig文件的方式这是最清晰、最符合k8s原生实践的方法。登录阿里云容器服务控制台找到你的Kubernetes集群。点击“连接信息”你会看到kubectl的配置命令里面包含了一个长长的certificate-authority-data和token。我们不需要执行命令而是点击“复制”按钮把整个配置内容复制下来。在本地电脑上创建一个文件比如叫k8s-config把内容粘贴进去保存。回到Jenkins再次进入凭据管理页面。类型选择“Secret file”。这是一个关键点很多人会选错。因为我们的kubeconfig是一个完整的配置文件用“Secret file”类型最合适。上传刚才保存的k8s-config文件并给它一个易记的ID比如aliyun-k8s-config。这样Jenkins就拿到了访问k8s集群的“钥匙”。后续在Pipeline脚本里我们就能通过withKubeConfig指令安全地使用这个凭据来执行kubectl命令了。3.3 配置构建工具与环境变量为了让Jenkins能执行mvn命令我们需要告诉它Maven装在哪里。进入“Manage Jenkins” - “Global Tool Configuration”。找到“Maven”部分点击“Maven安装...”。取消勾选“自动安装”因为咱们服务器上很可能已经装好了。在“MAVEN_HOME”里填写你服务器上Maven的安装绝对路径例如/usr/local/apache-maven-3.8.6。同样地我们也可以配置全局环境变量让所有Job都能用到。进入“Manage Jenkins” - “Configure System”找到“Global properties”勾选“Environment variables”。然后添加JAVA_HOME你的JDK安装路径。PATHEXTRA可以添加一些自定义路径比如$JAVA_HOME/bin:$MAVEN_HOME/bin。这样在Pipeline的sh步骤里就能直接调用java、mvn等命令了。4. 核心实战编写一个健壮且通用的Pipeline脚本配置搞定终于可以动手写Pipeline了。Jenkins Pipeline有两种写法声明式Declarative和脚本式Scripted。我推荐用声明式Pipeline结构更清晰像写配置文件一样特别适合新手。咱们的目标是写一个既能满足当前项目又具有一定通用性方便复用到其他项目的脚本。4.1 搭建Pipeline骨架与参数化先创建一个新的“流水线”类型的Job。在Pipeline定义里我们直接写脚本。一个基本的骨架如下但我们要把它变得更强大。pipeline { agent any // 指定在任何可用代理上运行 parameters { booleanParam(name: PRE_BUILD, defaultValue: true, description: 是否执行前置构建如编译公共依赖模块) choice(name: BRANCH, choices: [dev, test, master], description: 选择要构建部署的分支) string(name: IMAGE_TAG, defaultValue: latest, description: 自定义镜像标签默认使用时间戳) } environment { // 定义一些环境变量方便引用 REGISTRY harbor.your-company.com PROJECT_NAMESPACE your-namespace APP_NAME ${JOB_NAME.toLowerCase()} // 默认使用Job名作为应用名 } stages { stage(0. 前置构建检查) { steps { script { if (params.PRE_BUILD) { echo 触发前置构建任务... build job: pre-build-master, wait: true } else { echo 跳过前置构建。 } } } } stage(1. 拉取代码) { steps { checkout([ $class: GitSCM, branches: [[name: */${params.BRANCH}]], userRemoteConfigs: [[ credentialsId: gitlab-ssh-key, // 使用之前配置的SSH凭据 url: gitgitlab.your-company.com:your-group/your-repo.git ]] ]) } } stage(2. 单元测试与编译) { steps { sh echo 开始编译与测试... mvn clean test package -DskipTestsfalse -B # -B 表示批处理模式让输出更简洁 } } } }这个开头做了几件重要的事参数化构建通过parameters块我们定义了三个参数。一个是布尔值决定是否运行一个可能存在的、独立的前置构建Job比如专门构建基础jar包。一个是选择列表让构建时可以选择分支。一个是字符串参数用于自定义镜像标签。这样每次构建时都可以灵活选择而不是写死在脚本里。环境变量集中管理environment块里定义了镜像仓库地址、k8s命名空间等。这里用${JOB_NAME.toLowerCase()}是一个小技巧让应用名默认和Jenkins的Job名一致转成小写减少了硬编码。清晰的阶段每个stage代表流水线的一个清晰阶段日志看起来非常直观。4.2 多模块项目的构建与镜像打包优化对于单模块项目mvn package之后直接docker build就行。但对于多模块的Maven项目比如一个父POM下面有user-service,order-service等多个子模块我们需要更精细的控制。假设我们只修改了order-service希望只构建这个服务的镜像该怎么做stage(3. 构建Docker镜像) { steps { script { // 动态生成镜像标签分支名-提交ID前7位-时间戳 def commitId sh(returnStdout: true, script: git rev-parse --short HEAD).trim() def customTag ${params.BRANCH}-${commitId}-${currentBuild.startTimeInMillis / 1000} // 如果用户自定义了标签则优先使用 if (params.IMAGE_TAG ! latest) { customTag params.IMAGE_TAG } env.IMAGE_FULL_NAME ${REGISTRY}/${PROJECT_NAMESPACE}/${APP_NAME}:${customTag} echo 构建镜像: ${env.IMAGE_FULL_NAME} // 假设Dockerfile在项目根目录 sh docker build -t ${env.IMAGE_FULL_NAME} . # 如果你只需要构建特定子模块可以这样 # cd order-service docker build -t ${env.IMAGE_FULL_NAME} . } } } stage(4. 推送镜像到仓库) { steps { script { // 这里假设仓库是Harbor需要先登录 withCredentials([usernamePassword(credentialsId: harbor-credential, passwordVariable: HARBOR_PASSWORD, usernameVariable: HARBOR_USERNAME)]) { sh docker login -u ${HARBOR_USERNAME} -p ${HARBOR_PASSWORD} ${REGISTRY} docker push ${env.IMAGE_FULL_NAME} } // 推送成功后清理本地镜像释放磁盘空间 sh docker rmi ${env.IMAGE_FULL_NAME} } } }这里有几个优化点镜像标签策略没有使用简单的latest而是组合了分支名、Git短提交ID和时间戳。例如dev-a1b2c3d-1651234567。这样一看标签就知道这个镜像对应哪个分支、哪次提交、何时构建的回滚和排查问题极其方便。凭据安全使用仓库密码等敏感信息同样应该存储在Jenkins的“凭据”中。这里使用withCredentials指令可以安全地将凭据注入到环境变量中供脚本使用避免密码明文出现在脚本或日志里。资源清理构建并推送镜像后立即删除本地镜像这是一个好习惯能避免Jenkins服务器磁盘被慢慢占满。4.3 部署到阿里云k8s蓝绿部署的思想最激动人心的部分来了自动部署。原始文章里使用了kubectl set image来更新现有部署这是一种滚动更新Rolling Update。这里我想介绍一种更稳妥、在复杂项目中常用的策略蓝绿部署Blue-Green Deployment的思路。当然在Pipeline里完全实现蓝绿部署较复杂但我们可以吸收其“先启动新版本再切换流量”的核心思想做一个简化版。stage(5. 部署到Kubernetes) { steps { script { // 使用之前配置的kubeconfig文件凭据 withKubeConfig([credentialsId: aliyun-k8s-config, serverUrl: https://your-k8s-apiserver:6443]) { // 1. 检查当前部署是否存在 def deployExists sh( script: kubectl get deployment ${APP_NAME} -n ${PROJECT_NAMESPACE} --ignore-not-found, returnStatus: true ) 0 if (deployExists) { echo 执行滚动更新... // 方法一直接更新镜像原始文章方法 sh kubectl set image deployment/${APP_NAME} ${APP_NAME}${env.IMAGE_FULL_NAME} -n ${PROJECT_NAMESPACE} } else { echo 部署不存在创建新部署... // 方法二应用完整的K8s YAML文件更推荐 sh # 假设项目里有一个 k8s-deployment.yaml 文件模板 # 可以使用 sed 或 envsubst 替换模板中的变量如镜像名 export IMAGE_NAME${env.IMAGE_FULL_NAME} envsubst k8s-deployment.yaml | kubectl apply -n ${PROJECT_NAMESPACE} -f - } // 2. 等待部署完成 sh kubectl rollout status deployment/${APP_NAME} -n ${PROJECT_NAMESPACE} --timeout300s echo 部署成功 // 3. 可选进行简单的健康检查 sh # 获取Service的ClusterIP或NodePort进行curl访问测试 kubectl get svc ${APP_NAME}-svc -n ${PROJECT_NAMESPACE} -o jsonpath{.spec.clusterIP} # 可以在这里添加curl命令检查应用是否真的就绪 } } } }这段脚本做了几件事判断部署是否存在通过kubectl get deployment的返回状态判断是更新现有应用还是首次创建。这提高了脚本的通用性。两种部署方式对于更新使用kubectl set image这是最简单直接的滚动更新命令。对于新建我强烈推荐使用YAML文件声明式部署。在项目根目录维护一个k8s-deployment.yaml文件模板里面定义了Deployment、Service、ConfigMap等所有资源。在Pipeline里用环境变量替换掉镜像标签等动态部分然后kubectl apply。这种方式把k8s的资源定义和构建过程解耦管理起来清晰得多也方便进行版本控制。等待与确认kubectl rollout status会阻塞直到部署成功或超时。这确保了部署结果的可控性。健康检查部署成功不代表服务真的可用。可以添加一个步骤去调用新部署服务的健康检查接口确保应用真正启动成功。5. 进阶优化让流水线更智能、更可靠基础流水线跑通后我们可以追求更高阶的优化提升效率和可靠性。5.1 利用“凭据”管理所有密钥前面我们用到了Gitlab SSH Key、Harbor密码、kubeconfig文件。在Jenkins里所有这类敏感信息都应该存入“凭据”。Jenkins支持多种类型的凭据Secret text用于API Token、Username with password、SSH Key、Secret file等。在Pipeline中一律使用withCredentials或credentials绑定插件来引用例如// 使用用户名密码凭据 withCredentials([usernamePassword(credentialsId: harbor-login, usernameVariable: USER, passwordVariable: PASS)]) { sh docker login -u $USER -p $PASS $REGISTRY } // 使用Secret文件凭据如kubeconfig withCredentials([file(credentialsId: k8s-config-file, variable: KUBECONFIG)]) { sh KUBECONFIG${KUBECONFIG} kubectl get pods }这样做绝对安全密钥不会泄露在脚本、控制台输出或项目工作空间中。5.2 并行构建与流水线提速如果你的项目有多个独立的模块或者构建过程中有大量独立的集成测试并行执行是提速的关键。Jenkins Pipeline可以很容易地实现并行。stage(并行测试) { parallel { stage(单元测试) { steps { sh mvn test -DtestUnitTestSuite } } stage(集成测试) { steps { sh mvn verify -DtestIntegrationTestSuite } } stage(代码质量扫描) { steps { sh mvn sonar:sonar } } } }这个parallel块内的多个stage会同时启动。对于多模块项目你甚至可以写一个循环为每个需要独立构建部署的服务模块动态生成并行的部署阶段。这能极大缩短从代码提交到交付的周期。5.3 失败处理与通知机制一个健壮的流水线必须能妥善处理失败。我们可以使用post指令根据构建结果执行不同的后续操作。pipeline { agent any stages { // ... 你的各个stage ... } post { always { // 无论成功失败都执行适合做清理工作 echo 构建过程结束。清理工作空间... cleanWs() // 清理Jenkins工作空间 } success { // 只有成功时执行 echo 恭喜流水线执行成功 // 可以在这里触发下游Job或者发送成功通知到钉钉/企业微信 dingtalk ( robot: jenkins-robot, type: MARKDOWN, title: 构建成功通知, text: 项目 ${env.JOB_NAME} 构建成功\n镜像${env.IMAGE_FULL_NAME}\n构建号${env.BUILD_NUMBER} ) } failure { // 只有失败时执行 echo 很遗憾流水线执行失败。 // 发送告警通知并附上失败日志链接 dingtalk ( robot: jenkins-robot, type: MARKDOWN, title: 构建失败告警, text: 项目 ${env.JOB_NAME} 构建失败\n请立即检查${env.BUILD_URL}console ) } unstable { // 当构建被标记为不稳定时执行例如测试通过但覆盖率下降 echo 构建结果不稳定需要关注。 } } }通过post阶段我们实现了构建后的闭环成功则通知团队失败则立即告警并且始终清理环境为下一次构建做好准备。集成钉钉、企业微信或Slack等通知插件能让团队第一时间感知构建状态。5.4 将Pipeline脚本存入代码仓库Jenkinsfile到目前为止我们的Pipeline脚本都是写在Jenkins Job的配置页面里的。这有一个问题脚本的版本管理和修改追溯不方便。最佳实践是使用Jenkinsfile。在你的项目代码仓库根目录创建一个名为Jenkinsfile的文件把上面我们写的整个pipeline { ... }脚本内容放进去。然后在Jenkins Job配置中选择“Pipeline script from SCM”指定你的仓库地址和凭据并设置脚本路径为Jenkinsfile。这样做的好处巨大版本化Pipeline脚本和代码一起被Git管理任何修改都有记录可以回滚。代码评审修改Jenkinsfile需要提Pull Request经过团队评审。一致性每个分支都可以有对应的流水线行为比如dev分支自动部署到测试环境master分支部署到生产环境。复用多个类似项目可以共享同一个Jenkinsfile模板。6. 踩坑记录与经验之谈最后分享几个我实际踩过的坑希望能帮你绕过去。第一个坑权限问题无处不在。无论是Jenkins调用Docker还是执行kubectl命令本质都是Linux进程权限问题。确保Jenkins的运行用户通常是jenkins有足够的权限。对于Docker通常是把jenkins用户加入docker用户组。对于k8s就是靠我们前面精心配置的kubeconfig文件它里面包含了访问集群所需的认证信息。第二个坑网络与代理。如果你的Jenkins服务器在内网而需要从外网拉取依赖比如Maven中央库、NPM源或者推送镜像到公网仓库网络问题会让人头疼。确保Jenkins服务器能稳定访问所需的所有外部地址。必要时需要在Maven的settings.xml或Docker的daemon.json中配置代理。第三个坑脚本中的路径和上下文。在Pipeline的sh步骤里执行命令时要注意当前工作目录。checkout代码后默认就在工作空间根目录。如果你需要进入子目录操作一定要用cd命令切换或者使用dir步骤。例如dir(sub-module) { sh mvn package }。第四个坑资源清理。流水线跑多了Jenkins服务器上可能会堆积大量Docker镜像、临时文件导致磁盘爆满。一定要在post { always { ... } }阶段加入清理步骤比如cleanWs()清理工作空间docker system prune -f清理Docker无用资源谨慎使用确保不会误删正在使用的镜像。把Jenkins和阿里云k8s这套CI/CD流水线搭起来初期确实需要投入一些时间但一旦跑顺它带来的效率提升和部署质量的保障是巨大的。你会发现自己从繁琐的部署操作中解放出来有更多时间专注于代码和设计。更重要的是它建立了一套标准化的、可重复的交付流程这是团队工程能力走向成熟的重要一步。