Jenkins与外部系统集成

环境准备

软件版本

软件 版本
gitlab 14.3.0
Jenkins 2.303.1
Harbor 1.10.2
Sonar 9.1
Nexus 3.35.0-02
ArgoCD 2.1.3

部署gitlab

1
docker run --detach --hostname 10.8.242.28 --publish 443:443 --publish 80:80 --publish 1022:22 --name gitlab --restart always --volume /srv/gitlab/config:/etc/gitlab --volume /srv/gitlab/logs:/var/log/gitlab --volume /srv/gitlab/data:/var/opt/gitlab gitlab/gitlab-ce:12.10.3-ce.0

替换hostname为实际节点外网IP

部署Harbor

Harbor部署与管理
部署前先修改docker
编辑docker

1
2
3
4
vim /etc/docker/daemon.json
{
"insecure-registries" : ["0.0.0.0/0"]
}

重启docker

1
systemctl restart docker 

安装docker-compose

1
2
curl -L https://github.com/docker/compose/releases/download/1.24.1/docker-compose-`uname -s`-`uname -m` -o /usr/local/bin/docker-compose
chmod +x /usr/local/bin/docker-compose

下载harbor

1
https://github.com/goharbor/harbor/releases/download/v1.10.2/harbor-online-installer-v1.10.2.tgz

配置harbo.yaml

1
hostname: 172.31.48.86 //修改为实际节点IP

屏蔽https配置

安装harbor

1
./install.sh --with-clair
1
2
3
4
5
6
7
8
9
10
11
12
13
14
docker-compose  ps
Name Command State Ports
---------------------------------------------------------------------------------------------
clair /docker-entrypoint.sh Up (healthy) 6060/tcp, 6061/tcp
harbor-core /harbor/start.sh Up (healthy)
harbor-db /entrypoint.sh postgres Up (healthy) 5432/tcp
harbor-jobservice /harbor/start.sh Up
harbor-log /bin/sh -c /usr/local/bin/ ... Up (healthy) 127.0.0.1:1514->10514/tcp
harbor-portal nginx -g daemon off; Up (healthy) 80/tcp
nginx nginx -g daemon off; Up (healthy) 0.0.0.0:80->80/tcp
redis docker-entrypoint.sh redis ... Up 6379/tcp
registry /entrypoint.sh /etc/regist ... Up (healthy) 5000/tcp
registryctl /harbor/start.sh Up (healthy)

访问http://node_ip

admin/Harbor12345

创建测试项目

spring-petclinic官方示例项目地址:https://projects.spring.io/spring-petclinic/

本次实践针对Spring官方提供的spring-petclinic示例项目进行容器化部署,该项目采用Spring Boot + Thymeleaf开发,数据库可使用MySQL、H2等,本实践为操作方便直接使用内置的H2数据库。

注意:由于本实践采用的是H2内置数据库,所以每个应用实例的数据独立,也使得应用变成了有状态应用,而生产的最佳实践应该是数据采用外部存储,且应用采用无状态方式部署。

国内clone地址:https://gitee.com/wanshaoyuan/spring-petclinic.git

将此项目clone后上传到私有的gitlab中.

与Gitlab集成

安装gitlab插件

Gitlab中申请AccessToken

将申请成功的token保存好

配置Jenkins对接gitlab


添加凭证

测试连接

测试

读取gitlab中项目spring-petclinic项目中pom.xml文件

配置连接gitlab私有项目的密钥可以用ssh密钥也可以使用账号密码


分支处修改为main分支

构建

去cat这个文件输出内容

执行立即构建

输出结果为实际我们的pom.xml的文件内容

与Kubernetes集成构建分布式动态编译环境

安装Kubernetes插件

Jenkins与Kubernetes集成实现动态Slave Pod,需要安装Kubernetes插件:

  • kubernetes

安装Kubernetes Continuous Deploy插件

Jenkins访问kubernetes需要依赖于kubeconfig,为支持kubeconfig类型的凭据配置,需要安装Kubernetes Continuous Deploy插件:

  • Kubernetes Continuous Deploy

