基于jenkins docker实现CI/CD实践
温馨提示
Tips
项目简介利用 Jenkins、Docker、SonarQube 和 Harbor 技术,搭建一个完整的 CI/CD 管道,实现持续集成、持续交付和持续部署的流程。通过自动化构建、测试、代码质量检查和容器化部署,将开发人员从繁琐的手动操作中解放出来,提高团队的开发效率、软件质量和安全性,实现持续更新迭代和持续部署交付。
CICD流程图图片
jenkins.drawio.png流程说明 开发人员将代码提交到Gitlab代码仓库时,gitlab请求jenkins的webhook地址,触发持续构建和持续部署流程。Jenkins从Gitlab中拉取项目源码,编译并打成jar包,然后调用SonarQube完成代码扫描。扫描完成调用docker打包成容器镜像,并推送至Harbor镜像仓库。Jenkins发送SSH远程命令,让生产部署服务器到Harbor私有仓库拉取镜像到本地,然后创建容器。Jenkins完成CICD流程后,将结果邮件通知给开发和运维人员。用户访问项目服务器。服务器列表 服务器名称主机名IP部署服务代码托管服务器gitlab192.168.10.72Gitlab持续集成服务器jenkins192.168.10.73Jenkins、Maven、Docker代码审查服务器sonarqube192.168.10.71SonarQube镜像仓库服务器harbor192.168.10.100Docker、harbor服务部署服务器springboot192.168.10.74Docker项目代码仓库地址gitee:https://gitee.com/cuiliang0302/sprint_boot_demo
github:https://github.com/cuiliang0302/sprint-boot-demo
服务部署(rpm方式)gitlab部署参考文档:https://www.cuiliangblog.cn/detail/section/92727905
jenkins部署参考文档:https://www.cuiliangblog.cn/detail/section/15130009
docker部署参考文档:https://www.cuiliangblog.cn/detail/section/26447182
harbor部署参考文档:https://www.cuiliangblog.cn/detail/section/15189547
SonarQube部署参考文档:https://www.cuiliangblog.cn/detail/section/131467837
harbor项目权限配置创建项目Harbor的项目分为公开和私有的:
公开项目:所有用户都可以访问,通常存放公共的镜像,默认有一个library公开项目。
私有项目:只有授权用户才可以访问,通常存放项目本身的镜像。我们可以为微服务项目创建一个新的项目
图片
创建用户创建一个普通用户cuiliang。
图片
配置项目用户权限在spring_boot_demo项目中添加普通用户cuiliang,并设置角色为开发者。
图片
权限说明角色权限访客对项目有只读权限开发人员对项目有读写权限维护人员对项目有读写权限、创建webhook权限项目管理员除上述外,还有用户管理等权限上传下载镜像测试可参考文章https://www.cuiliangblog.cn/detail/section/15189547,此处不再赘述。
gitlab项目权限配置具体gitlab权限配置参考文档:https://www.cuiliangblog.cn/detail/section/131513569
创建开发组develop,用户cuiliang,项目springboot demo
创建组管理员用户登录,创建群组,组名称为develop,组权限为私有
图片
创建项目创建sprint boot demo项目,并指定develop,项目类型为私有
图片
创建用户创建一个普通用户cuiliang
图片
用户添加到组中将cuiliang添加到群组develop中,cuiliang角色为Developer
图片
开启分支推送权限图片
image.png用户权限验证使用任意一台机器模拟开发人员拉取代码,完成开发后推送至代码仓库。拉取仓库代码
[root@tiaoban opt]# git clone https://gitee.com/cuiliang0302/sprint_boot_demo.git正克隆到 'sprint_boot_demo'...remote: Enumerating objects: 69, done.remote: Counting objects: 100% (69/69), done.remote: Compressing objects: 100% (54/54), done.remote: Total 69 (delta 15), reused 0 (delta 0), pack-reused 0接收对象中: 100% (69/69), 73.15 KiB | 1.49 MiB/s, 完成.处理 delta 中: 100% (15/15), 完成.[root@tiaoban opt]# cd sprint_boot_demo/[root@tiaoban sprint_boot_demo]# lsemail.html Jenkinsfile LICENSE mvnw mvnw.cmd pom.xml readme.md sonar-project.properties src test推送至gitlab仓库
# 修改远程仓库地址[root@tiaoban sprint_boot_demo]# git remote set-url origin http://192.168.10.72/develop/sprint-boot-demo.git[root@tiaoban sprint_boot_demo]# git remote -vorigin http://192.168.10.72/develop/sprint-boot-demo.git (fetch)origin http://192.168.10.72/develop/sprint-boot-demo.git (push)# 推送代码至gitlab[root@tiaoban sprint_boot_demo]# git push --set-upstream origin --allUsername for 'http://192.168.10.72': cuiliangPassword for 'http://cuiliang@192.168.10.72': 枚举对象中: 55, 完成.对象计数中: 100% (55/55), 完成.使用 4 个线程进行压缩压缩对象中: 100% (34/34), 完成.写入对象中: 100% (55/55), 71.51 KiB | 71.51 MiB/s, 完成.总共 55(差异 10),复用 52(差异 9),包复用 0To http://192.168.10.72/develop/sprint-boot-demo.git * [new branch] main -> main分支 'main' 设置为跟踪 'origin/main'。
查看验证
图片
jenkins流水线配置拉取gitlab仓库代码具体步骤可参考文档:https://www.cuiliangblog.cn/detail/section/127410630
此处以账号密码验证为例,并启用webhook配置。jenkins流水线配置如下
图片
拉取代码部分的jenkinsfile如下pipeline { agent any stages { stage('拉取代码') { environment { // gitlab仓库信息 GITLAB_CRED = 'gitlab-cuiliang-password' GITLAB_URL = 'http://192.168.10.72/develop/sprint-boot-demo.git' } steps { echo '开始拉取代码' checkout scmGit(branches: [[name: '*/main']], extensions: [], userRemoteConfigs: [[credentialsId: '${GITLAB_CRED}', url: '${GITLAB_URL}']]) echo '拉取代码完成' } } }}当git仓库提交代码后,Gitlab会自动请求Jenkins的webhook地址,自动触发流水线,执行结果如下:
图片
Maven打包编译具体步骤可参考文档:https://www.cuiliangblog.cn/detail/section/131898197
打包编译部分的jenkinsfile如下
pipeline { agent any stages { stage('拉取代码') { …… } stage('打包编译') { steps { echo '开始打包编译' sh 'mvn clean package' echo '打包编译完成' } } }}
触发流水线结果如下
图片
SonarQube代码审查具体步骤可参考文档:https://www.cuiliangblog.cn/detail/section/165534414
代码审查阶段的jenkinsfile如下
pipeline { agent any stages { stage('拉取代码') { …… } stage('打包编译') { …… } stage('代码审查') { environment { // SonarQube信息 SONARQUBE_SCANNER = 'SonarQubeScanner' SONARQUBE_SERVER = 'SonarQubeServer' } steps{ echo '开始代码审查' script { def scannerHome = tool '${SONARQUBE_SCANNER}' withSonarQubeEnv('${SONARQUBE_SERVER}') { sh '${scannerHome}/bin/sonar-scanner' } } echo '代码审查完成' } } }}触发流水线结果如下
图片
代码审查结果如下图片
构建并推送镜像至仓库具体步骤可参考文档:https://www.cuiliangblog.cn/detail/section/166573065
构建并推送镜像的jenkinsfile如下
pipeline { agent any stages { stage('拉取代码') { …… } stage('打包编译') { …… } stage('代码审查') { …… } stage('构建镜像') { environment { // harbor信息 HARBOR_CRED = 'harbor-cuiliang-password' HARBOR_URL = 'harbor.local.com' HARBOR_PROJECT = 'spring_boot_demo' // 镜像信息 IMAGE_APP = 'demo' IMAGE_TAG = sh(script: 'git rev-parse --short HEAD', returnStdout: true).trim() IMAGE_NAME = '${HARBOR_URL}/${HARBOR_PROJECT}/${IMAGE_APP}:${IMAGE_TAG}' } steps { echo '开始构建镜像' script { docker.build '${IMAGE_NAME}' } echo '构建镜像完成' echo '开始推送镜像' script { docker.withRegistry('https://${HARBOR_URL}', '${HARBOR_CRED}') { docker.image('${IMAGE_NAME}').push() } } echo '推送镜像完成' echo '开始删除镜像' script { sh 'docker rmi -f ${IMAGE_NAME}' } echo '删除镜像完成' } } }}
触发流水线结果如下
图片
查看harbor镜像仓库,已上传镜像图片
docker运行服务远程执行命令具体内容可参考文档:https://www.cuiliangblog.cn/detail/section/166296541
部署运行阶段的jenkinsfile如下
pipeline { agent any environment { // 全局变量 HARBOR_CRED = 'harbor-cuiliang-password' IMAGE_NAME = '' IMAGE_APP = 'demo' } stages { stage('拉取代码') { …… } stage('打包编译') { …… } stage('代码审查') { …… } stage('构建镜像') { …… } stage('项目部署') { environment { // 目标主机信息 HOST_NAME = 'springboot1' } steps { echo '开始部署项目' // 获取harbor账号密码 withCredentials([usernamePassword(credentialsId: '${HARBOR_CRED}', passwordVariable: 'HARBOR_PASSWORD', usernameVariable: 'HARBOR_USERNAME')]) { // 执行远程命令 sshPublisher(publishers: [sshPublisherDesc(configName: '${HOST_NAME}', transfers: [sshTransfer( cleanRemote: false, excludes: '', execCommand: 'sh -x /opt/jenkins/springboot/deployment.sh ${HARBOR_USERNAME} ${HARBOR_PASSWORD} ${IMAGE_NAME} ${IMAGE_APP}', execTimeout: 120000, flatten: false, makeEmptyDirs: false, noDefaultExcludes: false, patternSeparator: '[, ]+', remoteDirectory: '/opt/jenkins/springboot', remoteDirectorySDF: false, removePrefix: '', sourceFiles: 'deployment.sh')], usePromotionTimestamp: false, useWorkspaceInPromotion: false, verbose: false )]) } echo '部署项目完成' } } }}触发流水线后运行结果如下
图片
登录springboot服务器验证[root@springboot ~]# docker psCONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMESe80896487125 harbor.local.com/spring_boot_demo/demo:8880a30 'java -jar /app.jar' About a minute ago Up About a minute (unhealthy) 0.0.0.0:8888->8888/tcp, :::8888->8888/tcp demo[root@springboot ~]# curl 127.0.0.1:8888/<h1>Hello SpringBoot</h1><p>Version:v2 Env:test</p>[root@springboot1 ~]# [root@springboot ~]# ls /opt/jenkins/springboot/deployment.sh Dockerfile email.html Jenkinsfile LICENSE mvnw mvnw.cmd pom.xml readme.md sonar-project.properties src target test添加邮件通知推送
发送邮件配置具体内容可参考文档:https://www.cuiliangblog.cn/detail/section/133029974
在项目根路径下新增email.html文件,内容如下
<!DOCTYPE html><html lang='en'> <head> <meta charset='UTF-8'> <meta name='viewport' content='width=device-width, initial-scale=1.0'> <title>${ENV, var='JOB_NAME'}-第${BUILD_NUMBER}次构建日志</title> <style> body { font-family: Arial, sans-serif; line-height: 1.6; margin: 0; padding: 0; } .container { max-width: 1000px; margin: 20px auto; padding: 20px; border: 1px solid #ccc; border-radius: 5px; } .container h2 { text-align: center; } .logo img { max-width: 150px; height: auto; } .content { padding: 20px; background-color: #f9f9f9; border-radius: 5px; } .footer { margin-top: 20px; text-align: center; } </style> </head> <body> <div class='container'> <div class='content'> <h2>Jenkins ${PROJECT_NAME}项目构建结果</h2> <p>尊敬的用户:</p> <p>${PROJECT_NAME}项目构建结果为<span style='color:red;font-weight: bold;'>${BUILD_STATUS}</span>,以下是详细信息:</p> <h4>构建信息</h4> <hr/> <ul> <li>项目名称:${PROJECT_NAME}</li> <li>构建编号:第${BUILD_NUMBER}次构建</li> <li>触发原因:${CAUSE}</li> <li>构建状态:${BUILD_STATUS}</li> <li>构建日志:<a href='${BUILD_URL}console'>${BUILD_URL}console</a></li> <li>构建Url:<a href='${BUILD_URL}'>${BUILD_URL}</a></li> <li>工作目录:<a href='${PROJECT_URL}ws'>${PROJECT_URL}ws</a></li> <li>项目Url:<a href='${PROJECT_URL}'>${PROJECT_URL}</a></li> </ul> <h4>失败用例</h4> <hr/> <p>$FAILED_TESTS</p> <h4>最近提交</h4> <hr/> <ul> ${CHANGES_SINCE_LAST_SUCCESS, reverse=true, format='%c', changesFormat='<li>%d [%a] %m</li>'} </ul> <h4>提交详情</h4> <hr/> <p><a href='${PROJECT_URL}changes'>${PROJECT_URL}changes</a></p> <p style='margin-top:50px'>如有任何疑问或需要帮助,请随时联系我们。</p> </div> <div class='footer'> <p>此为系统自动发送邮件,请勿回复。</p> </div> </div> </body></html>完整的jenkinsfile如下
pipeline { agent any environment { // 全局变量 HARBOR_CRED = 'harbor-cuiliang-password' IMAGE_NAME = '' IMAGE_APP = 'demo' } stages { stage('拉取代码') { environment { // gitlab仓库信息 GITLAB_CRED = 'gitlab-cuiliang-password' GITLAB_URL = 'http://192.168.10.72/develop/sprint-boot-demo.git' } steps { echo '开始拉取代码' checkout scmGit(branches: [[name: '*/main']], extensions: [], userRemoteConfigs: [[credentialsId: '${GITLAB_CRED}', url: '${GITLAB_URL}']]) echo '拉取代码完成' } } stage('打包编译') { steps { echo '开始打包编译' sh 'mvn clean package' echo '打包编译完成' } } stage('代码审查') { environment { // SonarQube信息 SONARQUBE_SCANNER = 'SonarQubeScanner' SONARQUBE_SERVER = 'SonarQubeServer' } steps{ echo '开始代码审查' script { def scannerHome = tool '${SONARQUBE_SCANNER}' withSonarQubeEnv('${SONARQUBE_SERVER}') { sh '${scannerHome}/bin/sonar-scanner' } } echo '代码审查完成' } } stage('构建镜像') { environment { // harbor仓库信息HARBOR_URL = 'harbor.local.com' HARBOR_PROJECT = 'spring_boot_demo' // 镜像名称 IMAGE_TAG = sh(script: 'git rev-parse --short HEAD', returnStdout: true).trim() } steps { echo '开始构建镜像' script { IMAGE_NAME = '${HARBOR_URL}/${HARBOR_PROJECT}/${IMAGE_APP}:${IMAGE_TAG}' docker.build '${IMAGE_NAME}' } echo '构建镜像完成' echo '开始推送镜像' script { docker.withRegistry('https://${HARBOR_URL}', '${HARBOR_CRED}') { docker.image('${IMAGE_NAME}').push() } } echo '推送镜像完成' echo '开始删除镜像' script { sh 'docker rmi -f ${IMAGE_NAME}' } echo '删除镜像完成' } } stage('项目部署') { environment { // 目标主机信息 HOST_NAME = 'springboot1' } steps { echo '开始部署项目' // 获取harbor账号密码 withCredentials([usernamePassword(credentialsId: '${HARBOR_CRED}', passwordVariable: 'HARBOR_PASSWORD', usernameVariable: 'HARBOR_USERNAME')]) { // 执行远程命令 sshPublisher(publishers: [sshPublisherDesc(configName: '${HOST_NAME}', transfers: [sshTransfer( cleanRemote: false, excludes: '', execCommand: 'sh -x /opt/jenkins/springboot/deployment.sh ${HARBOR_USERNAME} ${HARBOR_PASSWORD} ${IMAGE_NAME} ${IMAGE_APP}', execTimeout: 120000, flatten: false, makeEmptyDirs: false, noDefaultExcludes: false, patternSeparator: '[, ]+', remoteDirectory: '/opt/jenkins/springboot', remoteDirectorySDF: false, removePrefix: '', sourceFiles: 'deployment.sh')], usePromotionTimestamp: false, useWorkspaceInPromotion: false, verbose: false )]) } echo '部署项目完成' } } } post { always { echo '开始发送邮件通知' // 构建后发送邮件 emailext( subject: '构建通知:${PROJECT_NAME} - Build # ${BUILD_NUMBER} - ${BUILD_STATUS}!', body: '${FILE,path='email.html'}', to: 'cuiliang0302@qq.com' ) echo '邮件通知发送完成' } }}
触发流水线后运行结果如下
图片
邮件通知内容如下图片
至此,整个CICD流程完成。微
信
群
WeChat group
图片
本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报。