前端静态资源发布流水线
项目设置
项目配置部分主要是将网站源代码上传到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' : "[1;32m>>>>>>>>>>${value}>>>>>>>>>>[m",
'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平台
蒲公英平台
二维码