成功最有效的方法就是向有经验的人学习!

第六章 Jenkins流水线实践

前端静态资源发布流水线

项目设置

项目配置部分主要是将网站源代码上传到github,然后搭建用户访问的web服务器。再经过Jenkins配置发布代码到web服务器。

项目代码托管

将项目源代码上传到GitHub

搭建前端Nginx服务

安装Nginx服务

yum -y install nginx 
service nginx start 
chkconfig nginx on

创建站点目录

mkdir -p /opt/nginx/myweb

配置Nginx

vim /etc/nginx/conf.d/default.conf
server {
listen       80 default_server;
server_name  www.xxxxx.com;

include /etc/nginx/default.d/*.conf;

location / {
    root /opt/nginx/myweb;
    index index.html ;
}

error_page 404 /404.html;
    location = /40x.html {
}

error_page 500 502 503 504 /50x.html;
    location = /50x.html {
}

}
service nginx restart

Jenkins配置

创建项目

业务名称: cxy
应用名称: cxy-webdemo-ui
应用服务器: VM_7_14_centos
站点目录: /opt/nginx/myweb
服务端口: 80
发布用户: nginx
发布分支: master
项目地址: http://github.com/xxxx/cxy-webdemo-ui.git

编写Jenkinsfile(ShareLibrary)

ShareLibrary 目录结构

Jenkinsfile

#!groovy
@Library('devops-demo') _          //导入sharelibrary

def tools = new org.devops.tools()    

//Getcode
String srcUrl = "${env.srcUrl}".trim()
String srcType = "${env.srcType}".trim()
String branchName = "${env.branchName}".trim()
String tagName = "${env.tagName}".trim()
String moduleName = "${env.moduleName}".trim()

//Global 
String workspace = "/opt/jenkins/workspace"
String targetHosts = "${env.targetHosts}".trim()
String jobType = "${JOB_NAME}".split('_')[-1]
String credentialsId = "24982560-17fc-4589-819b-bc5bea89da77"
String serviceName = "${env.serviceName}".trim()
String javaVersion = "${env.javaVersion}".trim()
String dependency = "${env.dependency}".trim()
String port = "${env.port}".trim()
String user = "${env.user}".trim()
String targetDir = "${env.targetDir}".trim()
def runserver 
def buildDir = tools.BuildDir(workspace,srcType,tagName,moduleName)[0]  
def srcDir = tools.BuildDir(workspace,srcType,tagName,moduleName)[1]  

//Build
String midwareType = "${env.midwareType}".trim()
String buildType = "${env.buildType}".trim()
String buildShell = "${env.buildShell}".trim()

//Pipeline

ansiColor('xterm') {
node("master"){
    ws("${workspace}") {
        //Getcode
        stage("GetCode"){
            tools.PrintMes('获取代码','green')
            try {
                def getcode = new org.devops.getcode()
                getcode.GetCode(srcType,srcUrl,tagName,branchName,credentialsId)
            } catch(e){

            }    
        }

        //Build
        stage("RunBuild"){
            tools.PrintMes('应用打包','green')
            def build = new org.devops.build()

            try {
                if ("${midwareType}" == "Nginx"){
                    build.WebBuild(srcDir,serviceName)

                } else if ("${midwareType}" == "NodeJs"){
                    def webDist=srcDir + '/dist'
                    sh " cd ${srcDir} && ${buildShell} && cd -"
                    build.WebBuild(webDist,serviceName)
                }
                else {
                    build.Build(javaVersion,buildType,buildDir,buildShell)
                }
            }catch(e){
                currentBuild.description='运行打包失败!'
                error '运行打包失败!'
            }
        }

        //Deploy
        stage("RunDeploy"){
            tools.PrintMes('发布应用','green')
            def deploy = new org.devops.deploy()

            switch("${midwareType}"){
                case 'SpringBoot':
                    deploy.SpringBootInit(javaOption,dependency,credentialsId)
                    deploy.JavaDeploy('SpringBoot','jar',srcDir,user,targetHosts,targetDir+"/${serviceName}",port)
                    break;

                case 'Tomcat':
                    def tomcatDir=targetDir + "/${port}/webapps/"
                    deploy.JavaDeploy('Tomcat','war',srcDir,user,targetHosts,tomcatDir,port)
                    break;

                case 'NodeJs':
                    deploy.WebDeploy(user,serviceName,targetDir)
                    break;

                case 'Nginx': 
                    deploy.WebDeploy(user,serviceName,targetDir)
                    break;

                default:
                    error "中间件类型错误!"  
            }
        }
    }

}
}

org/src/devops/build.groovy

package org.devops

//构建打包
def Build(javaVersion,buildType,buildDir,buildShell){
if (buildType == 'maven'){
    Home = tool '/usr/local/apache-maven'
    buildHome = "${Home}/bin/mvn"
} else if (buildType == 'ant'){
    Home = tool 'ANT'
    buildHome = "${Home}/bin/ant"
} else if (buildType == 'gradle'){
    buildHome = '/usr/local/bin/gradle'
} else{
    error 'buildType Error [maven|ant|gradle]'
}
echo "BUILD_HOME: ${buildHome}"

//选择JDK版本
jdkPath = ['jdk7' : '/usr/local/jdk1.7.0_79',
           'jdk6' : '/usr/local/jdk1.6.0_45',
           'jdk8' : '/usr/java/jdk1.8.0_111',
           'jdk11': '/usr/local/jdk-11.0.1',
           'null' : '/usr/java/jdk1.8.0_111']
def javaHome = jdkPath["$javaVersion"]
if ("$javaVersion" == 'jdk11'){
   sh  """
    export JAVA_HOME=${javaHome}
    export PATH=\$JAVA_HOME/bin:\$PATH
    java -version
    cd ${buildDir} && ${buildHome} ${buildShell}
    """
} else {
    sh  """
        export JAVA_HOME=${javaHome}
        export PATH=\$JAVA_HOME/bin:\$PATH
        export CLASSPATH=.:\$JAVA_HOME/lib/dt.jar:\$JAVA_HOME/lib/tools.jar
        java -version
        cd ${buildDir} && ${buildHome} ${buildShell}
        """
}
}

//前端Build
def WebBuild(srcDir,serviceName){
def deployPath = "/srv/salt/${JOB_NAME}"
sh """
    [ -d ${deployPath} ] || mkdir -p ${deployPath}
    cd ${srcDir}/
    rm -fr *.tar.gz 
    tar zcf ${serviceName}.tar.gz * 
    cp ${serviceName}.tar.gz ${deployPath}/${serviceName}.tar.gz
    cd -
"""
}

org/src/devops/codescan.groovy

package org.devops

//代码扫描
def SonarScan(projectType,skipSonar,srcDir,serviceName,scanDir){
def scanHome = "/usr/local/sonar-scanner"
if (projectType == 'java'){
    if ("${buildType}" == 'gradle'){
        codepath = 'build/classes'
    } else{
        codepath = 'target/classes'
    }
    try {
        sh """
            cd ${srcDir} 
            ${scanHome}/bin/sonar-scanner -Dsonar.projectName=${serviceName} -Dsonar.projectKey=${serviceName}  \
            -Dsonar.sources=.  -Dsonar.language=java -Dsonar.sourceEncoding=UTF-8 \
            -Dsonar.java.binaries=${codepath} -Dsonar.java.coveragePlugin=jacoco \
            -Dsonar.jacoco.reportPath=target/jacoco.exec -Dsonar.junit.reportsPath=target/surefire-reports \
            -Dsonar.surefire.reportsPath=target/surefire-reports -Dsonar.projectDescription='devopsdevops'
         """ 
    } catch (e){
        currentBuild.description="代码扫描失败!"
        error '代码扫描失败!'
    }
} else if (projectType == 'web'){
    try {
        sh  """
            cd ${srcDir} 
            ${scanHome}/bin/sonar-scanner -Dsonar.projectName=${serviceName} \
            -Dsonar.projectKey=${serviceName} -Dsonar.sources=${scanDir} -Dsonar.language=js  
            cd - 
            """
    } catch (e){
        currentBuild.description="代码扫描失败!"
        error '代码扫描失败!'
    }
}
}

org/src/devops/deploy.groovy

package org.devops

//saltapi模板
def Salt(salthost,saltfunc,saltargs) {
/*result = salt(authtype: 'pam', 
            clientInterface: local( arguments: saltargs,
                                    function: saltfunc, 
                                    target: salthost, 
                                    targettype: 'list'),
            credentialsId: "f89abde3-49f0-4b75-917e-c4e49c483f4f", 
            servername: "http://127.0.0.1:9000")*/

sh """
    salt ${salthost} ${saltfunc} ${saltargs}
    """
//println(result)
//PrintMes(result,'blue')
//return  result
}

