Jenkins运维之路(Slave容器节点)

在前面的文章里面介绍了自由风格和Pipeline方式(裸机部署,docker项目部署)的部署过程,也做出来了模板,目前遇到一个问题就是公司给的运维服务器有限,在构建的过程中发现项目并没有语言版本完全统一,比如有jdk17、jdk21、jdk8,node16,node18等等,这种多语言环境的情况如果混合部署在一台打包服务器上会出现各种奇奇怪怪的问题,所以这次采取了直接使用jenkins的容器节点来进行项目构建,这样能够减少多语言项目构建的冲突。

1. 构建打包容器

这里仅以一个版本作为示例,其他的语言版本可以按这个思路来进行构建,另外请注意 maven node go在打包前一定先把私服地址什么的设置好要不直接到国外服务器下载慢的一批

1.1 构建目录结构

ruby 复制代码
# 目录结构(这套是带包的JDK21 Maven3.9 node-v22 sonar-5 docker-28.4)
root@ubuntu2204test99:~/cicd_images/cicd_jdk21_go_mvn3911_node22_sonarq5_docker284# tree -L 1
.
├── docker
├── Dockerfile
├── go
├── jdk-21.0.88cccaac7ca7e
├── maven-3.9.11
├── node-v22.19.0
└── sonar-scanner-5.0.2.4997

1.2 Dockerfile内容

bash 复制代码
# Dockerfile构建内容 使用的公司私服

FROM harbor.xxxxxx.com/devops/cicd-ubuntu2404-base:v1

