K8s+Jenkins+GitLab+Harbor+CICD持续集成、持续部署实战

 

 

两种部署方式介绍

Pipeline部署到Docker

通过Jenkins新建项目,以pipelin的方式从GitLab拉取代码,通过Maven编译代码、docker构建镜像并推送到Harbor仓库,最后以docker方式用docker命令部署到服务器。

Pipeline部署到K8s

通过Jenkins新建项目,以pipelin的方式从GitLab拉取代码,通过Maven编译代码、docker构建镜像并推送到Harbor仓库,最后以k8syaml配置清单方式,用kubectl命令部署到服务器。

说明

  • Jenkins新建项目pipeline有两种方式
    • Jenkins页面配置pipelinePipeline script
    • Jenkins页面指定从GitLab等仓库获取:Pipeline script from SCM

推荐第二种,每次在GitLab等仓库修改后还可以打标签,保留之前的配置;以下两种方式都会介绍,并且会举例这两种方式各自部署到DockerK8s的案例。

环境准备

针对所需要的服务部署教程,可通过以下地址跳转至我的其它相关博客,便于参考

环境变量

环境包下载

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

image-20220520133951937

image-20220520134050852

image-20220520134115998

image-20220520134856311

手动生成证书

参考:Jenkins 使用证书连接Kubernetes集群

生成密码为此处为:123456
上图的ca.crt秘钥再此生成后填入即可
最终生成文件:cert.pfx上传到jenkins >>系统配置>>全局凭据 >>添加凭据(如图)

创建凭据

image-20220520140402046

image-20220520140245491

配置k8s云集群

通过jenkins >>系统配置>> k8s集群配置>>集群pod模板加载凭据

image-20220520135542713

image-20220520135649069

Pipeline部署到Docker

部署到Docker,其实就是用Docker的方式部署应用到服务器

配置Jenkins任务

新建任务

任务名(test-pipeline) >> 选择流水线 >> 确定

参数化构建

如图配置

image-20220519170823842image-20220519170904757

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举例截图

image-20220519174657266

image-20220519173618910

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

新建配置文件

image-20220519175044769

image-20220519175924513

image-20220519180008658

创建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分支拉取最新代码

image-20220519185147247

image-20220519185248570

TAG2

增加v2内容,提交代码

image-20220519185532812

image-20220519185640734

构建测试

构建v1版本

image-20220519185942093

image-20220519180621362

image-20220519183837047

image-20220519184912377

构建v2版本

image-20220519185828345

image-20220519185912168

回滚v1版本

image-20220519185942093

image-20220519190012170

Jenkinsfile方式

每次都要通过jenkins界面来配置Jenkinsfile,并不好维护管理,下面我们将Jenkinsfile放到GitLab代码仓库,从Jenkins引入GitLab凭据,即可更新、回滚代码,这样代码和Jenkinsfile就统一在GitLab管理了!

新建Jenkins to GitLab凭据

JenkinsGiaLab拉取代码时使用

image-20220519191539300

image-20220519191625083

image-20220519192425230

image-20220519192544132

项目页面新建Jenkinsfile

image-20220519190814936

将pipeline内容粘贴到此

image-20220519190939394

复制项目地址

image-20220519193015084

粘贴项目地址

选择刚才新建的GitLab凭据

image-20220519193143011

image-20220519192912015

新增标签

为了好辨别,修改为v3,并新打一个标签加以区分

image-20220519193503635

image-20220519193622001

构建v3版本

image-20220519193649553

image-20220519193727777

回滚v2版本

image-20220519193756263

image-20220519193813257

至此,Pipeline部署到Docker全部完成!

Pipeline部署到K8s

部署到K8s,其实就是用kubectl的方式部署应用到服务器,与Docker方式区别如下:

1、在GitLab新建yaml配置清单,并写入部署内容

2、修改Jenkinsfiledocker部署命令改为kubectl方式

PS:要在jenkins容器内使用kubectl、docker命令,需启动jenkins时做好挂载映射

