从0搭建属于自己的jenkins持续集成平台

简介: Jenkins在日常工作中占据了一个非常重要的角色,帮助我们节省了大量用于构建的时间。有些公司有运维大哥对Jenkins进行维护,如果没有那只能自己动手了。俗话说的好自己动手丰衣足食,所以本文就从0开始搭建属于自己的Jenkins持续平台。主要包含,普通项目构建、流水线构建、多分支流水线构建并将构建结果辅以钉钉通知。

前言

  Jenkins在日常工作中占据了一个非常重要的角色,帮助我们节省了大量用于构建的时间。有些公司有运维大哥对Jenkins进行维护,如果没有那只能自己动手了。俗话说的好自己动手丰衣足食,所以本文就从0开始搭建属于自己的Jenkins持续平台。主要包含,普通项目构建流水线构建多分支流水线构建并将构建结果辅以钉钉通知。

前期准备

  • centos7 服务器一台

确认是否能安装docker

 Docker要求CentOS系统的内核版本高于3.10.通过uname -r命令查看你当前的内核版本。

[root@CentOS ~]# uname -r
3.10.0-1127.8.2.el7.x86_64

更改yum源为阿里云

备份旧源

mv /etc/yum.repos.d/CentOS-Base.repo /etc/yum.repos.d/CentOS-Base.repo.backup

下载最新的源

wget -O /etc/yum.repos.d/CentOS-Base.repo https://mirrors.aliyun.com/repo/Centos-7.repo

生成缓存

yum makecache

更新

yum update

安装docker

官方安装文档

yum install -y yum-utils

添加docker源

yum-config-manager \
    --add-repo \
    https://download.docker.com/linux/centos/docker-ce.repo

安装docker

yum install docker-ce

启动docker

systemctl start docker

更改docker镜像源

vim /etc/docker/daemon.json

加入阿里云源地址

{
    "registry-mirrors":["https://6kx4zyno.mirror.aliyuncs.com"]
}

重新读取配置

systemctl daemon-reload

重启docker

systemctl restart docker

安装jenkins

下载jenkins镜像

docker pull jenkins

启动jenkins

 设置端口为9090并映射jenkins_home到宿主机/home/jenkins_home

docker run -d --name jenkins -p 9090:8080 -v /home/jenkins_home:/var/jenkins_home jenkins

 可以通过docker ps查看运行的容器。

[root@CentOS home]# docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED              STATUS              PORTS                               NAMES
ec6a4da6b83f        jenkins             "/bin/tini -- /usr/l…"   About a minute ago   Up About a minute   50000/tcp, 0.0.0.0:9090->8080/tcp   jenkins
[root@CentOS home]#

把玩jenkins docker镜像遇到的volume权限问题

 在运行启动jenkins的命令时,可能会出现jenkins无法启动情况。

[root@CentOS home]# docker ps -a
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS                     PORTS               NAMES
b571f16dafbf        jenkins             "/bin/tini -- /usr/l…"   8 minutes ago       Exited (1) 8 minutes ago                       jenkins
[root@CentOS home]#

 可以通过docker logs 镜像名称查看启动日志。

[root@CentOS home]# docker logs jenkins
touch: cannot touch '/var/jenkins_home/copy_reference_file.log': Permission denied
Can not write to /var/jenkins_home/copy_reference_file.log. Wrong volume permissions?
[root@CentOS home]#

 查看输出的日志,如果出现 Permission denied 类似的错误。需要删除旧容器重新运行。

docker rm jenkins

 运行命令加入了-u 0重新运行。

docker run -d --name jenkins -p 9090:8080 -v /home/jenkins_home:/var/jenkins_home -u 0 jenkins

参考 https://blog.csdn.net/minicto/article/details/73539986

Jenkins初始化

 启动成功后输入 http://服务器:9090/

如果无法访问,请检查一下防火墙端口是否开放,如果是云服务器还需要检查安全组设置

  首次启动jenkins需要输入密码,需要进入容器内获取密码。密码位于/var/jenkins_home/secrets/initialAdminPassword

进入容器

docker exec -it jenkins /bin/bash

获取密码

