基于Docker+Jenkins+Gitlab搭建持续集成测试环境


  七牛云的测试域名到期了,所有图片的图床都崩了,早知如此不该图方便,自己做静态资源算了。进入今天的正题,到新公司一个月,项目开发前后端分离,差不多拉通了开发到测试的流程,在此记录一下。

  随着DevOps理念和敏捷理念的发展,我们希望通过自动化技术,加快项目的迭代。尤其是当使用微服务方案后,面临在大量的项目构建和部署工作,借助于jenkins的持续集成,可以快速把应用打包成docker镜像,实现自动部署。

jenkins

如图演示了以下的场景:

  • 开发者向自己的gitlab提交了代码

  • jenkins通过定时任务检测到了代码有变成,执行自动化构建过程

  • jenkins在自动化构建脚本中调用docker命令将构建好的镜像push到私有镜像中心harbor

  • 同时,jenkins也可以直接执行remote shell启动构建好的容器

  • 构建失败或者成功,可以及时将结果推送给相关人员,比如测试人员,安排测试

  • 服务端可以手动通过docker命令,从镜像仓库中心拉取镜像,进行手动部署

环境如下:

192.168.110.202 harbor
192.168.110.203 gitlab jenkins

  除了jenkins均采用docker部署。

1. 搭建harbor

docker run -d -p 5000:5000 -v /opt/docker-registry:/var/lib/registry registry 一开始用的registry,连个UI都没有,使用不便,弃了

harbor官方安装文档

采用offline安装包,在执行./prepare的时候抛出如下异常:

root@ubuntu:~/harbor# ./prepare 
Fail to generate key file: ./common/config/ui/private_key.pem, cert file: ./common/config/registry/root.crt

需要修改prepare文件,将第498行:

empty_subj = "/C=/ST=/L=/O=/CN=/"

修改如下:

empty_subj = "/C=US/ST=California/L=Palo Alto/O=VMware, Inc./OU=Harbor/CN=notarysigner"

配置daemon.json,去掉docker(每个docker client都需要配置)默认的https的访问vim /etc/docker/daemon.json

里面的内容是一个json对象,加上一项insecure-registries,地址自己更改:

{
    "insecure-registries":["192.168.1.78"]
}

然后重启docker,执行

systemctl daemon-reload

systemctl restart docker

2. 搭建gitlab

docker run --detach \
--hostname localhost \
--publish 443:443 --publish 80:80 \
--name gitlab \
--restart always \
--volume /opt/gitlab/config:/etc/gitlab \
--volume /opt/gitlab/logs:/var/log/gitlab \
--volume /opt/gitlab/data:/var/opt/gitlab \
gitlab/gitlab-ce:latest

  之后push demo代码。

3. jenkins安装

  这一块比较复杂,不讲了

4. 流水线demo

  前端用npm打包,后端用的maven。

