Jenkins CI&CD 自动化发布项目实战(下篇)

Jenkins CI&CD 自动化发布项目实战(下篇)

作者

刘畅

时间

2020-12-04

实验环境

centos7.5

主机名

ip

服务配置

软件

gitlab

172.16.1.71

2核/4G/60G

docker、gitlab

jenkins-master

172.16.1.72

2核/4G/60G

docker、jdk、maven、jenkins

jenkins-slave01

172.16.1.73

2核/2G/60G

jdk、maven、ansible

java-web01

172.16.1.74

2核/2G/60G

jdk、tomcat

java-web02

172.16.1.75

2核/2G/60G

jdk、tomcat

说明:本文docker、gitlab的安装就不赘述了,可以参考其它专题文档。

目录

1 Pipeline介绍 1

2 Pipeline语法 1

2.1
声明式流水线语法 1

2.2
脚本式流水线语法 2

2.3
生成Pipeline脚本 2

3 Pipeline指令 3

3.1
指令示例网址 3

3.2
常用指令 3

3.3
流水线脚本与源代码一起版本管理 4

4 流水线发布Java网站项目 4

4.1
前提条件 5

4.2
流水线代码 6

4.3
Build with Parameters 7

5 Gitlab
Webhooks自动触发发布 9

5.1
安装gitlab插件 9

5.2
在Jenkins项目里配置触发器生成URL和Token 9

5.3
在gitlab项目Webhooks中配置URL和Token 10

5.4
测试 11

6 集成Ansible发布多台服务器及项目回滚 11

6.1
前提条件 11

6.2
发布项目 12

6.3
回滚项目 14

6.4
测试 16

7 Jenkins备份与恢复 17

7.1
方式一、备份数据目录 17

7.2
方式二、Thinbackup 17

8 补充 19

8.1
Blue Ocean 19

8.2
修改jenkins时区的方法 20

1 Pipeline介绍

1Jenkins Pipeline是一套运行工作流框架,将原本独立运行单个或者多个节点的任务链接起来,实现单个任务难以完成的复杂流程编排和可视化。

2 Jenkins Pipeline是一套插件,支持在Jenkins中实现持续集成和持续交付。

3 Pipeline通过特定语法对简单到复杂的传输管道进行建模。

4 Jenkins Pipeline的定义被写入一个文本文件,称为Jenkinsfile。

Jenkins CI&CD 自动化发布项目实战(下篇)

5 安装pipeline插件

Jenkins CI&CD 自动化发布项目实战(下篇)

2 Pipeline语法

2.1
声明式流水线语法

Jenkinsfile(Declarative Pipeline)

pipeline {

agent any

stages {

stage('Build') {

steps
{

echo
'Build'

}

}

stage('Test')
{

steps
{

echo
'Test'

}

}

stage('Deploy')
{

steps
{

echo
'Deploy'

}

}

}

}

说明:

支持大部分Groovy,具有丰富的语法特性,易于编写和设计。

(1) Stages:

阶段,是Pipeline中最主要的组成部分,Jenkins将会按照Stages中描述的顺序从上往下的执行。

包含一系列一个或多个stage指令, stages部分是流水线描述的大部分"work" 的位置。

(2) Stage:

阶段,一个Pipeline可以划分为若干个 Stage,每个Stage代表一组操作,比如:Build、Test、Deploy。

(3) Steps:

步骤,Steps是最基本的操作单元,可以是打印一句话,也可以是构建一个Docker镜像,由各类Jenkins插件提供,比如命令:sh
'mvn',就相当于我们平时

shell终端中执行mvn命令一样。

steps 部分在给定的 stage
指令中执行的定义了一系列的一个或多个steps。

2.2
脚本式流水线语法

Jenkinsfile(Scripted Pipeline)

node {

stage('Build')
{

echo
'Build'

}

stage('Test')
{

echo
'Test'

}

stage('Deploy')
{

echo
'Deploy'

}

}

说明:

遵循与Groovy相同语法。

2.3
生成Pipeline脚本

1 创建Pipeline项目,在底部生成示例。

2 最后使用生成器生成Pipeline片段,完善Pipeline脚本。

Jenkins CI&CD 自动化发布项目实战(下篇)

3 Pipeline指令

3.1
指令示例网址

https://www.jenkins.io/zh/doc/book/pipeline/syntax/#parameters-example

