文章目录
痛点背景:为什么必须上CI/CD
你也遇到,当API网关后端服务完成了微服务拆分,从单体变成了12个独立服务。拆分后功能迭代快了,但部署噩梦开始了:
- 每次发版需要手动登录6台服务器,执行git pull、mvn package、scp、重启Tomcat
- 测试同学每天追着问"这个版本部署了吗?"
- 某次灰度发布,因为漏改了配置文件,导致20%的请求路由到了错误的集群,用户投诉率上升12%
| 阶段 | 耗时(分钟) | 人工操作次数 | 出错概率 |
|---|---|---|---|
| 代码合并 | 5 | 3 | 15% |
| 编译打包 | 8 | 1 | 5% |
| 上传服务器 | 3 | 2 | 10% |
| 停止服务 | 2 | 2 | 20% |
| 替换包 | 1 | 1 | 8% |
| 启动验证 | 8 | 3 | 25% |
| 总计 | 27 | 12 | 约83% |
最要命的是,每次部署都像在走钢丝------没人敢保证12步操作不出任何差错。
解决方案概览:我们选型Jenkins的3个理由
坦白说,选型时我们对比了GitLab CI、Drone CI和Jenkins。虽然Jenkins界面丑了点,但最终选它是因为:
- 插件生态无敌:Git、Maven、Docker、Kubernetes插件一应俱全,几乎覆盖所有工具链
- Pipeline as Code:用Jenkinsfile把部署流程代码化,版本可控
- 分布式构建:可以挂载多个Agent节点,并行构建效率高
最终成果:部署耗时从47分钟降到6分钟,人工操作从12步变成1次触发,上线后第一个月零部署事故。
核心实战章节
1. 环境搭建:从零到第一个Pipeline
问题背景
新买的服务器,CentOS 7.9,啥都没有。我们需要装Java、Jenkins、配置Git和Maven。
方案选型
- 安装方式:官方RPM包 vs Docker镜像
- 我们选了RPM包,因为要挂载物理机的Docker和Maven,Docker方式网络配置麻烦
原理剖析
Jenkins本质上是一个可扩展的自动化引擎,核心是Job和Pipeline。Job是单个任务,Pipeline是多个Stage组成的流水线。
可运行代码
bash
# 1. 安装Java 11(Jenkins 2.346+需要Java 11)
sudo yum install -y java-11-openjdk-devel
# 2. 添加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 yum install -y jenkins
# 3. 启动并设置开机自启
sudo systemctl start jenkins
sudo systemctl enable jenkins
# 4. 查看初始密码(用于首次登录)
sudo cat /var/lib/jenkins/secrets/initialAdminPassword
# 输出示例:a1b2c3d4e5f6
登录后安装推荐插件,创建第一个Pipeline Job:
groovy
// Jenkinsfile - 第一个Hello World流水线
pipeline {
agent any
stages {
stage('Checkout') {
steps {
echo '拉取代码...'
// 实际项目中这里配置Git仓库地址
// git 'https://github.com/your-repo/your-project.git'
}
}
stage('Build') {
steps {
echo '编译中...'
// sh 'mvn clean package -DskipTests'
}
}
stage('Test') {
steps {
echo '运行测试...'
// sh 'mvn test'
}
}
stage('Deploy') {
steps {
echo '部署完成!'
}
}
}
}
运行输出:
Started by user admin
[Pipeline] Start of Pipeline
[Pipeline] stage
[Pipeline] { (Checkout)
[Pipeline] echo
拉取代码...
[Pipeline] }
[Pipeline] stage
[Pipeline] { (Build)
[Pipeline] echo
编译中...
[Pipeline] }
[Pipeline] stage
[Pipeline] { (Test)
[Pipeline] echo
运行测试...
[Pipeline] }
[Pipeline] stage
[Pipeline] { (Deploy)
[Pipeline] echo
部署完成!
[Pipeline] }
[Pipeline] End of Pipeline
Finished: SUCCESS
⚠️ 注意事项 :首次安装后,建议立即配置Jenkins的JVM参数,否则默认内存分配只有256MB,跑几个Job就OOM了。在
/etc/sysconfig/jenkins中修改JAVA_OPTS="-Xmx2048m -Xms512m"。
2. 构建真正的CI流水线:代码提交自动编译测试
问题背景
手动触发Pipeline只是第一步,真正的CI是代码一提交就自动触发。我们需要配置Webhook和自动化构建。
方案选型
- Poll SCM(定时轮询)vs Webhook触发
- 我们选了Webhook,实时性更好,不浪费服务器资源
原理剖析
Git仓库(GitHub/GitLab)在代码push时,会向Jenkins的Webhook地址发送POST请求,Jenkins收到后触发对应的Pipeline。

