找回密码
 立即注册
首页 业界区 安全 Jenkins CI/CD流水线从零搭建:代码提交到自动部署全流 ...

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

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

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

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

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

  • 代码push后自动触发
  • 构建、测试、部署全自动
  • 每次部署有记录可查
  • 出问题一键回滚
二、整体架构
  1. 开发者 → GitLab → Jenkins → Docker镜像 → 目标服务器
  2. │         │          │           │
  3. │    webhook触发    构建+测试    推送到Harbor
  4. │                               │
  5. │                          部署到K8s/Docker
  6. │                               │
  7. └───────── 钉钉/企微通知 ←───────┘
复制代码
组件说明:

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

3.1 Docker方式安装(推荐)
  1. # 创建数据目录
  2. mkdir -p /data/jenkins
  3. chown -R 1000:1000 /data/jenkins
  4. # 启动Jenkins
  5. docker run -d \
  6.   --name jenkins \
  7.   --restart=always \
  8.   -p 8080:8080 \
  9.   -p 50000:50000 \
  10.   -v /data/jenkins:/var/jenkins_home \
  11.   -v /var/run/docker.sock:/var/run/docker.sock \
  12.   -v /usr/bin/docker:/usr/bin/docker \
  13.   jenkins/jenkins:lts
  14. # 查看初始密码
  15. docker logs jenkins 2>&1 | grep -A 5 "initial"
  16. # 或者
  17. cat /data/jenkins/secrets/initialAdminPassword
复制代码
3.2 访问配置


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

进入 Manage Jenkins → Plugins → Available plugins
  1. 必装:
  2. - Git
  3. - Pipeline
  4. - Docker Pipeline
  5. - SSH Agent
  6. - Publish Over SSH
  7. - GitLab / GitHub Integration
  8. - Blue Ocean(可视化界面)
  9. - DingTalk(钉钉通知)
  10. 推荐:
  11. - Credentials Binding
  12. - Build Timeout
  13. - Timestamper
  14. - AnsiColor(彩色日志)
复制代码
四、配置凭据

4.1 Git凭据

Manage Jenkins → Credentials → System → Global credentials
添加GitLab/GitHub的SSH私钥或用户名密码:
  1. Kind: SSH Username with private key
  2. ID: gitlab-ssh
  3. Username: git
  4. Private Key: (粘贴私钥内容)
复制代码
4.2 服务器SSH凭据
  1. Kind: SSH Username with private key
  2. ID: deploy-server
  3. Username: root
  4. Private Key: (部署服务器的私钥)
复制代码
4.3 Harbor凭据
  1. Kind: Username with password
  2. ID: harbor-auth
  3. Username: admin
  4. Password: Harbor密码
复制代码
五、第一个Pipeline

5.1 创建Pipeline项目

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

在项目根目录创建 Jenkinsfile:
  1. pipeline {
  2.     agent any
  3.    
  4.     environment {
  5.         APP_NAME = 'my-app'
  6.         GIT_REPO = 'git@gitlab.example.com:team/my-app.git'
  7.     }
  8.    
  9.     stages {
  10.         stage('拉取代码') {
  11.             steps {
  12.                 git branch: 'main',
  13.                     credentialsId: 'gitlab-ssh',
  14.                     url: "${GIT_REPO}"
  15.             }
  16.         }
  17.         
  18.         stage('编译构建') {
  19.             steps {
  20.                 sh 'mvn clean package -DskipTests'
  21.             }
  22.         }
  23.         
  24.         stage('单元测试') {
  25.             steps {
  26.                 sh 'mvn test'
  27.             }
  28.             post {
  29.                 always {
  30.                     junit '**/target/surefire-reports/*.xml'
  31.                 }
  32.             }
  33.         }
  34.         
  35.         stage('部署') {
  36.             steps {
  37.                 sshPublisher(
  38.                     publishers: [
  39.                         sshPublisherDesc(
  40.                             configName: 'deploy-server',
  41.                             transfers: [
  42.                                 sshTransfer(
  43.                                     sourceFiles: 'target/*.jar',
  44.                                     remoteDirectory: '/opt/app',
  45.                                     execCommand: '''
  46.                                         cd /opt/app
  47.                                         ./restart.sh
  48.                                     '''
  49.                                 )
  50.                             ]
  51.                         )
  52.                     ]
  53.                 )
  54.             }
  55.         }
  56.     }
  57.    
  58.     post {
  59.         success {
  60.             echo '部署成功!'
  61.         }
  62.         failure {
  63.             echo '部署失败!'
  64.         }
  65.     }
  66. }