3.2
常用指令

指令

描述

定义位置

生成方法(Declarative Directive Generator)

agent

指定流水线的执行节点

在pipeline块的顶层被定义

agent: Agent

options

流水线选项,一般用于设置阶段超时时间

在pipeline块的顶层或stage定义

options: Options

environment

定义所有步骤的环境变量,或者特定步骤

在pipeline块的顶层被定义

environment: Environment

parameters

触发流水线时提供的参数列表

在pipeline块的顶层被定义

parameters: Parameters

triggers

自动化触发

在pipeline块的顶层被定义

triggers: Triggers

tools

使用配置的工具,只支持maven、jdk、gradle

在pipeline块的顶层被定义

tools: Tools

input

用户交互输入

在stage定义

input: Input

post

流水线执行完成后执行

在pipeline块的底层被定义

post: Post Stage or Build
Conditions

指令

描述

定义位置

生成方法(片段生成器)

script

使用脚本式语法

在steps定义

script: Run arbitrary Pipeline
script

sh

shell命令

在steps定义

sh: Shell Script

withCredentials

从凭据中读取数据并赋值变量

在steps定义

withCredentials: Bind credentials to
variables

checkout

从版本仓库种拉取代码

在steps定义

checkout: Check out from version
control

ansiblePlaybook

ansiblePlaybook: Invoke an ansible
playbook

sshPublisher

通过ssh方式拷贝构建文件到远程服务器

在steps定义

sshPublisher: Send build artifacts over
SSH

(1) pipeline引用变量一定要使用双引号,与shell中引用变量方法相同"$变量名"。

(2)
pipeline配置的parameters参数会将功能解析到页面上显示。

(3)
script的if语句在括号内引用变量时可以不加$。

(4)
流水线写好后第一次构建(pipeline初始化)时可能因没有构建参数可选,会出现构建错误,第二次

再构建时就好了。

(5)
pipeline shell命令中,如果自己定义了变量,在引时,$需要进行转义,引用jenkins中的

变量不需要加转义。

3.3
流水线脚本与源代码一起版本管理

Jenkinsfile文件建议与源代码一起版本管理,实现流水线即代码(Pipeline as
Code)。

这样做的好处:

(1) 自动为所有分支创建流水线脚本。

(2) 方便流水线代码复查、追踪、迭代。

(3) 可被项目成员查看和编辑。

Jenkins CI&CD 自动化发布项目实战(下篇)

4 流水线发布Java网站项目

基于ssh远程拷贝项目文件发布到远程服务器

Jenkins CI&CD 自动化发布项目实战(下篇)

4.1
前提条件

1 配置通过免交互,秘钥方式连接远程应用服务器(公钥要发送到远程应用服务器上)

Manage Jenkins -> Configure System -> Publish over
SSH

Jenkins CI&CD 自动化发布项目实战(下篇)

2 配置连接gitlab的的全局凭证(用户名、密码方式)

Manage Jenkins -> Manage Credentials ->
凭据

Jenkins CI&CD 自动化发布项目实战(下篇)

3 创建项目名为java-mall-pipeline的jenkins项目,gitlab依然使用上篇实验的java项目。

4.2
流水线代码

