Docker in Docker(DinD) 是一种容器虚拟化嵌套技术,它允许用户在容器内部运行 Docker 容器,层层嵌套,从而实现更复杂的容器化操作和构建环境 。
01、背 景
Kubernetes集群计划迁移至1.24版本的集群,底层容器运行时换成了Containerd,然而当前的CI/CD流水线大多地方使用到了docker指令,要改成Containerd指令成本巨大,如何以最小的改动,最为高效地完成迁移便成了难题,为了攻克这个难题我们引入了DinD的方案进行实现,流水线都不用做任何改动,简直完美。
02、Containerd的由来
我们顺便了解下Containerd的由来,据说,在很早之前,Kubernetes就放风不再支持Docker,主要原因是Docker不支持其引入的CRI(容器运行时接口),还需要额外维护docker-shim组件,费时费力。
2015年,为了应对这一挑战,Docker 公司决定将容器运行时从 Docker 引擎中分离出来,并支持 CRI,将这个项目命名为 Containerd。
2017年,被捐赠给云原生计算基金会(CNCF),后来成为了 CNCF的顶级开源项目。
Containerd专注于容器的生命周期管理,包括容器的创建、启动、停止和删除等。这种专注使其更轻量、高效,并且与 Kubernetes 的设计理念更为契合,简化了集群管理的复杂性,提升了系统的性能和可靠性。
03、方案说明
为了使 Jenkins Slave 能够使用 Docker 命令,我们需要将 Docker 二进制文件和 docker.sock(套接字)映射到容器中。我们并不需要在容器内启动一个 Docker 进程,因为只要 Docker 的二进制文件能够通过 docker.sock 与守护进程通信即可。
如上图,我们大致会执行以下步骤:
1. 运行Docker daemonset,将K8S Node的/var/run/docker-ci映射到容器的/var/run/docker目录
2. 将K8S Node的/var/run/docker-ci中的docker.sock和docker映射到Jenkins Slave中
04、操作步骤
将下面内容保存为docker-ci-daemonset.yaml,然后通过kubectl apply进行部署
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: docker-ci # 因为是用来做docker镜像构建的,所以我把他命名为docker-ci
namespace: default # 命名空间,我是放在default命名空间下,可以自行修改
spec:
selector:
matchLabels:
app: docker-ci
template:
metadata:
labels:
app: docker-ci
spec:
initContainers:
- name: init-sysctl
image: busybox
command:
- rm
- '-f'
- /var/run/docker.sock # 启动前,删除一下docker.sock,防止因异常原因退出留有残留文件,影响docker启动
resources: {}
volumeMounts:
- name: host
mountPath: /var/run # 将宿主机卷挂载到init容器
terminationMessagePath: /dev/termination-log
terminationMessagePolicy: File
imagePullPolicy: Always
securityContext:
privileged: true # 赋予容器特权
containers:
- name: docker-ci
image: 'docker:20.10.24-dind' # Docker-in-Docker镜像
command:
- dockerd
- --host=unix:///var/run/docker.sock
- --host=tcp://0.0.0.0:8000
securityContext:
privileged: true # 赋予容器特权
resources:
limits:
cpu: '2'
memory: 4Gi
requests:
cpu: 300m
memory: 500Mi
volumeMounts:
- mountPath: /var/run
name: host # 将宿主机卷挂载到主容器
lifecycle:
postStart: # 添加postStart钩子
exec:
command:
- sh
- -c
- |
if [ -e /var/run/docker/docker ]; then
rm -f /var/run/docker/docker
fi
cp -a /usr/local/bin/docker /var/run/docker/
volumes:
- name: host
hostPath:
path: /var/run/docker-ci # 创建一个目录,避免将/var/run的所有文件挂载到容器中
好了,接下来就是修改Jenkins的配置,我们在需要用到docker指令的容器配置模版加上docker二进制和docker.sock的映射即可,如
- name: "jnlp"
image: "jenkins/jnlp-slave:3.27-1"
args: "^${computer.jnlpmac} ^${computer.name}"
resourceRequestCpu: "100m"
resourceLimitCpu: "500m"
resourceRequestMemory: "500Mi"
resourceLimitMemory: "2048Mi"
workspaceVolume:
emptyDirWorkspaceVolume:
memory: false
volumes:
- hostPathVolume:
hostPath: "/var/run/docker-ci/docker.sock"
mountPath: "/var/run/docker.sock"
- hostPathVolume:
hostPath: "/var/run/docker-ci/docker/docker"
mountPath: "/usr/bin/docker"
完成上述步骤,就可以在发布流程中使用docker指令了。
05、总 结
虽然DinD可以完美解决我的难题,但是也存在弊端,如安全性问题、容器不稳定等,都是我们需要关注的,这期就分享到这里,谢谢!