配置Kubernetes集群

配置 系统管理—>系统设置—>新增一个云

配置Jenkins URL,这里可以不配置api-server地址和证书key,连接kubernetes,所以默认会去读取放在JENKINS_HOME的.kube/目录的kubeconfig文件,用于连接集群。我这里是通过安装包的方式安装的Jenkins HOME在/var/lib/jenkins/目录,如果是通过容器方式启动,将kubeconfig文件直接放~/.kube/目录。
保存到Jenkins主机的config文件中

复制粘贴到Jenkins容器内的~/.kube/config文件中

1
2
docker exec -it jenkins mkdir /root/.kube/
docker cp config jenkins:/root/.kube/config

注意:
此方式Jenkins容器重启后,会将目录重新初始化覆盖掉,kubeconfig文件,生产环境可以直接挂载。

验证Pipeline流水线

以上Jenkins与Kubernetes的集成配置就基本完成了,下面在正式为Spring Petclinic应用创建Pipeline之前,先简单测试下Jenkins与Kubernetes集成Pipeline流水线是否正常。

  • 新建一个流水线类型的任务test-hello-pipeline

  • 准备流水线测试脚本
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
pipeline {
agent {
kubernetes {
cloud 'Kubernetes'
namespace 'default'
yaml """
apiVersion: v1
kind: Pod
spec:
containers:
- name: busybox
image: busybox
command:
- sleep
args:
- infinity
"""
}
}
stages {
stage('Test') {
steps {
container('busybox') {
sh "echo 'hello world'"
}
}
}
}
}

以上是一个简单的声明式pipeline,利用busybox镜像输出hello world字符串。

  • 添加流水线脚本

把测试脚本添加到任务的流水线脚本框中:

  • 保存流水线,并执行构建

  • 查看JOB运行结果

在Kubernetes中可以看到Jenkins自动创建了Pod来执行任务,任务执行完成以后,Pod自动删除。

Jenkins中查看下构建的控制台输出,正常输出了hello world

验证结果表明,Jenkins与Kubernetes配置成功,Pipeline运行正常。

Sonar-Qube对接实现代码质量扫描


安装sonarqube

初始化

1
2
3
helm repo add sonarqube https://SonarSource.github.io/helm-chart-sonarqube
helm repo update
kubectl create namespace sonarqube

helm安装sonarqube

1
helm install  sonarqube  --namespace sonarqube  sonarqube/sonarqube --set postgresql.persistence.enabled=false

注意:这里为了快速部署没有设置postgresql的持久化存储,有数据丢失风险,生产环境postgresql建议设计HA或持久化存储。
设置为NodePort对外暴露

1
kubectl patch svc sonarqube-sonarqube -p '{"spec": {"type": "NodePort"}}' -n sonarqube 

查看NodePort端口

1
2
3
4
5
kubectl  get svc -n sonarqube
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
sonarqube-postgresql ClusterIP 10.110.40.18 <none> 5432/TCP 4m41s
sonarqube-postgresql-headless ClusterIP None <none> 5432/TCP 4m41s
sonarqube-sonarqube NodePort 10.106.78.100 <none> 9000:30005/TCP 4m41s

查看启动成功

1
2
3
4
 kubectl get pod -n sonarqube
NAME READY STATUS RESTARTS AGE
sonarqube-postgresql-0 1/1 Running 0 4m7s
sonarqube-sonarqube-0 1/1 Running 0 4m7s

访问节点的30005端口

默认密码admin/admin

如果需要中文直接安装插件就好
administrator—->Marketplace
搜索Chinese—-安装

生成token
申请token
administrator—>security—>user—>token
保存生成token

Jenkins配置

安装插件
系统设置—>插件管理
安装SonarQube Scanner for Jenkins

配置插件
配置sonarQube-server

Server URL填写sonarqube的地址

Server authentication token填写刚刚创建的token,这里创建一个密钥
类型为Secret Text。Secret填写token详细信息,ID为此secret的名称

