基于Jenkins和Argocd实现CI/CD

CI/CD并不是陌生的东西,大部分企业都有自己的CI/CD,不过今天我要介绍的是使用Jenkins和GitOps实现CI/CD。

整体架构如下:

image-20201203113140692 image-20201204100252758 image-20201204100307692

涉及的软件以及版本信息如下:

软件 版本
kubernetes 1.17.9
docker 19.03.13
jenkins 2.249.3
argocd 1.8.0
gitlab 社区版11.8.1
sonarqube 社区版8.5.1
traefik 2.3.3
代码仓库 阿里云仓库

涉及的技术:

  • Jenkins shareLibrary
  • Jenkins pipeline
  • Jenkinsfile
  • Argocd
  • sonarqube api操作

软件安装

软件安装我这里不贴具体的安装代码了,所有的代码我都放在了github上,地址:https://github.com/Horus-K/kubernetes-software-yaml.git

所以这里默认你已经安装好所以软件了。

在Jenkins上安装如下插件

  • kubernetes
  • AnsiColor
  • HTTP Request
  • SonarQube Scanner
  • Utility Steps
  • Email Extension Template
  • Gitlab Hook
  • Gitlab
  • pipeline
  • timestamps

在Jenkins上配置Kubernetes集群信息

在系统管理–>系统配置–>cloud

image-20201203152604209 image-20201203163412452

在Jenkins上配置邮箱地址

系统设置–>系统配置–>Email

(1)设置管理员邮箱

image-20201203152809402

配置SMTP服务

image-20201203153105743

在Gitlab上准备一个测试代码

我这里有一个简单的java测试代码,地址如下:https://github.com/gazgeek/springboot-helloworld.git

可以将其导入到自己的gitlab仓库。

在Gitlab上创建一个共享库

创建一个共享库,我这里取名叫jenkins-share-lib,如下:

git@github.com:Horus-K/jenkins-share-lib.git

在Gitlab上创建一个YAML管理仓库

创建了一个叫devops-cd的共享仓库

https://github.com/Horus-K/jenkins-share-lib

在Jenkins上配置共享库

(1)需要在Jenkins上添加凭证

image-20201203154946283

(2)在Jenkins的系统配置里面配置共享库(系统管理–>系统配置)

image-20201203162518965

然后点击应用并保存

然后我们可以用一个简单的Jenkinsfile测试一下共享库,看配置是否正确。

在Jenkins上创建一个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
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
def labels = "slave-${UUID.randomUUID().toString()}"
// 引用共享库
@Library("jenkins_shareLibrary")

// 应用共享库中的方法
def tools = new org.devops.tools()

pipeline {
agent {
kubernetes {
label labels
yaml """
apiVersion: v1
kind: Pod
metadata:
labels:
some-label: some-label-value
spec:
volumes:
- name: docker-sock
hostPath:
path: /var/run/docker.sock
type: ''
containers:
- name: jnlp
image: registry.cn-hangzhou.aliyuncs.com/rookieops/inbound-agent:4.3-4
- name: maven
image: registry.cn-hangzhou.aliyuncs.com/rookieops/maven:3.5.0-alpine
command:
- cat
tty: true
- name: docker
image: registry.cn-hangzhou.aliyuncs.com/rookieops/docker:19.03.11
command:
- cat
tty: true
volumeMounts:
- name: docker-sock
mountPath: /var/run/docker.sock
"""
}
}
stages {
stage('Checkout') {
steps {
script{
tools.PrintMes("拉代码","green")
}
}
}
stage('Build') {
steps {
container('maven') {
script{
tools.PrintMes("编译打包","green")
}
}
}
}
stage('Make Image') {
steps {
container('docker') {
script{
tools.PrintMes("构建镜像","green")
}
}
}
}
}
}

到此共享库配置完成。

编写Jenkinsfile

整个java的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
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
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
def labels = "slave-${UUID.randomUUID().toString()}"

// 引用共享库
@Library("jenkins_shareLibrary")

// 应用共享库中的方法
def tools = new org.devops.tools()
def sonarapi = new org.devops.sonarAPI()
def sendEmail = new org.devops.sendEmail()
def build = new org.devops.build()
def sonar = new org.devops.sonarqube()

// 前端传来的变量
def gitBranch = env.branch
def gitUrl = env.git_url
def buildShell = env.build_shell
def image = env.image
def dockerRegistryUrl = env.dockerRegistryUrl
def devops_cd_git = env.devops_cd_git



