超详细!手把手教你用Jenkins pipeline + docker 打造前后端持续集成环境

基于Jenkins pipeline + docker 打造后端持续集成环境

本文将从零开始教你搭建一个实用的自动发布流程,文中相关示例代码已上传到 github仓库 需要的朋友可以自取~

发布流程设计

超详细!手把手教你用Jenkins pipeline + docker 打造前后端持续集成环境_第1张图片

  1. 开发人员提交代码、合并分支推送到指定分支
  2. Jenkins人工/定时触发项目构建
  3. Jenkins拉取、编译构建、打包镜像、推送到镜像仓库
  4. Jenkins 执行远程脚本:远程服务器 pull 指定镜像,重启新版本容器

搭建步骤

搭建Jenkins + Docker发布环境

  1. 安装新版docker
    $ vi /etc/yum.repos.d/docker.repo
    --- // 填入以下内容
    [dockerrepo]
    name=Docker Repository
    baseurl=https://yum.dockerproject.org/repo/main/centos/7/
    enabled=1
    gpgcheck=1
    gpgkey=https://yum.dockerproject.org/gpg
    ---
    $ sudo yum install -y docker-engine
    $ sudo systemctl enable docker.service
    $ sudo systemctl start docker
    $ docker -v
    
  2. 安装Jenkins,初始化设置
1. mkdir /var/jenkins_home
2. sudo chown -R 1000:1000 /var/jenkins_home // 开放权限
3. sudo docker run -d -u root -name jenkins -p 8080:8080 -v /var/jenkins_home:/var/jenkins_home -v /var/run/docker.sock:/var/run/docker.sock -v $(which docker):/bin/docker jenkinsci/blueocean

注意: 在启动Jenkins服务时要加上-v /var/run/docker.sock:/var/run/docker.sock -v $(which docker):/bin/docker,让我们可以在docker容器中使用外部docker (docker in docker)

当在jenkins启动成功后 浏览 http://localhost:8080 并等待 Unlock Jenkins 页面出现。
超详细!手把手教你用Jenkins pipeline + docker 打造前后端持续集成环境_第2张图片

在命令行中输入docker logs jenkins, 复制自动生成的字母数字密码(在两组星号之间)。 在 Unlock Jenkins 页面, 粘贴该密码到 Administrator password 字段并点击 Continue。
超详细!手把手教你用Jenkins pipeline + docker 打造前后端持续集成环境_第3张图片
选择install suggested pulgins 等待安装成功
创建用户,进入主页。

  1. 创建多分支流水线项目
    选择创建多分支流水线
    超详细!手把手教你用Jenkins pipeline + docker 打造前后端持续集成环境_第4张图片
    设置分支源(过滤出release开头的分支)
    超详细!手把手教你用Jenkins pipeline + docker 打造前后端持续集成环境_第5张图片
    完成设置。

代码中配置Dockerfile和Jenkinsfile

Jenkins执行流程

  1. 初始化配置:判断发布环境加载环境设置
  2. 用户自定义设置:确定本次需要执行的步骤
  3. 获取最新的代码
  4. 安装依赖项
  5. 运行单元测试
  6. 构建docker镜像推送到远程镜像仓库
  7. 登录远程服务器拉取最新镜像,并重启服务

编写Dockerfile做项目运行环境

FROM node:9.6.0

RUN apt-get update
RUN apt-get install vim -y

RUN npm install -g pm2 --registry=https://registry.npm.taobao.org

ARG PRO_ENV=test # 接受运行参数
ENV PRO_ENV=$PRO_ENV

# 复制需要的目录
COPY ./ /demo

WORKDIR /demo
RUN /bin/bash scripts/build.sh

CMD /bin/bash scripts/start.sh

配置Jenkinsfile

  1. 注册一个腾讯云账号,开通容器镜像服务(免费)

超详细!手把手教你用Jenkins pipeline + docker 打造前后端持续集成环境_第6张图片
将账号密码配置在jenkins凭据中
超详细!手把手教你用Jenkins pipeline + docker 打造前后端持续集成环境_第7张图片

  1. 配置一个config.json文件做环境配置:
{
  "version": "1.0.0",
  "registryName": "ccr.ccs.tencentyun.com/yohann/demo", 
  "env": {
    "test": {
      "credentialsId": "ssh-test”,
      "host": "123.206.25.28"
    },
    "pro": {
      "credentialsId": "ssh-test",
      "host": "123.206.25.28"
    }
  }
}

避免在代码中写入账号密码,我们在Jenkins中配置相关凭据:
超详细!手把手教你用Jenkins pipeline + docker 打造前后端持续集成环境_第8张图片

  1. 在git项目中增加Jenkinsfile,编写流水线流程

流水线语法支持两种格式:
1. 声明式流水线

pipeline {
    agent any
    stages {
        stage('Example') {
            steps {
                echo 'Hello World'
            }
        }
    }
}

