Jenkins

Jenkins图标

Jenkins是一个开源的持续集成的服务器,Jenkins开源帮助我们自动构建各类项目。Jenkins强大的插件式,使得Jenkins可以集成很多软件,可能帮助我们持续集成我们的工程项目。

一、什么是CI、CD

devops

Devops也就是开发运维一体化,而随着Devops的兴起,出现了持续集成持续交付以及持续部署的新方法,传统的软件开发和交付方法正在迅速变得过时。从历史上看,在敏捷时代,大多数公司会每月、每季度、每两年甚至每年发布部署或发布软件。然而,在DevOps时代,每周、每天、甚至每天多次。当SaaS正在占领世界时,我们可以轻松地动态更新应用程序,而无需强迫客户下载新组件。很多时候,他们甚至都不会意识到正在发生变化。开发团队通过软件交付流水线(Pipeline)实现自动化,以缩短交付周期,大多数团队都有自动化流程来检查代码并部署到新环境。

持续集成的重点是将各个开发人员的工作集合到一个代码仓库中。通常,每天都要进行几次,主要目的是尽早发现集成错误,使团队更加紧密结合,更好地协作。

持续交付的目的是最小化部署或释放过程中固有的摩擦。它的实现通常能够将构建部署的每个步骤自动化,以便任何时刻能够安全地完成代码发布(理想情况下)。

持续部署是一种更高程度的自动化,无论何时对代码进行重大更改,都会自动进行构建/部署。

Jenkins就是来实现以上持续集成工作的服务器。

二、持续集成环境搭建

Jenkins流程图:

Jenkins流程图

2.1 Gitlab代码托管服务器安装

Gitlab 是一个用于仓库管理系统的开源项目,使用Git作为代码管理工具,并在此基础上搭建起来的Web服务。

gitlab图标

  1. 开启postfix服务(支持gitlab发信功能)
1
2
systemctl start postfix
systemctl enable postfix
  1. 配置gitlab的yum源
1
2
3
4
5
6
7
8
9
vim /etc/yum.repos.d/gitlab-cd.repo
[gitlab-ce]
name=Gitlab CE Repository
baseurl=https://mirrors.tuna.tsinghua.edu.cn/gitlab-ce/yum/el$releasever/
gpgcheck=0
enabled=1

yum clean all
yum makecache
  1. 安装gitlab
1
yum -y install gitlab-ce
  1. 修改gitlab配置文件
1
2
3
4
5
6
7
8
9
vim /etc/gitlab/gitlab.rb
#用户访问所使用的URL,域名或者IP地址
external_url 'http://192.168.88.133'
#时区
gitlab_rails['time_zone'] = 'Asia/Shanghai'
#启用SMTP邮箱功能
gitlab_rails['smtp_enable'] = 'ture'
#使用SSH协议拉取代码所使用的连接端口
gitlab_rails['gitlab_shell_ssh_port'] = '22'
  1. 刷新配置
1
gitlab-ctl reconfigure
  1. 启动服务
1
2
3
gitlab-ctl restart
gitlab-ctl status
......
  1. docker-compose.yaml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
version: '3.8'
services:
gitlab:
container_name: gitlab
image: gitlab/gitlab-ce:latest
restart: always
environment:
GITLAB_OMNIBUS_CONFIG: |
external_url 'http://192.168.88.30'
gitlab_rails['time_zone'] = 'Asia/Shanghai'
gitlab_rails['gitlab_shell_ssh_port'] = '22'
ports:
- '80:80'
- '23:22'
volumes:
- '/root/cicd/gitlab/config:/etc/gitlab'
- '/root/cicd/gitlab/logs:/var/log/gitlab'
- '/root/cicd/gitlab/data:/var/opt/gitlab'
  1. 进入容器查看默认用户名和修改密码
1
2
3
4
5
6
7
8
9
gitlab-rails console
# 查找root用户
u=User.where(id:1).first
# 修改密码
u.password='toortoor'
# 确认修改密码
u.password_confirmation='toortoor'
# 保存修改
u.save

2.2 Gitlab创建组、用户、项目

Gitlab创建组:

  1. 添加组

gitlab创建组一

  1. 设置组名和权限

gitlab创建组二

Gitlab创建用户:

  1. 添加用户

gitlab创建用户一

  1. 配置选项

gitlab创建用户二gitlab创建用户三

  1. 将用户添加到项目组

gitlab创建用户四

Gitlab创建项目:

  1. 在指定组中添加项目

gitlab创建项目一

  1. 设置项目名称

gitlab创建项目二

2.5 项目上传到Gitlab

  1. 上传代码一
  2. 上传代码二
  3. 上传代码三
  4. 上传代码四
  5. 上传代码四
  6. 上传代码六
  7. 上传代码四
  8. 上传代码八
  9. 上传代码九
  10. 上传代码十

2.4 Jenkins安装

  1. 安装JDK
1
yum -y install java-1.8.0-openjdk*
  1. 放行端口
1
2
firewall-cmd --zone=public --add-port=8888/tcp --permanent
firewall-cmd --reload
  1. 添加Jenkins官方源并安装
1
2
3
wget -O /etc/yum.repos.d/jenkins.repo https://pkg.jenkins.io/redhat-stable/jenkins.repo
rpm --import https://pkg.jenkins.io/redhat-stable/jenkins.io.key
yum -y install jenkins
  1. 修改配置文件
1
2
3
vim /etc/sysconfig/jenkins
JENKINS_USER="root"
JENKINS_PORT="8888"
  1. 查看初始密码