pipeline {

agent {

label
'jenkins-slave01'

}

environment
{

gitlab_address
= "http://172.16.1.71/java-project/java-mall.git"

gitlab_auth
= "c1ba4aa8-f310-45e7-9939-bc1967a32d79"

publish_host
= "java-web01"

}

parameters
{

gitParameter
branch: '', branchFilter: 'origin/(.*)', defaultValue: 'master', description:
'请选择要发布的分支名称',
name: 'Branch', quickFilterEnabled: false, selectedValue: 'NONE', sortMode:
'NONE', tagFilter: '*', type: 'PT_BRANCH'

}

stages {

stage('拉取代码') {

steps
{

checkout([$class:
'GitSCM', branches: [[name: "${params.Branch}"]],
doGenerateSubmoduleConfigurations: false, extensions: [], submoduleCfg: [],
userRemoteConfigs: [[credentialsId: "${gitlab_auth}", url:
"${gitlab_address}"]]])

}

}

stage('编译构建') {

steps
{

sh
"/usr/local/maven/bin/mvn clean package
-Dmaven.test.skip=true"

}

}

stage('拷贝构建文件到远程主机并部署')
{

steps
{

sshPublisher(publishers:
[sshPublisherDesc(configName: "${publish_host}", transfers:
[sshTransfer(cleanRemote: false, excludes: '', execCommand:
'''#!/bin/bash

#
备份已部署的程序

tomcat=/usr/local/tomcat/webapps

BACKUP_DIR=/data/backup

[
! -d $BACKUP_DIR ] && mkdir -p $BACKUP_DIR

[
-f $tomcat/*.war ] && mv $tomcat/*.war $BACKUP_DIR/$(date
+"%F_%T")_ROOT.war

#
部署新程序并重启Tomcat

[
-d $tomcat/ROOT ] && rm -rf $tomcat/ROOT

mv
/tmp/$JOB_NAME/*.war $tomcat/ROOT.war

pid=$(ps
-ef |grep $tomcat |egrep -v \'grep\' |awk \'{print $2}\')

[
-n "$pid" ] && kill -9 $pid

export
JAVA_HOME=/usr/local/jdk

/usr/local/tomcat/bin/startup.sh''',
execTimeout: 120000, flatten: false, makeEmptyDirs: false, noDefaultExcludes:
false, patternSeparator: '[, ]+', remoteDirectory: 'tmp/$JOB_NAME',
remoteDirectorySDF: false, removePrefix: 'target', sourceFiles:
'target/*.war')], usePromotionTimestamp: false, useWorkspaceInPromotion: false,
verbose: false)])

}

}

}

}

// 说明:

// 以上脚本只要修改环境变量(environment)中的内容即可。

4.3 Build with Parameters

Jenkins CI&CD 自动化发布项目实战(下篇)

4.4
查看控制台输出

Jenkins CI&CD 自动化发布项目实战(下篇)

5 Gitlab
Webhooks
自动触发发布

5.1
安装gitlab插件

Jenkins CI&CD 自动化发布项目实战(下篇)

5.2
Jenkins项目里配置触发器生成URL和Token

java-mall-pipeline项目

Jenkins CI&CD 自动化发布项目实战(下篇)

5.3
在gitlab项目Webhooks中配置URLToken

java-mall项目

Jenkins CI&CD 自动化发布项目实战(下篇)

报错说明:

Jenkins CI&CD 自动化发布项目实战(下篇)

解决办法:

管理中心
->
设置 ->
网络 -> 外发请求

Jenkins CI&CD 自动化发布项目实战(下篇)

5.4
测试

在gitlab中修改java-mall项目的代码并提交后,发现jenkins的java-mall-pipline项目自动进行

构建及发布项目的操作,说明配置成功。

构建历史显示为gitlab推送

Jenkins CI&CD 自动化发布项目实战(下篇)

6 集成Ansible发布多台服务器及项目回滚

Jenkins CI&CD 自动化发布项目实战(下篇)

6.1
前提条件

1 配置通过免交互,秘钥方式连接远程应用服务器(公钥发送到远程应用服务器)的凭据,供ansiable使用,在172.16.1.73节点上安装ansiable。

Jenkins CI&CD 自动化发布项目实战(下篇)

2 配置连接gitlab的全局凭证(用户密码方式),方便连接gitlab

Jenkins CI&CD 自动化发布项目实战(下篇)

3 配置连接jenkins的全局凭证(用户密码方式),方便应用服务器发送代码版本文件。

Jenkins CI&CD 自动化发布项目实战(下篇)

4 远程应用服务器上要安装sshpass包,方便应用服务器免交互发送代码版本文件

6.2
发布项目

1 说明:

创建项目名为java-mall-pipeline的jenkins项目,gitlab依然使用上篇实验的java项目。

2 pipeline脚本