PS:可以不新建Jenkins任务,直接用test-pipeline2项目,然后修改Gitlab代码即可,新增一个yaml清单并修改Jenkinsfile,但为了好区分,此处复制新建一份Jenkins任务

配置Jenkins任务

新建任务

任务名(test-pipeline-jenkinsfile2) >> 选择流水线 >> 输入复制的项目名 >> 确定

image-20220520100337902

image-20220520103347587

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代码

不用创建项目,直接在原项目内进行配置

新建配置清单

image-20220520102122567

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

image-20220520102415617

image-20220520102651544

image-20220520102742008

TAG2

修改文件内容,部署后加以区分,打标签为v5

image-20220520102610168

image-20220520102818971

构建测试

构建v4版本

image-20220520103935040

image-20220520104132839

构建v5版本

选择v5master,或不选择,都是拉取最新的代码

image-20220520104449577

image-20220520104537263

回滚v4版本

image-20220520103935040

image-20220520104644190

Jenkinsfile方式

同上~

同上面Docker方式部署的Jenkfile方式配置,此处不再详细阐述,新建Jenkinsfile,将pipeline内容粘贴进去,修改jenkins界面为如下配置

image-20220520110652324

文件列表概览

image-20220520102255202

修改index.html

TAGv6

image-20220520111024144

image-20220520111125857

构建测试

构建v6版本

image-20220520111315341

image-20220520111621373

回滚v5版本

image-20220520111815886

image-20220520111832755

回滚失败说明

例如想回滚到v1版本,这是不可以的,因为Jenkinsfile是master的最新配置,内部指令有部分命令比如sed修改过nginx-jenkins-k8s-deoloy.yaml,而v1~v3是没有用到K8s的方式部署,所以nginx-jenkins-k8s-deoloy.yaml文件也就不存在,则就会出现报错的现象,理解即可,如图~

image-20220520112324503

image-20220520112513446

回滚失败修复

要想回滚到v1~v3版本,则需复制最开始的Jenkinsfile内容,粘贴到GitLabJenkinsfile进行构建,或修改Jenkins内的test-pipeline2传统方式项目配置的pipeline进行构建即可成功;

其实Docker方式部署从v1~v6没任何影响(要使用最初的docker的pipeline),但K8s是有影响的,是由于从v4版本开始,加入了sed、kubectl等命令,会检测不到部分需修改的文件。

Jenkinsfile方式

image-20220520115032746

image-20220520115053323

可以看到,Jenkinsfile方式也部署了Docker应用

Docker方式

image-20220520115150215

image-20220520115252730

image-20220520115305092

方案建议

【方案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,故修改之前就应该备份,等JenkinsfileGitLab方式部署无误,再回到Jenkinspipeline配置改为最初的Docker方式,这样就实现了上面【方案1、2】建议的效果!

  • 情况1:从docker迁移到k8s的场景(v1~v3版本):采用【1+2】方案即可
  • 情况2k8s/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

image-20220523134521311

image-20220523134436180

image-20220523134701257

创建项目

Sonarqube创建于Jenkins/Git同名的项目名称,便于区分

image-20220524100739566

image-20220524100938229

image-20220524101118255

image-20220524101403304

集成到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一致即可)

image-20220524110647744

image-20220524111304087

image-20220524110954738

配置Jenkins

安装插件

以下步骤在Jenkins内完成配置:

  • 安装插件并重启Jenkins:
    • Sonarqube
    • Multiple SCMs:解决多分支代码拉取问题的插件
  • 创建全局凭据
  • 系统配置:Sonarqube 连接到Jenkins`
  • 全局工具配置:Jenkins配置全局工具(sonar-scanner扫描仪)

image-20220523135117354

Maven认证

方式1:配置Jenkins凭据

凭据Token方式认证

image-20220523135053740

image-20220523140524551

image-20220523141047412

方式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标签版本进行构建测试

image-20220524111115567

image-20220524111443234

image-20220524111529420

posted @ 2022-06-09 18:31  秋风お亦冷  阅读(860)  评论(0编辑  收藏  举报