pipeline{
    agent {
        label 'master'
    }

    stages{
        stage('拉取代码'){
            steps{
                git branch: 'alarm_system_v2',
                credentialsId: '5d7cb03e-66df-4002-a2d5-70f8aa196ac1',
                url: 'http://192.168.110.8:10080/fehz/react-garden-system.git'
            }
        }

        stage('安装依赖,进行前端代码鉴定'){
            steps{
                nodejs('node-v10.6.0') {
                    sh'''
                    npm -v
                    node -v
                    npm install react-tree-module-web --registry=http://192.168.110.26:8088/repository/npm/
                    npm install react-common-module-web --registry=http://192.168.110.26:8088/repository/npm/
                    npm install
                    npm run lint --force
                    '''
                    checkstyle canComputeNew: false, defaultEncoding: '', healthy: '', pattern: 'eslint.xml', unHealthy: ''
                    hygieiaCodeQualityPublishStep checkstyleFilePattern: '**/*/eslint.xml', findbugsFilePattern: '', jacocoFilePattern: '', junitFilePattern: '', pmdFilePattern: ''
                }
            }
        }

        stage('npm打包'){
            steps{
                nodejs('node-v10.6.0'){
                    hygieiaBuildPublishStep buildStatus: 'InProgress'
                    sh'''
                    npm run build:test
                    cp /var/lib/jenkins/workspace/build_prj/*.js $WORKSPACE/node_modules/connect-cas2/lib/
                    '''
                    hygieiaBuildPublishStep buildStatus: 'Success'
                }
            }
        }

        stage('构建docker镜像'){
            steps{
                sh'''
                ###根据git库设置包名
                jar_name=192.168.110.202/isp/react-garden-alarm-system
                echo "包名:$jar_name"
                docker build -t $jar_name .
                '''
            }
        }

        stage('将docker镜像上传到镜像仓库'){
            steps{
                sh 'docker push 192.168.110.202/isp/react-garden-alarm-system:latest'
                sh 'docker image prune -f'
            }
        }

        stage('测试环境部署'){
            steps{
                sh'''
                echo "登录到192.168.110.211服务器执行部署脚本"
                ssh root@192.168.110.211 "sh /opt/jenkins/deploy.sh react_garden_alarm_system"
                '''
                hygieiaDeployPublishStep applicationName: 'react-garden-system-alarm-dev', artifactDirectory: '', artifactGroup: '', artifactName: '', artifactVersion: '', buildStatus: 'Success', environmentName: 'TEST'
            }
        }
    }
}

pipeline{
    agent {
        label 'master'
    }

    stages{
        stage('拉取代码'){
            steps{
                git branch: 'develop-isp',
                credentialsId: '5d7cb03e-66df-4002-a2d5-70f8aa196ac1',
                url: 'http://192.168.110.8:10080/liuran/framework-alarm.git/'
            }
        }

        stage('后端代码鉴定'){
            steps{
                build job:'garden-sonar-dev',
                parameters: [string(name: 'choose_service', value: 'framework-alarm-log-sonar')]
                hygieiaSonarPublishStep ceQueryIntervalInSeconds: '10', ceQueryMaxAttempts: '30'
            }
        }

        stage('构建并打包成docker镜像'){
            steps{
                withMaven(jdk: 'jdk', maven: 'apache-maven-3.5.4') {
                    hygieiaBuildPublishStep buildStatus: 'InProgress'
                    sh 'mvn clean package docker:build'
                    hygieiaBuildPublishStep buildStatus: 'Success'
                }
            }
        }

        stage('将docker镜像上传到镜像仓库'){
            steps{
                sh 'docker push 192.168.110.202/isp/java/framework-alarm-log:latest'
                sh 'docker image prune -f'
            }
        }

        stage('测试环境部署'){
            steps{
                sh'''
                echo "登录到192.168.110.211服务器执行脚本"
                ssh root@192.168.110.211 "sh /opt/jenkins/deploy.sh java_framework_alarm_log"
                echo "登录到192.168.110.212部署自动化测试环境"
                ssh root@192.168.110.212 "sh /root/isp-alarm.sh"
                '''
                hygieiaDeployPublishStep applicationName: 'garden-framework-alarm-log-dev', artifactDirectory: 'target', artifactGroup: 'com.letv.dashboard', artifactName: '*.jar', artifactVersion: '', buildStatus: 'Success', environmentName: 'TEST'
            }
        }
    }

    /*post {
        always {
            
            echo 'One way or another, I have finished'
  
        }
        success {
            echo 'I succeeeded!'
			hygieiaBuildPublishStep buildStatus: 'Success'
        }
        unstable {
            echo 'I am unstable :/'
			hygieiaBuildPublishStep buildStatus: 'Unstable'
        }
        failure {
            echo 'I failed :('
			hygieiaBuildPublishStep buildStatus: 'Failure'
			
        }
        changed {
            echo 'Things were different before...'
        }
    }*/
}

后面可以加个post阶段给老大发邮件。。

deploy.sh参考

#!/bin/sh

login_harbor(){

    REGISTRY="192.168.110.202"
    HARBOR_USER="admin"
    HARBOR_PASSWD="Harbor12345"

    docker login -u ${HARBOR_USER} -p ${HARBOR_PASSWD} ${REGISTRY}
}