pipeline {

agent {

label
"jenkins-slave01"

}

environment
{

gitlab_address
= "http://172.16.1.71/java-project/java-mall.git"

//
gitlab的地址

gitlab_auth
= "c1ba4aa8-f310-45e7-9939-bc1967a32d79"

//
连接gitlab的凭证

ansible_ssh_auth
= "d0651139-bbea-403a-9543-4f404e4778dd"

//
ansiable免交互连接远程服务的凭证

jenkins_auth
= "4061843c-c6dd-4b97-b986-94cbaa72fa3e"

//
远程服务器向jenkins发送代码版本的凭证

}

parameters
{

gitParameter
branch: '', branchFilter: 'origin/(.*)', defaultValue: 'master', description:
'请选择要发布的分支名称',
name: 'Branch', quickFilterEnabled: false, selectedValue: 'NONE', sortMode:
'NONE', tagFilter: '*', type: 'PT_BRANCH'

choice
choices: ['java-web01', 'java-web02'], description: '''请选择要发布的服务器组

[java-web01]

172.16.1.74

[java-web02]

172.16.1.75''',
name: 'Servers'

}

stages {

stage('拉取代码') {

steps
{

checkout([$class:
'GitSCM', branches: [[name: "${params.Branch}"]],
doGenerateSubmoduleConfigurations: false, extensions: [], submoduleCfg: [],
userRemoteConfigs: [[credentialsId: "${gitlab_auth}", url:
"${gitlab_address}"]]])

}

}

stage('编译构建') {

steps
{

sh
"/usr/local/maven/bin/mvn clean package
-Dmaven.test.skip=true"

}

}

stage('拷贝构建文件到远程主机并部署')
{

steps
{

//
读取连接Jenkins服务器用户名和密码

withCredentials([usernamePassword(credentialsId:
"${jenkins_auth}", passwordVariable: 'password', usernameVariable: 'username')])
{

//
===========================================

sh
"""

###################### 主机清单文件
############################

cat > /opt/jenkins_home/.hosts <<
EOF

[java-web01]

172.16.1.74

[java-web02]

172.16.1.75

EOF

###################### Playbook文件
############################

cat > /opt/jenkins_home/.playbook.yaml <<
"EOF"

- hosts: $Servers       # Jenkins参数化变量

gather_facts: no

vars:             # 定义playbook变量,下面{{}}引用这里的变量

workspace: $WORKSPACE   # WORKSPACE和BUILD_NUMBER引用Jenkins变量

job_name:
$JOB_NAME

tomcat_dir:
"/usr/local/tomcat/webapps"  # 自定义变量

tomcat:
"/usr/local/tomcat"

backup_dir:
"/data/backup"

backup_filename: "{{ job_name
}}_ROOT_\$(date +%F)_${BUILD_NUMBER}.war"  # 格式:项目名_文件名_日期_构建编号

jenkins_ip:
"172.16.1.72"

username:
$username

password:
$password

tasks:

- name: 在远程服务器上创建项目目录

file:

dest: /tmp/{{
job_name }}

state:
directory

- name: 推送部署包到远程服务器

copy: src="{{ item }}"
dest=/tmp/{{ job_name }}

with_fileglob:

- "{{ workspace
}}/target/*.war"

- name: 部署新程序并重启Tomcat

shell: |

# 备份已部署的程序

[ ! -d {{
backup_dir }} ] && mkdir -p {{ backup_dir }}

[ -f {{ tomcat_dir
}}/*.war ] && mv {{ tomcat_dir }}/*.war {{ backup_dir }}/{{
backup_filename }}

# 部署新程序并重启Tomcat

[ -d {{ tomcat_dir
}}/ROOT ] && rm -rf {{ tomcat_dir }}/ROOT

mv /tmp/{{
job_name }}/*.war {{ tomcat_dir }}/ROOT.war

pid=\$(ps -ef
|grep {{ tomcat }} |egrep -v 'grep' |awk '{print \$2}')

[ -n "\$pid" ]
&& kill -9 \$pid

export
JAVA_HOME=/usr/local/jdk

nohup
/usr/local/tomcat/bin/startup.sh &

ls {{ backup_dir
}} |sort -t '_' -k 4 -rn |head -n5 |awk 'BEGIN{printf "version="}{printf
\$0","}' > /tmp/{{ job_name }}/backup_version.txt

sshpass -p{{
password }} scp -o StrictHostKeychecking=no -p /tmp/{{ job_name
}}/backup_version.txt {{ username }}@{{ jenkins_ip
}}:/opt/jenkins_home

EOF

"""

//
===========================================

}

ansiblePlaybook(

playbook:
'/opt/jenkins_home/.playbook.yaml',

inventory:
'/opt/jenkins_home/.hosts',

credentialsId:
"${ansible_ssh_auth}"

)

}

}

}

}

// 说明:

// 以上脚本只要修改环境变量(environment)中的内容和要发布的主机组清单即可。

3 Build with Parameters

Jenkins CI&CD 自动化发布项目实战(下篇)

6.3
回滚项目

1 说明:

创建项目名为java-mall-pipeline_rollback的jenkins项目,回滚的是备份目录/data/backup下的备

份代码。

2 pipeline脚本

pipeline {

agent {

label
"jenkins-slave01"

}

environment
{

ansible_ssh_auth
= "d0651139-bbea-403a-9543-4f404e4778dd"

//
ansiable免交互连接远程服务的凭证

}

parameters
{

extendedChoice
description: '请选择回滚的备份版本',
multiSelectDelimiter: ',', name: 'BackupVersion', propertyFile:
'/var/jenkins_home/backup_version.txt', propertyKey: 'version', quoteValue:
false, saveJSONParameterToFile: false, type: 'PT_SINGLE_SELECT',
visibleItemCount: 5

choice choices: ['java-web01',
'java-web02'], description: '''请选择要回滚的服务器组

[java-web01]

172.16.1.74

[java-web02]

172.16.1.75''',
name: 'Servers'

}

stages {

stage('回滚') {

steps
{

sh
"""

###################### 主机清单文件
############################

cat > /opt/jenkins_home/.hosts <<
EOF

[java-web01]

172.16.1.74

[java-web02]

172.16.1.75

EOF

###################### Playbook文件
###########################

cat > /opt/jenkins_home/.playbook.yaml <<
"EOF"

- hosts: $Servers

gather_facts: no

vars:

tomcat_dir:
"/usr/local/tomcat/webapps"

tomcat:
"/usr/local/tomcat"

backup_dir:
"/data/backup"

rollbak_dir:
"/data/backup/rollback"

backup_filename:
$BackupVersion

tasks:

- name: 使用备份文件重新部署

shell: |

# 备份已部署的程序

[ ! -d {{
rollbak_dir }} ] && mkdir -p {{ rollbak_dir }}

mv {{ tomcat_dir
}}/ROOT.war {{ rollbak_dir }}/ROOT-\$(date +%F-%T).war

# 部署备份程序并重启Tomcat

[ -d {{ tomcat_dir
}}/ROOT ] && rm -rf {{ tomcat_dir }}/ROOT

cp {{ backup_dir
}}/{{ backup_filename }} {{ tomcat_dir }}/ROOT.war

pid=\$(ps -ef
|grep {{ tomcat }} |egrep -v 'grep' |awk '{print \$2}')

[ -n "\$pid" ]
&& kill -9 \$pid

export
JAVA_HOME=/usr/local/jdk

nohup {{ tomcat
}}/bin/startup.sh &

EOF

"""

//
===========================================

ansiblePlaybook(

playbook:
'/opt/jenkins_home/.playbook.yaml',

inventory:
'/opt/jenkins_home/.hosts',

credentialsId:
"${ansible_ssh_auth}"

)

}

}

}

}

// 说明:

// 以上脚本只要修改环境变量(environment)中的内容和要发布的主机组清单即可。

3 Build with Parameters

Jenkins CI&CD 自动化发布项目实战(下篇)

6.4
测试

1 发布项目

先发布两次项目(第一次因为webapps中没有项目,所以/data/backup目录下没有备份项目)。

2 回滚项目

可以看到参数化构建中有备份的项目可供选择。

7 Jenkins备份与恢复

7.1
方式一、备份数据目录

1 如果是基于jenkins war包安装的jenkins,备份目录为"/root/.jenkins/"

2 如果是基于docker方式安装的jenkins,备份目录为jenkins的持久化家目录(/opt/jenkins_home/)

7.2
方式二、Thinbackup

1 安装插件

Jenkins CI&CD 自动化发布项目实战(下篇)

2 Manage Jenkins -> ThinBackup

Jenkins CI&CD 自动化发布项目实战(下篇)

3 备份设置(Settings)

创建jenkins的备份目录,该目录必须存在

# mkdir -p
/opt/jenkins_home/backup

Jenkins CI&CD 自动化发布项目实战(下篇)

jenkins备份的主要是"系统配置"、"job"、"jenkins插件"即可。

参考文档:https://wiki.jenkins.io/pages/diffpagesbyversion.action?pageId=49512461&originalVersion=19&revisedVersion=48

4 备份测试

(1)
点击Backup
Now手动进行备份

(2)
查看备份内容

# ls -ld /opt/jenkins_home/jenkins_backup/

drwxr-xr-x 6 root root 4096 Dec  3 14:35
FULL-2020-12-03_14-35