cat /var/jenkins_home/secrets/initialAdminPassword
[root@CentOS jenkins_home]# docker exec -it jenkins /bin/bash
root@ec6a4da6b83f:/# cat /var/jenkins_home/secrets/initialAdminPassword
68eed23ad39541949972468e4f2ce1fd
root@ec6a4da6b83f:/#

  由于我们将/var/jenkins_home -- 挂载到--> /home/jenkins_home所以也可以直接cat /home/jenkins_home/secrets/initialAdminPassword 获取密码。

  输入密码以后,安装需要的插件,在安装途中由于网络原因会出现有些插件安装失败,这个可以不用理会。

设置jenkins的默认登录账号和密码

处理插件安装失败

  进入jenkins的主页面右上角可能会出现一些报错信息,主要是提示jenkins 需要的某些插件没有安装,或者说jenkins版本太低了,插件无法使用这个时候我们需要先升级jenkins做一个升级。

自动升级

Jenkins提供了自动升级的方式

手动升级

 可以去Jenkins的官网下载好最新jar包上传到服务器,也可以使用wget命令。

wget http://jenkins新版本的下载地址
#目前最新2.239
wget http://updates.jenkins-ci.org/download/war/2.239/jenkins.war

  Jenkins的更新主要是替换jenkins镜像里面的war包 ,我们可以把下载好的war包使用docker cp直接进行复制命令如下:

docker cp jenkins.war jenkins:/usr/share/jenkins

 重新启动Jenkins即可完成升级。

docker restart jenkins

更插件源

https://mirrors.tuna.tsinghua.edu.cn/jenkins/updates/update-center.json
  • 替换完源以后点击提交。
  • 然后进入插件管理页面将出错的插件重新安装。
  • 及时更新插件。

安装必要的插件

  • Localization: Chinese (Simplified) 1.0.14 汉化包 搜索关键字 chinese
  • Publish Over SSH 1.20.1 搜索关键字 ssh
  • DingTalk 钉钉通知 2.3.0

配置jenkins

全局工具配置

  主要配置 jdk、maven、git等常用环境。需要注意配置的别名,后续构建将会使用到。

配置jdk

  因为jenkins镜像自带jdk所以无需安装直接使用即可,进入Jenkins容器,使用java -verbose查看java安装路径。

docker exec -it jenkins /bin/bash
java -verbose

配置git

 进入容器内使用whereis git即可查询到git安装路径。

root@6a9fbb129cbe:~# whereis git
git: /usr/bin/git /usr/share/man/man1/git.1.gz
root@6a9fbb129cbe:~#

配置maven

 maven直接使用自动安装即可。

系统设置

配置服务器

点击新增即可添加服务器,主要配置:

  • Name 名称 - 构建的时候将会用到
  • Hostname 服务器地址
  • Username 用户名
  • Remote Directory 远程目录 - 上传文件的目录 默认配置根目录即可/

点击高级进行其他参数配置

  • 如果需要使用密码登录,则选中Use password authentication, or use a different key 复选框即可,如下图所示。

  除了配置密码还可以配置端口Port,跳板机Jump Host的参数,可以根据实际情况配置。默认可以使用密码。

  配置完成以后点击Test Configuration按钮,如果配置正常会出现Success 反之出现错误信息,可以根据错误信息,调整配置参数。

配置钉钉

  钉钉主要用于构建通知,在配置前需要在钉钉群内,添加自定义机器人。

自由风格的软件项目

  以https://gitee.com/huangxunhui/jenkins_demo.git为例。

新建项目

设置项目简介

源码管理

  • 配置仓库地址。
  • 配置凭证-主要用于拉取代码。
  • 配置需要构建的分支。

添加凭证

  如果项目是开源,则可以跳过这一步。反之需要设置凭证,要不然将无法拉取代码进行构建。

构建触发器

  可以根据实际情况选择,案例采用轮询的方式进行构建。

构建

构建后操作

  • 将jar包发送到相应的服务器。

  • Source files jar包的路径。支持通配符匹配.
  • Remove prefix 移除前缀,一般jar包的路径都存在于**/target下,如果不移除,会在目标服务器上建立相应的目录结构。
  • Remote directory 远程目录。

注意的点, 在之前配置服务器时也配置了Remote directory,这时候部署的实际目录是,服务器设置的远程目录+现在配置的远程目录。

  • Exec command 执行脚本,主要用于将jar发送到目标服务器后,执行相应的启动脚本。

