月度归档: 2020 年 2 月

在Kubernetes里使用gradle缓存加速编译和docker in docker例子

需求

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: {}

打造K8S与Jenkins的持续集成系统-CI部分

说明

持续集成系统包括CI和CD。在这篇文章里,我们只讨论CI的实现。关于K8S,Jenkins,私有仓库的安装这里不进行说明,只讲解如何将这些工具组合在一起。

1.拥有一个K8S集群

我的环境是6台虚拟机,3台Master,3台Slaver。如下图所示

运行结果如下图:

2.Jenkins中安装好了kubernetes插件


安装好之后,在系统设置中,能够添加kubernetes信息

3.根据Jenkins的配置要求,去自己的K8S平台上找到对应的信息。填入Jenkins中


我们根据图中的要求,可以在K8S里进行一个个的查找我们想要的信息

字段
查找方法
Kubernetes 地址
IP地址是在keepalived里配置的虚拟IP

在搭建k8S平台时,用到了haproxy,当时指向了**443端口
kubernetes名称 随意填写
kubernetes命名空间 需要在K8S里创建一个命名空间,然后在这个地方使用
凭据
1.进入K8S机器上,输入命令kubectl config view –minify –flatten查看凭据

2.把凭据信息复制成一个文件

3.在Jenkins里上传这个文件

4.这个凭据信息即可在Jenkins配置里使用
JENKINS地址 配置本次使用的Jenkins的url即可
Pod Retention 选择Never,Jenkins任务完成后就删除pod

4.添加私有密钥用于拉取我们的docker私有镜像

在K8S里输入如下命令
kubectl create secret docker-registry repo-k8s \
-n k8s-jx \
--docker-server='repo.local:5000' \
--docker-username='
zhenwei.li
' \
--docker-password='*******' \
--docker-email='357244849@qq
.com
'

5.在Jenkins里编写一个最简单的流水线任务。来测试Jenkins是否可以拉起K8S的节点,产生pod

5.1.从github上抄一段代码示例下来https://github.com/jenkinsci/kubernetes-plugin/blob/master/src/test/resources/org/csanchez/jenkins/plugins/kubernetes/pipeline/jnlpWorkingDir.groovy

podTemplate(label:'jnlp-slave',cloud: 'kubernetes',containers: [
    containerTemplate(name: 'busybox', image: 'nginx:latest', ttyEnabled: true)
],imagePullSecrets:['secret/repok8s']) {
    node ('jnlp-slave') {
        stage('Run') {
            sh 'env | sort'
            container('busybox') {
                sh 'echo "hello"'
                sh 'pwd'
                sh 'ls'
            }
        }
    }
}

5.2创建一个流水线任务

5.3运行这个流水线任务,查看结果

5.4在K8S上输入查找pod的命令,应该会发现有一个pod被运行起来了,名字中含有jnlp-slave的字样,2/2运行两个容器,一个是jnlp容器,一个是busybox容器

kubectl get pods --all-namespaces -o wide

6.在代码仓库中添加CI所需要的Jenkinsfile和BuildPod.yaml等流水线文件

文件名
用处
sonar-project.properties 定义代码需要执行什么语言的扫描,哪些目录需要剔除
JenkinsfileTag 定义打tag操作对应的流水线行为
Jenkinsfile 定义代码提交与合并的流水线行为
BuildPod.yaml K8S产生的pod中,包含哪些容器,容器的环境变量等参数设置

7.BuildPod.yaml中需要包含哪些内容(以nodejs代码举例)

8.Jenkinsfile中需要包含哪些内容

最后在Jenkins里查看运行效果


 


苏ICP备18047533号-1