复制代码
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 项目结构
  1. my-app/
  2. ├── src/
  3. ├── Dockerfile
  4. ├── Jenkinsfile
  5. ├── deploy/
  6. │   ├── docker-compose.yml
  7. │   └── k8s/
  8. │       ├── deployment.yaml
  9. │       └── service.yaml
  10. └── pom.xml
复制代码
6.2 Dockerfile
  1. FROM openjdk:11-jre-slim
  2. WORKDIR /app
  3. COPY target/*.jar app.jar
  4. ENV JAVA_OPTS="-Xms512m -Xmx512m"
  5. EXPOSE 8080
  6. ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -jar app.jar"]
复制代码
6.3 完整Jenkinsfile
  1. pipeline {
  2.     agent any
  3.    
  4.     environment {
  5.         // 基础配置
  6.         APP_NAME = 'my-app'
  7.         GIT_REPO = 'git@gitlab.example.com:team/my-app.git'
  8.         
  9.         // Docker配置
  10.         DOCKER_REGISTRY = 'harbor.example.com'
  11.         DOCKER_IMAGE = "${DOCKER_REGISTRY}/myteam/${APP_NAME}"
  12.         
  13.         // 部署配置
  14.         DEPLOY_HOST = '192.168.1.100'
  15.         DEPLOY_PATH = '/opt/apps/${APP_NAME}'
  16.     }
  17.    
  18.     options {
  19.         // 构建超时时间
  20.         timeout(time: 30, unit: 'MINUTES')
  21.         // 保留构建记录
  22.         buildDiscarder(logRotator(numToKeepStr: '20'))
  23.         // 时间戳
  24.         timestamps()
  25.     }
  26.    
  27.     stages {
  28.         stage('检出代码') {
  29.             steps {
  30.                 checkout([
  31.                     $class: 'GitSCM',
  32.                     branches: [[name: '*/main']],
  33.                     userRemoteConfigs: [[
  34.                         url: "${GIT_REPO}",
  35.                         credentialsId: 'gitlab-ssh'
  36.                     ]]
  37.                 ])
  38.                
  39.                 script {
  40.                     // 获取commit信息
  41.                     env.GIT_COMMIT_SHORT = sh(
  42.                         script: 'git rev-parse --short HEAD',
  43.                         returnStdout: true
  44.                     ).trim()
  45.                     env.GIT_COMMIT_MSG = sh(
  46.                         script: 'git log -1 --pretty=%B',
  47.                         returnStdout: true
  48.                     ).trim()
  49.                     
  50.                     // 镜像tag:时间戳+commit
  51.                     env.IMAGE_TAG = sh(
  52.                         script: 'date +%Y%m%d%H%M%S',
  53.                         returnStdout: true
  54.                     ).trim() + "-${env.GIT_COMMIT_SHORT}"
  55.                 }
  56.                
  57.                 echo "构建版本: ${env.IMAGE_TAG}"
  58.                 echo "提交信息: ${env.GIT_COMMIT_MSG}"
  59.             }
  60.         }
  61.         
  62.         stage('编译构建') {
  63.             steps {
  64.                 sh '''
  65.                     mvn clean package -DskipTests -U
  66.                 '''
  67.             }
  68.         }
  69.         
  70.         stage('单元测试') {
  71.             steps {
  72.                 sh 'mvn test'
  73.             }
  74.             post {
  75.                 always {
  76.                     junit allowEmptyResults: true,
  77.                           testResults: '**/target/surefire-reports/*.xml'
  78.                 }
  79.             }
  80.         }
  81.         
  82.         stage('代码扫描') {
  83.             steps {
  84.                 // SonarQube代码质量扫描(可选)
  85.                 sh '''
  86.                     mvn sonar:sonar \
  87.                         -Dsonar.host.url=http://sonar.example.com \
  88.                         -Dsonar.login=${SONAR_TOKEN}
  89.                 '''
  90.             }
  91.         }
  92.         
  93.         stage('构建Docker镜像') {
  94.             steps {
  95.                 script {
  96.                     docker.build("${DOCKER_IMAGE}:${env.IMAGE_TAG}")
  97.                     docker.build("${DOCKER_IMAGE}:latest")
  98.                 }
  99.             }
  100.         }
  101.         
  102.         stage('推送到Harbor') {
  103.             steps {
  104.                 script {
  105.                     docker.withRegistry("https://${DOCKER_REGISTRY}", 'harbor-auth') {
  106.                         docker.image("${DOCKER_IMAGE}:${env.IMAGE_TAG}").push()
  107.                         docker.image("${DOCKER_IMAGE}:latest").push()
  108.                     }
  109.                 }
  110.             }
  111.         }
  112.         
  113.         stage('部署到测试环境') {
  114.             when {
  115.                 branch 'develop'
  116.             }
  117.             steps {
  118.                 deployToServer('test', '192.168.1.200')
  119.             }
  120.         }
  121.         
  122.         stage('部署到生产环境') {
  123.             when {
  124.                 branch 'main'
  125.             }
  126.             steps {
  127.                 // 生产环境需要手动确认
  128.                 input message: '确认部署到生产环境?',
  129.                       ok: '确认部署'
  130.                
  131.                 deployToServer('prod', '192.168.1.100')
  132.             }
  133.         }
  134.     }
  135.    
  136.     post {
  137.         success {
  138.             script {
  139.                 sendDingTalkNotify('success')
  140.             }
  141.         }
  142.         failure {
  143.             script {
  144.                 sendDingTalkNotify('failure')
  145.             }
  146.         }
  147.         always {
  148.             // 清理工作空间
  149.             cleanWs()
  150.             // 清理本地Docker镜像
  151.             sh "docker rmi ${DOCKER_IMAGE}:${env.IMAGE_TAG} || true"
  152.         }
  153.     }
  154. }
  155. // 部署函数
  156. def deployToServer(String env, String host) {
  157.     sshagent(['deploy-server']) {
  158.         sh """
  159.             ssh -o StrictHostKeyChecking=no root@${host} '
  160.                 docker pull ${DOCKER_IMAGE}:${IMAGE_TAG}
  161.                 docker stop ${APP_NAME} || true
  162.                 docker rm ${APP_NAME} || true
  163.                 docker run -d \\
  164.                     --name ${APP_NAME} \\
  165.                     --restart=always \\
  166.                     -p 8080:8080 \\
  167.                     -e SPRING_PROFILES_ACTIVE=${env} \\
  168.                     -v /data/logs/${APP_NAME}:/app/logs \\
  169.                     ${DOCKER_IMAGE}:${IMAGE_TAG}
  170.             '
  171.         """
  172.     }
  173. }
  174. // 钉钉通知
  175. def sendDingTalkNotify(String status) {
  176.     def color = status == 'success' ? '#00FF00' : '#FF0000'
  177.     def statusText = status == 'success' ? '✅ 成功' : '❌ 失败'
  178.    
  179.     dingtalk (
  180.         robot: 'dingding-robot',
  181.         type: 'MARKDOWN',
  182.         title: "Jenkins构建通知",
  183.         text: [
  184.             "### Jenkins构建${statusText}",
  185.             "- 项目:${APP_NAME}",
  186.             "- 分支:${env.BRANCH_NAME}",
  187.             "- 版本:${env.IMAGE_TAG}",
  188.             "- 提交:${env.GIT_COMMIT_MSG}",
  189.             "- 耗时:${currentBuild.durationString}",
  190.             "- [查看详情](${env.BUILD_URL})"
  191.         ]
  192.     )
  193. }