配置sonarQUbe-agent
系统管理->全局工具配置——>SonarQube Scanner
此处配置为自动安装

FreeStyle风格任务下配置SonarQube

以上面的test-gitlab项目的spring-petclinic为例
先执行maven构建出class文件,在进行扫描,因为sonarQube扫描的对象是.class而不是.java文件。

1
docker run -i -v /var/jenkins_home/workspace/:/tmp  maven:3.6-jdk-8 mvn -f /tmp/spring-petclinic/pom.xml clean package -DskipTests

在构建阶段添加”Execute SonarQube Scanner”

输入以下内容

1
2
3
4
5
6
7
sonar.projectKey=test
sonar.projectName=test
sonar.projectVersion=1.0
sonar.sources=src
sonar.java.binaries=target/classes
sonar.language=java
sonar.sourceEncoding=UTF-8

注:
sonar.projectKey=Test #sonar那显示project-key
sonar.projectName=Test #sonar那显示project名字
sonar.projectVersion=1.0 ##sonar那显示project版本
sonar.sources=src #指定要扫描的源码目录。
sonar.java.binaries=target/classes #指定java文件编译后class文件目录。
sonar.language=java #只扫描的语言。
sonar.sourceEncoding=UTF-8 #指定源码的编码格式,一般都会去指定为UTF-8。

执行构建
Jenkins处查看

sonar处查看

Jenkins-Pipeline风格任务下配置SonarQube

使用Pipeline流水线,需要在添加以下步骤

1、在对应的代码库的根目录创建sonar-project.properties

1
2
3
4
5
6
7
8
9
sonar.projectKey=test2
sonar.projectName=test2
sonar.projectVersion=1.0
sonar.sources=src
sonar.java.binaries=target/classes
sonar.java.source=1.8
sonar.java.target=1.8
sonar.language=java
sonar.sourceEncoding=UTF-8

Pipeline中添加以下步骤

Pipeline中添加以下步骤

1
2
3
4
5
6
7
8
9
10
stage('SonarQube analysis') {
steps {
script {
def sonarqubeScannerHome = tool name: 'SonarQubeScanner'
withSonarQubeEnv('sonar') {
sh "${sonarqubeScannerHome}/bin/sonar-scanner"
}
}
}
}

注:
1、SonarQubeScanner为全局工具配置中的SonarQube Scanner的配置名称。
2、withSonarQubeEnv配置的sonar变量为全局——>系统配置sonar-server的配置名称

清空workspace

1
rm -rf /var/jenkins_home/workspace/spring-petclinic

Sonattype-Nexus

Nexus是开源的制品库,可以用来存储一些代码构建后的制品如jar包,npm包和docker镜像等。也可以将存放制品后的仓库做为私服,供给给后面需要内网编译的软件使用。

部署安装

软件版本:3.35.0-02
本次部署为了更加方便和快捷,采用Docker方式部署
创建目录

1
mkdir /var/nexus-data && chown -R 200 /var/nexus-data

Docker运行

1
docker run -d -p 8081:8081 --name nexus -v /var/nexus-data:/nexus-data sonatype/nexus3

初始账号和密码访问
账号:admin
密码:

1
cat /var/nexus-data/admin.password

创建仓库

仓库分为三种类型,proxy、group、hosted。
Proxy:Repository是代理仓库,可以配置上游仓库地址,如阿里云仓库地址。当本地仓库不到时,去向配置的上游仓库查找。
hosted:供本地使用的本地仓库。
group:仓库组,将多个仓库合成一个组,查找jar包时,会按照仓库组中的仓库顺序下载jar包。

这里创建两个名为spring-petclinic-releases、spring-petclinic-snapshots,类型为hotsted的Maven2仓库。

releases库主要用于存储正式版的制品,snapshots存储持续集成过程中产生的制品。

这里可以根据情况进行修改为release或snapshots

