vlambda博客
学习文章列表

阅读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里面的这段代码就是很有用的,来看下面这段代码,

阅读Jenkins插件代码系列之Docker Workflow Plugin

其含义就是如果是对象可以通过GlobalVariable扩展点来获取。基于这个特点,直接找 docker-workflow-plugin里面继承GlobalVariable的类就好啦。

下面是docker-workflow-plugin中找到的实现代码,意思是将docker解析为一个由Docker.groovy脚本里面定义的类的对象。

阅读Jenkins插件代码系列之Docker Workflow Plugin
阅读Jenkins插件代码系列之Docker Workflow Plugin

注意:这里给Docker.groovy脚本里类的构造函数传入的是一个CpsScript对象。

接下来这段很重要

继续分析inside()的实现,通过继续看Docker.groovy代码很容易找到其实现,实现如下图:

阅读Jenkins插件代码系列之Docker Workflow Plugin

重点分析docker.script.withDockerContainer这句,其中的script就是上面说的传入的CpsScript对象,那么CpsScript对象里面的withDockerContainer函数哪里找呢?还是使用我们上面找入口的方法,根据分析workflow-cps的代码逻辑应该在DSL里面通过扩展Step实现,如下图:

阅读Jenkins插件代码系列之Docker Workflow Plugin

发现withDockerContainer是通过Step扩展的,如下图代码:

阅读Jenkins插件代码系列之Docker Workflow Plugin

这里才是核心

分析其代码实现找到其核心逻辑,通过替换LauncherDecorator来替换底层的Launcher对象,代码如下:

阅读Jenkins插件代码系列之Docker Workflow Plugin

可能这样说有点空洞,直接来看一个例子和运行结果:

pipeline {
agent any

stages {
stage('Debug') {
steps {
script {
def toolimg = docker.image("seanly/maven:3-openjdk-17")
toolimg.inside {
oesStep stepId: "sample"
}
}
}
}
}
}

执行结果:

阅读Jenkins插件代码系列之Docker Workflow Plugin

由于oesStep里面有很多逻辑,比如下载包,解压,保存文件等,但是从最终结果来看,只在最后执行ant命令转化为了docker exec来执行,其他过程没啥特殊处理,下面是oesStep关键实现原理:

阅读Jenkins插件代码系列之Docker Workflow Plugin

里面的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的入口:阅读Jenkins插件代码系列之Docker Workflow Plugin

通过查找标注关联Agent实现:阅读Jenkins插件代码系列之Docker Workflow Plugin

下面是Docker Agent的实现:阅读Jenkins插件代码系列之Docker Workflow Plugin

后续的逻辑就和上面是一致的,也是再通过CpsScript查找docker实现。

这块分析就比较简单了,后面的文章详细分享。

Ant 脚本如何实现类似的功能?

首先实现一些基础的docker操作命令,借助Ant Macro的功能,比如:

阅读Jenkins插件代码系列之Docker Workflow Plugin

在扩展中将组装好的脚本直接调用上面命令使用就可以了

实现kubectl功能:阅读Jenkins插件代码系列之Docker Workflow Plugin

实现maven功能:

接下来放入Jenkinsfile编排中,效果如下:

这样这个风格就不会缩进很深。还有一个好处就是不用记各种语法。

最后

这个插件的想法倒是不错,这里的 ant 实现也是参考了这个插件的方式实现。

参考资料

[1]

Using Docker with Pipeline: https://www.jenkins.io/doc/book/pipeline/docker/