1987WEB视界-分享互联网热点话题和事件

您现在的位置是:首页 > WEB开发 > 正文

WEB开发

基于Jenkins和Argocd实现CI—CD

1987web2023-10-06WEB开发57

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

整体架构如下:

devops.png

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

软件版本
kubernetes1.17.9
docker19.03.13
jenkins2.249.3
argocd1.8.0
gitlab社区版11.8.1
sonarqube社区版8.5.1
traefik2.3.3
代码仓库阿里云仓库

涉及的技术:

  • Jenkins shareLibrary

  • Jenkins pipeline

  • Jenkinsfile

  • Argocd

  • sonarqube api操作

软件安装

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

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

在Jenkins上安装如下插件

  • kubernetes

  • AnsiColor

  • HTTP Request

  • SonarQube Scanner

  • Utility Steps

  • Email Extension Template

  • Gitlab Hook

  • Gitlab

在Jenkins上配置Kubernetes集群信息

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

image.png

在Jenkins上配置邮箱地址

系统设置-->系统配置-->Email

(1)设置管理员邮箱

image.png

配置SMTP服务

image.png

在Gitlab上准备一个测试代码

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

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

在Gitlab上创建一个共享库

首先在gitlab上创建一个共享库,我这里取名叫shareLibrary,如下:

image.png

然后创建src/org/devops目录,并在该目录下创建一下文件。

image.png

它们的内容分别如下:

build.groovy

package org.devops// docker容器直接builddef DockerBuild(buildShell){sh"""${buildShell}"""}

sendEmail.groovy

package org.devops//定义邮件内容def SendEmail(status,emailUser){emailext body:"""UTF-8">8" marginwidth="0" topmargin="8" marginheight="4" offset="0">95%" cellpadding="0" cellspacing="0" style="font-size: 11pt; font-family: Tahoma, Arial, Helvetica, sans-serif">本邮件由系统自动发出,无需回复!
各位同事,大家好,以下为${JOB_NAME}项目构建信息
CC0000">构建结果 - ${status}"0B610B"
>构建信息""",subject: "Jenkins-${JOB_NAME}项目构建信息",to: emailUser}

sonarAPI.groovy

package ore.devops// 封装HTTP请求def HttpReq(requestType,requestUrl,requestBody){// 定义sonar api接口def sonarServer ="http://sonar.devops.svc.cluster.local:9000/api"result = httpRequest authentication:sonar-admin-user,httpMode: requestType,contentType:"APPLICATION_JSON",consoleLogResponseBody:true,ignoreSslErrors:true,requestBody: requestBody,url:"${sonarServer}/${requestUrl}"returnresult}// 获取soanr项目的状态def GetSonarStatus(projectName){def apiUrl ="project_branches/list?project=${projectName}"// 发请求response = HttpReq("GET",apiUrl,"")// 对返回的文本做JSON解析response = readJSON text:"""${response.content}"""// 获取状态值result = response["branches"][0]["status"]["qualityGateStatus"]returnresult}// 获取sonar项目,判断项目是否存在def SearchProject(projectName){def apiUrl ="projects/search?projects=${projectName}"// 发请求response = HttpReq("GET",apiUrl,"")println"搜索的结果:${response}"// 对返回的文本做JSON解析response = readJSON text:"""${response.content}"""// 获取total字段,该字段如果是0则表示项目不存在,否则表示项目存在result = response["paging"]["total"]// 对result进行判断if(result.toString() =="0"){return"false"}else{return"true"}}// 创建sonar项目def CreateProject(projectName){def apiUrl ="projects/create?name=${projectName}&project=${projectName}"// 发请求response = HttpReq("POST",apiUrl,"")println(response)}// 配置项目质量规则def ConfigQualityProfiles(projectName,lang,qpname){def apiUrl ="qualityprofiles/add_project?language=${lang}&project=${projectName}&qualityProfile=${qpname}"// 发请求response = HttpReq("POST",apiUrl,"")println(response)}// 获取质量阈IDdef GetQualityGateId(gateName){def apiUrl ="qualitygates/show?name=${gateName}"// 发请求response = HttpReq("GET",apiUrl,"")// 对返回的文本做JSON解析response = readJSON text:"""${response.content}"""// 获取total字段,该字段如果是0则表示项目不存在,否则表示项目存在result = response["id"]returnresult}// 更新质量阈规则def ConfigQualityGate(projectKey,gateName){// 获取质量阈idgateId = GetQualityGateId(gateName)apiUrl ="qualitygates/select?projectKey=${projectKey}&gateId=${gateId}"// 发请求response = HttpReq("POST",apiUrl,"")println(response)}//获取Sonar质量阈状态def GetProjectStatus(projectName){apiUrl ="project_branches/list?project=${projectName}"response = HttpReq("GET",apiUrl,)response = readJSON text:"""${response.content}"""result = response["branches"][0]["status"]["qualityGateStatus"]//println(response)returnresult}

sonarqube.groovy

package ore.devopsdef SonarScan(projectName,projectDesc,projectPath){// sonarScanner安装地址def sonarHome ="/opt/sonar-scanner"// sonarqube服务端地址def sonarServer ="http://sonar.devops.svc.cluster.local:9000/"// 以时间戳为版本def scanTime = sh returnStdout:true, script:date +%Y%m%d%H%m%SscanTime = scanTime -"\n"sh"""${sonarHome}/bin/sonar-scanner  -Dsonar.host.url=${sonarServer}\-Dsonar.projectKey=${projectName}\-Dsonar.projectName=${projectName}\-Dsonar.projectVersion=${scanTime}\-Dsonar.login=admin \-Dsonar.password=admin \-Dsonar.ws.timeout=30 \-Dsonar.projectDescription="${projectDesc}"  \-Dsonar.links.homepage=http://baidu.com \-Dsonar.sources=${projectPath}\-Dsonar.sourceEncoding=UTF-8 \-Dsonar.java.binaries=target/classes \-Dsonar.java.test.binaries=target/test-classes \-Dsonar.java.surefire.report=target/surefire-reports -Xecho "${projectName}scan success!""""}