maven处配置
在spring-petclinic目录下创建conf/settings.xml文件用于存放连接Nexus3的凭证信息,正常可以在maven_home或~/.m2/目录有这文件。因为这里是使用Docker进行构建编译,所以这里直接与业务代码放置在一起。
settings.xml文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?xml version="1.0" encoding="UTF-8"?>
<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0 http://maven.apache.org/xsd/settings-1.0.0.xsd">
<pluginGroups>
</pluginGroups>
<proxies>
</proxies>
<servers>
<server>
<id>releases</id>
<username>账号</username>
<password>密码</password>
</server>
<server>
<id>snapshots</id>
<username>账号</username>
<password>密码</password>
</server>
</servers>
</settings>

关闭https检测,因为Nexus3使用的是http方式对外暴露所以需要关闭maven构建时强行要求https链接

src/checkstyle/nohttp-checkstyle.xml
注释
<module name="io.spring.nohttp.checkstyle.check.NoHttpCheck"/>
注释后:
<!-- <module name="io.spring.nohttp.checkstyle.check.NoHttpCheck"/> -->

pom.xml文件添加以下内容

1
2
3
4
5
6
7
8
9
10
11
12
13
<distributionManagement>
<repository>
<!--id的名字可以任意取,但是在setting文件中的属性<server>的ID与这里一致-->
<id>releases</id>
<!--指向仓库类型为host(宿主仓库)的储存类型为Release的仓库-->
<url>http://172.16.0.195:8081/repository/mspring-petclinic-releases/</url>
</repository>
<snapshotRepository>
<id>snapshots</id>
<!--指向仓库类型为host(宿主仓库)的储存类型为Snapshot的仓库-->
<url>http://172.16.0.195:8081/repository/spring-petclinic-snapshots/</url>
</snapshotRepository>
</distributionManagement>

执行编译

1
docker run -i -v /root/spring-petclinic/:/tmp  maven:3.6-jdk-8 mvn -f /tmp/pom.xml --settings /tmp/conf/settings.xml clean deploy 

编译完成上传成功后

在spring-petclinic-snapshots仓库内可见上传来的jar包,对应的jar包后面也接上了对应的时间戳,方便进行分类。

如果要上传到release仓库,将pom.xml中的<version>2.5.0-SNAPSHOT</version>中的-SNAPSHOT字段删除就表示为正式版。

ArgoCD集成实现CD端对接

编写并上传部署spring-petclinic的yaml文件和Dockerfile文件

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
apiVersion: apps/v1
kind: Deployment
metadata:
name: spring-petclinic-0-0-1
spec:
selector:
matchLabels:
app: spring-petclinic
version: 0.0.1
replicas: 1
template:
metadata:
labels:
app: spring-petclinic
version: 0.0.1
spec:
containers:
- name: spring-petclinic
image: registry.cn-shenzhen.aliyuncs.com/yedward/spring-petclinic:0.0.1
resources:
limits:
memory: 2Gi
cpu: 1
ports:
- containerPort: 8080
livenessProbe:
failureThreshold: 3
httpGet:
path: /actuator/health/liveness
port: 8080
scheme: HTTP
initialDelaySeconds: 30
periodSeconds: 5
successThreshold: 1
timeoutSeconds: 2
readinessProbe:
failureThreshold: 3
httpGet:
path: /actuator/health/readiness
port: 8080
scheme: HTTP
initialDelaySeconds: 30
periodSeconds: 5
successThreshold: 2
timeoutSeconds: 2
---
apiVersion: v1
kind: Service
metadata:
name: spring-petclinic-svc-0-0-1
spec:
selector:
app: spring-petclinic
version: 0.0.1
ports:
- port: 8080
targetPort: 8080
type: NodePort

将yaml中的镜像地址改为实际的镜像仓库地址和项目名称。
Dockerfile

