前言

Gitlab Runner的执行器有8个,常见的有Shell、Dcoker和Kubernetes。

Shell执行器配置简单,但是构建工具都需要手动安装,例如:JDK、Maven、Nodejs。如果此时有需求需要升级所有Shell执行器的JDK版本,还需要重新手动升级?(虽然可以写个脚本)

Docker和Kubernetes执行器比较灵活,缺点就是Docker执行器和Kubenetes执行器每次启动的容器或者Pod依赖包都是干净的,就需要重新拉取相关依赖包,比较耽误构建时间。

此时Shell执行器就比较占优势,因为是使用系统本机的构建工具,会在系统上留有构建缓存,例如:.mvn

那么怎么做,也能缓存Docker执行器的layer层呢?

这个问题的解决方法非常简单,与其为每个 Pod 运行一个 Docker DIND 服务的 sidecar 容器,不如让我们运行一个独立的 Docker DIND 容器,构建容器的所有 Docker CLI 都连接到这个一个 Docker 守护进程上,这个时候我们将 Docker layer 层进行持久化,也就起到了缓存的作用了。

Docker 镜像说明

查看官方的 docker 镜像

docker:latest

该镜像只包含 Docker 客户端,需要有 Docker daemon 支持,可以使用 docker:dind 的,也可以挂载宿主机的 /var/run/docker.sock

该镜像启动不需要 --privileged 参数。

docker:dind

该镜像包含 Docker 客户端(命令行工具)和 Docker daemon。

通过 docker history docker:dind 命令我们发现 docker:dind 是在 docker:latest 基础上又安装了 Docker daemon

启动 docker:dind 容器时,参数 --privileged 必须加上,否则 Docker daemon 启动时会报错。

正文开始

首先创建一个 PVC 来存储 Docker 的持久化数据,为了性能考虑,这里我们使用的是一个 Local PV:

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: local-volume
provisioner: kubernetes.io/no-provisioner
reclaimPolicy: Delete
volumeBindingMode: WaitForFirstConsumer

---
apiVersion: v1
kind: PersistentVolume
metadata:
name: docker-pv
spec:
capacity:
storage: 5Gi
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Retain
storageClassName: local-volume
local:
path: /mnt/k8s/docker # 数据存储的目录
nodeAffinity:
required:
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/hostname
operator: In
values:
- node1 # 运行在node1节点
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
labels:
app: docker-dind
name: docker-dind-data
namespace: kube-ops
spec:
accessModes:
- ReadWriteOnce
storageClassName: local-volume
resources:
requests:
storage: 5Gi

然后使用 Deployment 部署一个 Docker DIND 服务:

apiVersion: apps/v1
kind: Deployment
metadata:
name: docker-dind
namespace: kube-ops
labels:
app: docker-dind
spec:
selector:
matchLabels:
app: docker-dind
template:
metadata:
labels:
app: docker-dind
spec:
containers:
- image: docker:dind
name: docker-dind
args:
- --registry-mirror=https://ot2k4d59.mirror.aliyuncs.com/ # 指定一个镜像加速器地址
env:
- name: DOCKER_DRIVER
value: overlay2
- name: DOCKER_HOST
value: tcp://0.0.0.0:2375
- name: DOCKER_TLS_CERTDIR # 禁用 TLS
value: ""
volumeMounts:
- name: docker-dind-data-vol # 持久化docker根目录
mountPath: /var/lib/docker/
ports:
- name: daemon-port
containerPort: 2375
securityContext:
privileged: true # 需要设置成特权模式
volumes:
- name: docker-dind-data-vol
persistentVolumeClaim:
claimName: docker-dind-data

然后创建一个 Service 以方便构建的 Docker CLI 与其连接:

apiVersion: v1
kind: Service
metadata:
name: docker-dind
namespace: kube-ops
labels:
app: docker-dind
spec:
ports:
- port: 2375
targetPort: 2375
selector:
app: docker-dind

将 Docker DIND 服务部署完成后,我们就可以在 Gitlab CI 中使用这个守护程序来构建镜像了,如下所示:

tages:
- image

build_image:
stage: image
image: docker:latest
variables:
DOCKER_HOST: tcp://docker-dind:2375 # 通过 service dns 形式连接 docker dind 服务
script:
- docker info
- docker build -t xxxx .
- docker push xxxx
only:
- tags

由于我们缓存了 Docker layer 层,这个时候构建的速度会明显提升。最后随着镜像的大量构建会产生很多镜像数据,我们可以写一个 Cronjob 用来定时清除缓存:

apiVersion: batch/v1
kind: CronJob
metadata:
name: docker-dind-clear-cache
namespace: kube-ops
spec:
schedule: 0 0 * * 0 # 每周清理一次
jobTemplate:
metadata:
labels:
app: docker-dind
name: docker-dind-clear-cache
spec:
template:
spec:
restartPolicy: OnFailure
containers:
- name: clear-cache
image: docker:latest
command:
- docker
- system
- prune
- -af
env:
- name: DOCKER_HOST
value: tcp://docker-dind:2375