2.脚本化流水线

node {
    stage('Example') {
        if (env.BRANCH_NAME == 'master') {
            echo 'I only execute on the master branch'
        } else {
            echo 'I execute elsewhere'
        }
    }
}

脚本化的流水线自由度更高,因此我们选择脚本的写法

完整代码如下

import groovy.json.JsonSlurper

node {
    currentBuild.result = "SUCCESS"
    echo "PWD: ${pwd()}"

    // 判断发布环境
    if (env.BRANCH_NAME == 'release') {
        env.PRO_ENV = "pro"
    } else {
        env.PRO_ENV = "test"
    }

    // 默认设置
    env.VERSION = '1.0.0'
    env.credentialsId = ''
    env.host = ''
    env.registryName = ''
    def imageName = ''
    def input_result // 用户输入项

    
    try {
        stage('config') {
            echo "Branch: ${env.BRANCH_NAME}, Environment: ${env.PRO_ENV}"

            input_result = input message: 'Check Tasks', ok: 'ok', parameters: [
                booleanParam(name: 'install', defaultValue: false),
                booleanParam(name: 'test', defaultValue: true),
                booleanParam(name: 'deploy', defaultValue: true)
            ]
        }
        
        stage('Checkout'){
            // 重置本地修改项
            try {
                sh 'git checkout .'
            } catch (err) {

            }
            
            checkout scm

            // 读取配置信息
            if(fileExists('config.json')) {
                def str = readFile 'config.json'
                def jsonSlurper = new JsonSlurper()
                def obj = jsonSlurper.parseText(str)

                env.registryName = obj.registryName
                def envConifg = obj.env[env.PRO_ENV]
                
                echo "envConifg: ${envConifg}"

                env.VERSION = obj.version
                env.credentialsId = envConifg.credentialsId
                env.host = envConifg.host

                imageName = "${env.registryName}:${env.VERSION}_${env.PRO_ENV}_${BUILD_NUMBER}"

                echo "VERSION: ${env.VERSION} ${imageName}"
            }
            
            sh 'ls'
        }

        stage('Install'){
            if(input_result.install) {
                docker.image('node:9.6.0').inside {
                    sh 'node -v'
                    sh 'sh ./scripts/install.sh'
                }
            }
        }

        stage('Test'){
            if(input_result.test) {
                docker.image('node:9.6.0').inside {
                    sh 'sh ./scripts/test.sh'
                }
            }
        }

        stage('Build Docker'){
            // 构建上传镜像到容器仓库
            if(input_result.deploy) {
                def customImage = docker.build(imageName, "--build-arg PRO_ENV=${env.PRO_ENV} .")

                docker.withRegistry("https://${env.registryName}", 'docker-demo') {
                    /* Push the container to the custom Registry */
                    customImage.push()
                }
            }
        }

        stage('Deploy'){
            if(input_result.deploy) {
                // wechat服务器
                withCredentials([usernamePassword(credentialsId: env.credentialsId, usernameVariable: 'USER', passwordVariable: 'PWD')]) {
                    def otherArgs = '-p 8001:8001' // 区分不同环境的启动参数
                    def remote = [:]
                    remote.name = 'ssh-deploy'
                    remote.allowAnyHosts = true
                    remote.host = env.host
                    remote.user = USER
                    remote.password = PWD
                
                    if(env.PRO_ENV == "pro") {
                        otherArgs = '-p 3000:3000'
                    }

                    try {
                        sshCommand remote: remote, command: "docker rm -f demo"
                    } catch (err) {

                    }
                    sshCommand remote: remote, command: "docker run -d --name demo -v /etc/localtime:/etc/localtime -e PRO_ENV='${env.PRO_ENV}' ${otherArgs} ${imageName}"
                }

                // 删除旧的镜像
                sh "docker rmi -f ${imageName.replaceAll("_${BUILD_NUMBER}", "_${BUILD_NUMBER - 1}")}"
            }
        }
    }
    catch (err) {
        currentBuild.result = "FAILURE"
        throw err
    }

}

执行流水线

在git仓库中提交代码到release-test分支
在jenkins中打开blue-ocean执行该分支
超详细!手把手教你用Jenkins pipeline + docker 打造前后端持续集成环境_第9张图片
超详细!手把手教你用Jenkins pipeline + docker 打造前后端持续集成环境_第10张图片
超详细!手把手教你用Jenkins pipeline + docker 打造前后端持续集成环境_第11张图片

本项目git仓库

本项目中的示例代码已上传到 github仓库 需要的朋友可以自取~

参考文章

  • Jenkins Pipeline语法参考: https://blog.csdn.net/boonya/article/details/77941975
  • 构建多分支流水线项目:https://jenkins.io/zh/doc/tutorials/build-a-multibranch-pipeline-project/

你可能感兴趣的:(技术)