//前端类型发布
def WebDeploy(user,serviceName,targetDir){
try {
    println('清空发布目录')

    Salt(targetHosts,'cmd.run', "cmd=\" rm -fr  ${targetDir}/* \"")

    println('发布软件包')
    Salt(targetHosts,'cp.get_file', "salt://${JOB_NAME}/${serviceName}.tar.gz ${targetDir}/${serviceName}.tar.gz makedirs=True ")
    sleep 2;

    println('解压')
    Salt(targetHosts,'cmd.run', "cmd=\" cd ${targetDir} && tar zxf ${serviceName}.tar.gz  \"")
    sleep 2;

    println('授权')
    Salt(targetHosts,'cmd.run', "cmd=\"chown ${user}:${user} ${targetDir} -R  \"")
    sleep 2;
    println('获取发布文件')
    Salt(targetHosts,'cmd.run', "cmd=\" ls -l  ${targetDir} \"")

    println('删除缓存文件')
    sh "rm -fr /srv/salt/${JOB_NAME}/*"
} catch (e){
    currentBuild.description='包发布失败!'
    error '包发布失败!'
}
}

org/src/devops/getcode.groovy

package org.devops

//代码检出
def GetCode(srcType,srcUrl,tagName,branchName,credentialsId) {
//delete 'origin/'
if (branchName.startsWith('origin/')){
    branchName=branchName.minus("origin/")
} 

if(tagName == "null"){
    pathName = "*/${branchName}"
}else{
    pathName = "refs/tags/${tagName}"
}
checkout([$class: 'GitSCM', branches: [[name: "${pathName}"]], 
    doGenerateSubmoduleConfigurations: false, 
    extensions: [], submoduleCfg: [], 
    userRemoteConfigs: [[credentialsId: "${credentialsId}", 
    url: "${srcUrl}"]]])
}

org/src/devops/tools.groovy

package org.devops

//格式化输出
def PrintMes(value,color){
colors = ['red'   : "\033[40;31m >>>>>>>>>>>${value}<<<<<<<<<<< \033[0m",
          'blue'  : "\033[47;34m ${value} \033[0m",
          'green' : ">>>>>>>>>>${value}>>>>>>>>>>",
          'green1' : "\033[40;32m >>>>>>>>>>>${value}<<<<<<<<<<< \033[0m" ]
ansiColor('xterm') {
    println(colors[color])
}
}

//获取源码目录
def BuildDir(workspace,srcType,tagName,moduleName) {
def srcDir = workspace
if(srcType == "Git") {
    buildDir = "${workspace}"
    if(moduleName == "null"){
        srcDir = "${workspace}"
    }else{
        srcDir = "${workspace}/${moduleName}"
    }
}else{
    if(tagName == "null") {
        def srcTmp = srcUrl.split("/")[-1]
        srcDir = "${workspace}/${srcTmp}"
    }else{
        srcDir = "${workspace}/${tagName}"
    }
}
buildDir = srcDir
return [buildDir,srcDir]
}