1
2
3
4
5
6
7
FROM registry.cn-shenzhen.aliyuncs.com/yedward/openjdk:8-jre-slim
# 企业实际场景中应该通过USER指定以非root用户运行
USER appuser
EXPOSE 8080
COPY target/*.jar /app/
WORKDIR /app
CMD java -jar -Xms1024m -Xmx1024m /app/spring-petclinic.jar

上传到gitlab中的spring-petclinic项目中

部署ArgoCD

单节点部署
使用官网快速部署

1
2
kubectl create namespace argocd
kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.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
NAME                                      READY   STATUS    RESTARTS   AGE
pod/argocd-application-controller-0 1/1 Running 0 5d6h
pod/argocd-dex-server-74588646d-sz9g8 1/1 Running 0 2d2h
pod/argocd-redis-5ccdd9d4fd-csthm 1/1 Running 1 5d6h
pod/argocd-repo-server-5bbb8bdf78-mxkv7 1/1 Running 0 18h
pod/argocd-server-789fb45964-82mzx 1/1 Running 0 18h


NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/argocd-dex-server ClusterIP 10.43.180.172 <none> 5556/TCP,5557/TCP,5558/TCP 5d6h
service/argocd-metrics ClusterIP 10.43.184.97 <none> 8082/TCP 5d6h
service/argocd-redis ClusterIP 10.43.4.233 <none> 6379/TCP 5d6h
service/argocd-repo-server ClusterIP 10.43.9.45 <none> 8081/TCP,8084/TCP 5d6h
service/argocd-server NodePort 10.43.48.239 <none> 80:31320/TCP,443:31203/TCP 5d6h
service/argocd-server-metrics ClusterIP 10.43.149.186 <none> 8083/TCP 5d6h


NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/argocd-dex-server 1/1 1 1 5d6h
deployment.apps/argocd-redis 1/1 1 1 5d6h
deployment.apps/argocd-repo-server 1/1 1 1 5d6h
deployment.apps/argocd-server 1/1 1 1 5d6h

NAME DESIRED CURRENT READY AGE
replicaset.apps/argocd-dex-server-74588646d 1 1 1 5d6h
replicaset.apps/argocd-redis-5ccdd9d4fd 1 1 1 5d6h
replicaset.apps/argocd-repo-server-5bbb8bdf78 1 1 1 5d6h
replicaset.apps/argocd-server-789fb45964 1 1 1 5d6h

NAME READY AGE
statefulset.apps/argocd-application-controller 1/1 5d6h

使用NodePort方式为对外暴露

1
kubectl patch svc argocd-server -n argocd -p '{"spec": {"type": "NodePort"}}'

访问dashboard

默认帐号为admin,密码通过secret获取

1
kubectl -n argocd get secret argocd-initial-admin-secret -o jsonpath="{.data.password}" | base64 -d

配置ArgoCD

配置对接gitlab
setting——>Repositories->Connect repo using HTTPS

如果对应的git是私有库,pull需要帐号密码则需要在argo设置中配置repo connect

填写对应的帐号密码,如果是自签名证书需要将CA附上

创建Project
setting——>Projects
项目是argocd中的管理对象,也与之对应的发布权限相关联。
创建项目,并配置DESTINATIONS,能够发布到哪些集群和命名空间

创建Application

创建完后

点击sync会自动将yaml文件部署到k8s集群中。
可以在Kubernetes集群中查看到

1
2
3
kubectl get pod 
NAME READY STATUS RESTARTS AGE
spring-petclinic-0-0-1-6695b96956-xx9nw 1/1 Running 0 28m

Harbor中创建对应项目

在harbor中创建spring-petclinic项目

gitlab Webhook配置

当前Jenkins进行CI构建还是基于手动点击运行,可以配置基于gitlab的触发事件进行调用,如push、merge、tag push等事件触发回调Jenkins自动执行CI
jenkins处打开项目触发器

生成连接Secret token保存下来

Gitlab配置:
root登录后,需要开放安全配置,允许本地local网络连接
在menu选择admin——>settings-——>Network——>Outbound requests

勾选

1
2
Allow requests to the local network from web hooks and services  
Allow requests to the local network from system hooks

将Jenkins的ip添加到白名单中,保存。

项目——>setting——>webhooks

填写Jenkins对应的回调地址和token

点击Test settings即可在Jenkins处看见已经开始的构建任务。

保存配置

test这里选择基于事件回调。查看Jenkins处是否开始自动执行任务。

Jenkins配置

Argo是检查到yaml文件变化会进行自动发布到k8s中,那么我们只需要在Jenkins中增加修改和上传yaml阶段即可。

完整的构建阶段shell
编译阶段shell

1
docker run -i -v /var/jenkins_home/workspace/:/tmp  maven:3.6-jdk-8 mvn -f /tmp/spring-petclinic/pom.xml clean package -DskipTests

代码扫描阶段

1
2
3
4
5
6
7
sonar.projectKey=test
sonar.projectName=test
sonar.projectVersion=1.0
sonar.sources=src
sonar.java.binaries=target/classes
sonar.language=java
sonar.sourceEncoding=UTF-8

镜像构建阶段

1
2
3
docker login -u useradmin -p password harbor_ip
docker build -t harbor_ip/spring-petclinic/spring-petclinic:$BUILD_NUMBER .
docker push harbor_ip/spring-petclinic/spring-petclinic:$BUILD_NUMBER

注:
1、这里使用Jenkins内置BUILD_NUMBER号为镜像tag,跟Jenkins的CI号是匹配的。
2、将上传镜像的账号密码修改为实际的账号密码。

发布更新部署yaml阶段

1
2
3
4
5
6
7
8
9
git clone http://username:password@1.13.173.7/root/spring-petclinic.git
git config --global user.email "root@example.com"
git config --global user.name "root"
git remote set-url origin http://username:password@1.13.173.7/root/spring-petclinic.git
sed -i "s/spring-petclinic:.*/spring-petclinic:$BUILD_NUMBER/g" spring-petclinic/deployment.yaml
cd spring-petclinic/
git add deployment.yaml
git commit -m "update yaml"
git push origin main