tools.groovy

package org.devops//格式化输出def PrintMes(value,color){colors = [red:"\033[40;31m >>>>>>>>>>>${value}<<<<<<<<<<< \033[0m",blue:"\033[47;34m${value}\033[0m",green:"[1;32m>>>>>>>>>>${value}>>>>>>>>>>[m",green1:"\033[40;32m >>>>>>>>>>>${value}<<<<<<<<<<< \033[0m"]ansiColor(xterm) {println(colors[color])}}// 获取镜像版本defcreateVersion() {// 定义一个版本号作为当次构建的版本,输出结果 20191210175842_69returnnew Date().format(yyyyMMddHHmmss) +"_${env.BUILD_ID}"}// 获取时间defgetTime() {// 定义一个版本号作为当次构建的版本,输出结果 20191210175842returnnew Date().format(yyyyMMddHHmmss)}

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

我这里创建了一个叫devops-cd的共享仓库,如下:

image.png

然后以应用名创建一个目录,并在目录下创建以下几个文件。

image.png

它们的内容分别如下。

service.yaml

kind: ServiceapiVersion: v1metadata:name: the-servicenamespace: defaultspec:selector:deployment: hellotype: NodePortports:- protocol: TCPport: 8080targetPort: 8080

ingress.yaml

apiVersion: extensions/v1beta1kind: Ingressmetadata:name: the-ingressnamespace: defaultspec:rules:- host: test.coolops.cnhttp:paths:- backend:serviceName: the-serviceservicePort: 8080path: /

deploymeny.yaml

apiVersion: apps/v1kind: Deploymentmetadata:name: the-deploymentnamespace: defaultspec:replicas: 3selector:matchLabels:deployment: hellotemplate:metadata:labels:deployment: hellospec:containers:- args:- -jar- /opt/myapp.jar- --server.port=8080command:- javaenv:- name: HOST_IPvalueFrom:fieldRef:apiVersion: v1fieldPath: status.hostIPimage: registry.cn-hangzhou.aliyuncs.com/rookieops/myapp:latestimagePullPolicy: IfNotPresentlifecycle:preStop:exec:command:- /bin/sh- -c- /bin/sleep 30livenessProbe:failureThreshold: 3httpGet:path: /helloport: 8080scheme: HTTPinitialDelaySeconds: 60periodSeconds: 15successThreshold: 1timeoutSeconds: 1name: myappports:- containerPort: 8080name: httpprotocol: TCPreadinessProbe:failureThreshold: 3httpGet:path: /helloport: 8080scheme: HTTPperiodSeconds: 15successThreshold: 1timeoutSeconds: 1resources:limits:cpu:"1"memory: 2Girequests:cpu: 100mmemory: 1GiterminationMessagePath: /dev/termination-logterminationMessagePolicy: FilednsPolicy: ClusterFirstWithHostNetimagePullSecrets:- name: gitlab-registry

kustomization.yaml

Example configuration for the webserverat https://github.com/monopole/hellocommonLabels:app: helloresources:- deployment.yaml- service.yaml- ingress.yamlapiVersion: kustomize.config.k8s.io/v1beta1kind: Kustomizationimages:- name: registry.cn-hangzhou.aliyuncs.com/rookieops/myappnewTag:"20201127150733_70"namespace: dev

在Jenkins上配置共享库

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

image.png

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

image.png

然后点击应用并保存

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

在Jenkins上创建一个项目,如下:

image.png

然后在最地下的pipeline处贴入以下代码:

