阅读Jenkins插件代码系列之Docker Workflow Plugin
最近在读这篇文章 Using Docker with Pipeline[1],里面讲如何采用 docker
作为构建和测试环境。今天就主要分析一下内部实现原理,以及采用 Ant
如何实现类似的功能。
其必要性
肯定是有必要的,采用这种方式,就可以不用去agent中维护各种工具,所有构建过程中的工具都可以通过容器镜像获取。
比如下面这一小段,会基于seanly/maven:3-openjdk-17
镜像启动一个容器,mvn
命令会在容器中执行,这种方案完全不用在agent环境中安装maven工具。
script {
def _mvn = docker.image("seanly/maven:3-openjdk-17")
_mvn.inside {
sh 'mvn -B -e -U clean package -Dmaven.test.skip=true'
}
}
从用法解析原理
为了便于理解,我们会从两种语法上分析,先从 Scripted Pipeline
原理解释,因为底层实现都是 workflow-cps 插件,也就是上篇分析的 []。
脚本型流水线(Scripted Pipeline)模式
直接看代码,比如下面这段代码,因为Jenkinsfile的语法是基于Groovy的扩展,从语法角度来看这里的docker就应该是一个对象,image就是这个对象的方法。
node {
def _mvn = docker.image("seanly/maven:3-openjdk-17")
_mvn.inside {
sh 'mvn -B -e -U clean package -Dmaven.test.skip=true'
}
}
首先找入口
基于上面的脚本,如何找到其实现呢?这种就和之前的不太一样,不能通过xml配置里面的描述来定位,由于前一篇我们分析过了workflow-cps的原理,那么如果这里的docker是一个对象变量,那么从workflow-cps里面的这段代码就是很有用的,来看下面这段代码,
其含义就是如果是对象可以通过GlobalVariable
扩展点来获取。基于这个特点,直接找 docker-workflow-plugin里面继承GlobalVariable
的类就好啦。
下面是docker-workflow-plugin中找到的实现代码,意思是将docker解析为一个由Docker.groovy脚本里面定义的类的对象。
注意:这里给Docker.groovy脚本里类的构造函数传入的是一个CpsScript
对象。
接下来这段很重要
继续分析inside()
的实现,通过继续看Docker.groovy
代码很容易找到其实现,实现如下图:
重点分析docker.script.withDockerContainer
这句,其中的script就是上面说的传入的CpsScript
对象,那么CpsScript
对象里面的withDockerContainer
函数哪里找呢?还是使用我们上面找入口的方法,根据分析workflow-cps的代码逻辑应该在DSL里面通过扩展Step实现,如下图:
发现withDockerContainer是通过Step扩展的,如下图代码:
这里才是核心
分析其代码实现找到其核心逻辑,通过替换LauncherDecorator
来替换底层的Launcher
对象,代码如下:
可能这样说有点空洞,直接来看一个例子和运行结果:
pipeline {
agent any
stages {
stage('Debug') {
steps {
script {
def toolimg = docker.image("seanly/maven:3-openjdk-17")
toolimg.inside {
oesStep stepId: "sample"
}
}
}
}
}
}
执行结果:
由于oesStep
里面有很多逻辑,比如下载包,解压,保存文件等,但是从最终结果来看,只在最后执行ant
命令转化为了docker exec
来执行,其他过程没啥特殊处理,下面是oesStep
关键实现原理:
里面的Launcher
应该是在执行过程中被上下文给替换了,知道这个点就可以,可以不用深究,意思就是inside的{}
内部可以写一些别的逻辑,之前一直错误的以为只能写sh这种简单的命令执行。
这块就基本分析到这里,其他细节还是需要按需分析。
声明式流水线(Declarative Pipeline)模式
直接看写法,下面是一个在Debug
阶段配置docker
作为agent
的例子。
pipeline {
agent any
stages {
stage("Checkout Code") {
steps {
script {
deleteDir()
cleanWs()
git branch: "main", url: "https://jihulab.com/oes-workspace/spring-petclinic.git"
}
}
}
stage('Debug') {
agent {
docker {
image 'seanly/maven:3-openjdk-17'
reuseNode true
}
}
steps {
sh 'mvn -B -e -U clean package -Dmaven.test.skip=true'
}
}
}
}
因为这种语法比较特殊从 pipeline
开始里面的都是特殊的实现,找入口的方法是先找到 pipeline
的入口。
由于这里的脚本还是Jenkinsfile,那么就从pipeline
来看应该是一个对象,结合上面分析过的,如果是对象那么它的实现就是扩展GlobalVariable
实现,由于这个涉及另外一个重量级插件 pipeline-model-definition-plugin 这里就不详细展开。
下面是pipeline
的入口:
通过查找标注关联Agent实现:
下面是Docker Agent的实现:
后续的逻辑就和上面是一致的,也是再通过CpsScript
查找docker
实现。
这块分析就比较简单了,后面的文章详细分享。
Ant 脚本如何实现类似的功能?
首先实现一些基础的docker
操作命令,借助Ant Macro的功能,比如:
在扩展中将组装好的脚本直接调用上面命令使用就可以了
实现kubectl功能:
实现maven功能:
接下来放入Jenkinsfile编排中,效果如下:
这样这个风格就不会缩进很深。还有一个好处就是不用记各种语法。
最后
这个插件的想法倒是不错,这里的 ant
实现也是参考了这个插件的方式实现。
参考资料
Using Docker with Pipeline: https://www.jenkins.io/doc/book/pipeline/docker/