1
2
cat /var/lib/jenkins/secrets/initialAdminPassword
......
  1. docker-compose.yaml
1
2
3
4
5
6
7
8
9
10
11
version: '3.8'
services:
jenkins:
container_name: jenkins
image: jenkins:2.60.3
restart: always
ports:
- '8080:8080'
- '50000:50000'
volumes:
- '/root/cicd/jenkins/home:/var/jenkins_home'

2.5 Jenkins中文插件安装

  1. 由于Jenkins官方下载插件很慢,我们修改为国内Jenkins插件地址,jenkins -> Manage jenkins -> Manage Plugins

更换清华插件地址

  1. 更换配置文件中的地址
1
2
3
4
5
cp /var/lib/jenkins/updates/default.json /var/lib/jenkins/updates/default.json.backup

sed -i 's/https:\/\/updates.jenkins.io\/download/https:\/\/mirrors.tuna.tsinghua.edu.cn\/jenkins/g' /var/lib/jenkins/updates/default.json && sed -i 's/http:\/\/www.google.com/https:\/\/www.baidu.com/g' /var/lib/jenkins/updates/default.json

systemctl restart jenkins
  1. 安装中文插件

安装中文插件

2.6 Jenkins用户权限管理

  1. 由于Jenkins功能较为简介,都要靠安装插件来丰富体验,所以用户管理需要安装role-based插件

role-based插件

  1. 在Configure Global Security开启刚刚安装的插件

role-based插件二

  1. 在Manage and Assign Roles中创建角色baserole并分配具体权限

role-based插件三

role-based插件三

role-based插件五

  1. 用户没加入角色前是没有访问权限的,将用户加入到角色baserole即可,但只有部分权限

role-based插件五

role-based插件五

2.7 Jenkins安装凭证管理插件

  1. 安装Credential Binding插件,安装完就可以看到多了凭据的功能

Credential-Binding插件一

Credential-Binding插件二

2.8 Jenkins普通用户密码认证

  1. 为了Jenkins能够拉取gitlab的代码,需要安装git插件

安装git插件

1
yum -y install git
  1. 创建普通用户凭证,注意这里的用户是gitlab创建好的用户

创建普通用户凭证

  1. 创建项目添加普通用户凭证

创建项目添加普通用户凭证

  1. build now构建

普通用户凭证build构建项目一

  1. Jenkins主机上查看是否构建成功

普通用户凭证build构建项目二

2.9 Jenkins使用ssh免密认证

  1. 在Jenkins主机上生产公钥私钥
1
ssh-keygen -t rsa
  1. 在gitlab上添加公钥

在gitlab上添加公钥

  1. 在Jenkins上添加ssh凭证

在Jenkins上添加ssh凭证

  1. 创建项目添加ssh凭证

创建项目添加ssh凭证

  1. build now构建项目后

ssh凭证build构建项目

2.10 Jenkins安装Maven

  1. 下载maven