实现要点 :在Jenkins中安装"GitLab Plugin"或"GitHub Integration Plugin",然后在项目配置中勾选"Build when a change is pushed to GitLab/GitHub"。Git仓库端需要配置Webhook URL为http://your-jenkins:8080/project/your-project。
groovy
// Jenkinsfile - 带参数构建和通知的CI流水线
pipeline {
agent any
// 定义构建参数
parameters {
string(name: 'BRANCH', defaultValue: 'main', description: '要构建的分支')
choice(name: 'ENV', choices: ['dev', 'test', 'prod'], description: '部署环境')
}
// 环境变量
environment {
PROJECT_NAME = 'api-gateway'
MAVEN_HOME = '/usr/local/maven'
}
stages {
stage('Checkout') {
steps {
checkout([
$class: 'GitSCM',
branches: [[name: "${params.BRANCH}"]],
userRemoteConfigs: [[url: 'https://gitlab.com/team/api-gateway.git']]
])
}
}
stage('Build') {
steps {
sh '''
echo "开始编译 ${PROJECT_NAME}..."
${MAVEN_HOME}/bin/mvn clean package -DskipTests
'''
}
post {
success {
echo '编译成功!'
// 归档构建产物
archiveArtifacts artifacts: 'target/*.jar', fingerprint: true
}
failure {
echo '编译失败,请检查代码!'
}
}
}
stage('Unit Test') {
steps {
sh '${MAVEN_HOME}/bin/mvn test'
}
post {
always {
// 收集测试报告
junit 'target/surefire-reports/*.xml'
}
}
}
stage('Code Quality') {
steps {
// 集成SonarQube做代码质量检查
sh '${MAVEN_HOME}/bin/mvn sonar:sonar'
}
}
}
post {
success {
// 构建成功发送通知
emailext(
subject: "[CI] ${PROJECT_NAME} 构建成功 - #${BUILD_NUMBER}",
body: "分支: ${params.BRANCH}\n环境: ${params.ENV}\n构建日志: ${BUILD_URL}",
to: 'team@company.com'
)
}
failure {
emailext(
subject: "[CI] ${PROJECT_NAME} 构建失败 - #${BUILD_NUMBER}",
body: "请立即查看构建日志: ${BUILD_URL}",
to: 'team@company.com'
)
}
}
}
避坑提示:当时我们犯了一个低级错误------Webhook配置好后,发现Jenkins收不到请求。排查了半天,发现是防火墙没开8080端口。更坑的是,公司内网GitLab和Jenkins不在同一个网段,需要配置Nginx反向代理。建议先手动curl测试Webhook地址是否可达。
3. CD部署实战:从Jenkins到Kubernetes的自动化部署
问题背景
CI搞定了,但部署还是手动。每次构建完,需要登录K8s集群执行kubectl set image。我们需要实现:代码合并到main分支后,自动构建镜像并部署到K8s。
方案选型
- 方案A:Jenkins直接调用kubectl命令
- 方案B:Jenkins构建镜像后,通过Helm Chart部署
- 我们选了方案B,因为Helm支持版本管理和回滚,更符合生产环境要求
原理剖析
整个CD流程:代码编译 → 构建Docker镜像 → 推送到镜像仓库 → 更新Helm Chart版本 → 部署到K8s集群。