//saltapi模板
def Salt(salthost,saltfunc,saltargs) {
result = salt(authtype: 'pam', 
            clientInterface: local( arguments: saltargs,
                                    function: saltfunc, 
                                    target: salthost, 
                                    targettype: 'list'),
            credentialsId: "c4ec3410-7f97-40fa-8ad9-be38a7bbbcd8", 
            servername: "http://127.0.0.1:8000")
println(result)
//PrintMes(result,'blue')
return  result
}

构建测试

NodeJs项目发布流水线

项目设置

项目配置部分主要是将网站源代码上传到github,然后搭建用户访问的web服务器。再经过Jenkins配置发布代码到web服务器。

项目代码托管

将项目源代码上传到GitHub

搭建前端Nginx服务

安装Nginx服务

yum -y install nginx 
service nginx start 
chkconfig nginx on

创建站点目录

mkdir -p /opt/nginx/myweb

配置Nginx

vim /etc/nginx/conf.d/default.conf
server {
listen       80 default_server;
server_name  www.xxxxx.com;

include /etc/nginx/default.d/*.conf;

location / {
    root /opt/nginx/myweb;
    index index.html ;
}

error_page 404 /404.html;
    location = /40x.html {
}

error_page 500 502 503 504 /50x.html;
    location = /50x.html {
}

}
service nginx restart

Jenkins配置

创建项目

业务名称: cxy
应用名称: cxy-vuedemo-ui
应用服务器: VM_7_14_centos
站点目录: /opt/nginx/myweb
服务端口: 80
发布用户: nginx
发布分支: master
项目地址: http://github.com/xxxx/cxy-webdemo-ui.git

编写Jenkinsfile

Jenkinsfile

#!groovy

//Getcode
String srcUrl = "${env.srcUrl}".trim()
String branchName = "${env.branchName}".trim()

//Global 
String workspace = "/opt/jenkins/workspace"
String targetHosts = "${env.targetHosts}".trim()
String credentialsId = "24982560-17fc-4589-819b-bc5bea89da77"
String serviceName = "${env.serviceName}".trim()
String port = "${env.port}".trim()
String user = "${env.user}".trim()
String targetDir = "${env.targetDir}".trim()

//Build
String buildShell = "${env.buildShell}".trim()

//代码检出
def GetCode(srcUrl,branchName,credentialsId) {
checkout([$class: 'GitSCM', branches: [[name: "${pathName}"]], 
    doGenerateSubmoduleConfigurations: false, 
    extensions: [], submoduleCfg: [], 
    userRemoteConfigs: [[credentialsId: "${credentialsId}", 
    url: "${srcUrl}"]]])
}

//Pipeline

ansiColor('xterm') {
node("master"){
    ws("${workspace}") {
        //Getcode
        stage("GetCode"){
            GetCode(srcUrl,branchName,credentialsId)
        }

        //Build
        stage("RunBuild"){
            sh """ 
                ${buildShell} 
                cd dist && tar zcf ${serviceName}.tar.gz * 
                mkdir -p /srv/salt/${JOB_NAME}/
                rm -fr /srv/salt/${JOB_NAME}/*
                mv ${serviceName}.tar.gz /srv/salt/${JOB_NAME}/

               """
        }

        //Deploy
        stage("RunDeploy"){
            sh """ 
                salt ${targetHosts} cmd.run "rm -fr ${targetDir}/*"
                salt ${targetHosts} cp.get_file "salt://${JOB_NAME}/${serviceName}.tar.gz ${targetDir}/${serviceName}.tar.gz makedirs=True "
                salt ${targetHosts} cmd.run "cd ${targetDir} && tar zxf ${serviceName}.tar.gz "
                salt ${targetHosts} cmd.run "chown ${user}:${user} ${targetDir} -R  "
                salt ${targetHosts} cmd.run "ls -l "

               """

        }
    }              
}       
}

3.构建测试

Dotnet项目发布流水线

demo地址: https://github.com/zeyangli/dotnet-HelloWorld.git

安装dotnet开发环境(centos7)

rpm --import https://packages.microsoft.com/keys/microsoft.asc

[root@VM_0_12_centos ~]# cat /etc/yum.repos.d/dotnetdev.repo 
[packages-microsoft-com-prod]
name=packages-microsoft-com-prod 
baseurl= https://packages.microsoft.com/yumrepos/microsoft-rhel7.3-prod
enabled=1
gpgcheck=1
gpgkey=https://packages.microsoft.com/keys/microsoft.asc

yum install libunwind libicu
yum install dotnet-sdk-2.1.103

dotnet --version

创建Jenkins项目

Jenkinsfile

String buildShell = "${env.buildShell}"
String targetDir  = "${env.targetDir}"

node("runner"){
    stage("checkout"){
        checkout scm
    }

    stage("build"){
        sh " ${buildShell} "
    }

    stage("publish"){
        sh " mkdir -p ${targetDir} "
        sh " dotnet publish -o ${targetDir}  && ls -l ${targetDir}"

    }
}

构建日志
成功 Console Output

Started by user admin
Obtained new-jenkinsfile from git https://github.com/zeyangli/dotnet-HelloWorld.git
Running in Durability level: MAX_SURVIVABILITY
[Pipeline] Start of Pipeline
[Pipeline] node
Running on runner in /var/lib/jenkins/workspace/demo/demo-dotnet-service
[Pipeline] {
[Pipeline] stage
[Pipeline] { (checkout)
[Pipeline] checkout
using credential 24982560-17fc-4589-819b-bc5bea89da77
Fetching changes from the remote Git repository
 > /root/bin/git rev-parse --is-inside-work-tree # timeout=10
 > /root/bin/git config remote.origin.url https://github.com/zeyangli/dotnet-HelloWorld.git # timeout=10
Fetching upstream changes from https://github.com/zeyangli/dotnet-HelloWorld.git
 > /root/bin/git --version # timeout=10
using GIT_ASKPASS to set credentials gitlab
 > /root/bin/git fetch --tags --progress https://github.com/zeyangli/dotnet-HelloWorld.git +refs/heads/*:refs/remotes/origin/*
Checking out Revision ed6460aaa029e1a29654c3eec369ec97783a8fb1 (refs/remotes/origin/master)
Commit message: "Update new-jenkinsfile"
[Pipeline] }
[Pipeline] // stage
[Pipeline] stage
[Pipeline] { (build)
[Pipeline] sh
 > /root/bin/git rev-parse refs/remotes/origin/master^{commit} # timeout=10
 > /root/bin/git rev-parse refs/remotes/origin/origin/master^{commit} # timeout=10
 > /root/bin/git config core.sparsecheckout # timeout=10
 > /root/bin/git checkout -f ed6460aaa029e1a29654c3eec369ec97783a8fb1
 > /root/bin/git rev-list --no-walk 75976c755992589ce9ebb4799d623e6ed3447a2e # timeout=10
+ dotnet restore
  Restore completed in 107.58 ms for /var/lib/jenkins/workspace/demo/demo-dotnet-service/HelloWorld/HelloWorld.csproj.
+ dotnet build
Microsoft (R) Build Engine version 15.6.82.30579 for .NET Core
Copyright (C) Microsoft Corporation. All rights reserved.

  Restore completed in 49.87 ms for /var/lib/jenkins/workspace/demo/demo-dotnet-service/HelloWorld/HelloWorld.csproj.
  HelloWorld -> /var/lib/jenkins/workspace/demo/demo-dotnet-service/HelloWorld/bin/Debug/netcoreapp2.0/HelloWorld.dll

Build succeeded.
    0 Warning(s)
    0 Error(s)

Time Elapsed 00:00:02.30
[Pipeline] }
[Pipeline] // stage
[Pipeline] stage
[Pipeline] { (publish)
[Pipeline] sh
+ mkdir -p /opt/dotnet
[Pipeline] sh
+ dotnet publish -o /opt/dotnet
Microsoft (R) Build Engine version 15.6.82.30579 for .NET Core
Copyright (C) Microsoft Corporation. All rights reserved.

  Restore completed in 54 ms for /var/lib/jenkins/workspace/demo/demo-dotnet-service/HelloWorld/HelloWorld.csproj.
  HelloWorld -> /var/lib/jenkins/workspace/demo/demo-dotnet-service/HelloWorld/bin/Debug/netcoreapp2.0/HelloWorld.dll
  HelloWorld -> /opt/dotnet/
+ ls -l /opt/dotnet
total 20
-rw-r--r-- 1 root root  440 Apr  4 21:03 HelloWorld.deps.json
-rw-r--r-- 1 root root 7168 Apr  4 21:02 HelloWorld.dll
-rw-r--r-- 1 root root 1364 Apr  4 21:02 HelloWorld.pdb
-rw-r--r-- 1 root root  146 Apr  4 21:03 HelloWorld.runtimeconfig.json
[Pipeline] }
[Pipeline] // stage
[Pipeline] }
[Pipeline] // node
[Pipeline] End of Pipeline
Finished: SUCCESS

JAVA-Springboot项目发布流水线

demo地址: https://github.com/zeyangli/springboot-helloworld.git

创建Jenkins项目
serviceName: 服务的名称
buildShell : 项目打包命令
targetHosts: 应用发布主机(需安装salt-minion)
targetDir: 应用的发布目录
user: 发布用户
port: 应用的启动端口

Jenkinsfile

String buildShell = "${env.buildShell}"
String targetHosts = "${env.targetHosts}"
String targetDir = "${env.targetDir}"
String serviceName = "${env.serviceName}"
String user = "${env.user}"
String port = "${env.port}"
def jarName

node("master"){
    //检出代码
    stage("checkout"){
        checkout scm
    }

    //执行构建
    stage("build"){
        def mvnHome = tool 'M3'
        sh " ${mvnHome}/bin/mvn ${buildShell} "

        jarName = sh returnStdout: true, script: "cd target && ls *.jar"
        jarName = jarName - "\n"
        //归档制品
        sh "mkdir -p /srv/salt/${serviceName} && mv  service.sh target/${jarName} /srv/salt/${serviceName} "
    }

    //发布应用
    stage("deploy"){
        sh " salt ${targetHosts} cmd.run ' rm -fr  ${targetDir}/*.jar '"
        sh " salt ${targetHosts} cp.get_file salt://${serviceName}/${jarName}  ${targetDir}/${jarName} mkdirs=True"
        sh " salt ${targetHosts} cp.get_file salt://${serviceName}/service.sh  ${targetDir}/service.sh mkdirs=True"
        sh " salt ${targetHosts} cmd.run 'chown ${user}:${user} ${targetDir} -R '"
        sh " salt ${targetHosts} cmd.run 'su - ${user} -c \"cd ${targetDir} &&  sh service.sh stop\" ' "
        sh " salt ${targetHosts} cmd.run 'su - ${user} -c \"cd ${targetDir} &&  sh service.sh start ${jarName} ${port} ${targetDir}\" ' "
    }

}

服务启动脚本

#!/bin/bash

app=$2
port=$3
targetDir=$4

start(){
    cd ${targetDir}
    nohup java -jar ${app} --server.port=${port} >>/dev/null 2>&1& echo $! > service.pid
    cd -

}

stop(){
    pid=`cat service.pid`
    if [ -z $pid ]
    then 
        echo "pid"
    else
        kill -9 ${pid}
        kill -9 ${pid}
        kill -9 ${pid}
    fi
}

case $1 in
    start)
        start
        ;;
    stop)
        stop
        ;;

    restart)
        stop
        sleep 5
        start
        ;;
    *)
        echo "[start|stop|restart]"
        ;;

esac

构建日志

Started by user admin
Obtained Jenkinsfile from git https://github.com/zeyangli/springboot-helloworld.git
Running in Durability level: MAX_SURVIVABILITY
[Pipeline] Start of Pipeline
[Pipeline] node
Running on Jenkins in /var/lib/jenkins/workspace/demo/demo-springboot-service
[Pipeline] {
[Pipeline] stage
[Pipeline] { (checkout)
[Pipeline] checkout
using credential 24982560-17fc-4589-819b-bc5bea89da77
 > /root/bin/git rev-parse --is-inside-work-tree # timeout=10
Fetching changes from the remote Git repository
 > /root/bin/git config remote.origin.url https://github.com/zeyangli/springboot-helloworld.git # timeout=10
Fetching upstream changes from https://github.com/zeyangli/springboot-helloworld.git
 > /root/bin/git --version # timeout=10
using GIT_ASKPASS to set credentials gitlab
 > /root/bin/git fetch --tags --progress https://github.com/zeyangli/springboot-helloworld.git +refs/heads/*:refs/remotes/origin/*
 > /root/bin/git rev-parse refs/remotes/origin/master^{commit} # timeout=10
 > /root/bin/git rev-parse refs/remotes/origin/origin/master^{commit} # timeout=10
Checking out Revision 552e73b0d8c9ef58f131b57f8e35c436f272ce14 (refs/remotes/origin/master)
 > /root/bin/git config core.sparsecheckout # timeout=10
 > /root/bin/git checkout -f 552e73b0d8c9ef58f131b57f8e35c436f272ce14
Commit message: "Update Jenkinsfile"
 > /root/bin/git rev-list --no-walk 974b431aa8579f896e182e34d1b6fba895129b7d # timeout=10
[Pipeline] }
[Pipeline] // stage
[Pipeline] stage
[Pipeline] { (build)
[Pipeline] tool
[Pipeline] sh
+ /usr/local/apache-maven-3.6.0/bin/mvn clean install -DskipTests
[INFO] Scanning for projects...
[INFO] 
[INFO] -----------------------< com.gazgeek:helloworld >-----------------------
[INFO] Building helloworld 0.0.1-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO] 
[INFO] --- maven-clean-plugin:2.5:clean (default-clean) @ helloworld ---
[INFO] Deleting /var/lib/jenkins/workspace/demo/demo-springboot-service/target
[INFO] 
[INFO] --- jacoco-maven-plugin:0.7.2.201409121644:prepare-agent (prepare-agent) @ helloworld ---
[INFO] argLine set to -javaagent:/root/.m2/repository/org/jacoco/org.jacoco.agent/0.7.2.201409121644/org.jacoco.agent-0.7.2.201409121644-runtime.jar=destfile=/var/lib/jenkins/workspace/demo/demo-springboot-service/target/jacoco.exec
[INFO] 
[INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ helloworld ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] Copying 1 resource
[INFO] Copying 0 resource
[INFO] 
[INFO] --- maven-compiler-plugin:3.1:compile (default-compile) @ helloworld ---
[INFO] Changes detected - recompiling the module!
[INFO] Compiling 2 source files to /var/lib/jenkins/workspace/demo/demo-springboot-service/target/classes
[INFO] 
[INFO] --- maven-resources-plugin:2.6:testResources (default-testResources) @ helloworld ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] skip non existing resourceDirectory /var/lib/jenkins/workspace/demo/demo-springboot-service/src/test/resources
[INFO] 
[INFO] --- maven-compiler-plugin:3.1:testCompile (default-testCompile) @ helloworld ---
[INFO] Changes detected - recompiling the module!
[INFO] Compiling 3 source files to /var/lib/jenkins/workspace/demo/demo-springboot-service/target/test-classes
[INFO] 
[INFO] --- maven-surefire-plugin:2.17:test (default-test) @ helloworld ---
[INFO] Tests are skipped.
[INFO] 
[INFO] --- maven-jar-plugin:2.5:jar (default-jar) @ helloworld ---
[INFO] Building jar: /var/lib/jenkins/workspace/demo/demo-springboot-service/target/helloworld-0.0.1-SNAPSHOT.jar
[INFO] 
[INFO] --- spring-boot-maven-plugin:1.2.2.RELEASE:repackage (default) @ helloworld ---
[INFO] 
[INFO] --- maven-install-plugin:2.5.2:install (default-install) @ helloworld ---
[INFO] Installing /var/lib/jenkins/workspace/demo/demo-springboot-service/target/helloworld-0.0.1-SNAPSHOT.jar to /root/.m2/repository/com/gazgeek/helloworld/0.0.1-SNAPSHOT/helloworld-0.0.1-SNAPSHOT.jar
[INFO] Installing /var/lib/jenkins/workspace/demo/demo-springboot-service/pom.xml to /root/.m2/repository/com/gazgeek/helloworld/0.0.1-SNAPSHOT/helloworld-0.0.1-SNAPSHOT.pom
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  5.497 s
[INFO] Finished at: 2019-04-05T07:41:23+08:00
[INFO] ------------------------------------------------------------------------
[Pipeline] sh
+ cd target
+ ls helloworld-0.0.1-SNAPSHOT.jar
[Pipeline] sh
+ mkdir -p /srv/salt/demo-springboot-service
+ mv service.sh target/helloworld-0.0.1-SNAPSHOT.jar /srv/salt/demo-springboot-service
[Pipeline] }
[Pipeline] // stage
[Pipeline] stage
[Pipeline] { (deploy)
[Pipeline] sh
+ salt VM_0_12_centos cmd.run ' rm -fr  /opt/javatest/*.jar '
VM_0_12_centos:
[Pipeline] sh
+ salt VM_0_12_centos cp.get_file salt://demo-springboot-service/helloworld-0.0.1-SNAPSHOT.jar /opt/javatest/helloworld-0.0.1-SNAPSHOT.jar mkdirs=True
VM_0_12_centos:
    /opt/javatest/helloworld-0.0.1-SNAPSHOT.jar
[Pipeline] sh
+ salt VM_0_12_centos cp.get_file salt://demo-springboot-service/service.sh /opt/javatest/service.sh mkdirs=True
VM_0_12_centos:
    /opt/javatest/service.sh
[Pipeline] sh
+ salt VM_0_12_centos cmd.run 'chown tomcat:tomcat /opt/javatest -R '
VM_0_12_centos:
[Pipeline] sh
+ salt VM_0_12_centos cmd.run 'su - tomcat -c "cd /opt/javatest &&  sh service.sh stop" '
VM_0_12_centos:
[Pipeline] sh
+ salt VM_0_12_centos cmd.run 'su - tomcat -c "cd /opt/javatest &&  sh service.sh start helloworld-0.0.1-SNAPSHOT.jar 8080 /opt/javatest" '
VM_0_12_centos:
    /opt/javatest
[Pipeline] }
[Pipeline] // stage
[Pipeline] }
[Pipeline] // node
[Pipeline] End of Pipeline
Finished: SUCCESS

Golang项目发布流水线

demo地址: https://github.com/zeyangli/golang-helloworld-web.git

安装开发环境

wget  https://dl.google.com/go/go1.10.2.linux-amd64.tar.gz
tar zxf go1.10.2.linux-amd64.tar.gz -C /usr/local/

vim /etc/profile
export GO_PATH=/usr/local/go
export PATH=$PATH:$GO_PATH/bin

source /etc/profile
go version

useradd golang

Jenkins项目
serviceName: 服务名称
buildShell: 构建命令
targetHosts: 发布目标主机
user: 执行用户
targetDir: 发布目标主机的工作目录

Jenkinsfile
将build完成的二进制文件、static、service.sh生产压缩包。 移动到发布目录,发布,解压包,启动服务。

String buildShell = "${env.buildShell}"
String targetHosts = "${env.targetHosts}"
String targetDir = "${env.targetDir}"
String serviceName = "${env.serviceName}"
String user = "${env.user}"

node("master"){
    stage("checkout"){
        checkout scm
    }

    stage("build"){   
        sh """ 
               export GOPATH=/usr/local/go
               export PATH=$PATH:\$GOPATH/bin
               ${buildShell}
               mkdir -p /srv/salt/${serviceName} 
               tar zcf ${serviceName}.tar.gz main static service.sh 
               rm -fr /srv/salt/${serviceName}/*
               mv ${serviceName}.tar.gz /srv/salt/${serviceName} 
           """
    }

    stage("deploy"){
        sh " salt ${targetHosts} cmd.run ' rm -fr  ${targetDir}/* '"
        sh " salt ${targetHosts} cp.get_file salt://${serviceName}/${serviceName}.tar.gz  ${targetDir}/${serviceName}.tar.gz mkdirs=True"
        sh " salt ${targetHosts} cmd.run 'chown ${user}:${user} ${targetDir} -R '"
        sh " salt ${targetHosts} cmd.run 'su - ${user} -c \" cd ${targetDir} && tar zxf ${serviceName}.tar.gz \" '"
        sh " salt ${targetHosts} cmd.run 'su - ${user} -c \"cd ${targetDir} &&  sh service.sh stop\" ' "
        sh " salt ${targetHosts} cmd.run 'su - ${user} -c \"cd ${targetDir} &&  sh service.sh start ${targetDir}\" ' "
    }

}

服务控制脚本

#!/bin/bash

targetDir=$2

start(){
    cd ${targetDir}
    nohup ./main >>/dev/null 2>&1& echo $! > service.pid
    cd -

}

stop(){
    pid=`cat service.pid`
    if [ -z $pid ]
    then 
        echo "pid"
    else
        kill -9 ${pid}
        kill -9 ${pid}
        kill -9 ${pid}
    fi
}

case $1 in
start)
    start
    ;;
stop)
    stop
    ;;

restart)
    stop
    sleep 5
    start
    ;;
*)
    echo "[start|stop|restart]"
    ;;

esac

构建输出

Started by user admin
Obtained Jenkinsfile from git https://github.com/zeyangli/golang-helloworld-web.git
Running in Durability level: MAX_SURVIVABILITY
[Pipeline] Start of Pipeline
[Pipeline] node
Running on Jenkins in /var/lib/jenkins/workspace/demo/demo-golang-service
[Pipeline] {
[Pipeline] stage
[Pipeline] { (checkout)
[Pipeline] checkout
using credential 24982560-17fc-4589-819b-bc5bea89da77
 > /root/bin/git rev-parse --is-inside-work-tree # timeout=10
Fetching changes from the remote Git repository
 > /root/bin/git config remote.origin.url https://github.com/zeyangli/golang-helloworld-web.git # timeout=10
Fetching upstream changes from https://github.com/zeyangli/golang-helloworld-web.git
 > /root/bin/git --version # timeout=10
using GIT_ASKPASS to set credentials gitlab
 > /root/bin/git fetch --tags --progress https://github.com/zeyangli/golang-helloworld-web.git +refs/heads/*:refs/remotes/origin/*
 > /root/bin/git rev-parse refs/remotes/origin/master^{commit} # timeout=10
 > /root/bin/git rev-parse refs/remotes/origin/origin/master^{commit} # timeout=10
Checking out Revision 16a66ed8523442dd04bc6890fd30ff26455fa4c3 (refs/remotes/origin/master)
 > /root/bin/git config core.sparsecheckout # timeout=10
 > /root/bin/git checkout -f 16a66ed8523442dd04bc6890fd30ff26455fa4c3
Commit message: "Update Jenkinsfile"
 > /root/bin/git rev-list --no-walk 09d6c9f794f1337f1ec0928106991efd5354ddf8 # timeout=10
[Pipeline] }
[Pipeline] // stage
[Pipeline] stage
[Pipeline] { (build)
[Pipeline] sh
+ export GOPATH=/usr/local/go
+ GOPATH=/usr/local/go
+ export PATH=/sbin:/usr/sbin:/bin:/usr/bin:/usr/local/go/bin
+ PATH=/sbin:/usr/sbin:/bin:/usr/bin:/usr/local/go/bin
+ go clean
warning: GOPATH set to GOROOT (/usr/local/go) has no effect
+ go build main.go
warning: GOPATH set to GOROOT (/usr/local/go) has no effect
+ mkdir -p /srv/salt/demo-golang-service
+ tar zcf demo-golang-service.tar.gz main static service.sh
+ rm -fr /srv/salt/demo-golang-service/demo-golang-service.tar.gz
+ mv demo-golang-service.tar.gz /srv/salt/demo-golang-service
[Pipeline] }
[Pipeline] // stage
[Pipeline] stage
[Pipeline] { (deploy)
[Pipeline] sh
+ salt VM_0_12_centos cmd.run ' rm -fr  /opt/go/* '
VM_0_12_centos:
[Pipeline] sh
+ salt VM_0_12_centos cp.get_file salt://demo-golang-service/demo-golang-service.tar.gz /opt/go/demo-golang-service.tar.gz mkdirs=True
VM_0_12_centos:
    /opt/go/demo-golang-service.tar.gz
[Pipeline] sh
+ salt VM_0_12_centos cmd.run 'chown golang:golang /opt/go -R '
VM_0_12_centos:
[Pipeline] sh
+ salt VM_0_12_centos cmd.run 'su - golang -c " cd /opt/go && tar zxf demo-golang-service.tar.gz " '
VM_0_12_centos:
[Pipeline] sh
+ salt VM_0_12_centos cmd.run 'su - golang -c "cd /opt/go &&  sh service.sh stop" '
VM_0_12_centos:
    cat: service.pid: No such file or directory
    pid
[Pipeline] sh
+ salt VM_0_12_centos cmd.run 'su - golang -c "cd /opt/go &&  sh service.sh start /opt/go" '
VM_0_12_centos:
    /opt/go
[Pipeline] }
[Pipeline] // stage
[Pipeline] }
[Pipeline] // node
[Pipeline] End of Pipeline
Finished: SUCCESS

移动端Android项目发布流水线

搭建Android打包环境(Centos)

安装JDK

下载地址: http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html

tar zxf jdk-8u201-linux-x64.tar.gz -C /usr/local

#添加到/etc/profile
export JAVA_HOME=/usr/local/jdk1.8.0_201
export PATH=$PATH:$JAVA_HOME/bin

source /etc/profile

java -version

安装Android SDK Tools

需要翻墙: https://developer.android.com/studio/index.html

#解压后会有一个tools目录
unzip  sdk-tools-linux-4333796.zip -d /usr/local

export ANDROID_HOME=/usr/local/
export PATH=$PATH:$ANDROID_HOME/tools/bin

source /etc/profile

sdkmanager --list  #验证环境变量配置准确

[root@VM_7_14_centos ~]# sdkmanager --list | head -10
[======================Warning: File /root/.android/repositories.cfg could not be loaded.
Installed packages:=====================] 100% Computing updates...             
  Path                 | Version | Description                    | Location             
  -------              | ------- | -------                        | -------              
  build-tools;20.0.0   | 20.0.0  | Android SDK Build-Tools 20     | build-tools/20.0.0/  
  build-tools;23.0.1   | 23.0.1  | Android SDK Build-Tools 23.0.1 | build-tools/23.0.1/  
  build-tools;26.0.2   | 26.0.2  | Android SDK Build-Tools 26.0.2 | build-tools/26.0.2/  
  build-tools;28.0.3   | 28.0.3  | Android SDK Build-Tools 28.0.3 | build-tools/28.0.3/  
  platform-tools       | 28.0.2  | Android SDK Platform-Tools     | platform-tools/      
  platforms;android-19 | 4       | Android SDK Platform 19        | platforms/android-19/
  platforms;android-22 | 2       | Android SDK Platform 22        | platforms/android-22/

安装Gradle

下载地址:https://gradle.org/gradle-download/

unzip -d /usr/local gradle-5.3-bin.zip
export GRADLE_HOME=/usr/local/gradle-5.3
export PATH=$PATH:$GRADLE_HOME/bin

source /etc/profile

[root@VM_7_14_centos ~]# gradle -v

------------------------------------------------------------
Gradle 5.3
------------------------------------------------------------

Build time:   2019-03-20 11:03:29 UTC
Revision:     f5c64796748a98efdbf6f99f44b6afe08492c2a0

Kotlin:       1.3.21
Groovy:       2.5.4
Ant:          Apache Ant(TM) version 1.9.13 compiled on July 10 2018
JVM:          1.8.0_201 (Oracle Corporation 25.201-b09)
OS:           Linux 2.6.32-696.el6.x86_64 amd64

SDKmanager

sdkmanager --list #获取已安装的和可用的包
sdkmanager "platforms;android-28"  #安装和这个包
sdkmanager --uninstall  "platforms;android-28"  #卸载这个包

FAQ

GLIBC_2.14’ not found 需要升级glibc

手动发布Android项目

检出代码

项目地址: https://github.com/zeyangli/helloworld-android-gradle.git

git clone https://github.com/zeyangli/helloworld-android-gradle.git

构建打包

目录: helloworld-android-gradle

cd helloworld-android-gradle
./gradlew build

Download https://jcenter.bintray.com/com/loopj/android/android-async-http/1.4.9/android-async-http-1.4.9.jar
Download https://jcenter.bintray.com/cz/msebera/android/httpclient/4.3.6/httpclient-4.3.6.jar
Download https://jcenter.bintray.com/com/google/code/gson/gson/2.8.1/gson-2.8.1.jar
Download https://dl.google.com/dl/android/maven2/android/arch/lifecycle/common/1.0.0/common-1.0.0.jar
Download https://dl.google.com/dl/android/maven2/android/arch/core/common/1.0.0/common-1.0.0.jar
Unknown file extension: app/src/main/res/mipmap-xhdpi/ic_launcher.png
Unknown file extension: app/src/main/res/mipmap-mdpi/ic_launcher.png
Unknown file extension: app/src/main/res/mipmap-xxhdpi/ic_launcher.png
Unknown file extension: app/src/main/res/mipmap-hdpi/ic_launcher.png
Unknown file extension: app/src/main/res/mipmap-xxxhdpi/ic_launcher.png

> Task :app:lint
Ran lint on variant release: 4 issues found
Ran lint on variant debug: 4 issues found
Wrote HTML report to file:///root/helloworld-android-gradle/app/build/reports/lint-results.html
Wrote XML report to file:///root/helloworld-android-gradle/app/build/reports/lint-results.xml

BUILD SUCCESSFUL in 1m 49s
58 actionable tasks: 50 executed, 8 up-to-date

上传包到fir

debug APK: helloworld-android-gradle/app/build/outputs/apk/debug
release APK: helloworld-android-gradle/app/build/outputs/apk/release

下载测试

Jenkins发布流水线(Fir|蒲公英)

项目配置

项目规范

打包存放路径: 统一在app/build/outputs/apk/[debug|release]目录下。

编写上传包脚本(支持fim/pgyer)

参考文档: – fir.im平台发布应用API文档 – 蒲公英平台发布应用API文档

获取上传凭证: 获取cert.binary中的数据。
上传APK: 定义包信息并上传。

#coding:utf8

import requests
import sys
import json

from requests.packages.urllib3.exceptions import InsecureRequestWarning
requests.packages.urllib3.disable_warnings(InsecureRequestWarning)

class ApkManage(object):
def __init__(self):
    self.url = "http://api.fir.im/apps"

def getCert(self):
    dataargs = {'type' : 'android',
                'bundle_id' : bundleid,
                'api_token' : apitoken}

    response = requests.post(self.url,data=dataargs)
    #print(response.status_code,response.text)
    cert = json.loads(response.text)
    #print(cert)

    return cert['cert']['binary']

def uploadFir(self):
    certdata = self.getCert()

    try:
        print("upload apk to fir......")
        apkfile = {'file' : open(apkpath,'rb')}
        params = {"key"   : certdata['key'],
                  "token" : certdata['token'],
                  "x:name": appname ,
                  "x:build" : buildid,
                  "x:version" : appversion}
        response = requests.post(certdata['upload_url'],files=apkfile,data=params,verify=False)
        print(response.text)
        if int(response.status_code) == 200 :
            print("upload success!  return -->" + str(response.status_code))
        else:
            print("upload error! return -->" + str(response.status_code))

    except Exception as e:
        print("error: " + str(e))

def uploadPgyer(self):
    url = 'https://qiniu-storage.pgyer.com/apiv1/app/upload'
    try:
        #print("upload apk to pgyer ......")
        apkfile = {'file' : open(apkpath,'rb')}
        params = {"uKey" : '7b70873bb4d6xxxxx1d2ae5',
                  "_api_key" : 'a9acab611e1xxxxxxx5cae360a5ab'}

        response = requests.post(url,files=apkfile,data=params,verify=False)
        #print(response.text)
        qrcodes = json.loads(response.text)['data']['appQRCodeURL']
        if int(response.status_code) == 200 :
            #print("upload success!  return -->" + str(response.status_code))
            print(qrcodes)
        else:
            print("upload error! return -->" + str(response.status_code))

    except Exception as e:
        raise

if __name__ == '__main__':
bundleid = sys.argv[1]
apitoken = sys.argv[2]
apkpath = sys.argv[3]
appname = sys.argv[4]
buildid = sys.argv[5]
appversion = sys.argv[6]
platform= sys.argv[7]

server = ApkManage()

if platform == 'fir':
    server.uploadFir()
elif platform == 'pgyer':
    server.uploadPgyer()

使用方式

python upapk.py demo-android-app-10 65d7edxxxxxxx7c4fabda25 app.apk  demo-android-app 10 10.12 fir

编写Jenkinsfile

Jenkinsfile简单的包含三个stage,分别是:

Checkout: 检出代码(这种方式是直接获取Jenkinsfile的项目地址,Jenkinsfile在项目中可以这样写)。
Build: 构建打包 (执行gradle构建命令)。
Upload: 上传包到平台(更改包名,调用脚本上传)。
node("master"){
stage("Checkout"){
checkout scm
}

stage("Build"){
sh 'chmod +x ./gradlew '
sh " ${params.buildShell} "
}

stage("Upload"){
  /*sh """ 
     mv app/build/outputs/apk/debug/app-debug.apk ./${params.apkName}.apk
     python uploadapk.py ${params.bundleId} \
     ${params.apiToken} "${params.apkName}.apk" \
     "${params.apkName}" "${BUILD_ID}" \
     "${params.apkVersion}" "${params.appPlatform}"

     """*/
  sh "mv app/build/outputs/apk/debug/app-debug.apk ./${params.apkName}.apk"
  def result 
  result = sh returnStdout: true, script: """python uploadapk.py ${params.bundleId} \
                                             ${params.apiToken} "${params.apkName}.apk" \
                                             "${params.apkName}" "${BUILD_ID}" \
                                             "${params.apkVersion}" "${params.appPlatform}" """

  result = result - "\n"
  println(result)
currentBuild.description="<img src=${result}>"
}
}

Jenkins配置

添加全局变量(android sdk)

导航->系统设置

创建Pipeline


这个项目因为Jenkinsfile和项目代码放在了一起,所以这个项目上的srcType、srcUrl、branchName参数暂时无效。

buildShell : 打包命令(debug|release)。

./gradlew clean assembleDebug
./gradlew clean assembleRelease

bundleId: App的bundleId(发布新应用时必填)。

apiToken: 在fir.im平台创建。 获取用户token: 用户->apitoken

apkVersion : apk的版本。

apkName: apk的名称。

构建测试

检出代码

构建打包


发布APK

Fir平台

蒲公英平台

二维码

赞(1) 打赏
未经允许不得转载:陈桂林博客 » 第六章 Jenkins流水线实践
分享到

大佬们的评论 抢沙发

全新“一站式”建站,高质量、高售后的一条龙服务

微信 抖音 支付宝 百度 头条 快手全平台打通信息流

橙子建站.极速智能建站8折购买虚拟主机

觉得文章有用就打赏一下文章作者

非常感谢你的打赏,我们将继续给力更多优质内容,让我们一起创建更加美好的网络世界!

支付宝扫一扫打赏

微信扫一扫打赏

登录

找回密码

注册