基于Jenkins实现的CI/CD方案

基于Jenkins实现的CI/CD方案

前言

最近基于Jenkins的基座,搭建了一套适用于我们项目小组的持续集成环境。现在把流程整理分享出来,希望可以给大家提供一些帮助和思路。

使用到的组件和版本

组件名称 组件版本 作用
Harbor 2.7.3 镜像仓库
Jenkins 2.319.2 持续集成工具
Pipeline 2.6 Jenkins插件,编排流水线脚本
SSH Pipeline Steps 2.0.0 Jenkins插件,提供远程执行ssh能力
Git 4.10.1 Jenkins插件,提供拉取git代码仓库的能力
Httpd 2.4.18 HTTP服务器,用于归档编译后的软件包,镜像包
Maven 3.6.3 后端代码构建工具
Node 8.17.0 前端代码构建工具
Docker 20.10.0 容器版本
Docker Compose v2.18.1 容器编排

我这边是基于Jenkins的Pipeline+Docker的方式进行的任务编排,Jenkins是找的一个别人做好的,内置了绝大多数插件的容器版本,链接地址:https://hub.docker.com/r/h1kkan/jenkins-docker。这个做好的镜像里面没有SSH Pipeline Steps这个插件,需要自己额外下载一下,这边需要注意一下插件和Jenkins的对应版本。

基本流程图

这边有几个需要注意的地方,简单说明一下

  1. 首先需要配置代码仓库的webhook,这个网上有很多资料,可以自行参考配置一下

  2. dockerfile,docker-compose.yml这些文件需要内置在代码仓库中或者服务器内(我们是dockerfile文件内置在代码仓库,docker-compose.yml文件放在服务器里面固定目录)

  3. 在第五步,执行远端ssh时,需要去更改docker-compose.yml中的image节点,一开始准备用shell去做的,但是实现有点难度,然后就内置了一个python脚本,用yaml这个库去实现的,具体脚本updateImageLabel.py参考:

    python 复制代码
    import yaml
    import sys
    
    filename = sys.argv[1]
    # 加载docker-compose.yml文件
    with open(filename,'r') as f:
        data = yaml.load(f)
    # 更新镜像标签
    data['services'][sys.argv[2]]['image'] = sys.argv[3]
    with open(filename, 'w') as yaml_file:
        yaml_file.write(yaml.dump(data, default_flow_style=False))

    使用时,传入三个参数,分别是docker-compose.yml文件路径,需要更新的服务,更新后的镜像标签

    shell 复制代码
    python3 updateImageLabel.py ${dockerComposePath}  ${service}  ${image}
  4. Harbor的安装可以参考我之前的博客,安装完成如果是http的话,还需要配置一下insecure-registries仓库信息,并且执行docker login命令登录镜像仓库,用于拉取Harbor仓库镜像

    shell 复制代码
    {
      "insecure-registries": [
        "harbor服务器地址"
      ]
    }
  5. 第七步是可选的,做这个是为了方便每一个版本的归档,这边可以归档软件包或者save之后的镜像包

具体实现步骤

下面具体写一下实现的步骤,因为涉及的东西很多,有一些能找到的通用的步骤我就先不写了,大家可以自行去百度或者Google。

基础中间件部署

这边只讲Jenkins和Httpd的部署,Harbor部署可以参考我之前的文档。

Jenkins部署

通过docker的方式拉起Jenkins

shell 复制代码
docker run -u root -e TZ=Asia/Shanghai --name=jenkins -d -e TZ="Asia/Shanghai" -p 8080:8080 -p 50000:50000 -v /data/jenkins:/var/jenkins_home -v /run/docker.sock:/var/run/docker.sock -v /data/archive:/data/archive h1kkan/jenkins-docker:2.319.2

容器起来之后,需要进入到容器内部执行一下docker login的命令,在Jenkins容器内部也生成一套Harbor的凭证。

第三个挂载目录是用来开给Httpd服务器的,用于归档软件包和镜像包。

Httpd服务器部署

通过docker拉起Httpd服务器

shell 复制代码
docker run -p 8001:80 -v /data/archive:/usr/local/apache2/htdocs/ -d --name httpd httpd:2.4.18 

这边挂载目录就是Jenkins开出来的目录

Maven、Node、JDK等基础镜像

可以先从外网拉取对应版本,然后本地打成tar包之后上传到服务器解压,再tag之后推送到自己的Harbor仓库。

在Jenkins配置Git凭证

