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

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

发布流程设计

流程图

  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
    
         
         
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
  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

   
   
  • 1
  • 2
  • 3

注意: 在启动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 页面出现。
在这里插入图片描述

在命令行中输入docker logs jenkins, 复制自动生成的字母数字密码(在两组星号之间)。 在 Unlock Jenkins 页面, 粘贴该密码到 Administrator password 字段并点击 Continue。
在这里插入图片描述
选择install suggested pulgins 等待安装成功
创建用户,进入主页。

  1. 创建多分支流水线项目
    选择创建多分支流水线
    在这里插入图片描述
    设置分支源(过滤出release开头的分支)
    在这里插入图片描述
    完成设置。

代码中配置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

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
配置Jenkinsfile
  1. 注册一个腾讯云账号,开通容器镜像服务(免费)

在这里插入图片描述
将账号密码配置在jenkins凭据中
在这里插入图片描述

  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"
    }
  }
}

 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

避免在代码中写入账号密码,我们在Jenkins中配置相关凭据:
在这里插入图片描述

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

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

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

 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

2.脚本化流水线

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

 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

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

完整代码如下

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执行该分支
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

本项目git仓库

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

参考文章

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