配置完成点击保存即可。

点击开始构建

发送钉钉通知


流水线

  流水线构建,将上述构建步骤代码化,方便调整。

项目创建

流水线编写

  由于配置步骤类似,前面简单的步骤可以参照,自由风格的软件项目。这里主要讲流水线如何编写。

注意右下角的流水线语法,后续会用上。

  我们可以点击右上角的下拉按钮,生成一个简单的流水线。比如说hello world。

pipeline {
   
   

    // 表示所有机器都能运行   
   agent any

   stages {
   
   
      stage('Hello') {
   
   
         steps {
   
   
            echo 'Hello World'
         }
      }
   }
}

  通过上面的pipeline可以知道,有一个Hello的步骤,这个步骤执行的是,输出hello world。依葫芦画瓢,一次完整的构建我们可以总结出如下几个步骤:拉取代码(checkout) -> 打包(build) -> 部署(deploy)。

pipeline {
   
   
   agent any

   stages {
   
   
      stage('checkout') {
   
   
         steps {
   
   

         }
      }

      stage('build') {
   
   
         steps {
   
   

         }
      }

    stage('deploy') {
   
   
         steps {
   
   

         }
      }
   }
}

  步骤梳理好了,这个时候就可以完善对应的步骤了,这就需要用到提到的,流水线语法。

将生成好的流水线脚本复制到对应的步骤即可。

注意:如果使用到maven需要将maven引入,tools相应的内容就是配置maven时配置的别名。

pipeline {
   
   
   agent any

    // 工具
    tools {
   
   
        maven 'maven'
        jdk 'jdk'
    }

   stages {
   
   
      stage('checkout') {
   
   
         steps {
   
   
            checkout([$class: 'GitSCM', branches: [[name: '*/master']], doGenerateSubmoduleConfigurations: false, extensions: [], submoduleCfg: [], userRemoteConfigs: [[url: 'https://gitee.com/huangxunhui/jenkins_demo.git']]])
         }
      }

      stage('build') {
   
   
         steps {
   
   
            sh 'mvn clean package -Dmaven.test.skip=true'            
         }
      }

    stage('deploy') {
   
   
         steps {
   
   
            sshPublisher(publishers: [sshPublisherDesc(configName: 'dev', transfers: [sshTransfer(cleanRemote: false, excludes: '', execCommand: '''cd /home/project 
nohup java -jar jenkins_demo.jar > nohup.out 2>&1 &''', execTimeout: 120000, flatten: false, makeEmptyDirs: false, noDefaultExcludes: false, patternSeparator: '[, ]+', remoteDirectory: '/home/project', remoteDirectorySDF: false, removePrefix: '/target', sourceFiles: '**/target/jenkins_demo.jar')], usePromotionTimestamp: false, useWorkspaceInPromotion: false, verbose: false)])
         }
      }

   }
}

配置完成点击应用即可。

构建测试

上面演示的是将流水线配置在jenkins内,其实我们还可以从SCM中获取,比如git

我们可以建立一个仓库专门维护不同项目的构建脚本Jenkinsfile,也可以在每个项目下,建立对应的Jenkinsfile.

  注意的点:项目中的Jenkinsfile需要和配置的一致。比如说上面的配置,是扫描项目根目录下名字为Jenkinsfile的文件。

所以我们可以在jenkins_demo仓库内添加Jenkinsfile文件。

配置点击完成,即可。


多分支流水线

  在日常开发中,通常是基于git-flow进行开发的,前面两种都是基于单分支构建,如果每个分支都去配置,那将耗费大量时间。所以多分支流水线就是用来解决这个问题的。

创建项目

配置分支源

构建配置

扫描触发器

完成上述配置,点击应用即可。

编写jenkinsfile文件

  核心思想是,根据不同的分支使用不同的打包命令,发送到不同的服务器进行运行。