这个网上也很多,就不细致展开了,可以自己查一下

Pipeline流水线编排

后端流水线pipeline脚本
groovy 复制代码
import java.text.SimpleDateFormat
import java.util.TimeZone

// 构建版本
def createVersion() {
    def simpleDateFormat = new SimpleDateFormat("yyyyMMddHHmmss")
    simpleDateFormat.timeZone = TimeZone.getTimeZone("Asia/Shanghai")
    return simpleDateFormat.format(new Date()) + "_${env.branch}"
}

def getTime() {
    def simpleDateFormat = new SimpleDateFormat("yyyyMMddHHmmss")
    simpleDateFormat.timeZone = TimeZone.getTimeZone("Asia/Shanghai")
    return simpleDateFormat.format(new Date())
}

// 获取远端服务器信息 
def GetRemoteServer(ip, username, password) {
    def remote = [:]
    remote.name = ip
    remote.host = ip
    remote.user = username
    remote.password = password
    remote.allowAnyHosts = true
    return remote
}

pipeline {
    agent none
    environment {
        _version = createVersion()
        _time = getTime()
    }
    stages {
        stage('Git Checkout') {
            agent any
            steps {
                checkout([
	                $class: 'GitSCM', 
	                branches: [[name: "${branch}"]], 
	                doGenerateSubmoduleConfigurations: false, 
	                extensions: [], 
	                gitTool: 'Default', 
	                submoduleCfg: [], 
	                userRemoteConfigs: [[url: '{{git_url}}',credentialsId: '{{credentialsId}}',]]
                ])
            }
        }
        stage('Source Package') {
            agent {
                docker { 
                    image 'local-maven:3.6.3-openjdk-8' 
                    args '-v maven-repo:/usr/share/maven/ref'
                }
            }
            steps {
                sh 'mvn clean install -Dmaven.test.skip=true'
            }
        }
      
        stage('Build Image') {
            agent any
            steps {
                sh 'docker build -f $WORKSPACE/CI/dockerfile --build-arg JARNAME=backend.jar -t 127.0.0.1:18080/library/backend:${_version} $WORKSPACE/backend/target/'
                sh 'docker push 127.0.0.1:18080/library/backend:${_version}'
                sh 'docker rmi 127.0.0.1:18080/library/backend:${_version}'
            }
        }

        stage('Publish To Env') {
            agent any
            steps {
                script {
                    def remote = [:]
                    remote = GetRemoteServer('127.0.0.1', 'username', 'password')
                  	sshCommand remote: remote, command: "python3 updateImageLabel.py docker-compose.yml backend 127.0.0.1:18080/library/backend:${_version}"
                  	sshCommand remote: remote, command: "docker-compose -f docker-compose.yml up -d --build backend"
                }
            }
        }
          
        stage('Archive Package') {
            agent any
            steps {
                sh 'mkdir -p /data/archive/backend/${branch}/${_time}'
                sh 'cp $WORKSPACE/backend/backend.jar /data/archive/backend/${branch}/${_time}'
            }
        }
    }
}

解释一下这个脚本

  • 首选镜像的版本是时间戳+分支的格式,类似20231026095511_dev,可以根据项目组的要求进行生成。

  • GetRemoteServer方法主要是提供获取远端服务器的信息,主要使用了SSH Pipeline Steps这个插件的能力,传入ip,username,password三个参数,这边密码是明文写死在脚本里面的,这个插件也支持密码加密保存,如果安全性要求比较高的可以换成加密的方式。

  • Git Checkout 这个stage主要是进行代码的下载,根据${branch}这个参数来指定代码仓库的版本,参数的具体配置可以看下Jenkins参数化构建的相关指导和文章。

  • Source Package就是代码仓库的打包,这边指定使用Docker镜像作为执行的Agent,只要指定镜像和编译命令就可以。这边我还挂载了一个maven-repo的volume,主要是为了缓存Jar包,不用每次都去下载。

  • Build Image步骤进行代码的构建,通过docker-build命令去生成镜像,这边的dockerfile文件是内置在我们代码库中的,参考如下:

    dockerfile 复制代码
    FROM 127.0.0.1:18080/library/java:8.0
    ARG JARNAME
    COPY ${JARNAME} /data/
  • Publish To Env 推送到环境,这边核心就是连接到远程服务器,通过修改镜像标签的脚本去更新docker-compose.yml文件中的镜像标签,然后重新构建容器

  • Archive Package 是否归档,演示的脚本里面只是归档了编译后的文件,还可以归档镜像等等。归档的路径为分支名/时间戳