deploy_new_container(){
    # $1 : $CONTAINER_NAME
    # $2 : $OLD_IMAGE_NAME 
    # $3 : $IMAGE_NAME 
    # $4 : $PORT 
    cid=$(docker ps -a | grep "$1" | awk '{print $1}')
    if [ -n "$cid" ];then
        docker rm -f $cid
    fi
    echo "******删除旧容器完成******"

    iid=$(docker images | grep "$2" | awk '{print $3}')
    if [ -n "$iid" ];then
        docker rmi -f $iid
    fi
    echo "******删除旧镜像完成******"

    docker pull $3
    echo "******拉取新镜像完成******"

    if [[ $3 =~ "java" ]];then
        docker run -d --restart always -p $4:$4 --name $1 -e JAVA_OPTS="-Denv=UAT" $3
        echo "******远程部署后端容器完成******"
    elif [[ $3 =~ "react" ]];then
        docker run -d --restart always -p $4:$4 --name $1 -e ENV_OPTS="--env test" $3
        echo "******远程部署前端容器完成******"
    else
        echo "镜像名字不合法,请确认!"
    fi
}

# 后端微服务
java_framework_alarm_log(){
    CONTAINER_NAME="java-alarm-test"
    OLD_IMAGE_NAME="192.168.110.202/isp/java/framework-alarm-log"
    IMAGE_NAME="192.168.110.202/isp/java/framework-alarm-log:latest"
    PORT="8086"

    deploy_new_container $CONTAINER_NAME $OLD_IMAGE_NAME $IMAGE_NAME $PORT
}

java_isp_ias(){
    CONTAINER_NAME="java-ias-test"
    OLD_IMAGE_NAME="192.168.110.202/isp/java/isp-ias"
    IMAGE_NAME="192.168.110.202/isp/java/isp-ias:latest"
    PORT="8089"

    deploy_new_container $CONTAINER_NAME $OLD_IMAGE_NAME $IMAGE_NAME $PORT
}

# 前端
react_garden_alarm_system(){
    CONTAINER_NAME="react-alarm-test"
    OLD_IMAGE_NAME="192.168.110.202/isp/react-garden-alarm-system"
    IMAGE_NAME="192.168.110.202/isp/react-garden-alarm-system:latest"
    PORT="8003"

    deploy_new_container $CONTAINER_NAME $OLD_IMAGE_NAME $IMAGE_NAME $PORT
}

react_garden_inbreak_system(){
    CONTAINER_NAME="react-ias-test"
    OLD_IMAGE_NAME="192.168.110.202/isp/react-garden-inbreak-system"
    IMAGE_NAME="192.168.110.202/isp/react-garden-inbreak-system:latest"
    PORT="8005"

    deploy_new_container $CONTAINER_NAME $OLD_IMAGE_NAME $IMAGE_NAME $PORT
}

login_harbor

case $1 in 
    java_framework_alarm_log)
        java_framework_alarm_log
    ;;
    java_isp_ias)
        java_isp_ias
    ;;
    react_garden_alarm_system)
        react_garden_alarm_system
    ;;
    react_garden_inbreak_system)
        react_garden_inbreak_system
    ;;
    *)
    echo "请输入正确参数!
    java_framework_alarm_log
    java_isp_ias
    react_garden_alarm_system
    react_garden_inbreak_system"
    ;;
esac

5. 问题

  1. gitlab需通过root账号登录,允许本地webhook。这是因为gitlab和jenkins在同一个节点部署,实际分开部署不会有这个问题
gitlab-rails console production   #gitlab居然是用ruby写的
u = User.where(email: 'admin@example.com').first
u.password='new_password'
u.save!

gitlab配置Admin area:

Setting->Network->Outbound requests

勾选Allow requests to the local network from hooks and services

jenkins配置:

Jenkins–>Jenkins Manages–>Configure System,找到GitLab配置,去掉勾选

系统管理–>全局安全配置,勾选匿名用户具有可读权限和去掉CSRF防止跨站点请求伪造