pipeline {
agent {
kubernetes {
label labels
yaml """
apiVersion: v1
kind: Pod
metadata:
labels:
some-label: some-label-value
spec:
volumes:
- name: docker-sock
hostPath:
path: /var/run/docker.sock
type: ''
- name: maven-cache
persistentVolumeClaim:
claimName: maven-cache-pvc
containers:
- name: jnlp
image: registry.cn-hangzhou.aliyuncs.com/rookieops/inbound-agent:4.3-4
- name: maven
image: registry.cn-hangzhou.aliyuncs.com/rookieops/maven:3.5.0-alpine
command:
- cat
tty: true
volumeMounts:
- name: maven-cache
mountPath: /root/.m2
- name: docker
image: registry.cn-hangzhou.aliyuncs.com/rookieops/docker:19.03.11
command:
- cat
tty: true
volumeMounts:
- name: docker-sock
mountPath: /var/run/docker.sock
- name: sonar-scanner
image: registry.cn-hangzhou.aliyuncs.com/rookieops/sonar-scanner:latest
command:
- cat
tty: true
- name: kustomize
image: registry.cn-hangzhou.aliyuncs.com/rookieops/kustomize:v3.8.1
command:
- cat
tty: true
"""
}
}

environment{
auth = 'joker'
}

options {
timestamps() // 日志会有时间
skipDefaultCheckout() // 删除隐式checkout scm语句
disableConcurrentBuilds() //禁止并行
timeout(time:1,unit:'HOURS') //设置流水线超时时间
}


stages {
// 拉取代码
stage('GetCode') {
steps {
checkout([$class: 'GitSCM', branches: [[name: "${gitBranch}"]],
doGenerateSubmoduleConfigurations: false,
extensions: [],
submoduleCfg: [],
userRemoteConfigs: [[credentialsId: '83d2e934-75c9-48fe-9703-b48e2feff4d8', url: "${gitUrl}"]]])
}
}

// 单元测试和编译打包
stage('Build&Test') {
steps {
container('maven') {
script{
tools.PrintMes("编译打包","blue")
build.DockerBuild("${buildShell}")
}
}
}
}
// 代码扫描
stage('CodeScanner') {
steps {
container('sonar-scanner') {
script {
tools.PrintMes("代码扫描","green")
tools.PrintMes("搜索项目","green")
result = sonarapi.SearchProject("${JOB_NAME}")
println(result)

if (result == "false"){
println("${JOB_NAME}---项目不存在,准备创建项目---> ${JOB_NAME}!")
sonarapi.CreateProject("${JOB_NAME}")
} else {
println("${JOB_NAME}---项目已存在!")
}

tools.PrintMes("代码扫描","green")
sonar.SonarScan("${JOB_NAME}","${JOB_NAME}","src")

sleep 10
tools.PrintMes("获取扫描结果","green")
result = sonarapi.GetProjectStatus("${JOB_NAME}")

println(result)
if (result.toString() == "ERROR"){
toemail.Email("代码质量阈错误!请及时修复!",userEmail)
error " 代码质量阈错误!请及时修复!"

} else {
println(result)
}
}
}
}
}
// 构建镜像
stage('BuildImage') {
steps {
withCredentials([[$class: 'UsernamePasswordMultiBinding',
credentialsId: 'dockerhub',
usernameVariable: 'DOCKER_HUB_USER',
passwordVariable: 'DOCKER_HUB_PASSWORD']]) {
container('docker') {
script{
tools.PrintMes("构建镜像","green")
imageTag = tools.createVersion()
sh """
docker login ${dockerRegistryUrl} -u ${DOCKER_HUB_USER} -p ${DOCKER_HUB_PASSWORD}
docker build -t ${image}:${imageTag} .
docker push ${image}:${imageTag}
docker rmi ${image}:${imageTag}
"""
}
}
}
}
}
// 部署
stage('Deploy') {
steps {
withCredentials([[$class: 'UsernamePasswordMultiBinding',
credentialsId: 'ci-devops',
usernameVariable: 'DEVOPS_USER',
passwordVariable: 'DEVOPS_PASSWORD']]){
container('kustomize') {
script{
APP_DIR="${JOB_NAME}".split("_")[0]
sh """
git remote set-url origin http://${DEVOPS_USER}:${DEVOPS_PASSWORD}@${devops_cd_git}
git config --global user.name "Administrator"
git config --global user.email "coolops@163.com"
git clone http://${DEVOPS_USER}:${DEVOPS_PASSWORD}@${devops_cd_git} /opt/devops-cd
cd /opt/devops-cd
git pull
cd /opt/devops-cd/${APP_DIR}
kustomize edit set image ${image}:${imageTag}
git commit -am 'image update'
git push origin master
"""
}
}
}
}
}
// 接口测试
stage('InterfaceTest') {
steps{
sh 'echo "接口测试"'
}
}
}
// 构建后的操作
post {
success {
script{
println("success:只有构建成功才会执行")
currentBuild.description += "\n构建成功!"
// deploy.AnsibleDeploy("${deployHosts}","-m ping")
sendEmail.SendEmail("构建成功",toEmailUser)
// dingmes.SendDingTalk("构建成功 ✅")
}
}
failure {
script{
println("failure:只有构建失败才会执行")
currentBuild.description += "\n构建失败!"
sendEmail.SendEmail("构建失败",toEmailUser)
// dingmes.SendDingTalk("构建失败 ❌")
}
}
aborted {
script{
println("aborted:只有取消构建才会执行")
currentBuild.description += "\n构建取消!"
sendEmail.SendEmail("取消构建",toEmailUser)
// dingmes.SendDingTalk("构建失败 ❌","暂停或中断")
}
}
}
}

需要在Jenkins上创建两个凭证,一个id叫dockerhub,一个叫ci-devops,还有一个叫sonar-admin-user。

dockerhub是登录镜像仓库的用户名和密码。

ci-devops是管理YAML仓库的用户名和密码。

sonar-admin-user是管理sonarqube的用户名和密码。

然后将这个Jenkinsfile保存到shareLibrary的根目录下,命名为java.Jenkinsfile。

然后添加以下参数化构建。