实现要点:Jenkins需要配置K8s集群的kubeconfig文件,或者安装Kubernetes插件。我们选择在Jenkins Agent节点上安装kubectl和helm,并配置好集群访问凭证。
groovy
// Jenkinsfile - 完整的CI/CD流水线(含Docker镜像构建和K8s部署)
pipeline {
agent any
environment {
// Docker镜像仓库地址
REGISTRY = 'registry.company.com'
IMAGE_NAME = 'api-gateway'
IMAGE_TAG = "${BUILD_NUMBER}-${GIT_COMMIT.take(7)}"
// K8s命名空间
K8S_NAMESPACE = 'production'
// Helm Chart路径
CHART_PATH = './deploy/helm/api-gateway'
}
stages {
stage('Checkout') {
steps {
checkout scm
}
}
stage('Build & Test') {
steps {
sh 'mvn clean package -DskipTests'
sh 'mvn test'
}
post {
success {
junit 'target/surefire-reports/*.xml'
}
}
}
stage('Build Docker Image') {
steps {
script {
// 构建Docker镜像
docker.build("${REGISTRY}/${IMAGE_NAME}:${IMAGE_TAG}")
}
}
}
stage('Push Image') {
steps {
script {
// 登录并推送镜像
docker.withRegistry("https://${REGISTRY}", 'docker-registry-credentials') {
docker.image("${REGISTRY}/${IMAGE_NAME}:${IMAGE_TAG}").push()
// 同时打上latest标签
docker.image("${REGISTRY}/${IMAGE_NAME}:${IMAGE_TAG}").push('latest')
}
}
}
}
stage('Deploy to K8s') {
steps {
script {
// 更新Helm Chart中的镜像版本
sh """
sed -i 's|image:.*|image: ${REGISTRY}/${IMAGE_NAME}:${IMAGE_TAG}|' ${CHART_PATH}/values.yaml
"""
// 执行Helm部署
sh """
helm upgrade --install api-gateway ${CHART_PATH} \
--namespace ${K8S_NAMESPACE} \
--set image.repository=${REGISTRY}/${IMAGE_NAME} \
--set image.tag=${IMAGE_TAG} \
--wait \
--timeout 5m
"""
}
}
}
stage('Verify Deployment') {
steps {
script {
// 验证Pod是否正常运行
sh """
kubectl rollout status deployment/api-gateway \
-n ${K8S_NAMESPACE} \
--timeout=3m
"""
// 检查服务健康状态
sh """
curl -f http://api-gateway.${K8S_NAMESPACE}.svc.cluster.local/health
"""
}
}
}
}
post {
success {
emailext(
subject: "[CD] ${IMAGE_NAME} 部署成功 - v${IMAGE_TAG}",
body: "镜像版本: ${IMAGE_TAG}\n部署环境: ${K8S_NAMESPACE}\n访问地址: https://api.company.com",
to: 'team@company.com'
)
}
failure {
emailext(
subject: "[CD] ${IMAGE_NAME} 部署失败 - v${IMAGE_TAG}",
body: "请立即查看构建日志: ${BUILD_URL}",
to: 'team@company.com'
)
}
}
}
避坑提示:第一次跑CD流水线时,helm upgrade卡了10分钟超时了。我立即查看了监控,发现是K8s集群的节点资源不足,新的Pod一直Pending。根因是我们在values.yaml中配置的resources.requests太高,而集群剩余资源不够。解决方法是先调整资源限制,或者增加集群节点。建议在部署前加一个资源检查步骤。
整体效果验证
| 指标 | 优化前 | 优化后 | 提升幅度 |
|---|---|---|---|
| 部署耗时 | 47分钟 | 6分钟 | 87.2% |
| 人工操作步骤 | 12步 | 1次触发 | 91.7% |
| 部署出错率 | 约15% | 0.3% | 98% |
| 代码到上线时间 | 2小时+ | 15分钟 | 87.5% |
| 团队部署工作量 | 3人天/周 | 0.5人天/周 | 83.3% |
最直观的变化:以前发版日大家如临大敌,现在开发同学自己点一下就能上线。而且因为每次部署都是自动化流程,再也不会出现"漏改配置文件"这种低级错误。
经验总结与避坑指南
最佳实践
- Pipeline as Code:所有流水线逻辑写在Jenkinsfile中,和代码一起版本管理
- 参数化构建:用parameters定义分支、环境等变量,避免硬编码
- 分阶段验证:每个Stage后加post块处理成功/失败逻辑,及时发现问题
- 凭证管理:所有密码、Token用Jenkins Credentials管理,不要明文写在代码里
避坑指南
- Webhook配置:确保Jenkins地址能被Git仓库访问,防火墙、Nginx反向代理要提前测试
- Docker构建缓存:每次构建都从零开始?配置Docker层缓存,构建时间能减少60%
- K8s资源限制:部署前检查集群资源,避免Pod一直Pending
- 回滚策略 :一定要配置Helm的
--history-max参数,保留最近5个版本用于回滚
尚未解决的问题
目前我们的流水线还不支持金丝雀发布(灰度发布),每次都是全量更新。我们计划在下个版本引入Istio,实现流量百分比控制,逐步切流。
常见问题答疑
Q1:Jenkins构建速度太慢,怎么办?
A:首先检查Agent节点配置,建议至少4核8G。其次开启Pipeline的并发构建(parallel关键字),把单元测试、代码检查等独立任务并行执行。我们实测并行后构建时间从12分钟降到4分钟。
Q2:如何实现多环境部署(dev/test/prod)?
A:用参数化构建,定义ENV参数。在Pipeline中根据ENV值加载不同的配置文件,部署到不同的K8s命名空间。注意生产环境的审批流程:可以加一个input步骤,要求管理员手动确认后才执行部署。
Q3:Jenkins的凭证怎么管理才安全?
A:绝对不要写在代码里!用Jenkins的Credentials插件,支持Username with password、SSH Key、Secret text等多种类型。在Pipeline中通过credentials()函数引用,Jenkins会自动注入环境变量。
参考资料
互动与交流
以上就是我们在Jenkins CI/CD流水线实战中趟过的坑和总结的经验。每个团队的技术栈和业务场景各不相同,但底层的方法论总是相通的。
欢迎在评论区聊聊:
- 你在Jenkins落地时,踩过最深刻的坑是什么?
- 对文中Helm部署方案,你有没有更好的替代思路(比如Kustomize)?
- 你所在团队在CI/CD上还有哪些"独门秘籍"?
我会认真回复每条评论,好的问题我会单独写一篇文章来展开。如果觉得这篇干货够硬,欢迎点赞收藏,让它帮助到更多同行。
下篇预告:
下一篇我将分享《GitLab CI vs Jenkins:我们为什么最终选择了Jenkins?》,深入拆解两种方案的优劣对比和迁移过程中的踩坑记录,同样会给出可直接复现的配置示例,敬请期待。