劳暄美 发表于 2025-12-3 14:25:09

Jenkins CI/CD流水线从零搭建:代码提交到自动部署全流程

以前每次上线都是:打包→上传→部署→测试,一套流程下来半小时。现在代码一推,自动构建、自动测试、自动部署,喝杯咖啡的功夫就上线了。
一、为什么要搞CI/CD?

先说说我们之前的"人肉部署"流程:

[*]开发写完代码,提交Git
[*]运维拉代码到本地
[*]mvn clean package 打包
[*]scp上传到服务器
[*]停服务、备份、替换jar包
[*]启动服务、查看日志
[*]测试人员验证
问题:

[*]耗时长:一次部署30分钟起步
[*]容易出错:手抖删错文件、忘记备份
[*]不可追溯:谁什么时候部署的?部署了什么版本?
[*]效率低下:一天最多部署3-4次
搞CI/CD之后:

[*]代码push后自动触发
[*]构建、测试、部署全自动
[*]每次部署有记录可查
[*]出问题一键回滚
二、整体架构

开发者 → GitLab → Jenkins → Docker镜像 → 目标服务器
│         │          │         │
│    webhook触发    构建+测试    推送到Harbor
│                               │
│                        部署到K8s/Docker
│                               │
└───────── 钉钉/企微通知 ←───────┘组件说明:

[*]GitLab:代码仓库(也可以用GitHub、Gitee)
[*]Jenkins:CI/CD引擎
[*]Harbor:Docker镜像仓库
[*]目标环境:K8s集群或Docker服务器
三、Jenkins安装部署

3.1 Docker方式安装(推荐)

# 创建数据目录
mkdir -p /data/jenkins
chown -R 1000:1000 /data/jenkins

# 启动Jenkins
docker run -d \
--name jenkins \
--restart=always \
-p 8080:8080 \
-p 50000:50000 \
-v /data/jenkins:/var/jenkins_home \
-v /var/run/docker.sock:/var/run/docker.sock \
-v /usr/bin/docker:/usr/bin/docker \
jenkins/jenkins:lts

# 查看初始密码
docker logs jenkins 2>&1 | grep -A 5 "initial"
# 或者
cat /data/jenkins/secrets/initialAdminPassword3.2 访问配置


[*]浏览器打开 http://你的IP:8080
[*]输入初始密码
[*]选择"安装推荐的插件"
[*]创建管理员账号
3.3 必装插件

进入 Manage Jenkins → Plugins → Available plugins:
必装:
- Git
- Pipeline
- Docker Pipeline
- SSH Agent
- Publish Over SSH
- GitLab / GitHub Integration
- Blue Ocean(可视化界面)
- DingTalk(钉钉通知)

推荐:
- Credentials Binding
- Build Timeout
- Timestamper
- AnsiColor(彩色日志)四、配置凭据

4.1 Git凭据

Manage Jenkins → Credentials → System → Global credentials
添加GitLab/GitHub的SSH私钥或用户名密码:
Kind: SSH Username with private key
ID: gitlab-ssh
Username: git
Private Key: (粘贴私钥内容)4.2 服务器SSH凭据

Kind: SSH Username with private key
ID: deploy-server
Username: root
Private Key: (部署服务器的私钥)4.3 Harbor凭据

Kind: Username with password
ID: harbor-auth
Username: admin
Password: Harbor密码五、第一个Pipeline

5.1 创建Pipeline项目

New Item → Pipeline → 输入项目名
5.2 简单的Jenkinsfile

在项目根目录创建 Jenkinsfile:
pipeline {
    agent any
   
    environment {
      APP_NAME = 'my-app'
      GIT_REPO = 'git@gitlab.example.com:team/my-app.git'
    }
   
    stages {
      stage('拉取代码') {
            steps {
                git branch: 'main',
                  credentialsId: 'gitlab-ssh',
                  url: "${GIT_REPO}"
            }
      }
      
      stage('编译构建') {
            steps {
                sh 'mvn clean package -DskipTests'
            }
      }
      
      stage('单元测试') {
            steps {
                sh 'mvn test'
            }
            post {
                always {
                  junit '**/target/surefire-reports/*.xml'
                }
            }
      }
      
      stage('部署') {
            steps {
                sshPublisher(
                  publishers: [
                        sshPublisherDesc(
                            configName: 'deploy-server',
                            transfers: [
                              sshTransfer(
                                    sourceFiles: 'target/*.jar',
                                    remoteDirectory: '/opt/app',
                                    execCommand: '''
                                        cd /opt/app
                                        ./restart.sh
                                    '''
                              )
                            ]
                        )
                  ]
                )
            }
      }
    }
   
    post {
      success {
            echo '部署成功!'
      }
      failure {
            echo '部署失败!'
      }
    }
}5.3 配置GitLab Webhook