(3)
还原备份到新的jenkins服务器上

1) 在thinBackup插件中设置备份文件所在的目录。

2) 点击Restore还原备份(注意:要耐心等待备份的完成)。

Jenkins CI&CD 自动化发布项目实战(下篇)

3) 备份还原后重启jenkins后生效,jenkins的系统设置和创建的jenkins项目都在。

重启:Manage
Jenkins -> Reload Configuration from disk

注意:

A 此时jenkins-slave可能会出现断联的状态,需要进行从新配置连接。

B 插件可能会报依赖问题,安装缺少的依赖。

C 拉取gitlab代码的配置会报如下错误,需要重新配置gitlab的全局用户凭证信息即可。Jenkins CI&CD 自动化发布项目实战(下篇)

以上jenkins备份后还原到的是新的jenkins服务器,如果是还原到原有的jenkins服务器上不会

出现以上注意点。

8 补充

8.1 Blue
Ocean

1 安装插件

Jenkins CI&CD 自动化发布项目实战(下篇)

2 什么是Blue Ocean

https://www.jenkins.io/zh/doc/book/blueocean/

(1) Blue Ocean 重新思考Jenkins的用户体验,从头开始设计Jenkins Pipeline, 但仍然与*式作业兼容,Blue Ocean减少了混乱而且进一步明确了团队中每个成员。

(2)
Blue Ocean 的主要特性包括

1) 持续交付(CD)Pipeline的复杂可视化,可以让您快速直观地理解管道状态。

2) Pipeline 编辑器 -
引导用户通过直观的、可视化的过程来创建Pipeline,从而使Pipeline的创建变得平易近人。

3) 个性化以适应团队中每个成员不同角色的需求。

4) 在需要干预或出现问题时精确定位。 Blue
Ocean展示Pipeline中需要关注的地方,简化异常处理,提高生产力。

5) 本地集成分支和合并请求, 在与GitHub和Bitbucket中的其他人协作编码时实现最大程度的开发人员生产力。

3 视图

Jenkins CI&CD 自动化发布项目实战(下篇)

8.2
修改jenkins时区的方法

查看系统设置:http://172.16.1.72:8080/systemInfo

jenkins默认使用UTC时间,想要修改为CST时间,按如下步骤操作:

1 临时生效

(1) 打开【系统管理】->【脚本命令行】运行下面的命令

(2) System.setProperty('org.apache.commons.jelly.tags.fmt.timeZone',
'Asia/Shanghai')

2 永久生效

docker run ... -e
JAVA_OPTS=-Duser.timezone=Asia/Shanghai

示例:

docker run -d --name jenkins -p 8080:8080 -p
50000:50000 -u root \

-v /opt/jenkins_home:/var/jenkins_home
\

-v /var/run/docker.sock:/var/run/docker.sock
\

-v /usr/bin/docker:/usr/bin/docker \

-v /usr/local/maven:/usr/local/maven
\

-v /usr/local/jdk:/usr/local/jdk \

-v /etc/localtime:/etc/localtime \

--restart=always \

-e JAVA_OPTS=-Duser.timezone=Asia/Shanghai
\

--name jenkins jenkins/jenkins:lts

3 补充

(1)
通过jenkins RPM包安装的jenkins

# vim /etc/sysconfig/jenkins

JENKINS_JAVA_OPTIONS="-Duser.timezone=Asia/Shanghai"

(2)
通过jenkins war包安装的jenkins

# vim
/usr/local/tomcat/bin/catalina.sh

Jenkins CI&CD 自动化发布项目实战(下篇)

JAVA_OPTS="-Duser.timezone=Asia/Shanghai"

补充:

# jenkins以root用户运行,也可以不写

export JENKINS_USER="root"

(3)
通过系统设置时区

参考文档:https://www.jenkins.io/doc/book/using/change-time-zone/

# 您可能需要更改显示的时区以匹配您自己的时区。通过转到用户配置页面,可以将

# 设置User Defined Time
Zone为与您自己的匹配。

Jenkins CI&CD 自动化发布项目实战(下篇)

Jenkins CI&CD 自动化发布项目实战(下篇)

上一篇:持续集成之②:整合jenkins与代码质量管理平台Sonar并实现构建失败邮件通知


下一篇:北大开源全新中文分词工具包:准确率远超THULAC、结巴分词