CI/CD并不是陌生的东西,大部分企业都有自己的CI/CD,不过今天我要介绍的是使用Jenkins和GitOps实现CI/CD。
整体架构如下:
涉及的软件以及版本信息如下:
软件 |
版本 |
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
在Jenkins上配置邮箱地址
系统设置–>系统配置–>Email
(1)设置管理员邮箱
配置SMTP服务
在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上添加凭证
(2)在Jenkins的系统配置里面配置共享库(系统管理–>系统配置)
然后点击应用并保存
然后我们可以用一个简单的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() 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构建成功!" sendEmail.SendEmail("构建成功",toEmailUser) } } failure { script{ println("failure:只有构建失败才会执行") currentBuild.description += "\n构建失败!" sendEmail.SendEmail("构建失败",toEmailUser) } } aborted { script{ println("aborted:只有取消构建才会执行") currentBuild.description += "\n构建取消!" sendEmail.SendEmail("取消构建",toEmailUser) } } } }
|
需要在Jenkins上创建两个凭证,一个id叫dockerhub,一个叫ci-devops,还有一个叫sonar-admin-user。
dockerhub是登录镜像仓库的用户名和密码。
ci-devops是管理YAML仓库的用户名和密码。
sonar-admin-user是管理sonarqube的用户名和密码。
然后将这个Jenkinsfile保存到shareLibrary的根目录下,命名为java.Jenkinsfile。
然后添加以下参数化构建。