赞
踩
由于公司内部项目众多,大量的项目使用同一套流程做CICD
因此本章主要通过使用groovy实现Jenkins的sharedLibrary的开发,以提取项目在CICD实践过程中的公共逻辑,提供一系列的流程的接口供公司内各项目调用。
开发完成后,对项目进行Jenkinsfile的改造,最后仅需通过简单的Jenkinsfile的配置,即可优雅的完成CICD流程的整个过程,此方式已在大型企业内部落地应用。
由于流水线被组织中越来越多的项目所采用,常见的模式很可能会出现。 在多个项目之间共享流水线有助于减少冗余并保持代码 “DRY”。
流水线支持引用 “共享库” ,可以在外部源代码控制仓库中定义并加载到现有的流水线中。
@Library('my-shared-library') _
在实际运行过程中,会把library中定义的groovy功能添加到构建目录中:
/var/jenkins_home/jobs/test-maven-build/branches/feature-CDN-2904.cm507o/builds/2/libs/my-shared-library/vars/devops.groovy
使用library后,Jenkinsfile大致的样子如下:
@Library('my-shared-library') _ ... stages { stage('build image') { steps { container('tools') { devops.buildImage("Dockerfile","172.21.51.67:5000/demo:latest") } } } } post { success { script { container('tools') { devops.notificationSuccess("dingTalk") } } } } ...
补录章节:Groovy及SpringBoot、SpringCloud都会使用
链接:https://pan.baidu.com/s/1B-bg2_IsB8dU7_62IEtnTg
提取码:wx6j
安装路径:D:\software\jdk
环境变量:
解压路径:D:\software\groovy-3.0.2
环境变量:
安装路径:D:\software\IntelliJ IDEA 2019.2.3
新建项目测试
共享库的目录结构如下:
(root)
+- src # Groovy source files
| +- org
| +- foo
| +- Bar.groovy # for org.foo.Bar class
+- vars
| +- foo.groovy # for global 'foo' variable
| +- foo.txt # help for 'foo' variable
src
目录应该看起来像标准的 Java 源目录结构。当执行流水线时,该目录被添加到类路径下。
vars
目录定义可从流水线访问的全局变量的脚本。 每个 *.groovy
文件的基名应该是一个 Groovy (~ Java) 标识符, 通常是 camelCased
。
新建Groovy项目
变量
使用数据类型的本地语法,或者使用def关键字
// Defining a variable in lowercase
int x = 5;
// Defining a variable in uppercase
int X = 6;
// Defining a variable with the underscore in it's name
def _Name = "Joe";
println(x);
println(X);
println(_Name);
方法
调用本地方法
def sum(int a, int b){
return a + b
}
println(sum(1,2))
调用类中的方法
# Hello.groovy package demo def sayHi(String content) { return ("hi, " + content) } # Demo.groovy import demo.Hello def demo() { return new Hello().sayHi("devops") } println(demo()) # 级联调用 # Hello.groovy package demo def init(String content) { this.content = content return this } def sayHi() { println("hi, " + this.content) return this } def sayBye() { println("bye " + this.content) } # Demo.groovy import demo.Hello def demo() { new Hello().init("devops").sayHi().sayBye() } demo()
异常捕获
def exceptionDemo(){
try {
def val = 10 / 0
println(val)
}catch(Exception e) {
println(e.toString())
throw e
}
}
exceptionDemo()
计时器与循环
import groovy.time.TimeCategory
use( TimeCategory ) {
def endTime = TimeCategory.plus(new Date(), TimeCategory.getSeconds(15))
def counter = 0
while(true) {
println(counter++)
sleep(1000)
if (new Date() >= endTime) {
println("done")
break
}
}
}
解析yaml文件
import org.yaml.snakeyaml.Yaml
def readYaml(){
def content = new File('myblog.yaml').text
Yaml parser = new Yaml()
def data = parser.load(content)
def kind = data["kind"]
def name = data["metadata"]["name"]
println(kind)
println(name)
}
readYaml()
先来看一下如何使用shared library实现最简单的helloworld输出功能,来理清楚使用shared library的流程。
package com.luffy.devops /** * @author Yongxin * @version v0.1 */ /** * say hello * @param content */ def hello(String content) { this.content = content return this } def sayHi() { echo "Hi, ${this.content},how are you?" return this } def answer() { echo "${this.content}: fine, thank you, and you?" return this } def sayBye() { echo "i am fine too , ${this.content}, Bye!" return this }
在gitlab创建项目,把library代码推送到镜像仓库。
[系统管理] -> [系统设置] -> [ Global Pipeline Libraries ]
jenkins/pipelines/p11.yaml
@Library('luffy-devops') _ pipeline { agent { label 'jnlp-slave'} stages { stage('hello-devops') { steps { script { devops.hello("树哥").sayHi().answer().sayBye() } } } } post { success { echo 'Congratulations!' } failure { echo 'Oh no!' } always { echo 'I will always say Hello again!' } } }
创建vars/devops.groovy
import com.luffy.devops.Hello
def hello(String content) {
return new Hello().hello(content)
}
需要实现的逻辑点:
devops.groovy
/**
*
* @param repo, 172.21.51.67:5000/demo/myblog/xxx/
* @param tag, v1.0
* @param dockerfile
* @param credentialsId
* @param context
*/
def docker(String repo, String tag, String credentialsId, String dockerfile="Dockerfile", String context=".") {
return new Docker().docker(repo, tag, credentialsId, dockerfile, context)
}
Docker.groovy
逻辑中需要注意的点:
package com.luffy.devops /** * * @param repo * @param tag * @param credentialsId * @param dockerfile * @param context * @return */ def docker(String repo, String tag, String credentialsId, String dockerfile="Dockerfile", String context="."){ this.repo = repo this.tag = tag this.dockerfile = dockerfile this.credentialsId = credentialsId this.context = context this.fullAddress = "${this.repo}:${this.tag}" this.isLoggedIn = false return this } /** * build image * @return */ def build() { this.login() retry(3) { try { sh "docker build ${this.context} -t ${this.fullAddress} -f ${this.dockerfile} " }catch (Exception exc) { throw exc } return this } } /** * push image * @return */ def push() { this.login() retry(3) { try { sh "docker push ${this.fullAddress}" }catch (Exception exc) { throw exc } } return this } /** * docker registry login * @return */ def login() { if(this.isLoggedIn || credentialsId == ""){ return this } // docker login withCredentials([usernamePassword(credentialsId: this.credentialsId, usernameVariable: 'USERNAME', passwordVariable: 'PASSWORD')]) { def regs = this.getRegistry() retry(3) { try { sh "docker login ${regs} -u $USERNAME -p $PASSWORD" } catch (Exception exc) { echo "docker login err, " + exc.toString() } } } this.isLoggedIn = true; return this; } /** * get registry server * @return */ def getRegistry(){ def sp = this.repo.split("/") if (sp.size() > 1) { return sp[0] } return this.repo }
Jenkinsfile
需要先在Jenkins端创建仓库登录凭据credential-registry
@Library('luffy-devops') _ pipeline { agent { label 'jnlp-slave'} options { timeout(time: 20, unit: 'MINUTES') gitLabConnection('gitlab') } environment { IMAGE_REPO = "172.21.51.67:5000/demo/myblog" IMAGE_CREDENTIAL = "credential-registry" } stages { stage('checkout') { steps { container('tools') { checkout scm } } } stage('docker-image') { steps { container('tools') { script{ devops.docker( "${IMAGE_REPO}", "${GIT_COMMIT}", IMAGE_CREDENTIAL ).build().push() } } } } } post { success { echo 'Congratulations!' } failure { echo 'Oh no!' } } }
目前的构建镜像逻辑中缺少如下内容:
需要针对上述问题,做出优化
优化try逻辑
def build() { this.login() def isSuccess = false def errMsg retry(3) { try { sh "docker build ${this.context} -t ${this.fullAddress} -f ${this.dockerfile}" isSuccess = true }catch (Exception err) { //ignore errMsg = err.toString() } // check if build success if(isSuccess){ //todo }else { // throw exception,aborted pipeline error errMsg } return this } }
通知gitlab端构建任务及状态
def build() { this.login() def isSuccess = false def errMsg = "" retry(3) { try { sh "docker build ${this.context} -t ${this.fullAddress} -f ${this.dockerfile} " isSuccess = true }catch (Exception err) { //ignore errMsg = err.toString() } // check if build success def stage = env.STAGE_NAME + '-build' if(isSuccess){ updateGitlabCommitStatus(name: '${stage}', state: 'success') }else { updateGitlabCommitStatus(name: '${stage}', state: 'failed') // throw exception,aborted pipeline error errMsg } return this } }
钉钉消息通知格式
由于每个stage都需要构建通知任务,因此抽成公共的逻辑,为各stage调用
BuildMessage.groovy
package com.luffy.devops
def updateBuildMessage(String source, String add) {
if(!source){
source = ""
}
env.BUILD_TASKS = source + add + "\n \n "
return env.BUILD_TASKS
}
Docker.groovy
中调用
def getObject(String repo, String tag, String credentialsId, String dockerfile="Dockerfile", String context="."){ ... this.msg = new BuildMessage() return this } ... def build() { ... // check if build success def stage = env.STAGE_NAME + '-build' if(isSuccess){ updateGitlabCommitStatus(name: '${stage}', state: 'success') this.msg.updateBuildMessage(env.BUILD_TASKS, "${stage} OK... √") }else { updateGitlabCommitStatus(name: '${stage}', state: 'failed') this.msg.updateBuildMessage(env.BUILD_TASKS, "${stage} Failed... x") // throw exception,aborted pipeline error errMsg } return this } }
使用Jenkinsfile
来验证上述修改是否正确:
@Library('luffy-devops') _
pipeline {
agent { label 'jnlp-slave'}
options {
timeout(time: 20, unit: 'MINUTES')
gitLabConnection('gitlab')
}
environment {
IMAGE_REPO = "172.21.51.67:5000/demo/myblog"
IMAGE_CREDENTIAL = "credential-registry"
DINGTALK_CREDS = credentials('dingTalk')
}
stages {
stage('checkout') {
steps {
container('tools') {
checkout scm
}
}
}
stage('git-log') {
steps {
script{
sh "git log --oneline -n 1 > gitlog.file"
env.GIT_LOG = readFile("gitlog.file").trim()
}
sh 'printenv'
}
}
stage('build-image') {
steps {
container('tools') {
script{
devops.docker(
"${IMAGE_REPO}",
"${GIT_COMMIT}",
IMAGE_CREDENTIAL
).build().push()
}
}
}
}
}
post {
success {
sh """
curl 'https://oapi.dingtalk.com/robot/send?access_token=${DINGTALK_CREDS_PSW}' \
-H 'Content-Type: application/json' \
-d '{
"msgtype": "markdown",
"markdown": {
"title":"myblog",
"text": "声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/不正经/article/detail/630548
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。