K8s+Jenkins+GitLab+Harbor+CICD持续集成、持续部署实战
引
两种部署方式介绍
Pipeline部署到Docker
通过Jenkins
新建项目,以pipelin
的方式从GitLab
拉取代码,通过Maven
编译代码、docker
构建镜像并推送到Harbor
仓库,最后以docker
方式用docker
命令部署到服务器。
Pipeline部署到K8s
通过Jenkins
新建项目,以pipelin
的方式从GitLab
拉取代码,通过Maven
编译代码、docker
构建镜像并推送到Harbor
仓库,最后以k8s
的yaml
配置清单方式,用kubectl
命令部署到服务器。
说明
Jenkins
新建项目pipeline
有两种方式Jenkins
页面配置pipeline
:Pipeline script
Jenkins
页面指定从GitLab
等仓库获取:Pipeline script from SCM
推荐第二种,每次在GitLab
等仓库修改后还可以打标签,保留之前的配置;以下两种方式都会介绍,并且会举例这两种方式各自部署到Docker
和K8s
的案例。
环境准备
针对所需要的服务部署教程,可通过以下地址跳转至我的其它相关博客,便于参考
环境变量
环境包下载
Java & Maven 环境配置
K8s
所有节点执行以下操作,以下仅拿k8s-master02
举例
# 解压 Java & Maven
[root@k8s-master02 ~]# tar xf packages/jdk-8u171-linux-x64.tar.gz -C /usr/local/
[root@k8s-master02 ~]# tar xf packages/apache-maven-3.5.0-bin.tar.gz -C /usr/local/
# 添加工具变量
[root@k8s-master02 ~]# echo 'export PATH=$PATH:/usr/local/jdk1.8.0_171/bin/' >> /etc/profile
[root@k8s-master02 ~]# echo 'export PATH=$PATH:/usr/local/apache-maven-3.5.0/bin/' >> /etc/profile
# 刷新环境变量
[root@k8s-master02 ~]# source /etc/profile
# 查看 Java & Maven 版本
[root@k8s-master02 ~]# java -version | grep version
openjdk version "1.8.0_312"
OpenJDK Runtime Environment (build 1.8.0_312-b07)
OpenJDK 64-Bit Server VM (build 25.312-b07, mixed mode)
[root@k8s-master02 ~]# mvn -version | grep version
Java version: 1.8.0_312, vendor: Red Hat, Inc.
OS name: "linux", version: "4.18.0-348.2.1.el8_5.x86_64", arch: "amd64", family: "unix"
服务部署
Docker
部署:Docker安装+使用
Jenkins
部署:Jenkins部署(推荐使用Docker
方式)
Harbor
部署:Jenkins流水线容器化+Harbor私有仓库部署
GitLab
部署:Git+GitLab介绍+部署+使用
K8s
集群部署:二进制方式部署点我~ Kubeadmin方式部署点我~
Jenkins免密
Jenkins
容器必须针对k8s
所有节点都做免密,方便免密部署服务
[root@k8s-master02 ~]# docker exec -it jenkins bash
root@a871f73778b5:/# cd /root
# 生成秘钥文件
root@a871f73778b5:~# ssh-keygen
# 分发至各个节点
root@a871f73778b5:~# for i in 1 2 3 6 7;do ssh-copy-id -i .ssh/id_rsa.pub root@172.23.0.24$i;done
PS:若分发失败,则复制jenkins公钥,添加至各个节点的 /root/.ssh/authorized_keys
root@a871f73778b5:~# cat .ssh/id_rsa.pub
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDbpy4VHzQ3Fl8JFxetee/xNVF8adoFdt14RtFeDmf6dDfbaAJxl29P5dj86LjMLj9OvAupJ0KmRTqpiZDvIcwMxFneW+DUEoxSIB/t++i3TswHimX8Ips4zZKLdNRG88KPUNXsreQAPT79lzdX5TDSpgm9+xBD33zEhKfbj87KZCNlvT60b4GBRBBt1BAdM69+kLs6C5DLXNFfdqlyA6frtGZwk3HJn0zYucWMLwzBILuPsWLMMm+3oUOej92Y1ZsBpn96mVNSTzpt3Uozcp5v+4flle5g4cp0sk3KVc2u8wl0neNJbFgCrl8NXEQZjRE0iw/Ry4Z5vn6sXDTG3AqAP/dJ0ng2stCncFk0aZFGoBy+dK8ilzbiHUbbb/F/5NEWBxqQ/CBFwRZknX9JcvgxO9koPTH8VkTrSYRyqU4BCFtid0b9ePJzeHVMootwfsg9BrvkrWMA2adhK5yTU1Xtvy+HGLy/ixzLcOyn/nLpeCYwXFlZHvORwJqEVsV2hQM= root@a871f73778b5
# 手动添加jenkins公钥到所有节点文件,若无此文件用ssh-keygen一路回车创建即可
[root@k8s-node02 ~]# echo "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDbpy4VHzQ3Fl8JFxetee/xNVF8adoFdt14RtFeDmf6dDfbaAJxl29P5dj86LjMLj9OvAupJ0KmRTqpiZDvIcwMxFneW+DUEoxSIB/t++i3TswHimX8Ips4zZKLdNRG88KPUNXsreQAPT79lzdX5TDSpgm9+xBD33zEhKfbj87KZCNlvT60b4GBRBBt1BAdM69+kLs6C5DLXNFfdqlyA6frtGZwk3HJn0zYucWMLwzBILuPsWLMMm+3oUOej92Y1ZsBpn96mVNSTzpt3Uozcp5v+4flle5g4cp0sk3KVc2u8wl0neNJbFgCrl8NXEQZjRE0iw/Ry4Z5vn6sXDTG3AqAP/dJ0ng2stCncFk0aZFGoBy+dK8ilzbiHUbbb/F/5NEWBxqQ/CBFwRZknX9JcvgxO9koPTH8VkTrSYRyqU4BCFtid0b9ePJzeHVMootwfsg9BrvkrWMA2adhK5yTU1Xtvy+HGLy/ixzLcOyn/nLpeCYwXFlZHvORwJqEVsV2hQM= root@a871f73778b5" >> /root/.ssh/authorized_keys
Jenkins连接到K8s
后期通过
K8s
方式部署需要接通到Jenkins
,则进行如下配置
配置Jenkins
手动生成证书
生成密码为此处为:
123456
上图的ca.crt
秘钥再此生成后填入即可
最终生成文件:cert.pfx
上传到jenkins >>
系统配置>>
全局凭据>>
添加凭据(如图)
创建凭据
配置k8s云集群
通过
jenkins >>
系统配置>> k8s
集群配置>>
集群pod
模板加载凭据
Pipeline部署到Docker
部署到
Docker
,其实就是用Docker
的方式部署应用到服务器
配置Jenkins任务
新建任务
任务名(test-pipeline
) >> 选择流水线 >> 确定
参数化构建
如图配置
pipeline内容
说明:
1、registry是
Harbor
仓库,尽量用域名代替,用ip+端口
镜像名称只能写一个冒号,否则报错2、代码编译内的环境变量,必须在此处添加,测试在
jenkins
容器添加的话,容器重启失效
#!/usr/bin/env groovy
// 定义变量名
def registry = "172.23.0.244:30004"
def project = "build"
def app_name = "nginx-jenkins-docker"
def image_name = "${registry}/${project}/${app_name}-${Branch}-246-${BUILD_NUMBER}"
def git_address = "ssh://git@k8s-master03:30022/root/test-docker2.git"
def docker_registry_auth = "40134f96-3fc0-4345-b172-74d729de2366"
def git_auth = "859ba7d0-2aa9-45bb-a304-44cee35d24ef"
// 流水线脚本
pipeline {
agent any
stages {
stage('拉取代码'){
steps {
checkout([$class: 'GitSCM', branches: [[name: '${Branch}']], userRemoteConfigs: [[credentialsId: "${git_auth}", url: "${git_address}"]]])
}
}
stage('代码编译'){
steps {
sh """
# 环境变量
PATH=$PATH:/usr/local/jdk1.8.0_171/bin
JAVA_HOME=/usr/local/jdk1.8.0_171/
PATH=$PATH:/usr/local/apache-maven-3.5.0/bin
# 代码编译
# mvn clean package -Dmaven.test.skip=true
sleep 2
"""
}
}
stage('构建镜像'){
steps {
withCredentials([usernamePassword(credentialsId: "${docker_registry_auth}", passwordVariable: 'password', usernameVariable: 'username')]) {
sh """
docker login -u ${username} -p '${password}' ${registry}
echo '
#FROM ${registry}/base/spring-boot:v2
FROM nginx:1.18.0
LABEL maitainer Paul
#COPY ehu-c2c-deploy/target/ehu-c2c.jar /app.jar
COPY nginx.jenkins.docker.conf /etc/nginx/conf.d/
COPY index.html /etc/nginx/conf.d/
COPY nginx.conf /etc/nginx/
RUN rm -f /etc/nginx/conf.d/default.conf
CMD ["nginx", "-g", "daemon off;"]
' > Dockerfile
docker build -t ${app_name} .
docker tag ${app_name}:latest ${image_name}
docker push ${image_name}
docker rmi ${image_name}
docker rmi ${app_name}
"""
}
}
}
stage('部署到Docker'){
steps {
withCredentials([usernamePassword(credentialsId: "${docker_registry_auth}", passwordVariable: 'password', usernameVariable: 'username')]) {
sh """
ssh root@k8s-node01 -p 22 "docker rm -f nginx-jenkins-docker |true"
ssh root@k8s-node01 -p 22 "docker login -u ${username} -p '${password}' ${registry}"
ssh root@k8s-node01 -p 22 "docker pull ${image_name}"
ssh root@k8s-node01 -p 22 "docker run -itd --init -p 80:80 --name nginx-jenkins-docker -h n1 ${image_name}"
"""
}
}
}
}
// 若安装了jenkins钉钉插件,可以不配置此段代码,在项目配置页面直接勾选即可
post {
success {
//当此Pipeline成功时打印消息
echo 'success'
dingtalk robot:'46d25bcf-120d-468d-8204-9b4dedf8d23f',
type: 'LINK',
title:"构建成功!",
text:["升级服务器:246\n- IP访问:http://172.23.0.246:80 \n- 域名访问:nginx.jenkins.docker"],
messageUrl:"http://jenkins.peng.cn/job/test-pipeline2/"
}
failure {
//当此Pipeline失败时打印消息
echo 'failure'
dingtalk robot:'46d25bcf-120d-468d-8204-9b4dedf8d23f',
type: 'LINK',
title:"构建失败!",
text:["- 升级服务器:246\n- IP访问:http://172.23.0.246:80 \n- 域名访问:nginx.jenkins.docker"],
messageUrl:"http://jenkins.peng.cn/job/test-pipeline2/"
}
}
}
配置Gitlab代码
创建项目
进入Gitlab
新建项目如图,创建后会生成一坨代码,手动粘贴到jenkins
容器内,我已创建了test
,所以用test2
举例截图
Git推拉测试
[root@k8s-master02 ~]# docker exec -it jenkins bash
root@a871f73778b5:/# cd /var/jenkins_home/
root@a871f73778b5:/var/jenkins_home# 输入页面生成的代码,如下回车
# Git global setup
git config --global user.name "root"
git config --global user.email "1710724925@qq.com"
# Create a new repository
git clone ssh://git@k8s-master03:30022/root/test-docker2.git
cd test-docker2
touch README.md
git add README.md
git commit -m "add README"
git push -u origin master
# Push an existing folder
cd existing_folder
git init
git remote add origin ssh://git@k8s-master03:30022/root/test-docker2.git
git add .
git commit -m "Initial commit"
git push -u origin master
# Push an existing Git repository
cd existing_repo
git remote rename origin old-origin
git remote add origin ssh://git@k8s-master03:30022/root/test-docker2.git
git push -u origin --all
git push -u origin --tags
# 以下为输出结果:推拉测试成功!刷新gitlab页面会产生Readme.md文件
Cloning into 'test-docker2'...
warning: You appear to have cloned an empty repository.
[master (root-commit) f2cec84] add README
1 file changed, 0 insertions(+), 0 deletions(-)
create mode 100644 README.md
Enumerating objects: 3, done.
Counting objects: 100% (3/3), done.
Writing objects: 100% (3/3), 209 bytes | 209.00 KiB/s, done.
Total 3 (delta 0), reused 0 (delta 0), pack-reused 0
To ssh://k8s-master03:30022/root/test-docker2.git
* [new branch] master -> master
Branch 'master' set up to track remote branch 'master' from 'origin'.
bash: cd: existing_folder: No such file or directory
Reinitialized existing Git repository in /var/jenkins_home/test-docker2/.git/
error: remote origin already exists.
On branch master
Your branch is up to date with 'origin/master'.
nothing to commit, working tree clean
Branch 'master' set up to track remote branch 'master' from 'origin'.
Everything up-to-date
bash: cd: existing_repo: No such file or directory
Branch 'master' set up to track remote branch 'master' from 'origin'.
Everything up-to-date
Everything up-to-date
新建配置文件
创建3
个文件,并插入以下代码块内容,根据自身情况调整即可:
nginx.conf
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log notice;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
log_format access_json '{"@timestamp":"$time_iso8601",'
'"host":"$server_addr",'
'"clientip":"$remote_addr",'
'"size":$body_bytes_sent,'
'"responsetime":$request_time,'
'"upstreamtime":"$upstream_response_time",'
'"upstreamhost":"$upstream_addr",'
'"http_host":"$host",'
'"url":"$uri",'
'"domain":"$host",'
'"xff":"$http_x_forwarded_for",'
'"referer":"$http_referer",'
'"status":"$status"}';
access_log /var/log/nginx/access.log access_json;
sendfile on;
#tcp_nopush on;
keepalive_timeout 65;
#gzip on;
include /etc/nginx/conf.d/*.conf;
}
nginx.jenkins.docker.conf
server {
listen 80;
server_name nginx.jenkins.docker.cn;
charset utf-8;
#access_log /var/log/nginx/host.access.log main;
location / {
root /etc/nginx/conf.d/;
index index.html index.htm;
}
}
index.html
我是 test2-docker!
修改index.html内容
TAG1
在
Gialab
内针对当前版本打标签,Jenkins
后续更新代码只需打标签就可指定升级或回滚,不选择默认从master
分支拉取最新代码
TAG2
增加
v2
内容,提交代码
构建测试
构建v1版本
构建v2版本
回滚v1版本
Jenkinsfile方式
每次都要通过
jenkins
界面来配置Jenkinsfile
,并不好维护管理,下面我们将Jenkinsfile
放到GitLab
代码仓库,从Jenkins
引入GitLab
凭据,即可更新、回滚代码,这样代码和Jenkinsfile
就统一在GitLab
管理了!
新建Jenkins to GitLab凭据
供
Jenkins
从GiaLab
拉取代码时使用
项目页面新建Jenkinsfile
将pipeline内容粘贴到此
复制项目地址
粘贴项目地址
选择刚才新建的
GitLab
凭据
新增标签
为了好辨别,修改为
v3
,并新打一个标签加以区分
构建v3版本
回滚v2版本
至此,Pipeline
部署到Docker
全部完成!
Pipeline部署到K8s
部署到
K8s
,其实就是用kubectl
的方式部署应用到服务器,与Docker
方式区别如下:1、在
GitLab
新建yaml
配置清单,并写入部署内容2、修改
Jenkinsfile
内docker
部署命令改为kubectl
方式PS:要在
jenkins
容器内使用kubectl、docker
命令,需启动jenkins
时做好挂载映射PS:可以不新建
Jenkins
任务,直接用test-pipeline2
项目,然后修改Gitlab
代码即可,新增一个yaml
清单并修改Jenkinsfile
,但为了好区分,此处复制新建一份Jenkins
任务
配置Jenkins任务
新建任务
任务名(test-pipeline-jenkinsfile2
) >> 选择流水线 >> 输入复制的项目名 >> 确定
Jenkinsfile
修改处:
git_address
stage('yaml')
stage('部署到K8s')
messageUrl
#!/usr/bin/env groovy def registry = "172.23.0.244:30004" def project = "build" def app_name = "nginx-jenkins-docker" def image_name = "${registry}/${project}/${app_name}-${Branch}-246-${BUILD_NUMBER}" def git_address = "ssh://git@k8s-master03:30022/root/test-docker2.git" def docker_registry_auth = "40134f96-3fc0-4345-b172-74d729de2366" def git_auth = "859ba7d0-2aa9-45bb-a304-44cee35d24ef" pipeline { agent any stages { stage('拉取代码'){ steps { checkout([$class: 'GitSCM', branches: [[name: '${Branch}']], userRemoteConfigs: [[credentialsId: "${git_auth}", url: "${git_address}"]]]) } } stage('代码编译'){ steps { sh """ # 环境变量 PATH=$PATH:/usr/local/jdk1.8.0_171/bin JAVA_HOME=/usr/local/jdk1.8.0_171/ PATH=$PATH:/usr/local/apache-maven-3.5.0/bin # 代码编译 # mvn clean package -Dmaven.test.skip=true sleep 2 """ } } stage('构建镜像'){ steps { withCredentials([usernamePassword(credentialsId: "${docker_registry_auth}", passwordVariable: 'password', usernameVariable: 'username')]) { sh """ docker login -u ${username} -p '${password}' ${registry} echo ' #FROM ${registry}/base/spring-boot:v2 FROM nginx:1.18.0 LABEL maitainer Paul #COPY ehu-c2c-deploy/target/ehu-c2c.jar /app.jar COPY nginx.jenkins.docker.conf /etc/nginx/conf.d/ COPY index.html /etc/nginx/conf.d/ COPY nginx.conf /etc/nginx/ RUN rm -f /etc/nginx/conf.d/default.conf CMD ["nginx", "-g", "daemon off;"] ' > Dockerfile docker build -t ${app_name} . docker tag ${app_name}:latest ${image_name} docker push ${image_name} docker rmi ${image_name} docker rmi ${app_name} """ } } } // 与docker方式不同,从此处开始修改 stage('YAML'){ steps { sh """ sed -i "s#{VERSION}#${image_name}#g" nginx-jenkins-k8s-deoloy.yaml """ } } stage('部署到K8s'){ steps { withCredentials([usernamePassword(credentialsId: "${docker_registry_auth}", passwordVariable: 'password', usernameVariable: 'username')]) { sh """ ssh root@k8s-node01 -p 22 "docker login -u ${username} -p '${password}' ${registry}" ssh root@k8s-node01 -p 22 "docker pull ${image_name}" kubectl apply -f nginx-jenkins-k8s-deoloy.yaml -n test
// 当yaml更改时,利用apply可以重新部署
kubectl apply -f nginx-jenkins-k8s-deoloy.yaml -n test
// 当yaml未更改、修改了代码文件时,Pod可能不会重启,故用rollout restart来滚动更新
// 即:先创建新Pod,再删除旧Pod,不会影响当前Pod的用户使用体验,从而达到无缝切换
kubectl rollout restart deployment -n test nginx"""
}
}
}
}
post {
success {
//当此Pipeline成功时打印消息
echo 'success'
dingtalk robot:'46d25bcf-120d-468d-8204-9b4dedf8d23f',
type: 'LINK',
title:"构建成功!",
text:["升级服务器:246\n- IP访问:http://172.23.0.246:80 \n- 域名访问:nginx.jenkins.docker"],
messageUrl:"http://jenkins.peng.cn/job/test-pipeline-jenkinsfile2/"
}
failure {
//当此Pipeline失败时打印消息
echo 'failure'
dingtalk robot:'46d25bcf-120d-468d-8204-9b4dedf8d23f',
type: 'LINK',
title:"构建失败!",
text:["- 升级服务器:246\n- IP访问:http://172.23.0.246:80 \n- 域名访问:nginx.jenkins.docker"],
messageUrl:"http://jenkins.peng.cn/job/test-pipeline-jenkinsfile2/"
}
}
}
配置Gitlab
代码
不用创建项目,直接在原项目内进行配置
新建配置清单
nginx-jenkins-k8s-deoloy.yaml
kind: Deployment
apiVersion: apps/v1
metadata:
name: nginx
namespace: test
labels:
app: nginx
spec:
replicas: 1
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
nodeName: k8s-node01
containers:
- name: nginx
## 变量名,通过Jenkinsfile定义dockerfile的sed命令替换为最新构建的版本,花括号可加可不加
image: {VERSION}
# IfNotPresent:默认值,本地有则使用本地镜像,不拉取
# Always:总是拉取
# Never:只使用本地镜像,从不拉取
# PS:但由于每次构建都定义了新的版本号,所以本地肯定没有,所以会拉取,没具体定义到版本号比如latest或固定版本,则会生效
imagePullPolicy: IfNotPresent
ports:
- containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: nginx
labels:
app: nginx
spec:
ports:
- port: 80
protocol: TCP
targetPort: 80
nodePort: 31000
selector:
app: nginx
type: NodePort
修改index.html内容
TAG1
修改文件内容,部署后加以区分,打标签为
v4
TAG2
修改文件内容,部署后加以区分,打标签为
v5
构建测试
构建v4版本
构建v5版本
选择
v5
或master
,或不选择,都是拉取最新的代码
回滚v4版本
Jenkinsfile方式
同上~
同上面
Docker
方式部署的Jenkfile
方式配置,此处不再详细阐述,新建Jenkinsfile
,将pipeline
内容粘贴进去,修改jenkins
界面为如下配置
文件列表概览
修改index.html
TAGv6
构建测试
构建v6版本
回滚v5版本
回滚失败说明
例如想回滚到
v1
版本,这是不可以的,因为Jenkinsfile
是master的最新配置,内部指令有部分命令比如sed
修改过nginx-jenkins-k8s-deoloy.yaml
,而v1~v3
是没有用到K8s
的方式部署,所以nginx-jenkins-k8s-deoloy.yaml
文件也就不存在,则就会出现报错的现象,理解即可,如图~
回滚失败修复
要想回滚到
v1~v3
版本,则需复制最开始的Jenkinsfile
内容,粘贴到GitLab
的Jenkinsfile
进行构建,或修改Jenkins
内的test-pipeline2
传统方式项目配置的pipeline
进行构建即可成功;其实
Docker
方式部署从v1~v6
没任何影响(要使用最初的docker的pipeline
),但K8s
是有影响的,是由于从v4
版本开始,加入了sed、kubectl
等命令,会检测不到部分需修改的文件。
Jenkinsfile方式
可以看到,Jenkinsfile
方式也部署了Docker
应用
Docker方式
方案建议
【方案1】:
test-pipeline-jenkinsfile2
项目用于>v3
之后的代码,在GitLab
配置(k8s方式部署
)【方案2】
test-pipeline2
项目用于回滚v1~v6
代码,在Jenkins
配置(Docker方式部署
)PS:第二种方案必须采用最开始的
docker run
方式的pipeline
,才能无限回滚,若采用kubectl
方式部署,则只能回滚到v1~v3
版本
由于我们当初编写Jenkinsfile
时是在Jenkins
页面修改的pipeline
替换为kubectl
,而后又将Jenkinsfile
迁移至GitLab
,故修改之前就应该备份,等Jenkinsfile
在GitLab
方式部署无误,再回到Jenkins
将pipeline
配置改为最初的Docker
方式,这样就实现了上面【方案1、2
】建议的效果!
- 情况
1
:从docker
迁移到k8s
的场景(v1~v3
版本):采用【1+2
】方案即可 - 情况
2
:k8s/docker
场景,全版本都采用Jenkinsfile
方式即可
集成Sonarqube代码质量检测
Sonarqube
:是一个用于管理源代码质量的平台,它可以从快速的不同维度检测代码质量,可以定位代码中潜在的错误、错误。它支持包括Java、Python、Php、C/C++ 、C#、HTML、JavaScript、PL/SQL、Objective C
等二十五种编程语言的代码质量管理与检测。可作为我们日常开发中检测代码质量的重要工具。此处采用
K8s
方式部署Sonarqube
代码质量检测工具,并集成到DevOps
中
Sonar
可以从以下七个维度检测代码质量,而作为开发人员至少需要处理前5
种代码质量问题:
- 不遵循代码标准
sonar
可以通过PMD,CheckStyle,Findbugs
等等代码规则检测工具规范代码编写
- 潜在的缺陷
sonar
可以通过PMD,CheckStyle,Findbugs
等等代码规则检测工具检 测出潜在的缺陷。
- 糟糕的复杂度分布
- 文件、类、方法等,如果复杂度过高将难以改变,这会使得开发人员 难以理解它们, 且如果没有自动化的单元测试,对于程序中的任何组件的改变都将可能导致需要全面的回归测试
- 重复
- 显然程序中包含大量复制粘贴的代码是质量低下的,
sonar
可以展示 源码中重复严重的地方
- 显然程序中包含大量复制粘贴的代码是质量低下的,
- 注释不足或者过多
- 没有注释将使代码可读性变差,特别是当不可避免地出现人员变动 时,程序的可读性将大幅下降 而过多的注释又会使得开发人员将精力过多地花费在阅读注释上,亦违背初衷
- 缺乏单元测试
sonar
可以很方便地统计并展示单元测试覆盖率
- 糟糕的设计
- 通过
sonar
可以找出循环,展示包与包、类与类之间的相互依赖关系,可以检测自定义的架构规则 通过sonar
可以管理第三方的jar
包,可以利用LCOM4
检测单个任务规则的应用情况, 检测耦合
- 通过
说明
postgres-sonar.yaml
本集群中
kubernetes
底层存储使用的是nfs-client
,并且以nfs-client
作为存储创建了storageclass
便于动态创建pv
[root@k8s-master01 ~]# kubectl get nodes
NAME STATUS ROLES AGE VERSION
k8s-master01 Ready master 131d v1.21.8
k8s-master02 Ready master 131d v1.21.8
k8s-master03 Ready master 131d v1.21.8
k8s-node01 Ready node 131d v1.21.8
k8s-node02 Ready node 131d v1.21.8
[root@k8s-master01 ~]# kubectl get sc
NAME PROVISIONER RECLAIMPOLICY
nfs-client cluster.local/nfs-client-nfs-client-provisioner Retain Immediate true 131d
PostgreSQL
在
k8s
集群部署PostgreSQL
,需要将数据库的数据文件持久化,因此需要创建对应的pv
,本次安装通过storageclass
创建pv
由于
postgre
只需要集群内部连接,因此采用Headless service
来创建数据库对应的svc
,数据库的端口是5432
postgres-sonar-deploy.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: postgres-sonar
namespace: devops
labels:
app: postgres-sonar
spec:
replicas: 1
selector:
matchLabels:
app: postgres-sonar
template:
metadata:
labels:
app: postgres-sonar
spec:
containers:
- name: postgres-sonar
image: postgres:11.4
imagePullPolicy: IfNotPresent
ports:
- containerPort: 5432
env:
- name: POSTGRES_DB
value: "sonarDB"
- name: POSTGRES_USER
value: "sonarUser"
- name: POSTGRES_PASSWORD
value: "123456"
resources:
limits:
cpu: 1000m
memory: 2048Mi
requests:
cpu: 500m
memory: 1024Mi
volumeMounts:
- name: data
mountPath: /var/lib/postgresql/data
volumes:
- name: data
persistentVolumeClaim:
claimName: postgres-data
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: postgres-data
namespace: devops
spec:
accessModes:
- ReadWriteMany
storageClassName: "nfs-client"
resources:
requests:
storage: 1Gi
---
apiVersion: v1
kind: Service
metadata:
name: postgres-sonar
namespace: devops
labels:
app: postgres-sonar
spec:
clusterIP: None
ports:
- port: 5432
protocol: TCP
targetPort: 5432
selector:
app: postgres-sonar
Sonarqube
SonarQube
需要依赖数据库存储数据,且SonarQube7.9
及其以后版本将不再支持Mysql
,所以这里推荐设置PostgreSQL
作为SonarQube
的数据库
SonarQube版本:7.9.1
- 通过官方的
sonar
镜像部署,通过环境变量指定连接数据库的地址信息,同样通过storageclass
来提供存储卷,通过NodePort
方式暴露服务。- 与常规部署不同的是,这里对
sonar
通过init container
进行了初始化,执行修改了容器的vm.max_map_count
大小。修改这里的原因可以参考官方文档- 修改此权限需要授权能执行系统命令
securityContext: privileged: true
sonarqube-deploy.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: sonarqube
namespace: devops
labels:
app: sonarqube
spec:
replicas: 1
selector:
matchLabels:
app: sonarqube
template:
metadata:
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
ports:
- containerPort: 9000
env:
- name: SONARQUBE_JDBC_USERNAME
value: "sonarUser"
- name: SONARQUBE_JDBC_PASSWORD
value: "123456"
- name: SONARQUBE_JDBC_URL
value: "jdbc:postgresql://postgres-sonar:5432/sonarDB"
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
volumeMounts:
- mountPath: /opt/sonarqube/conf
name: data
subPath: conf
- mountPath: /opt/sonarqube/data
name: data
subPath: data
- mountPath: /opt/sonarqube/extensions
name: data
subPath: extensions
volumes:
- name: data
persistentVolumeClaim:
claimName: sonarqube-data
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: sonarqube-data
namespace: devops
spec:
accessModes:
- ReadWriteMany
storageClassName: "nfs-client"
resources:
requests:
# 一般建议10Gi
storage: 2Gi
---
apiVersion: v1
kind: Service
metadata:
name: sonarqube
namespace: devops
labels:
app: sonarqube
spec:
type: NodePort
ports:
- name: sonarqube
port: 9000
targetPort: 9000
nodePort: 30005
protocol: TCP
selector:
app: sonarqube
部署
[root@k8s-master01 sonarqube]# ls
1.postgres-sonar.yaml 2.sonarqube-deploy.yaml
[root@k8s-master01 sonarqube]# kubectl apply -f ./
deployment.apps/postgres-sonar created
persistentvolumeclaim/postgres-data created
service/postgres-sonar created
deployment.apps/sonarqube created
persistentvolumeclaim/sonarqube-data created
service/sonarqube created
[root@k8s-master01 sonarqube]# kubectl get pod,svc,pvc -n devops | grep sonar
pod/postgres-sonar-868597cc46-sn6m5 1/1 Running 0 75m
pod/sonarqube-7765668f8-82rxl 1/1 Running 0 74m
service/postgres-sonar ClusterIP None <none> 5432/TCP 75m
service/sonarqube NodePort 192.168.196.245 <none> 9000:30005/TCP 71m
persistentvolumeclaim/sonarqube-data Bound pvc-91cfff5f0645 2Gi RWX nfs-client 74m
配置Sonarqube
创建Token
登录到
Sonarqube
控制台创建Token
,用户名密码:admin/123456
说明:首次登录用户名密码为
admin/admin
,系统会强制用户修改密码
Sonarqube Token For Jenkins:45c225b8f07f251b675c45f943a5b00af299718f
创建项目
Sonarqube
创建于Jenkins/Git
同名的项目名称,便于区分
集成到Jenkinsfile
打开
Gitlab
对应项目,并写入Jenkinsfile
内
# Jenkinsfile代码质量检测
mvn sonar:sonar \
-Dsonar.projectKey=test-pipeline-jenkinsfile2 \
-Dsonar.host.url=http://172.23.0.244:30005 \
-Dsonar.login=45c225b8f07f251b675c45f943a5b00af299718f
# Sonarqube其他参数
sonar.projectKey=sonarTest(项目键值)
sonar.projectName=sonarTest(项目名称)
sonar.projectVersion=1.0(项目版本)
sonar.sourceEncoding=UTF-8(字符集编码)
sonar.language=java(代码语言)
sonar.sources=$WORKSPACE/XXX(指定sonar检查代码的地址多分支逗号分隔)
sonar.java.binaries=$WORKSPACE/XXX(二进制文件路径,此路径与sources一致即可)
配置Jenkins
安装插件
以下步骤在
Jenkins
内完成配置:
- 安装插件并重启
Jenkins:
Sonarqube
Multiple SCMs
:解决多分支代码拉取问题的插件- 创建全局凭据
- 系统配置:Sonarqube
连接到
Jenkins`- 全局工具配置:Jenkins配置全局工具(sonar-scanner扫描仪)
Maven认证
方式1:配置Jenkins凭据
凭据
Token
方式认证
方式2:配置Maven
用户名/密码方式认证
settgings.xml
:加入以下配置(需重启Jenkins
)重启后测试:
cd /var/jenkins_home/workspace/test-pipeline2/ && mvn sonar:sonar
<profile>
<id>sonar</id>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
<properties>
<sonar.login>admin</sonar.login>
<sonar.password>123456</sonar.password>
<sonar.host.url>http://172.23.0.244:30005</sonar.host.url>
</properties>
</profile>
</profiles>
<activeProfiles>
<activeProfile>sonar</activeProfile>
</activeProfiles>
构建测试
针对刚打的
v7
标签版本进行构建测试