贫穷的Jenkins agent实践

是时候展示真正的贫穷了

一般来说呢,如果是一个没啥用户的服务其实系统资源开销是比较小的。而相比之下,项目构建和测试阶段花费的系统资源会高于服务运行需要的开销。

举个极端例子,一个前端的react项目,构建大概会花费2-3核的cpu外加1-2G的内存。而最终的runtime其实就是一个nginx,用户不多的话,cpu基本就没有开销,内存也就10M左右。

对于贫穷的我来说,就只能想一些奇妙的操作来省钱。


基本方向

贫穷的直接影响就是资源短缺,这里短缺的说到底就是cpu和内存资源的短缺。因此搜罗各种边边角角的资源就成了非常必要的事情。不论是各个环境的盈余资源,还是作为监控的树莓派,以及本地的资源,统统都是可以塞进一个agent的地方。

总而言之,需要随时随地,在各异的环境与条件下启动一个agent来给Jenkins使用。

网络环境各异是必然的。以测试环境为例,其网络是在家里面的子网。Jenkins master是无法直接连接到测试环境,而家里的网络却可以访问到Jenkins master。

所以,简而言之,除了利用docker抹平除了网络以外的各种环境差异,还采取主动连接master的方式解决网络问题。


实现原理

3件事情:

  1. 主动把Jenkins里已经下线的agent清理掉。
  2. 创建一个agent
  3. 获得agent的secret,并将agent连接到master

清理已经下线的agent

清理的脚本是由python写成。使用的是python-jenkins这个库。任务就是把所有当前的agent扫一遍,然后看看“同类”的agent下线了的,就干掉。

虽然按照正常操作,agent在收到关闭命令的时候应该会主动把自己删除掉。但我这里没有这么做。理由是,agent下线的原因可能是本地开发用笔记本合上了盖子。同时又懒得做一个独立的管理服务来做这个事情。

创建agent

这里的创建agent主要指的是,在Jenkins master上创建一个待连接的agent。创建过程通过jenkins-cli.jar完成,具体命令是:
cat agent.xml | java -jar jenkins-cli.jar -s https://$JENKINS_HOST/ -auth $USERNAME:$TOKEN create-node $HOSTNAME

就是通过用户名以及相应的token,以agent.xml为模板,创建一个以机器名为名字的agent。

这里的关键是agent.xml如何获得。

个人的做法是,首先在Jenkins的页面上手动创建出一个agent,然后再用get-node来获取我当前手动创建出来的agent的xml配置。
具体可以参考:https://stackoverflow.com/a/30731429/6274938

连接到master

要连接到master,首先要一个secret。也就是一个命令的事情:
curl -L -s -u $USERNAME:$TOKEN -X GET https://$JENKINS_HOST/computer/$HOSTNAME/slave-agent.jnlp | sed "s/.*<application-desc main-class=\"hudson.remoting.jnlp.Main\"><argument>\([a-z0-9]*\).*/\1/" > secret-file
大致来说,我也是抄过来的,具体来源忘了。但按说应该能够随手搜到很多类似操作。

最后连接也只是一个命令:
java -jar agent.jar -jnlpUrl https://$JENKINS_HOST/computer/$HOSTNAME/slave-agent.jnlp -secret @secret-file


部分代码

Dockerfile:

FROM openjdk:8

RUN apt-get update
RUN apt-get -y upgrade

RUN apt-get install -y apt-transport-https ca-certificates curl
RUN curl -fsSL "https://download.docker.com/linux/debian/gpg" | apt-key add
RUN echo "deb [arch=amd64] https://download.docker.com/linux/debian stretch stable" > /etc/apt/sources.list.d/docker.list
RUN apt-get update
RUN apt-get install -y docker-ce

RUN apt-get install -y python-pip
RUN pip install python-jenkins

ADD register.sh ./
RUN chmod u+x ./register.sh

ADD cleanup.py ./
ADD agent.xml ./

CMD service docker start && ./register.sh

其实可以看到这里在镜像里安装了docker和python-jenkins,而且采用了jdk8作为base image。就是一脸要docker in docker跑构建,或者使用docker.sock的样子。

register.sh:

python ./cleanup.py

wget -c https://$JENKINS_HOST/jnlpJars/jenkins-cli.jar
wget -c https://$JENKINS_HOST/jnlpJars/agent.jar

cat slave.xml | java -jar jenkins-cli.jar -s https://$JENKINS_HOST/ -auth $USERNAME:$TOKEN create-node $HOSTNAME

curl -L -s -u $USERNAME:$TOKEN -X GET https://$JENKINS_HOST/computer/$HOSTNAME/slave-agent.jnlp | sed "s/.*<application-desc main-class=\"hudson.remoting.jnlp.Main\"><argument>\([a-z0-9]*\).*/\1/" > secret-file

java -jar agent.jar -jnlpUrl https://$JENKINS_HOST/computer/$HOSTNAME/slave-agent.jnlp -secret @secret-file

这里其实没什么,就是如原理中所说的实施。


使用场景

我这里大概有3类使用场景:1. prod环境上使用;2. test环境上使用;3. 本地环境使用。三个场景其实没啥太大的差别,主要区别是:docker in docker与否,系统资源限制,网络资源限制。

Prod环境上使用

prod环境选择了privileged而不是docker.sock。主要是怕我pipeline脚本写高兴了,万一写了一个docker rm -f $(docker ps -aq)。那我那台机器就没有了。不知道是不是因为prod的系统资源紧张,反正感觉这样要慢很多。

prod环境的系统资源相对是比较紧张的。因此agent相应的资源就会少很多。

prod环境的网络却是非常好的。访问什么都很快。

Test环境上使用

test环境选择了privileged而不是docker.sock。理由和prod环境一样,不想重新setup node。

test环境的系统资源相对宽松一些。但测试环境的机器毕竟是J1900的cpu,性能也就那样。

test环境的网络是糟糕的。由于test环境物理位置是我家,所以你懂的,干啥都得走我那套网络。延迟爆炸。

本地环境使用

本地环境使用是指:当我开启我的开发机的时候(开发机可以是一台虚拟机),将这台开发机作为Jenkins的agent来跑build。

其实原理也很简单,就是启动时脚本跑一下dockers命令就可以了。而且好处也明显,当我在开发的时候,能至少保证一个agent可用。

本地环境果断选择了docker.sock。又快又好。大不了直接docker system prune

本地环境的系统资源非常充沛。再怎么也是9代i7下的8核24g虚拟机。

本地环境的网络就随缘。毕竟和机器所处的网络和位置有很大关系。


我们穷人是这样的……人穷了会因为生活的压力而多多少少在生活实践上变得有点变态……