让代码push后自动触发构建:

[*]Jenkins项目 → Configure → Build Triggers
[*]勾选 "Build when a change is pushed to GitLab"
[*]复制Webhook URL
在GitLab项目设置:

[*]Settings → Webhooks
[*]URL填Jenkins的Webhook地址
[*]Trigger选择Push events
六、完整的Docker化部署Pipeline

这是我实际在用的生产级Pipeline:
6.1 项目结构

my-app/
├── src/
├── Dockerfile
├── Jenkinsfile
├── deploy/
│   ├── docker-compose.yml
│   └── k8s/
│       ├── deployment.yaml
│       └── service.yaml
└── pom.xml6.2 Dockerfile

FROM openjdk:11-jre-slim

WORKDIR /app

COPY target/*.jar app.jar

ENV JAVA_OPTS="-Xms512m -Xmx512m"

EXPOSE 8080

ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -jar app.jar"]6.3 完整Jenkinsfile

pipeline {
    agent any
   
    environment {
      // 基础配置
      APP_NAME = 'my-app'
      GIT_REPO = 'git@gitlab.example.com:team/my-app.git'
      
      // Docker配置
      DOCKER_REGISTRY = 'harbor.example.com'
      DOCKER_IMAGE = "${DOCKER_REGISTRY}/myteam/${APP_NAME}"
      
      // 部署配置
      DEPLOY_HOST = '192.168.1.100'
      DEPLOY_PATH = '/opt/apps/${APP_NAME}'
    }
   
    options {
      // 构建超时时间
      timeout(time: 30, unit: 'MINUTES')
      // 保留构建记录
      buildDiscarder(logRotator(numToKeepStr: '20'))
      // 时间戳
      timestamps()
    }
   
    stages {
      stage('检出代码') {
            steps {
                checkout([
                  $class: 'GitSCM',
                  branches: [],
                  userRemoteConfigs: [[
                        url: "${GIT_REPO}",
                        credentialsId: 'gitlab-ssh'
                  ]]
                ])
               
                script {
                  // 获取commit信息
                  env.GIT_COMMIT_SHORT = sh(
                        script: 'git rev-parse --short HEAD',
                        returnStdout: true
                  ).trim()
                  env.GIT_COMMIT_MSG = sh(
                        script: 'git log -1 --pretty=%B',
                        returnStdout: true
                  ).trim()
                  
                  // 镜像tag:时间戳+commit
                  env.IMAGE_TAG = sh(
                        script: 'date +%Y%m%d%H%M%S',
                        returnStdout: true
                  ).trim() + "-${env.GIT_COMMIT_SHORT}"
                }
               
                echo "构建版本: ${env.IMAGE_TAG}"
                echo "提交信息: ${env.GIT_COMMIT_MSG}"
            }
      }
      
      stage('编译构建') {
            steps {
                sh '''
                  mvn clean package -DskipTests -U
                '''
            }
      }
      
      stage('单元测试') {
            steps {
                sh 'mvn test'
            }
            post {
                always {
                  junit allowEmptyResults: true,
                        testResults: '**/target/surefire-reports/*.xml'
                }
            }
      }
      
      stage('代码扫描') {
            steps {
                // SonarQube代码质量扫描(可选)
                sh '''
                  mvn sonar:sonar \
                        -Dsonar.host.url=http://sonar.example.com \
                        -Dsonar.login=${SONAR_TOKEN}
                '''
            }
      }
      
      stage('构建Docker镜像') {
            steps {
                script {
                  docker.build("${DOCKER_IMAGE}:${env.IMAGE_TAG}")
                  docker.build("${DOCKER_IMAGE}:latest")
                }
            }
      }
      
      stage('推送到Harbor') {
            steps {
                script {
                  docker.withRegistry("https://${DOCKER_REGISTRY}", 'harbor-auth') {
                        docker.image("${DOCKER_IMAGE}:${env.IMAGE_TAG}").push()
                        docker.image("${DOCKER_IMAGE}:latest").push()
                  }
                }
            }
      }
      
      stage('部署到测试环境') {
            when {
                branch 'develop'
            }
            steps {
                deployToServer('test', '192.168.1.200')
            }
      }
      
      stage('部署到生产环境') {
            when {
                branch 'main'
            }
            steps {
                // 生产环境需要手动确认
                input message: '确认部署到生产环境?',
                      ok: '确认部署'
               
                deployToServer('prod', '192.168.1.100')
            }
      }
    }
   
    post {
      success {
            script {
                sendDingTalkNotify('success')
            }
      }
      failure {
            script {
                sendDingTalkNotify('failure')
            }
      }
      always {
            // 清理工作空间
            cleanWs()
            // 清理本地Docker镜像
            sh "docker rmi ${DOCKER_IMAGE}:${env.IMAGE_TAG} || true"
      }
    }
}

// 部署函数
def deployToServer(String env, String host) {
    sshagent(['deploy-server']) {
      sh """
            ssh -o StrictHostKeyChecking=no root@${host} '
                docker pull ${DOCKER_IMAGE}:${IMAGE_TAG}
                docker stop ${APP_NAME} || true
                docker rm ${APP_NAME} || true
                docker run -d \\
                  --name ${APP_NAME} \\
                  --restart=always \\
                  -p 8080:8080 \\
                  -e SPRING_PROFILES_ACTIVE=${env} \\
                  -v /data/logs/${APP_NAME}:/app/logs \\
                  ${DOCKER_IMAGE}:${IMAGE_TAG}
            '
      """
    }
}

// 钉钉通知
def sendDingTalkNotify(String status) {
    def color = status == 'success' ? '#00FF00' : '#FF0000'
    def statusText = status == 'success' ? '✅ 成功' : '❌ 失败'
   
    dingtalk (
      robot: 'dingding-robot',
      type: 'MARKDOWN',
      title: "Jenkins构建通知",
      text: [
            "### Jenkins构建${statusText}",
            "- 项目:${APP_NAME}",
            "- 分支:${env.BRANCH_NAME}",
            "- 版本:${env.IMAGE_TAG}",
            "- 提交:${env.GIT_COMMIT_MSG}",
            "- 耗时:${currentBuild.durationString}",
            "- [查看详情](${env.BUILD_URL})"
      ]
    )
}七、多环境部署策略

7.1 分支策略

main分支   → 生产环境
develop分支→ 测试环境
feature/*    → 开发环境(可选)7.2 环境变量管理

stage('部署') {
    steps {
      script {
            def envConfig = [
                'dev': [
                  host: '192.168.1.201',
                  profile: 'dev',
                  jvmOpts: '-Xms256m -Xmx256m'
                ],
                'test': [
                  host: '192.168.1.202',
                  profile: 'test',
                  jvmOpts: '-Xms512m -Xmx512m'
                ],
                'prod': [
                  host: '192.168.1.100',
                  profile: 'prod',
                  jvmOpts: '-Xms2g -Xmx2g'
                ]
            ]
            
            def config = envConfig
            // 使用config部署...
      }
    }
}八、跨网络部署:异地环境怎么办?

我们公司测试环境在办公室、生产环境在阿里云,网络不通。
方案1:公网暴露Jenkins(不推荐)

把Jenkins暴露到公网,风险太大。
方案2:VPN(传统方案)

缺点:

[*]经常断
[*]速度慢
[*]配置复杂
方案3:SD-WAN组网(我在用的)

用星空组网把Jenkins和各环境服务器组到一个虚拟网络:
办公室Jenkins (192.168.188.10)
    │
    ├── 办公室测试服务器 (192.168.188.20)
    ├── 阿里云生产服务器 (192.168.188.30)
    └── 腾讯云灾备服务器 (192.168.188.40)配置超简单:
# 在Jenkins服务器
curl -sSL https://down.starvpn.cn/linux.sh | bash
xkcli login your_token && xkcli up

# 在各环境服务器执行同样操作组网后,Jenkins直接用虚拟IP连接所有服务器:
environment {
    TEST_HOST = '192.168.188.20'
    PROD_HOST = '192.168.188.30'
}效果:

[*]办公室到阿里云延迟:35ms(以前VPN要100ms+)
[*]构建产物上传速度:50MB/s(以前20MB/s)
[*]稳定性:3个月没断过
九、常见问题排查

9.1 构建超时

options {
    timeout(time: 30, unit: 'MINUTES')
}

// 或者单个stage超时
stage('构建') {
    options {
      timeout(time: 10, unit: 'MINUTES')
    }
    steps {
      sh 'mvn package'
    }
}9.2 Docker权限问题

# Jenkins容器需要访问宿主机Docker
docker run -v /var/run/docker.sock:/var/run/docker.sock ...

# 或者把jenkins用户加到docker组
usermod -aG docker jenkins9.3 SSH连接失败

// 跳过SSH主机密钥检查
sh "ssh -o StrictHostKeyChecking=no root@${host} '...'"9.4 Maven下载慢

配置阿里云镜像,在Jenkins服务器的 /data/jenkins/.m2/settings.xml:
<mirrors>
    <mirror>
      <id>aliyun</id>
      <mirrorOf>central</mirrorOf>
      <url>https://maven.aliyun.com/repository/public</url>
    </mirror>
</mirrors>十、最佳实践

10.1 Pipeline即代码

把Jenkinsfile放在代码仓库,不要在Jenkins界面写Pipeline。
10.2 敏感信息用凭据

// 不要这样
sh "docker login -u admin -p 123456 harbor.example.com"

// 要这样
withCredentials([usernamePassword(
    credentialsId: 'harbor-auth',
    usernameVariable: 'USER',
    passwordVariable: 'PASS'
)]) {
    sh "docker login -u $USER -p $PASS harbor.example.com"
}10.3 保留构建记录

options {
    buildDiscarder(logRotator(
      numToKeepStr: '30',      // 保留30次构建
      artifactNumToKeepStr: '10'// 保留10次制品
    ))
}10.4 构建通知必须有

不管成功失败,都要通知到人:
post {
    success {
      dingtalk(robot: 'dingding', type: 'TEXT', text: ['构建成功'])
    }
    failure {
      dingtalk(robot: 'dingding', type: 'TEXT', text: ['构建失败,请检查'])
    }
}十一、效果对比

指标人肉部署CI/CD单次部署耗时30分钟5分钟日部署次数3-4次不限出错率高低可追溯性无完整记录回滚速度30分钟1分钟实际收益:

[*]运维从"部署机器人"变成"平台建设者"
[*]开发专注写代码,不用管部署
[*]出问题能快速定位是哪次提交引入的
总结

搞CI/CD这事儿,前期投入时间是值得的:

[*]先搭个最简单的Pipeline跑起来
[*]逐步加入测试、扫描、通知
[*]多环境用组网工具打通
[*]把Pipeline当代码管理
一旦跑顺了,后面省的时间是前期投入的N倍。
有问题评论区交流~

来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

喳谍 发表于 2025-12-15 17:32:18

新版吗?好像是停更了吧。

富账慕 发表于 2025-12-29 13:28:03

喜欢鼓捣这些软件,现在用得少,谢谢分享!

厌外 发表于 2026-1-1 00:33:33

谢谢分享,辛苦了

勺缓曜 发表于 2026-1-3 15:08:27

感谢,下载保存了

叟减 发表于 2026-1-3 21:40:12

喜欢鼓捣这些软件,现在用得少,谢谢分享!

崆蛾寺 发表于 2026-1-13 07:45:57

不错,里面软件多更新就更好了

讣丢 发表于 2026-1-13 20:15:46

前排留名,哈哈哈

岑韬哎 发表于 2026-1-20 11:08:33

谢谢分享,试用一下

仰翡邸 发表于 2026-1-20 18:28:58

感谢分享,学习下。

思矿戳 发表于 2026-1-22 04:40:19

这个好,看起来很实用

驼娑 发表于 2026-2-4 03:20:47

热心回复!

左丘雅秀 发表于 2026-2-7 06:57:04

感谢发布原创作品,程序园因你更精彩

全愉婉 发表于 2026-2-10 13:27:16

谢谢楼主提供!

诈知 发表于 2026-2-10 15:25:16

谢谢楼主提供!

卒挪 发表于 2026-2-13 09:17:17

用心讨论,共获提升!

阜逐忍 发表于 2026-2-21 11:34:00

感谢发布原创作品,程序园因你更精彩

尹心菱 发表于 2026-2-22 20:55:11

喜欢鼓捣这些软件,现在用得少,谢谢分享!

磁呃泵 发表于 2026-2-27 00:18:02

鼓励转贴优秀软件安全工具和文档!

溥价 发表于 2026-3-3 11:00:33

懂技术并乐意极积无私分享的人越来越少。珍惜
页: [1] 2
查看完整版本: Jenkins CI/CD流水线从零搭建:代码提交到自动部署全流程