前端流水线pipeline脚本
groovy 复制代码
import java.text.SimpleDateFormat
import java.util.TimeZone

// 构建版本
def createVersion() {
    def simpleDateFormat = new SimpleDateFormat("yyyyMMddHHmmss")
    simpleDateFormat.timeZone = TimeZone.getTimeZone("Asia/Shanghai")
    return simpleDateFormat.format(new Date()) + "_${env.branch}"
}

def getTime() {
    def simpleDateFormat = new SimpleDateFormat("yyyyMMddHHmmss")
    simpleDateFormat.timeZone = TimeZone.getTimeZone("Asia/Shanghai")
    return simpleDateFormat.format(new Date())
}

// 获取远端服务器信息 
def GetRemoteServer(ip, username, password) {
    def remote = [:]
    remote.name = ip
    remote.host = ip
    remote.user = username
    remote.password = password
    remote.allowAnyHosts = true
    return remote
}

pipeline {
    agent none
    environment {
        _version = createVersion()
        _time = getTime()
    }
    stages {
        stage('Git Checkout') {
            agent any
            steps {
                checkout([
	                $class: 'GitSCM', 
	                branches: [[name: "${branch}"]], 
	                doGenerateSubmoduleConfigurations: false, 
	                extensions: [], 
	                gitTool: 'Default', 
	                submoduleCfg: [], 
	                userRemoteConfigs: [[url: '{{git_url}}',credentialsId: '{{credentialsId}}',]]
                ])
            }
        }
        stage('Source Package') {
            agent {
                docker { 
                    image 'node:8.17.0' 
                }
            }
            steps {
                script {
                    sh 'npm install --registry=http://registry.npm.taobao.org'
                }
            }
        }
        stage('Image Build') {
            agent any
            steps {
                sh 'docker build -f $WORKSPACE/CI/dockerfile -t 127.0.0.1:18080/library/frontend:${_version} $WORKSPACE/target/'
                sh 'docker push 127.0.0.1:18080/library/frontend:${_version}'
                sh 'docker rmi 127.0.0.1:18080/library/frontend:${_version}'
            }
        }
        stage('Publish To Env') {
            agent any
            steps {
                script {
                    def remote = [:]
                    remote = GetRemoteServer('127.0.0.1', 'username', 'password')
                    // 更新docker-compose.yml文件,修改镜像
                    sshCommand remote: remote, command: "python3 updateImageLabel.py docker-compose.yml frontend 127.0.0.1:18080/library/frontend:${_version}"
                    sshCommand remote: remote, command: "docker-compose -f docker-compose.yml up -d --build frontend"

                }
            }
        }
        stage('Archive Package') {
            agent any
            steps {
                sh 'mkdir -p /data/archive/frontend/${branch}/${environment}/${_time}'
                sh 'cd $WORKSPACE/target/dist/ && zip -r dist.zip *'
                sh 'cp $WORKSPACE/target/dist/dist.zip /data/archive/frontend/${branch}/${environment}/${_time}'
            }
        }
    }
}

前端和后端除了构建方式不一样,其他基本都相同。

结语

参考地址:

https://www.jenkins.io/doc/book/pipeline/

https://www.jenkins.io/doc/pipeline/steps/ssh-steps/

相关推荐
萨格拉斯救世主15 分钟前
jenkins使用slave节点进行node打包报错问题处理
运维·jenkins
川石课堂软件测试26 分钟前
性能测试|docker容器下搭建JMeter+Grafana+Influxdb监控可视化平台
运维·javascript·深度学习·jmeter·docker·容器·grafana
pk_xz1234562 小时前
Shell 脚本中变量和字符串的入门介绍
linux·运维·服务器
小珑也要变强2 小时前
Linux之sed命令详解
linux·运维·服务器
Lary_Rock4 小时前
RK3576 LINUX RKNN SDK 测试
linux·运维·服务器
一坨阿亮8 小时前
Linux 使用中的问题
linux·运维
wclass-zhengge10 小时前
Docker篇(Docker Compose)
运维·docker·容器
李启柱10 小时前
项目开发流程规范文档
运维·软件构建·个人开发·设计规范
力姆泰克11 小时前
看电动缸是如何提高农机的自动化水平
大数据·运维·服务器·数据库·人工智能·自动化·1024程序员节
BPM_宏天低代码11 小时前
低代码 BPA:简化业务流程自动化的新趋势
运维·低代码·自动化