1
2
3
4
wget https://mirrors.bfsu.edu.cn/apache/maven/maven-3/3.6.3/binaries/apache-maven-3.6.3-bin.tar.gz
mkdir maven
tar -xf apache-maven-3.6.3-bin.tar.gz
cp apache-maven-3.6.3-bin.tar.gz/* maven/
  1. 配置环境变量
1
2
3
4
5
6
vim /etc/profile
export JAVA_HOME=/usr/lib/jvm/java-1.8.0-openjdk
export MAVEN_HOME=/root/maven
export PATH=$PATH:$JAVA_HOME/bin:/$MAVEN_HOME/bin
source /etc/profile
mvn -v
  1. 全局工具配置里新增jdk和maven

新增jdk

新增maven

  1. 在系统配置里添加三个变量

新增变量

  1. 修改maven配置文件
1
2
3
4
5
6
7
8
9
mkdir /root/repo
vim /root/maven/conf/settings.xml
<localRepository>/root/repo</localRepository>
<mirror>
<id>aliyunmaven</id>
<mirrorOf>*</mirrorOf>
<name>阿里云公共仓库</name>
<url>https://maven.aliyun.com/repository/public</url>
</mirror>
  1. 测试maven构建项目

测试maven一

测试maven二

2.11 Tomcat安装和配置

  1. 安装jdk和tomcat
1
2
3
4
5
6
yum -y install java-1.8.0-openjdk*
wget https://downloads.apache.org/tomcat/tomcat-9/v9.0.43/bin/apache-tomcat-9.0.43.tar.gz
tar -xf apache-tomcat-9.0.43.tar.gz
mkdir /root/tomcat
mv apache-tomcat-9.0.43.tar.gz/* /root/tomcat
./root/tomcat/bin/startup.sh
  1. 添加tomcat管理用户
1
2
3
4
5
6
7
8
9
vim /root/tomcat/conf/tomcat-users.xml
<role rolename="tomcat"/>
<role rolename="role1"/>
<role rolename="manager-gui"/>
<role rolename="manager-script"/>
<role rolename="admin-gui"/>
<role rolename="admin-script"/>
<role rolename="manager-status"/>
<user username="tomcat" password="toortoor" roles="manager-gui,manager-script,tomcat,admin-gui,admin-script"/>
1
2
3
4
5
vim /root/tomcat/webapps/manager/META-INF/context.xml
<!--
<Valve className="org.apache.catalina.valves.RemoteAddrValve"
allow="127\.\d+\.\d+\.\d+|::1|0:0:0:0:0:0:0:1" />
-->
  1. 测试

tomcat安装一

tomcat安装二

三、Jenkins构建项目

Jenkins构建的项目类型主要为以下三种:

  1. 自由风格软件项目(freestyle project)
  2. Maven项目(maven project)
  3. 流水线项目(pipeline project)

3.1 自由风格软件项目构建

  1. 要将项目部署到tomcat服务器上,需要安装deploy to container插件

  2. Jenkins -> 新建items

freestyle一

  1. 使用ssh免密登录来拉取代码

freestyle二

  1. 使用maven编译打包

freestyle四

  1. 部署

freestyle五

  1. 再次构建后可以回到tomcat查看是否部署成功

freestyle六

3.2 Maven项目构建

  1. 安装Maven Integration插件

  2. 创建maven项目

maven一

  1. 构建设置,其余设置都相同

maven二

  1. 构建后查看是否部署成功

maven三

3.3 Pipeline project简介

pipeline就是一套运行在Jenkins上的工作流框架,将独立运行的单个或多个节点的任务连接起来,实现复杂流程的编排和可视化的工作。

优点有:

  • 自动地为所有分支创建流水线构建过程并拉取请求。
  • 在流水线上代码复查/迭代 (以及剩余的源代码)。
  • 对流水线进行审计跟踪。
  • 该流水线的真正的源代码,可以被项目的多个成员查看和编辑。

3.4 Pipeline流水线项目构建

  1. 安装pipeline插件
  2. 新建流水线项目

pipeline一

声明式流水线

  1. pipeline二

  2. pipeline三

  3. pipeline四

  4. pipeline五

  5. pipeline六

  6. pipeline七

3.5 Jenkinsfile脚本文件

pipeline脚本内容放在Jenkins服务器不好管理,所以就在项目添加一个Jenkinsfile文件并推送到gitlab上,实现直接拉取执行。

  1. 在项目下添加一个Jenkinsfile,将pipeline内容复制进去并推送到gitlab上

Jenkinsfile一

  1. 在pipeline项目中修改为在gitlab拉取Jenkinsfile文件

Jenkinsfile二

四、Jenkins构建细节

4.1 Jenkins常用的构建触发器

Jenkins内置4种构建触发器:

  • 触发远程构建
  • 其它工程构建后触发
  • 定时构建
  • 轮询SCM

4.2 触发远程构建

  1. 在项目中设置触发器

触发远程构建一

  1. 在浏览器输入JENKINS_URL/job/pipeline-project/build?token=TOKEN_NAME即可触发

触发远程构建二

4.3 其它工程构建后触发

是指某一工程构建后才会触发构建,这里测试freestyle工程构建后触发pipeline构建

  1. 在pipeline配置构建后触发

构建后触发一

  1. freestyle工程构建后就会触发pipeline构建

4.4 定时构建

五个*分别代表分时日月周,H/2则代表每分时日月周

定时构建一

4.5 轮询SCM

和定时构建一样是设置时间来进行构建,不过轮询只有在代码仓库发生变化后才会触发,相当于定时检查

注意:轮询会定时扫描仓库的代码,增大系统的开销,不建议使用

轮询SCM一

4.6 Gitlab Hook自动触发构建

SCM轮询是Jenkins服务器主动检测gitlab中的代码有没有发生变化,而gitlab的webhook可以实现代码变更后向Jenkins服务器主动发送构建请求,实现自动构建。

  1. 在Jenkins安装gitlab和gitlab hook插件

gitlabhook一

  1. 在gitlab配置中允许发出请求

gitlabhook二

  1. 在项目中添加第一步中的地址

gitlabhook三

  1. 配置Jenkins允许接受gitlab发送过来的请求

gitlabhook四

4.7 Jenkins参数化构建

前边实操演示的都是默认从master下拉取代码,如果要实现在其他分支拉取代码,就需要设置参数化构建。

  1. 在Jenkins的项目中添加参数,这里选择字符串参数

参数化构建一

  1. 修改Jenkinsfile文件中拉取代码部分的master为变量,并推送到gitlab

参数化构建二

  1. 在项目中添加一个v1分支

参数化构建三

参数化构建四

  1. 在Jenkins中拉取v1代码构建

参数化构建五

参数化构建六

4.8 配置邮件服务器发送构建结果

  1. 安装Email Extension插件

  2. 在Jenkins中配置

配置邮件服务器发送构建结果一配置邮件服务器发送构建结果二

配置邮件服务器发送构建结果三

配置邮件服务器发送构建结果四

  1. 在项目中创建一个email.html文件,添加以下内容并推送到gitlab中
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>${PROJECT_NAME}-第${BUILD_NUMBER}次构建日志</title>
</head>

<body leftmargin="8" marginwidth="0" topmargin="8" marginheight="4">
<table width="95%" cellpadding="0" cellspacing="0"
style="font-size: 11pt; font-family: Tahoma, Arial, Helvetica, sans-serif">
<tr>
<td>(本邮件是程序自动下发的,请勿回复!)<br/></td>
</tr>
<tr>
<td><h2>
<font color="#0000FF">构建结果 - ${BUILD_STATUS}</font>
</h2></td>
</tr>
<tr>
<td><br />
<b><font color="#0B610B">构建信息</font></b>
<hr size="2" width="100%" align="center" /></td>
</tr>
<tr>
<td>
<ul>
<li>项目名称 : ${PROJECT_NAME}</li>
<li>构建编号 : 第${BUILD_NUMBER}次构建</li>
<li>触发原因: ${CAUSE}</li>
<li>构建日志: <a href="${BUILD_URL}console">${BUILD_URL}console</a></li>
<li>构建 Url : <a href="${BUILD_URL}">${BUILD_URL}</a></li>
<li>工作目录 : <a href="${PROJECT_URL}ws">${PROJECT_URL}ws</a></li>
<li>项目 Url : <a href="${PROJECT_URL}">${PROJECT_URL}</a></li>
</ul>
</td>
</tr>
<tr>
<td><b><font color="#0B610B">Changes Since Last Successful Build:</font></b>
<hr size="2" width="100%" align="center" /></td>
</tr>
<tr>
<td>
<ul>
<li>历史变更记录 : <a href="${PROJECT_URL}changes">${PROJECT_URL}changes</a></li>
</ul> ${CHANGES_SINCE_LAST_SUCCESS,reverse=true, format="Changes for Build #%n:<br />%c<br />",showPaths=true,changesFormat="<pre>[%a]<br />%m</pre>",pathFormat=" %p"}
</td>
</tr>
<tr>
<td> <hr size="2" width="100%" align="center" /></td>
</tr>
<tr>
<td></td>
</tr>
<tr>
<td><b><font color="#0B610B">构建情况总览:</font></b>${TEST_COUNTS,var="fail"}<br/>
<hr size="2" width="100%" align="center" /></td>
</tr>
<tr>
<td><textarea cols="80" rows="30" readonly="readonly"
style="font-family: Courier New">${BUILD_LOG,maxLines=23}</textarea>
</td>
</tr>
</table>
</body>
</html>
  1. 在Jenkinsfile中添加发送email功能,post即指构建后操作
1
2
3
4
5
6
7
8
9
post {
always {
emailext(
subject: '构建通知:${PROJECT_NAME} - Build # ${BUILD_NUMBER} - ${BUILD_STATUS}!',
body: '${FILE,path="email.html"}',
to: 'chenqiming13@qq.com'
)
}
}
  1. 构建测试是否收到邮件

配置邮件服务器发送构建结果五

五、Jenkins+SonarQube代码审查

sonarqube图标

sonarqube是一个用于管理代码质量的开放平台,可以快速定位代码中潜在的错误。

环境要求:

  1. JDK11
  2. PostgreSQL

5.1 安装PostgreSQL

  1. 新版本的sonarqube不再支持MySQL,所以在Jenkins服务器上安装PostgreSQL并创建一个sonar数据库
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#安装postgresql
yum -y install https://download.postgresql.org/pub/repos/yum/9.6/redhat/rhel-7-x86_64/pgdg-redhat-repo-latest.noarch.rpm
yum -y install postgresql96-server postgresql96-contrib

#初始化postgresql
postgresql-9.6-setup initdb
systemctl enable postgresql-9.6.service
systemctl start postgresql-9.6.service

#初始化之后会自动创建postgres用户,切换用户
su - postgres

#psql进入命令行模式配,\q退出
psql
alter user postgres with password 'toortoor';
create database sonar;
create user sonar;
alter user sonar with password 'toortoor';
alter role sonar createdb;
alter role sonar superuser;
alter role sonar createrole;
alter database sonar owner to sonar;
\q

postgresql一

  1. 开启远程访问和远程连接
1
2
3
4
5
vim /var/lib/pgsql/9.6/data/postgresql.conf
listen_addresses = '*'
vim /var/lib/pgsql/9.6/data/pg_hba.conf
host all all 127.0.0.1/32 trust
host all all 192.168.88.1/32 trust
  1. 重启服务

5.2 安装SonarQube

  1. 安装sonarqube
1
2
3
4
5
6
wget https://binaries.sonarsource.com/Distribution/sonarqube/sonarqube-8.7.1.42226.zip
unzip sonarqube-8.7.1.42226.zip
mv sonarqube-8.7.1.42226 sonarqube
useradd sonar
chown -R sonar:sonar sonarqube/*
mv sonarqube /opt/sonarqube
  1. 修改sonar配置文件
1
2
3
4
5
6
vim sonarqube/conf/sonar.properties
sonar.jdbc.username=sonar
sonar.jdbc.password=toortoor
sonar.jdbc.url=jdbc:postgresql://localhost/sonarqube?currentSchema=my_schema
sonar.jdbc.url=jdbc:postgresql://localhost/sonar?currentSchema=public
sonar.web.port=9000
  1. 启动sonarqube
1
2
3
#只能用sonar用户启动
su sonar
./opt/sonarqube/bin/linux-x86-64/sonar.sh start
  1. 遇到的问题
1
2
3
4
5
6
#sonar用户线程数不够,*代表所有用户
cat /etc/security/limits.conf
* soft nofile 65536
* hard nofile 65536
* soft noproc 65535
* hard noproc 65535
  1. deploy.yaml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
apiVersion: apps/v1
kind: Deployment
metadata:
name: sonarqube
labels:
app: sonarqube
spec:
replicas: 1
selector:
matchLabels:
app: sonarqube
template:
metadata:
name: sonarqube
labels:
app: sonarqube
spec:
initContainers:
- name: init-sysctl
image: busybox
imagePullPolicy: IfNotPresent
command: ["sysctl", "-w", "vm.max_map_count=262144"]
securityContext:
privileged: true
containers:
- name: sonarqube
image: sonarqube:lts-community
imagePullPolicy: IfNotPresent
ports:
- containerPort: 9000
env:
# 此处用到了集群现有的pg
# 数据库用户sonarqube
# 数据库名sonarqube
- name: SONARQUBE_JDBC_USERNAME
value: "sonarqube"
- name: SONARQUBE_JDBC_PASSWORD
value: "dangerous"
- name: SONARQUBE_JDBC_URL
value: "jdbc:postgresql://dcs-installer-gitlab-postgresql.dcs-system:5432/sonarqube"
livenessProbe:
httpGet:
path: /sessions/new
port: 9000
initialDelaySeconds: 60
periodSeconds: 30
readinessProbe:
httpGet:
path: /sessions/new
port: 9000
initialDelaySeconds: 60
periodSeconds: 30
failureThreshold: 6
resources:
limits:
cpu: 2000m
memory: 2048Mi
requests:
cpu: 1000m
memory: 1024Mi

---
apiVersion: v1
kind: Service
metadata:
name: sonarqube
spec:
type: NodePort
selector:
app: sonarqube
ports:
- port: 9000
targetPort: 9000
protocol: TCP

5.3 Jenkins整合SonarQube

实现代码审查一

  1. 安装sonarqube scanner插件
  2. 在Jenkins安装sonarqube scanner

实现代码审查二

  1. 在sonarqube中生成密钥

实现代码审查二

  1. 在系统配置中配置sonarqube server,利用刚刚生成的密钥

实现代码审查五

实现代码审查六

5.4 实现代码审查

非流水线项目添加代码审查

  1. 在项目中添加构建步骤

实现代码审查七

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#Must be unique in a given SonarQube instance
# SonarQube创建项目时的key
sonar.projectKey=freestyle_project

#This is the name and version displayed in the SonarQube UI.
# 项目名称
sonar.projectName=freestyle_project
# 项目版本
sonar.projectVersion=1.0

#Path is relative to the sonar-project.properties file.
#This property is optional if sonar.modules is set.
sonar.sources=.
# 忽略扫描的目录
sonar.exclusions=**/test/**,**/target/**

sonar.java.source=11
sonar.java.target=11

#Encoding of the source code.Default is default system encoding.
sonar.sourceEncoding=UTF-8
  1. 构建项目
  2. 回到sonarqube就可以看到提交的代码审查了

实现代码审查八

流水线项目添加代码审查

  1. 在项目中创建sonar-project.properties
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#Must be unique in a given SonarQube instance
sonar.projectKey=pipeline_project

#This is the name and version displayed in the SonarQube UI.
sonar.projectName=pipeline_project
sonar.projectVersion=1.0

#Path is relative to the sonar-project.properties file.
#This property is optional if sonar.modules is set.
sonar.sources=.
sonar.exclusions=**/test/**,**/target/**

sonar.java.source=11
sonar.java.target=11

#Encoding of the source code.Default is default system encoding.
sonar.sourceEncoding=UTF-8
  1. 修改Jenkinsfile文件
1
2
3
4
5
6
7
8
9
10
11
12
stage('code checking'){
steps {
script {
//引入scanner工具
scannerHome = tool 'sonar-scanner'
}
//引入sonarqube服务器环境
withSonarQubeEnv('sonarqube') {
sh "${scannerHome}/bin/sonar-scanner"
}
}
}
  1. 将Jenkinsfile和sonar-project.properties推送到gitlab
  2. 构建项目后在sonarqube就可以看到代码审查了

实现代码审查九

六、Jenkins+Docker+SpringCloud微服务持续集成

微服务架构

大致流程:

  1. 开发人员push代码到gitlab。
  2. Jenkins服务器进行编译打包并构建镜像推送到harbor镜像仓库(服务器的JAVA环境要与项目对应)。
  3. Jenkins服务器触发远程命令使生产服务器(tomcat)从私有仓库拉取镜像。
  4. 生产服务器(tomcat)生成容器,项目上线。
  5. 用户访问。

6.1 Harbor部署

  1. 下载
1
curl -O https://github.com/goharbor/harbor/releases/download/v2.3.4/harbor-offline-installer-v2.3.4.tgz
  1. 修改 harbor.yml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
cp harbor.yml.tmpl harbor.yml
egrep -v '^#|^$|*#' harbor.yml
hostname: 192.168.88.30
http:
port: 81
harbor_admin_password: toortoor
database:
password: toortoor
max_idle_conns: 100
max_open_conns: 900
data_volume: /root/cicd/harbor/data
trivy:
ignore_unfixed: false
skip_update: false
insecure: false
jobservice:
max_job_workers: 10
notification:
webhook_job_max_retry: 10
chart:
absolute_url: disabled
log:
level: info
local:
rotate_count: 50
rotate_size: 200M
location: /root/cicd/harbor/var
_version: 2.3.0
proxy:
http_proxy:
https_proxy:
no_proxy:
components:
- core
- jobservice
- trivy
  1. docker-compose.yaml 文件中指定 harbor 访问端口为 80,避免与 gitlab 冲突, 修改为 81

  2. 通过脚本部署

1
2
./prepare
./install.sh
  1. 创建私有仓库

微服务CICD一

  1. 修改 daemon.json,让 docker 信任此仓库
1
2
3
4
{
"registry-mirrors": ["https://5v5rh037.mirror.aliyuncs.com"],
"insecure-registries": ["192.168.88.30:81"]
}
  1. 登录远程镜像仓库
1
docker login -u admin -p toortoor 192.168.88.30:81

6.2 提交代码到Gitlab

微服务CICD二

6.3 Jenkins创建流水线工程

  1. 创建工程

微服务CICD三


微服务CICD四


微服务CICD七

  1. 在项目根目录下创建 Jenkinsfile,通过语法生成器进行编写

微服务CICD五

  1. Jenkinsfile 拉取代码部分

微服务CICD六

  1. 在流水线工程中创建参数,如果有多个服务就可以根据参数选择进行构建

微服务CICD十一

  1. Jenkinsfile 添加编译打包微服务工程部分,以及通过 Dockerfile 构建镜像部分
  • 在 pom.xml 中添加 Dockerfile 依赖
1
2
3
4
5
6
7
8
9
10
<plugin>
<groupId>com.spotify</groupId>
<artifactId>dockerfile-maven-plugin</artifactId>
<version>1.4.10</version>
<configuration>
<repository>${project.artifactId}</repository>
<buildArgs>
<JAR_NAME>target/${project.build.finalname}.jar</JAR_NAME>
</buildArgs>
</plugin>
  • 在项目根目录添加 Dockerfile
1
2
3
4
5
6
FROM openjdk:11
ARG JAR_NAME
WORKDIR /usr/src/myapp
EXPOSE 8080
COPY ./${JAR_NAME} /usr/src/myapp/
ENTRYPOINT ["java", "-jar", "/usr/src/myapp/${JAR_NAME}"]
  1. Jenkinsfile 添加上传镜像至 Harbor 部分
  • 在 Jenkins 中添加 Harbor 账户凭证,以便于上传代码,这里用到的是 Harbor 的用户名和密码

微服务CICD八

  • 保留好凭证ID

微服务CICD九

  • 在语法生成器中生成新语法用于登录 Harbor 仓库

微服务CICD十

  1. Jenkins 安装 Publish Over SSH 插件,能够对远程主机发送 shell 命令,安装完后先给远程主机发送公钥
1
ssh-copy-id root@192.168.88.30
  • 发送完后在系统配置进行配置

微服务CICD十二


微服务CICD十三

  • 接着生成流水线语法,去远程执行脚本文件 test.sh

微服务CICD十四

test.sh

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
#!/bin/bash

harbor_url=$1
project_name=$2
image_name=$3
service_port=$4

# 删除none容器
echo "docker rmi none..."
docker images | grep none | awk '{ print "docker rmi "$3 }' | sh &>/dev/null

# 检查是否有已运行容器
num=`docker ps | grep $project_name | wc -l`
if [ $num -gt 0 ]
then
docker stop $project_name && docker rm $project_name &>/dev/null
if [ `echo $?` -eq 0 ]
then
echo "docker stop $project_name && docker rm $project_name [OK]"
else
echo "ERROR: docker stop $project_name && docker rm $project_name"
fi
fi

# 检查是否有重命名镜像
temp=`docker images | grep $image_name | awk '{ print $1":"$2 }' | wc -l`
if [ $temp -eq 1 ]
then
docker rmi $image_name &>/dev/null
if [ `echo $?` -eq 0 ]
then
echo "docker rmi $image_name [OK]"
else
echo "ERROR: docker rmi $image_name"
fi
fi

# 登录镜像仓库
echo "docker login repository..."
docker login -u admin -p toortoor $harbor_url &>/dev/null
if [ `echo $?` -ne 0 ]
then
echo "ERROR: failed to login repository"
else
echo "docker login repository [OK]"
fi

# 拉取镜像
echo "docker pull image..."
docker pull $image_name &>/dev/null
if [ `echo $?` -ne 0 ]
then
echo "ERROR: failed to pull image"
else
echo "docker pull image [OK]"
fi

# 运行容器
echo "docker run container..."
docker run -d --name $project_name -p $service_port:8080 $image_name &>/dev/null
if [ `echo $?` -ne 0 ]
then
echo "ERROR: failed to run container"
else
echo "docker run container [OK]"
fi
  1. 最终的 Jenkinsfile
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
// git凭证id
def git_auth = "bad37d0d-7260-43a2-a350-9abf6e99753a"
// git凭证url
def git_url = "http://192.168.88.30/test_group/test_project.git"
// Harbor地址
def harbor_url = "192.168.88.30:81"
// 镜像仓库名称
def harbor_repository = "test"
// Harbor用户凭证ID
def harbor_auth = "c147d241-a254-48d6-be1c-0d5f0964ce9a"
// 镜像版本号
def image_version = "1.0"
// 拉取的微服务名称
def project_name = "test"
// 微服务所需端口号
def service_port = "8081"


node {
// 拉取代码
stage('Pull Code') {
git branch: "${branch}", credentialsId: "${git_auth}", url: "${git_url}"
}

// 编译,打包微服务工程,构建镜像
stage('Mvn clean package and Docker build') {
sh "mvn clean package dockerfile:build"
}

// 上传镜像至Harbor
stage('Push image to Harbor') {
// 镜像命名,打标签
def image_name = "${project_name}:${image_version}"
sh "docker tag ${project_name}:latest ${harbor_url}/${harbor_repository}/${image_name} && docker rmi ${project_name}:latest"
// 登录Harbor仓库
withCredentials([usernamePassword(credentialsId: "${harbor_auth}", passwordVariable: 'harbor_password', usernameVariable: 'harbor_user')]) {
sh "docker login -u ${harbor_user} -p ${harbor_password} ${harbor_url}"
}
// 镜像上传
sh "docker push ${harbor_url}/${harbor_repository}/${project_name}:${image_version}"
}

// 远程连接服务器拉取镜像运行
stage('Pull image and Running container') {
// 获取镜像命名
def image_name = "${project_name}:${image_version}"
sshPublisher(publishers: [sshPublisherDesc(configName: '192.168.88.30', transfers: [sshTransfer(cleanRemote: false, excludes: '', execCommand: './root/test.sh $harbor_url $project_name $harbor_url/$harbor_repository/image_name $service_port', execTimeout: 120000, flatten: false, makeEmptyDirs: false, noDefaultExcludes: false, patternSeparator: '[, ]+', remoteDirectory: '', remoteDirectorySDF: false, removePrefix: '', sourceFiles: '')], usePromotionTimestamp: false, useWorkspaceInPromotion: false, verbose: false)])
}
}

6.4 微服务架构CICD优化

从以上配置中可以看到,每次构建都只能选择一个服务,且部署的服务器也只有一台,是不符合生产环境的,主要需求有以下三个:

  • 多个微服务同时构建流水线工程
  • 批量处理镜像
  • 将微服务部署服务器集群
  1. 安装 Extended Choice Parameter 插件,实现多个微服务同时构建

微服务CICD十五

  1. 在创建流水线时设置选项

微服务CICD十六


微服务CICD十七


微服务CICD十八

  1. 将公钥下发,并在系统设置的 Publish over SSH 中添加多台主机
  2. 在流水线工程中添加多一个多选项配置,用于选择要部署的服务器

微服务CICD十九

  1. 修改流水线语法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
// git凭证id
def git_auth = "bad37d0d-7260-43a2-a350-9abf6e99753a"
// git凭证url
def git_url = "http://192.168.88.30/test_group/test_project.git"
// Harbor地址
def harbor_url = "192.168.88.30:81"
// 镜像仓库名称
def harbor_repository = "test"
// Harbor用户凭证ID
def harbor_auth = "c147d241-a254-48d6-be1c-0d5f0964ce9a"
// 镜像版本号
def image_version = "1.0"

node {
// 获取服务名
def selectedProjectNames = "${project_name}".split(",")
// 获取集群地址
def selectedServices = "${publish_server}".split(",")

// 拉取代码
stage('Pull Code') {
git branch: "${branch}", credentialsId: "${git_auth}", url: "${git_url}"
}

// 编译,打包微服务工程,构建镜像
stage('Mvn clean package and Docker build') {
sh "mvn clean package dockerfile:build"
}

// 上传镜像至Harbor
stage('Push image to Harbor') {
for(int i=0; i<selectedProjectNames.length; i++) {
// 获取每个选项
def project_info = selectedProjectNames[i]
// 获取选项中的微服务名称
def current_project_name = "${project_info}".split("@")[0]
// 镜像命名,打标签
def image_name = "${current_project_name}:${image_version}"
sh "docker tag ${current_project_name}:latest ${harbor_url}/${harbor_repository}/${image_name} && docker rmi ${current_project_name}:latest"
// 登录Harbor仓库
withCredentials([usernamePassword(credentialsId: "${harbor_auth}", passwordVariable: 'harbor_password', usernameVariable: 'harbor_user')]) {
sh "docker login -u ${harbor_user} -p ${harbor_password} ${harbor_url}"
}
// 镜像上传
sh "docker push ${harbor_url}/${harbor_repository}/${current_project_name}:${image_version}"
}
}

// 远程连接服务器拉取镜像运行
stage('Pull image and Running container') {
for(int i=0; i<selectedProjectNames.length; i++) {
// 获取每个选项
def project_info = selectedProjectNames[i]
// 获取选项中的微服务名称
def current_project_name = "${project_info}".split("@")[0]
// 获取选项中的微服务所需端口
def current_project_port = "${project_info}".split("@")[1]
// 需要拉取的镜像
def image_name = "${current_project_name}:${image_version}"
// 遍历所有服务器,分别部署
for(int j=0; j<selectedServices.length; j++) {
// 获取当前服务器
def current_server = selectedServices[j]
// 加上参数配置,根据配置文件的不同选择不同的服务器部署
if(current_server == "192.168.88.31") {
activeProfile = activeProfile+"serviceName-server1"
}else if(current_server == "192.168.88.32") {
activeProfile = activeProfile+"serviceName-server2"
}
// 远程执行脚本
sshPublisher(publishers: [sshPublisherDesc(configName: "${current_server}", transfers: [sshTransfer(cleanRemote: false, excludes: '', execCommand: './root/test.sh $harbor_url $current_project_name $harbor_url/$harbor_repository/$image_name $current_project_port $activeProfile', execTimeout: 120000, flatten: false, makeEmptyDirs: false, noDefaultExcludes: false, patternSeparator: '[, ]+', remoteDirectory: '', remoteDirectorySDF: false, removePrefix: '', sourceFiles: '')], usePromotionTimestamp: false, useWorkspaceInPromotion: false, verbose: false)])
}
}
}
}
  1. 修改 test.sh
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
#!/bin/bash

harbor_url=$1
project_name=$2
image_name=$3
service_port=$4
profile=$6

# 删除none容器
echo "docker rmi none..."
docker images | grep none | awk '{ print "docker rmi "$3 }' | sh &>/dev/null

# 检查是否有已运行容器
num=`docker ps | grep $project_name | wc -l`
if [ $num -gt 0 ]
then
docker stop $project_name && docker rm $project_name &>/dev/null
if [ `echo $?` -eq 0 ]
then
echo "docker stop $project_name && docker rm $project_name [OK]"
else
echo "ERROR: docker stop $project_name && docker rm $project_name"
fi
fi

# 检查是否有重命名镜像
temp=`docker images | grep $image_name | awk '{ print $1":"$2 }' | wc -l`
if [ $temp -eq 1 ]
then
docker rmi $image_name &>/dev/null
if [ `echo $?` -eq 0 ]
then
echo "docker rmi $image_name [OK]"
else
echo "ERROR: docker rmi $image_name"
fi
fi

# 登录镜像仓库
echo "docker login repository..."
docker login -u admin -p toortoor $harbor_url &>/dev/null
if [ `echo $?` -ne 0 ]
then
echo "ERROR: failed to login repository"
else
echo "docker login repository [OK]"
fi

# 拉取镜像
echo "docker pull image..."
docker pull $image_name &>/dev/null
if [ `echo $?` -ne 0 ]
then
echo "ERROR: failed to pull image"
else
echo "docker pull image [OK]"
fi

# 运行容器
echo "docker run container..."
docker run -d --name $project_name -p $service_port:8080 $image_name $profile &>/dev/null
if [ `echo $?` -ne 0 ]
then
echo "ERROR: failed to run container"
else
echo "docker run container [OK]"
fi

七、基于K8S的CICD

基于 Kubernetes 平台来实现 CICD 功能。

K8SCICD一

7.1 Jenkins对接K8S

  • 首先通过 bitnami helm 部署 Jenkins(Master),需开放 jnlp 50000 端口
1
2
3
4
5
6
7
# 添加repo源
helm repo add bitnami https://charts.bitnami.com/bitnami
# 下载chart到本地
helm pull bitnami/jenkins
# 根据需求修改values.yaml
# 部署
helm install jenkins jeknins/

K8SCICD十六

  • RBAC 授权,SA 名为 jenkins,后续会用到
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
apiVersion: v1
kind: ServiceAccount
metadata:
name: jenkins
namespace: nextcloud

---

kind: Role
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
name: jenkins
namespace: nextcloud
rules:
- apiGroups: [""]
resources: ["pods"]
verbs: ["create","delete","get","list","patch","update","watch"]
- apiGroups: [""]
resources: ["pods/exec"]
verbs: ["create","delete","get","list","patch","update","watch"]
- apiGroups: [""]
resources: ["pods/log"]
verbs: ["get","list","watch"]
- apiGroups: [""]
resources: ["secrets"]
verbs: ["get"]
- apiGroups: ["extensions", "apps"]
resources: ["deployments"]
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
- apiGroups: [""]
resources: ["services"]
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]

---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: RoleBinding
metadata:
name: jenkins
namespace: nextcloud
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: jenkins
subjects:
- kind: ServiceAccount
name: jenkins
namespace: nextcloud
  • 获取刚刚创建的 ca.crt 信息
1
kubectl get secret jenkins-token-xxx -oyaml | awk '/ca.crt/{ print $2 }' | base64 -d
  • 在 Jenkins 创建凭据

K8SCICD二

  • 添加云,这里用到刚刚创建的凭据

K8SCICD三


K8SCICD四


设置 Pod Template,即 Jenkins Slave,将包含 jnlp 一个容器,来实现编译打包等功能。

K8SCICD五


K8SCICD六


这里需挂载宿主机的 docker.sock、docker 和 kubectl 的二进制文件。

K8SCICD七


然后使用到刚刚创建的 SA。

K8SCICD八


测试链接。

K8SCICD九

7.2 构建前准备

需要在项目的根目录下准备好 Dockerfile、Jenkinsfile、deploy.yaml。

  • Jenkinsfile
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
// gitlab及harbur凭据认证等信息
def git_auth = "764f29f3-8955-4551-a23a-659aa4c8deaa"
def git_url = "http://192.168.159.12:30098/root/nextcloud.git"
def harbor_url = "192.168.159.101"
def harbor_repository = "nextcloud"
def harbor_auth = "7ebc36f8-73a3-417f-864f-24856490b1a2"
def project_name = "nextcloud"

// jenkins-slave为pod template中的name
node('jenkins-slave') {

// jnlp为pod template中的容器名称
container('jnlp') {

// pipeline步骤
stage('Git Clone') {
git branch: "${branch}", credentialsId: "${git_auth}", url: "${git_url}"
}

stage('Docker Build') {
sh "docker build -t ${harbor_url}/${harbor_repository}/${project_name}:${branch}-${version} ."
}

stage('Docker Push') {
withCredentials([usernamePassword(credentialsId: "${harbor_auth}", passwordVariable: 'harbor_password', usernameVariable: 'harbor_user')]) {
sh "docker login -u ${harbor_user} -p ${harbor_password} ${harbor_url}"
}
sh "docker push ${harbor_url}/${harbor_repository}/${project_name}:${branch}-${version}"
sh "docker rmi ${harbor_url}/${harbor_repository}/${project_name}:${branch}-${version}"
}
}

// 在deploy.yaml中将镜像tag设为了build-tag,方便通过sed修改
stage('Sed YAML') {
sh "sed -i 's#build-tag#${branch}-${version}#g' deploy.yaml"
}

stage('Deploy to K8s') {
sh "kubectl apply -f deploy.yaml"
}

}

7.3 创建流水线项目

这里添加了两个字符参数,用于构建前选择分支和 tag 的标签定义。

K8SCICD十


K8SCICD十一


构建测试,会发现集群中创建了一个 jenkins-slave-xxx 的 pod,pod 中包含 jnlp 一个容器,通过观察日志可发现与 Jenkins Master 进行了连接。

K8SCICD十三


Jenkins 中查看流水线进度。

K8SCICD十四


流水线结束后,通过 kubectl 可看到项目已部署,且使用的镜像是刚刚构建好的。

K8SCICD十五