# 安装 Git 并清理缓存
RUN apt-get update && \
    apt-get install -y git && \
    apt-get clean && \
    rm -rf /var/lib/apt/lists/*

COPY go /usr/local/go
COPY jdk-21.0.8 /usr/local/jdk-21.0.8
COPY maven-3.9.11 /usr/local/maven-3.9.11
COPY node-v22.19.0 /usr/local/node-v22.19.0
COPY sonar-scanner-5.0.2.4997 /usr/local/sonar-scanner-5.0.2.4997
COPY docker/docker /usr/bin/docker

# 设置环境变量
ENV JAVA_HOME=/usr/local/jdk-21.0.8
ENV SCANNER_HOME=/usr/local/sonar-scanner-5.0.2.4997
ENV MAVEN_HOME=/usr/local/maven-3.9.11
ENV NODE_HOME=/usr/local/node-v22.19.0
ENV GO_HOME=/usr/local/go

# 更新 PATH 和 CLASSPATH
ENV PATH=$PATH:$JAVA_HOME/bin:$MAVEN_HOME/bin:$NODE_HOME/bin:$GO_HOME/bin:$SCANNER_HOME/bin
ENV CLASSPATH=.:$JAVA_HOME/jre/lib/rt.jar:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar
ENV NODE_OPTIONS=--max_old_space_size=2048

# 设置 Go 和 npm 的国内源
RUN go env -w GO111MODULE=on && \
    go env -w GOPROXY=https://mirrors.aliyun.com/goproxy/,direct && \
    npm config set registry http://mirrors.cloud.tencent.com/npm/

# 使用 bash 启动容器
CMD ["/bin/bash"]

1.3 打包并推送至私服

ruby 复制代码
root@ubuntu2204test99:~# docker build -t harbor.muscledog.top/devops/cicd-ubuntu2404-jdk21:v1 .
root@ubuntu2204test99:~# docker push harbor.muscledog.top/devops/cicd-ubuntu2404-jdk21:v1

2. 容器节点流水线

2.1 项目Dockerfile内容

bash 复制代码
# 项目的dockerfile名称MutilBuildDockerfile,使用多段构建

FROM harbor.xxx.com/devops/cicd-ubuntu2404-jdk21:v1 AS builder
WORKDIR /build
COPY . .
RUN mvn clean package -DskipTests


FROM harbor.xxx.top/devops/ubuntu/jre:21-24.04
WORKDIR /app
COPY --from=builder /build/target/spring-boot-3-hello-world-1.0.0-SNAPSHOT.jar /app/spring-boot-3-hello-world-1.0.0-SNAPSHOT.jar
ENTRYPOINT ["java", "-jar", "spring-boot-3-hello-world-1.0.0-SNAPSHOT.jar"]

这里我将容器相关构建的代码片段输出到这里,全部的代码片段太多了而且和前面文章有些是一样的 没必要!

kotlin 复制代码
def createVersion() {
  // 创建了一个方法createVersion()
  // 定义一个时间戳+构建ID作为版本号,为tag使用
  return new Date().format('yyyyMMddHHmmss') + "_${env.BUILD_ID}"
}

pipeline {
  agent { node { label 'node47' } }

  environment {
    GIT_REPO_URL = 'git@xxxx.com:xxxx/spring-boot-3-hello-world-jdk21.git'
    RED = "\u001B[31m"
    GREEN = "\u001B[32m"   // 绿色
    PURPLE = "\u001B[35m"  // 紫色
    RESET = "\u001B[0m"    // 重置
    REGISTRY_URL = 'harbor.xxxx.com'
    HARBOR_URL = 'https://harbor.xxxx.com'
    PROJECT_GROUP = 'devops'
    PROJECT_NAME = 'hello-world-jdk21'
    CONTAINER_NAME = 'hello-world-jdk21'
    OPS_SHARE_LIBRARY = 'git@xxxx.com:xxxx/ops-share-librarya.git'
  }

  options {
    timeout(time: 10, unit: 'MINUTES')
    disableConcurrentBuilds()
    timestamps()
  }

  parameters {
    gitParameter(
      name: 'BRANCH_TAG',
      type: 'PT_BRANCH_TAG',
      defaultValue: 'master',
      description: '请选择你要部署的分支或Tag',
      useRepository: 'git@xxxx.com:xxxx/spring-boot-3-hello-world-jdk21.git',
      quickFilterEnabled: true
    )
    booleanParam(defaultValue: false, description: '是否进行项目回滚?', name: 'ROLLBACK_TAG')
  }

  stages {
    stage('Check requirement') {
      steps {
        script {
          if (params.ROLLBACK_TAG && env.BRANCH_TAG.startsWith('rel-')) {
            echo "条件满足:ROLLBACK_TAG 为真,且 BRANCH_TAG 以 rel- 开头,继续执行后续步骤。"
          } else if (!params.ROLLBACK_TAG && !env.BRANCH_TAG.startsWith('rel-')) {
            echo "条件满足:ROLLBACK_TAG 为假,且BRANCH_TAG 不以 rel- 开头,继续执行后续步骤。"
          } else {
            echo "条件不满足,终止整个流程。"
            error("构建中止:条件不满足。")
          }
        }
      }
    }

    stage('Show info') {
      steps {
        script {
          if (params.ROLLBACK_TAG) {
            wrap([$class: 'BuildUser']) {
              echo "Built by: ${env.BUILD_USER_ID}"
              currentBuild.description = "Built by: ${env.BUILD_USER_ID}, Rollback: ${env.BRANCH_TAG}, tag: '项目回滚'}"
            }
          } else {
            _tag = createVersion()
            wrap([$class: 'BuildUser']) {
              echo "Built by: ${env.BUILD_USER_ID}"
              currentBuild.description = "Built by: ${env.BUILD_USER_ID}, Branch: ${env.BRANCH_TAG}, tag: ${_tag}"
            }
          }
        }
      }
    }

    stage('CleanWorkDir') {
      steps {
        cleanWs()
      }
    }
    //这里开始使用容器进行构建
    stage('SlaveDocker49Node'com
      steps {
        node('node47') {
          script {
            //这里使用先前构建好的打包容器进行项目构建,将构建的缓存映射到服务器目录当中加快以后构建的速度,同时将主机的docker进程映射到docker容器中
            docker.image('harbor.xxx.com/devops/cicd-ubuntu2404-jdk21:v1').inside('-v /root/Cache/m2/:/usr/local/maven-3.9.11/m2 -v /var/run/docker.sock:/var/run/docker.sock -v /root/.ssh:/root/.ssh') {
              stage('Checkout') {
                script {
                  checkout([$class: 'GitSCM', 
                    branches: [[name: params.BRANCH_TAG]], 
                    userRemoteConfigs: [[url: "${GIT_REPO_URL}", credentialsId: "GiteeKey"]]])
                }
              }
              stage('Build') {
                script {
                  if (!params.BRANCH_TAG.startsWith('rel-')) {
                    ansiColor('xterm') {
                      echo "${GREEN}项目开始构建${RESET}"
                      echo "Building"
                    }
                    // 构建项目镜像,指定Dockerfile名称
                    def image = docker.build("${REGISTRY_URL}/${PROJECT_GROUP}/${PROJECT_NAME}:${_tag}", "-f MutilBuildDockerfile .")
                    // 构建成功后推送到私服当中
                    docker.withRegistry("${HARBOR_URL}", "Harbor") {
                      image.push()
                    }
                    // 删除已经推送完毕的镜像
                    sh "docker rmi ${REGISTRY_URL}/${PROJECT_GROUP}/${PROJECT_NAME}:${_tag}"
                    if (params.BRANCH_TAG.startsWith("rel-")) {
                      echo "选择的是标签,不打标签。"
                    } else if (params.BRANCH_TAG == "master" || params.BRANCH_TAG == "origin/master") {
                      sh "git tag rel-${_tag}"
                      sh "git push origin rel-${_tag}"
                      echo "为分支 ${params.BRANCH_TAG} 打标签 rel-${_tag}。"
                    } else {
                      echo "当前分支 ${params.BRANCH_TAG} 不是 master,不打标签。"
                    }
                  } else {
                    echo "跳过构建:BRANCH_TAG 以 'rel-' 开头。"
                  }
                }
              }
            }
          }
        }
      }
    }
    // 同样Ansible部署也使用了容器来对项目进行推送部署
    stage('SlaveAnsible49Node') {
      steps {
        node('node47') {
          script {
            // 这里使用私服中的ansible镜像进行部署,将密钥挂载到容器当中
            docker.image('harbor.xxx.com/devops/ansible:2.18.6').inside('-v /root/.ssh:/root/.ssh') {
              stage('Checkout') {
                script {
                  checkout([$class: 'GitSCM', 
                    branches: [[name: '*/master']], 
                    userRemoteConfigs: [[url: "${OPS_SHARE_LIBRARY}", credentialsId: "GiteeKey"]]])
                }
              }
              stage('Ansible in Slave1') {
                script {
                  // 定义私服和ansible的相关变量信息
                  withCredentials([usernamePassword(credentialsId: 'Harbor', usernameVariable: 'HARBOR_USERNAME', passwordVariable: 'HARBOR_PASSWORD')]) {
                    def extraVars = [
                      container_name: "${CONTAINER_NAME}",
                      docker_registry: "${HARBOR_URL}",
                      docker_username: "${HARBOR_USERNAME}",
                      docker_password: "${HARBOR_PASSWORD}"
                    ].collectEntries { [(it.key): it.value] }
                    
                    if (!env.BRANCH_TAG.startsWith('rel-')) {
                      extraVars['new_image_name'] = "${REGISTRY_URL}/${PROJECT_GROUP}/${PROJECT_NAME}:${_tag}"
                      
                      ansiblePlaybook(
                        playbook: "./Ansible/HelloWorld-Pipeline/deploy.yml",
                        inventory: "./Ansible/HelloWorld-Pipeline/hosts",
                        extraVars: extraVars
                      )
                    } else {
                      def branchTag = env.BRANCH_TAG
                      def extractedValue = branchTag.replaceFirst(/^rel-/, '')
                      println "Extracted Value: ${extractedValue}"
                      
                      extraVars['rel_image_name'] = "${REGISTRY_URL}/${PROJECT_GROUP}/${PROJECT_NAME}:${extractedValue}"
                      
                      ansiblePlaybook(
                        playbook: "./Ansible/HelloWorld-Pipeline/rollback.yml",
                        inventory: "./Ansible/HelloWorld-Pipeline/hosts",
                        extraVars: extraVars
                      )
                    }
                  }
                }
              }
            }
          }
        }
      }
    }
  } // 结束 stages

  post {
    always {
      script {
        println("流水线结束后,经常做的事情")
      }
    }
    success {
      script {
        def notificationText = []
        notificationText.add("- 成功构建部署: ${JOB_NAME}项目!\n")
        notificationText.add("- 版本: ${BRANCH_TAG}\n")
        notificationText.add("- 持续时间: ${currentBuild.durationString}\n")
        notificationText.add("- 任务: #${JOB_NAME}")
        
        if (params.BRANCH_TAG == "master" || params.BRANCH_TAG == "origin/master") {
          notificationText.add("- 标签: rel-${_tag}\n")
        }
        
        dingtalk (
          robot: 'BuildBoy',
          type: 'MARKDOWN',
          title: "success: ${JOB_NAME}",
          text: notificationText
        )
      }
    }
    failure {
      dingtalk (
        robot: 'BuildBoy',
        type: 'MARKDOWN',
        title: "fail: ${JOB_NAME}",
        text: ["- 失败构建部署:${JOB_NAME}项目!\n- 版本:${BRANCH_TAG}\n- 持续时间:${currentBuild.durationString}\n- 任务:#${JOB_NAME}"]
      )
    }
    aborted {
      dingtalk (
        robot: 'BuildBoy',
        type: 'MARKDOWN',
        title: "aborted: ${JOB_NAME}",
        text: ["- 流水线被取消:${JOB_NAME}项目!\n- 版本:${BRANCH_TAG}\n- 持续时间:${currentBuild.durationString}\n- 任务:${JOB_NAME}"]
      )
    }
  }
}

3.测试流水线

null

image-20250919143911634

这样整个构建的过程就再容器当中进行,而不会影响到你现在的系统环境,感觉这种方式还比较nice,如果文章帮助到了您记得来个赞哦!

相关推荐
刘立军6 小时前
内网的服务如何能在公网使用?
运维
飞询7 小时前
docker 部署 sftp
运维·docker
云偶1 天前
从零搭建 Jenkins Android 自动发包体系
jenkins
LH_R1 天前
OneTerm开源堡垒机实战(四):访问授权与安全管控
运维·后端·安全
Raymond运维1 天前
MariaDB源码编译安装(二)
运维·数据库·mariadb
libraG2 天前
Jenkins打包问题
前端·npm·jenkins
JuiceFS2 天前
从 MLPerf Storage v2.0 看 AI 训练中的存储性能与扩展能力
运维·后端
chen9452 天前
mysql 3节点mgr集群部署
运维·后端
LH_R2 天前
OneTerm开源堡垒机实战(三):功能扩展与效率提升
运维·后端·安全