复制代码
七、多环境部署策略

7.1 分支策略
  1. main分支     → 生产环境
  2. develop分支  → 测试环境  
  3. feature/*    → 开发环境(可选)
复制代码
7.2 环境变量管理
  1. stage('部署') {
  2.     steps {
  3.         script {
  4.             def envConfig = [
  5.                 'dev': [
  6.                     host: '192.168.1.201',
  7.                     profile: 'dev',
  8.                     jvmOpts: '-Xms256m -Xmx256m'
  9.                 ],
  10.                 'test': [
  11.                     host: '192.168.1.202',
  12.                     profile: 'test',
  13.                     jvmOpts: '-Xms512m -Xmx512m'
  14.                 ],
  15.                 'prod': [
  16.                     host: '192.168.1.100',
  17.                     profile: 'prod',
  18.                     jvmOpts: '-Xms2g -Xmx2g'
  19.                 ]
  20.             ]
  21.             
  22.             def config = envConfig[env.DEPLOY_ENV]
  23.             // 使用config部署...
  24.         }
  25.     }
  26. }
复制代码
八、跨网络部署:异地环境怎么办?

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

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

缺点:

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

用星空组网把Jenkins和各环境服务器组到一个虚拟网络:
  1. 办公室Jenkins (192.168.188.10)
  2.     │
  3.     ├── 办公室测试服务器 (192.168.188.20)
  4.     ├── 阿里云生产服务器 (192.168.188.30)
  5.     └── 腾讯云灾备服务器 (192.168.188.40)
复制代码
配置超简单:
  1. # 在Jenkins服务器
  2. curl -sSL https://down.starvpn.cn/linux.sh | bash
  3. xkcli login your_token && xkcli up
  4. # 在各环境服务器执行同样操作
复制代码
组网后,Jenkins直接用虚拟IP连接所有服务器:
  1. environment {
  2.     TEST_HOST = '192.168.188.20'
  3.     PROD_HOST = '192.168.188.30'
  4. }
复制代码
效果:

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

9.1 构建超时
  1. options {
  2.     timeout(time: 30, unit: 'MINUTES')
  3. }
  4. // 或者单个stage超时
  5. stage('构建') {
  6.     options {
  7.         timeout(time: 10, unit: 'MINUTES')
  8.     }
  9.     steps {
  10.         sh 'mvn package'
  11.     }
  12. }
复制代码
9.2 Docker权限问题
  1. # Jenkins容器需要访问宿主机Docker
  2. docker run -v /var/run/docker.sock:/var/run/docker.sock ...
  3. # 或者把jenkins用户加到docker组
  4. usermod -aG docker jenkins
复制代码
9.3 SSH连接失败
  1. // 跳过SSH主机密钥检查
  2. sh "ssh -o StrictHostKeyChecking=no root@${host} '...'"
复制代码
9.4 Maven下载慢

配置阿里云镜像,在Jenkins服务器的 /data/jenkins/.m2/settings.xml:
  1. <mirrors>
  2.     <mirror>
  3.         <id>aliyun</id>
  4.         <mirrorOf>central</mirrorOf>
  5.         <url>https://maven.aliyun.com/repository/public</url>
  6.     </mirror>
  7. </mirrors>
复制代码
十、最佳实践

10.1 Pipeline即代码

把Jenkinsfile放在代码仓库,不要在Jenkins界面写Pipeline。
10.2 敏感信息用凭据
  1. // 不要这样
  2. sh "docker login -u admin -p 123456 harbor.example.com"
  3. // 要这样
  4. withCredentials([usernamePassword(
  5.     credentialsId: 'harbor-auth',
  6.     usernameVariable: 'USER',
  7.     passwordVariable: 'PASS'
  8. )]) {
  9.     sh "docker login -u $USER -p $PASS harbor.example.com"
  10. }
复制代码
10.3 保留构建记录
  1. options {
  2.     buildDiscarder(logRotator(
  3.         numToKeepStr: '30',      // 保留30次构建
  4.         artifactNumToKeepStr: '10'  // 保留10次制品
  5.     ))
  6. }
复制代码
10.4 构建通知必须有

不管成功失败,都要通知到人:
  1. post {
  2.     success {
  3.         dingtalk(robot: 'dingding', type: 'TEXT', text: ['构建成功'])
  4.     }
  5.     failure {
  6.         dingtalk(robot: 'dingding', type: 'TEXT', text: ['构建失败,请检查'])
  7.     }
  8. }
复制代码
十一、效果对比

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

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

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

  • 先搭个最简单的Pipeline跑起来
  • 逐步加入测试、扫描、通知
  • 多环境用组网工具打通
  • 把Pipeline当代码管理
一旦跑顺了,后面省的时间是前期投入的N倍。
有问题评论区交流~
[code][/code]
来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

相关推荐

您需要登录后才可以回帖 登录 | 立即注册