def labels ="slave-${UUID.randomUUID().toString()}"// 引用共享库@Library("jenkins_shareLibrary")// 应用共享库中的方法def tools = new org.devops.tools()pipeline {agent {kubernetes {label labelsyaml"""apiVersion: v1kind: Podmetadata:labels:some-label: some-label-valuespec:volumes:- name: docker-sockhostPath:path: /var/run/docker.socktype:containers:- name: jnlpimage: registry.cn-hangzhou.aliyuncs.com/rookieops/inbound-agent:4.3-4- name: mavenimage: registry.cn-hangzhou.aliyuncs.com/rookieops/maven:3.5.0-alpinecommand:- cattty: true- name: dockerimage: registry.cn-hangzhou.aliyuncs.com/rookieops/docker:19.03.11command:- cattty: truevolumeMounts:- name: docker-sockmountPath: /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")}}}}}}

然后点击保存并运行,如果看到输出有颜色,就代表共享库配置成功,如下:

image.png

到此共享库配置完成。

编写Jenkinsfile

整个java的Jenkinsfile如下:

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.branchdef gitUrl = env.git_urldef buildShell = env.build_shelldef image = env.imagedef dockerRegistryUrl = env.dockerRegistryUrldef devops_cd_git = env.devops_cd_gitpipeline {agent {kubernetes {label labelsyaml"""apiVersion: v1kind: Podmetadata:labels:some-label: some-label-valuespec:volumes:- name: docker-sockhostPath:path: /var/run/docker.socktype:- name: maven-cachepersistentVolumeClaim:claimName: maven-cache-pvccontainers:- name: jnlpimage: registry.cn-hangzhou.aliyuncs.com/rookieops/inbound-agent:4.3-4- name: mavenimage: registry.cn-hangzhou.aliyuncs.com/rookieops/maven:3.5.0-alpinecommand:- cattty: truevolumeMounts:- name: maven-cachemountPath: /root/.m2- name: dockerimage: registry.cn-hangzhou.aliyuncs.com/rookieops/docker:19.03.11command:- cattty: truevolumeMounts:- name: docker-sockmountPath: /var/run/docker.sock- name: sonar-scannerimage: registry.cn-hangzhou.aliyuncs.com/rookieops/sonar-scanner:latestcommand:- cattty: true- name: kustomizeimage: registry.cn-hangzhou.aliyuncs.com/rookieops/kustomize:v3.8.1command:- cattty: 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 10tools.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-cdcd /opt/devops-cdgit pullcd /opt/devops-cd/${APP_DIR}kustomize edit set image${image}:${imageTag}git commit -am image updategit push origin master"""}}}}}// 接口测试stage(InterfaceTest) {steps{shecho "接口测试"}}}// 构建后的操作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。

image.png

在Jenkins上配置项目

在Jenkins上新建一个项目,如下:

image.png

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

image.png
image.png
image.png
image.png
image.png
image.png
image.png

然后在流水线处配置Pipeline from SCM

image.png
image.png

此处需要注意脚本名。

然后点击应用保存,并运行。

image.png

也可以在sonarqube上看到代码扫描的结果。

image.png

在Argocd上配置CD流程

在argocd上添加代码仓库,如下:

image.png
image.png

然后创建应用,如下:

image.png
image.png

点击创建后,如下:

image.png

点进去可以看到更多的详细信息。

image.png

argocd有一个小bug,它ingress的健康检查必须要loadBalance有值,不然就不通过,但是并不影响使用。

然后可以正常访问应用了。

image.png

node项目的Jenkinsfile大同小异,由于我没有测试用例,所以并没有测试。

集成Gitlab,通过Webhook触发Jenkins

在Jenkins中选择项目,在项目中配置gitlab触发,如下:

image.png

生成token,如下

image.png

在gitlab上配置集成。进入项目-->项目设置-->集成

image.png

配置Jenkins上生成的回调URL和TOKEN

image.png

到此配置完成,然后点击下方test,可以观察是否触发流水线。

image.png

也可以通过修改仓库代码进行测试。

写在最后

本片文章是纯操作步骤,大家在测试的时候可能会对Jenkinsfile做细微的调整,不过整体没什么问题。

公众号:运维开发故事

github:https://github.com/orgs/sunsharing-note/dashboard

爱生活,爱运维

如果你觉得文章还不错,就请点击右上角选择发送给朋友或者转发到朋友圈。您的支持和鼓励是我最大的动力。喜欢就请关注我吧~

扫码二维码

关注我,不定期维护优质内容

温馨提示

如果你喜欢本文,请分享到朋友圈,想要获得更多信息,请关注我。

........................

GitLab + Jenkins + Harbor Toolchain Workflow
  1. 首先我们需要完成 GitLab、Jenkins 和 Harbor 三个工具的部署;
  2. 接着我们需要在 GitLab 上创建一个代码库,并且在 Jenkins 上创建相应的流水线,这个流程最好也自动化(确实可以自动化);
  3. 然后适当地配置这三个工具,实现如下 CI 流程:
  4. 当用户推送代码到 GitLab,也就是 GitLab 上相应代码库产生 push 或者 merge 事件的时候,这个事件能够自动触发 Jenkins 上的流水线执行;
  5. Jenkins 上流水线执行的结果能够回显到 GitLab;
  6. Jenkins 上完成了编译、构建等等流程后,最终制品是一个

    使用GitLab+Jenkins实现CI—CD?