需求
1.我们的代码编译需要用到gradle6.2版本,jdk13版本,docker in docker策略
2.因为是在CI环境中使用,所以gradle容器会因为流水线的触发,不停的启动和删除。下载jar包会非常消耗时间,我们需要持久化这些gradle缓存。
3.挂载这些gradle缓存文件到机器上,可以用ceph集群和NFS,这里我偷懒,先用NFS做,后期资源充足再换成ceph。
4.因为我们有并行流水线的可能,所以gradle容器可能一次不止一个,而gradle的caches一次只能被一个进程占用,为了避免多容器占用同一个gradle的caches,我们需要有策略。
已经踏过的坑
1.不能使用apline来制作gradle容器,因为我们的代码里有用到protoc,他会在gradle里安装protoc-3.10.1-linux-x86_64.exe,但是这个程序并不兼容alpine系统,最终会导致报错。报错信息如下图
2.不要使用adoptopenjdk/openjdk13:latest镜像来制作Dockerfile,这个镜像用的jdk版本是jdk13 ea版本。这个ea版本和正常的jdk13有一些细微的差点,这会导致gradle最后编译失败,报错信息如下图
3.不要直接把gradle缓存目录直接挂载进去,这会导致多任务时阻塞,同时编译失败,如下图
解决过程
1.创建NFS
NFS的搭建是比较简单的。在网上可以很容易搜索到教程,这里需要注意节点机器上也需要安装NFS工具,apt-get install nfs-common,不然会报错,导致K8S在这个节点上创建不了容器,因为挂载volume就失败了
我们在NFS服务器上创建一个目录,用于存放gradle的缓存
vi /etc/exports
添加
/nfsdata/gradle-cache/ *(rw,sync,no_root_squash,no_subtree_check)
2.创建gradle镜像,用于k8s中编译使用
使用Dockerfile,内容如下
FROM gradle:6.2.0-jdk13
# 因为需要docker in docker 所以这里用阿里云一键安装docker
RUN curl -fsSL https://get.docker.com | bash -s docker --mirror Aliyun
# 随意创建一个文件
RUN echo "test" > test.log
# 因为使用了私有仓库,所以之间拷贝docker配置文件进去
COPY daemon.json /etc/docker/daemon.json
# 原来的gradle命令是"jshell"这里我们替换掉,避免容器启动后,程序结束就消失
ENTRYPOINT ["tail","-f","test.log"]
docker build -t gradle_lzw .
3.使用这个gradle镜像运行起来,进行一次编译,把gradle caches拷贝出来,放到NFS中
#运行容器docker run -d -v /var/run/docker.sock:/var/run/docker.sock gradle_lzw
# 进入容器编译代码,docker exec -it gradle_lzw gradle XXXXX(gradle编译命令)
# 复制出缓存文件docker cp gradle:/home/gradle/.gradle /home/temp/local/.gradle
关于gradle的缓存可以看这个https://github.com/keeganwitt/docker-gradle
4.把上一步获得的gradle缓存文件上传到NFS服务器上
scp -r root@{有gradle缓存的机器IP}:/home/temp/local/.gradle /nfsdata/gradle-cache/.
5.为了避免多容器并发占用gradle缓存目录,我们只能绕开直接挂载。我们先把缓存文件挂载到一个无用的目录中,然后再从这个目录复制到gradle指定的缓存目录中l
项目流水线配置
Jenkinsfile内容参考:
pipeline {
agent {
kubernetes {
//label使用项目名称,因为不同的项目,build方式是不同的,如果错误的使用了相同的label。Jenkins就不会去读取BuildPod.yaml
label 'jnlp-项目名称'
//
yamlFile 'BuildPod.yaml'
}
}
stages {
stage('build') {
//使用gradle容器
container('gradle'){
sh '''
//复制缓存文件
cp -rf /opt/.gradle/caches /home/gradle/.gradle/caches
gradle {build 命令}
'''
}
}
}
}
BuildPod.yaml配置,gradle-cache是缓存目录,dind是docker in docker的必要容器
kind: Pod
metadata:
labels:
some-label: some-label-value
spec:
containers:
- name: jnlp
image: jenkins/jnlp-slave
tty: true
volumeMounts:
- name: workspace-volume
mountPath: /home/jenkins
- name: gradle
image: gradle_lzw:latest
tty: true
volumeMounts:
- name: workspace-volume
mountPath: /home/jenkins
- name: gradle-cache
mountPath: /opt/.gradle
env:
- name: DOCKER_HOST
value: tcp://localhost:2375
- name: dind
image: docker:18.05-dind
securityContext:
privileged: true
volumeMounts:
- name: dind-storage
mountPath: /var/lib/docker
imagePullSecrets:
- name: repok8s
volumes:
- name: gradle-cache
nfs:
server: {NFS服务器IP}
path: "/nfsdata/gradle-cache/.gradle"
- name: workspace-volume # pod中有一个volume让其中的多个容器共享
emptyDir: {}
- name: dind-storage
emptyDir: {}