在配置一个构建后删除操作,避免构建后缓存影响下次构建

执行构建,构建成功后查看对应的k8s中的部署的业务镜像版本号是否与实际应用部署的环境变量相同。

1
2
3
4
5
6
7
8
9
kubectl describe pod/spring-petclinic-0-0-1-699954b589-h7n58

Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Scheduled 57m default-scheduler Successfully assigned default/spring-petclinic-0-0-1-7969df6996-dn2cc to rke-node2
Normal Pulled 57m kubelet Container image "1.13.173.7:8080/spring-petclinic/spring-petclinic:15" already present on machine
Normal Created 57m kubelet Created container spring-petclinic
Normal Started 57m kubelet Started container spring-petclinic

访问节点ip+spring-petclinic服务暴露出来的NodePort端口

这是一个宠物医院的管理系统,可以通过此页面进行宠物管理。

备注

Jenkins内置环境变量
直接访问${YOUR_JENKINS_HOST}/env-vars.html即可

1
2
3
4
5
6
7
8
9
10
11
12
13
BUILD_NUMBER, 唯一标识一次build,例如23;
BUILD_ID,基本上等同于BUILD_NUMBER,但是是字符串,例如2011-11-15_16-06-21;
JOB_NAME, job的名字,例如JavaHelloWorld;
BUILD_TAG,作用同BUILD_ID,BUILD_NUMBER,用来全局地唯一标识一此build,例如jenkins-JavaHelloWorld-23;
EXECUTOR_NUMBER, 例如0;
NODE_NAME,slave的名字,例如MyServer01;
NODE_LABELS,slave的label,标识slave的用处,例如JavaHelloWorld MyServer01;
JAVA_HOME, java的home目录,例如C:\Program Files (x86)\Java\jdk1.7.0_01;
WORKSPACE,job的当前工作目录,例如c:\jenkins\workspace\JavaHelloWorld;
HUDSON_URL = JENKINS_URL, jenkins的url,例如http://localhost:8000/ ;
BUILD_URL,build的url 例如http://localhost:8000/job/JavaHelloWorld/23/;
JOB_URL, job的url,例如http://localhost:8000/job/JavaHelloWorld/;
SVN_REVISION,svn 的revison, 例如4;