pipeline {
   
   
    // 指定集群 any 表示所有
    agent any

    // 工具
    tools {
   
   
        maven 'maven'
        jdk 'jdk'
    }

    // 定义常量
    environment {
   
   

        // 钉钉机器人编号
        rebootId = 'a3c07482-d031-47a6-8542-05ac56c5f17a'

        // 开始logo
        imageOfStart = 'https://www.easyicon.net/api/resizeApi.php?id=1229977&size=128'

        // 成功logo
        imageOfSuccess = 'https://www.easyicon.net/api/resizeApi.php?id=1194837&size=128'

        // 失败logo
        imageOfFailure = 'https://www.easyicon.net/api/resizeApi.php?id=1201052&size=128'

        // 不稳定logo
        imageOfUnstable = 'https://www.easyicon.net/api/resizeApi.php?id=1219854&size=128'

        // 终止logo
        imageOfAborted = 'https://www.easyicon.net/api/resizeApi.php?id=1183198&size=128'

        // 认证Id
        credentialsId = '98e9c197-f0ae-44c3-8f67-4ca0339028a8'

        // 仓库地址
        repositoryUrl = 'https://gitee.com/huangxunhui/jenkins_demo.git'

        // 打包命令 - 项目需要配置maven多环境
        mavenProd = 'mvn clean package -P prod -Dmaven.test.skip=true'
        mavenTest = 'mvn clean package -P test -Dmaven.test.skip=true'
        mavenDev = 'mvn clean package -P dev -Dmaven.test.skip=true'

        // 服务器名称 - 案例测试-全部部署到dev环境
        devServer = 'dev'
        testServer = 'dev'
        prodServer = 'dev'

        // sshPublisher 配置
        removePrefix = '/target'
        remoteDirectory = '/home/project/jenkins_demo'
        sourceFiles = '**/target/jenkins_demo.jar'

        execCommandProd = 'cd /home/project && ./manage.sh jenkins_demo/ restart'
        execCommandTest = 'cd /home/project && ./manage.sh jenkins_demo/ restart'
        execCommandDev = 'cd /home/project && ./manage.sh jenkins_demo/ restart'

    }

    stages {
   
   

        stage('开始构建通知'){
   
   
            steps {
   
   
                dingtalk (
                        robot: "${rebootId}",
                        type: 'LINK',
                        title: "${env.JOB_NAME}",
                        text: [
                            "开始构建-编号为#${BUILD_NUMBER}"
                        ],
                        messageUrl: "${env.BUILD_URL}",
                        picUrl: "${imageOfStart}"
                )
            }
        }

        stage('拉取代码'){
   
   
            steps {
   
   
                echo "拉取 ${BRANCH_NAME} 分支的代码。"
                checkout([{
   
   mathJaxContainer[0]}{
   
   BRANCH_NAME}"]], doGenerateSubmoduleConfigurations: false, extensions: [], submoduleCfg: [], userRemoteConfigs: [[credentialsId: "{
   
   mathJaxContainer[1]}{
   
   repositoryUrl}"]]])
            }
        }

        stage('进行打包'){
   
   
            steps {
   
   
                script {
   
   
                    if (env.BRANCH_NAME == 'master') {
   
   
                        sh "${mavenProd}"
                    } else if (env.BRANCH_NAME == 'test') {
   
   
                        sh "${mavenTest}"
                    } else if (env.BRANCH_NAME == 'dev') {
   
   
                        sh "${mavenDev}"
                    } else {
   
   
                        sh "${mavenDev}"
                    }
                }
            }
        }

        stage('项目部署'){
   
   
            steps {
   
   
                script {
   
   
                    if (env.BRANCH_NAME == 'master') {
   
   
                        // 部署生产环境
                        sshPublisher(publishers: [sshPublisherDesc(configName: "${prodServer}", transfers: [sshTransfer(cleanRemote: false, excludes: '', execCommand: "${execCommandProd}", execTimeout: 120000, flatten: false, makeEmptyDirs: false, noDefaultExcludes: false, patternSeparator: '[, ]+', remoteDirectory:  "${remoteDirectory}", remoteDirectorySDF: false, removePrefix: "${removePrefix}", sourceFiles: "${sourceFiles}")], usePromotionTimestamp: false, useWorkspaceInPromotion: false, verbose: false)])
                    } else if (env.BRANCH_NAME == 'test') {
   
   
                        // 部署测试环境
                        sshPublisher(publishers: [sshPublisherDesc(configName: "${testServer}", transfers: [sshTransfer(cleanRemote: false, excludes: '', execCommand: "${execCommandTest}", execTimeout: 120000, flatten: false, makeEmptyDirs: false, noDefaultExcludes: false, patternSeparator: '[, ]+', remoteDirectory:  "${remoteDirectory}", remoteDirectorySDF: false, removePrefix: "${removePrefix}", sourceFiles: "${sourceFiles}")], usePromotionTimestamp: false, useWorkspaceInPromotion: false, verbose: false)])
                    } else if (env.BRANCH_NAME == 'dev') {
   
   
                        // 部署开发环境
                        sshPublisher(publishers: [sshPublisherDesc(configName: "${devServer}", transfers: [sshTransfer(cleanRemote: false, excludes: '', execCommand: "${execCommandDev}", execTimeout: 120000, flatten: false, makeEmptyDirs: false, noDefaultExcludes: false, patternSeparator: '[, ]+', remoteDirectory:  "${remoteDirectory}", remoteDirectorySDF: false, removePrefix: "${removePrefix}", sourceFiles: "${sourceFiles}")], usePromotionTimestamp: false, useWorkspaceInPromotion: false, verbose: false)])
                    } else {
   
   
                        sshPublisher(publishers: [sshPublisherDesc(configName: "${devServer}", transfers: [sshTransfer(cleanRemote: false, excludes: '', execCommand: "${execCommandTest}", execTimeout: 120000, flatten: false, makeEmptyDirs: false, noDefaultExcludes: false, patternSeparator: '[, ]+', remoteDirectory:  "${remoteDirectory}", remoteDirectorySDF: false, removePrefix: "${removePrefix}", sourceFiles: "${sourceFiles}")], usePromotionTimestamp: false, useWorkspaceInPromotion: false, verbose: false)])
                    }
                }
            }
        }
    }

    // 流水线结束通知
    post {
   
   

        // 成功通知
        success {
   
   
            dingtalk (
                    robot: "${rebootId}",
                    type: 'LINK',
                    title: "${env.JOB_NAME}",
                    text: [
                        "构建成功-编号为#${BUILD_NUMBER}"
                    ],
                    messageUrl: "${env.BUILD_URL}",
                    picUrl: "${imageOfSuccess}"
                )
        }

        // 失败通知
        failure {
   
   
            dingtalk (
                    robot: "${rebootId}",
                    type: 'LINK',
                    title: "${env.JOB_NAME}",
                    text: [
                        "构建失败-编号为#${BUILD_NUMBER}"
                    ],
                    messageUrl: "${env.BUILD_URL}",
                    picUrl: "${imageOfFailure}"
            )
        }

        // 构建不稳定通知
        unstable {
   
   
            dingtalk (
                    robot: "${rebootId}",
                    type: 'LINK',
                    title: "${env.JOB_NAME}",
                    text: [
                        "构建不稳定-编号为#${BUILD_NUMBER}"
                    ],
                    messageUrl: "${env.BUILD_URL}",
                    picUrl: "${imageOfUnstable}"
            )
        }

        // 构建终止通知
        aborted {
   
   
            dingtalk (
                    robot: "${rebootId}",
                    type: 'LINK',
                    title: "${env.JOB_NAME}",
                    text: [
                        "构建终止-编号为#${BUILD_NUMBER}"
                    ],
                    messageUrl: "${env.BUILD_URL}",
                    picUrl: "${imageOfAborted}"
            )
        }
    }
}

使用到的启动脚本manage.sh

钉钉机器人插件使用文档

构建结果

结尾

  如果觉得对你有帮助,可以多多评论,多多点赞哦,也可以到我的主页看看,说不定有你喜欢的文章,也可以随手点个关注哦,谢谢。

  我是不一样的科技宅,每天进步一点点,体验不一样的生活。我们下期见!

相关文章
|
3月前
|
云安全 人工智能 安全
Dify平台集成阿里云AI安全护栏,构建AI Runtime安全防线
阿里云 AI 安全护栏加入Dify平台,打造可信赖的 AI
2851 166
|
7月前
|
运维 安全 关系型数据库
【产品升级】Dataphin V5.1版本发布:跨云数据集成、指标管理、平台运维带来重大更新!
V5.1版本新增多项功能:对接AWS生态(支持Amazon EMR、Redshift等),强化研发技术支撑(如API认证升级、全量任务隔离),完善运营消费链路(新增业务指标管理、指标关系图),提升平台综合能力(自定义菜单、缩短升级停机时间)。这些功能助力企业实现高效数据治理与分析,未来还将拓展智能化与国际化支持。
411 0
|
4月前
|
人工智能 安全 API
Dify平台集成安全护栏最佳实践
Dify平台提供低代码构建AI大模型应用的解决方案,支持云服务与私有化部署。本文介绍了在工作流和Agent中集成安全护栏的最佳实践,包括插件和扩展API两种方案。插件方式适用于工作流,一键安装实现输入输出防控;扩展API方式适用于Agent和工作流私有化部署场景,通过本地服务适配安全护栏API。文中还详细说明了操作步骤、前提条件及常见问题处理方法,帮助用户快速实现内容安全控制。
|
6月前
|
人工智能 搜索推荐 API
AI-Compass DeepSearch深度搜索生态:集成阿里ZeroSearch、字节DeerFlow、MindSearch等前沿平台,实现超越传统关键词匹配的智能信息检索革命
AI-Compass DeepSearch深度搜索生态:集成阿里ZeroSearch、字节DeerFlow、MindSearch等前沿平台,实现超越传统关键词匹配的智能信息检索革命
AI-Compass DeepSearch深度搜索生态:集成阿里ZeroSearch、字节DeerFlow、MindSearch等前沿平台,实现超越传统关键词匹配的智能信息检索革命
|
5月前
|
供应链 监控 搜索推荐
35页PPT|零售行业自助数据分析方法论:指标体系构建平台集成、会员与商品精细化运营实践
在零售行业环境剧变的背景下,传统“人找货”模式正被“货找人”取代。消费者需求日益个性化,购买路径多元化,企业亟需构建统一的指标体系,借助BI平台实现数据驱动的精细化运营。本文从指标体系构建、平台集成到会员与商品运营实践,系统梳理零售经营分析的方法论,助力企业实现敏捷决策与业务闭环。
35页PPT|零售行业自助数据分析方法论:指标体系构建平台集成、会员与商品精细化运营实践
|
10月前
|
人工智能 网络协议 Java
RuoYi AI:1人搞定AI中台!开源全栈式AI开发平台,快速集成大模型+RAG+支付等模块
RuoYi AI 是一个全栈式 AI 开发平台,支持本地 RAG 方案,集成多种大语言模型和多媒体功能,适合企业和个人开发者快速搭建个性化 AI 应用。
2089 77
RuoYi AI:1人搞定AI中台!开源全栈式AI开发平台,快速集成大模型+RAG+支付等模块
|
6月前
|
机器学习/深度学习 人工智能 监控
CI/CD与模型监控平台集成MLOps系统实现的全面路径
MLOps是机器学习模型在生产环境中持续优化、部署和维护的关键。通过CI/CD流水线和模型监控平台的结合,可以大大提高模型开发和运维的效率,实现高效、稳定的模型服务。随着AI技术的快速发展,MLOps将在企业级AI应用中发挥越来越重要的作用。
CI/CD与模型监控平台集成MLOps系统实现的全面路径
|
6月前
|
人工智能 JavaScript 安全
一文教你高效集成Qwen Code与ModelGate千万免费Toknn模型网关平台
本文详解如何高效集成Qwen Code与ModelGate模型网关平台,涵盖环境搭建、API配置、代码生成等关键步骤,助你实现智能编程与多模型管理,大幅提升AI开发效率。
|
10月前
|
SQL 关系型数据库 MySQL
【亲测有用】数据集成平台能力演示(支持国产数据库DaMeng与KingBase)
杭州奥零数据科技有限公司成立于2023年,专注于数据中台业务,维护开源项目AllData并提供商业版解决方案。AllData提供数据集成、存储、开发、治理及BI展示等一站式服务,支持AI大模型应用,助力企业高效利用数据价值。
【亲测有用】数据集成平台能力演示(支持国产数据库DaMeng与KingBase)
|
11月前
|
人工智能 安全 机器人
LangBot:无缝集成到QQ、微信等消息平台的AI聊天机器人平台
LangBot 是一个开源的多模态即时聊天机器人平台,支持多种即时通信平台和大语言模型,具备多模态交互、插件扩展和Web管理面板等功能。
2161 14
LangBot:无缝集成到QQ、微信等消息平台的AI聊天机器人平台

推荐镜像

更多