背景
2024年过年前最后一个版本,接到了一个奇怪的任务,大致要求是这样的:k8s 给用户创建一个 pod,用户在容器中可以使用 docker 相关的功能,并且 inner docker 和外部 k8s是隔离的,有点类似于公有云的虚拟机的效果。
其实,最初的需求是这样的,甲方有一批机器和显卡,想把这些资源给自家员工使用,当做 playground,跑跑算法、训练训练模型之类的。其实这个需求并不复杂,按照公有云厂商的设计照着抄就可以了(提供公有镜像,用户可以上传自己的私有镜像),但是最终产品同学定了 docker in docker 的方案:不为用户管理私有镜像,只提供一个公共的基础镜像,并预装好 docker,让用户在里面自己捯饬。虽然我们几个执行的人都 get 不到这样到底有多大用,但是上面拍板了,也只能先这么定了。
调研
一、DinD
网上稍微一搜,就可以找到 DinD(Docker in Docker) 这个方案,但是很遗憾,github 的页面上提示:
This repository has been archived by the owner on May 24, 2019. It is now read-only.
从 2019 年起作者就不再更新它了,不过好在作者为我们指了另一条路:
If you came here because you would like to run a testing system like Jenkins in a container, and want that container to spin up more containers, then please read this blog post first. Thank you!
上面链接的文章里,作者分析了原先 DinD 方案的更新历程以及优势劣势,并且推荐了一个船新的方案 Sysbox.
二、 Sysbox
Sysbox 是一个开源的 container runtime(a sprecialized "runc"),在2022年5月的时候,被 Docker 官方收购,有了官方的背书,我们用起来就更有底气了一些。
Sysbox 的诞生,是为了解决下面这些问题(中文翻译来自ChatGPT3.5):
-
Enhancing the isolation of containerized microservices (root in the container maps to an unprivileged user on the host).
增强容器化微服务的隔离性(容器内的root用户映射到主机上的非特权用户) -
Enabling a highly capable root user inside the container without compromising host security.
在容器内启用高度能力的root用户,而不影响主机安全。 -
Securing CI/CD pipelines by enabling Docker-in-Docker (DinD) or Kubernetes-in-Docker (KinD) without insecure privileged containers or host Docker socket mounts.
通过启用Docker-in-Docker(DinD)或Kubernetes-in-Docker(KinD),确保CI/CD流水线的安全性,而不使用不安全的特权容器或主机Docker套接字挂载。 -
Enabling the use of containers as "VM-like" environments for development, local testing, learning, etc., with strong isolation and the ability to run systemd, Docker, IDEs, and more inside the container.
在开发、本地测试、学习等方面,以强大的隔离性和在容器内运行systemd、Docker、IDE等的能力,使容器成为“类似虚拟机”的环境。 -
Running legacy apps inside containers (instead of less efficient VMs).
在容器内运行传统应用程序(而不是效率较低的虚拟机)。 -
Replacing VMs with an easier, faster, more efficient, and more portable container-based alternative, one that can be deployed across cloud environments easily.
用更简单、更快、更高效、更可移植的基于容器的替代方案替换虚拟机,可以轻松部署在各种云环境中。 -
Partitioning bare-metal hosts into multiple isolated compute environments with 2X the density of VMs (i.e., deploy twice as many VM-like containers as VMs on the same hardware at the same performance).
将裸金属主机划分为多个隔离的计算环境,其容器密度是虚拟机的两倍(即在相同性能硬件上部署两倍于虚拟机的容器)。 -
Partitioning cloud instances (e.g., EC2, GCP, etc.) into multiple isolated compute environments without resorting to expensive nested virtualization.
在不使用昂贵的嵌套虚拟化的情况下,将云实例(例如EC2、GCP等)划分为多个隔离的计算环境。
Sysbox 通过下面两个主要途径,来增强容器原有的功能(中文翻译来自ChatGPT3.5):
-
Improves container isolation
提升容器隔离性-
Linux user-namespace on all containers (i.e., root user in the container has zero privileges on the host).
在所有容器上使用Linux用户命名空间(即,容器中的root用户在主机上没有任何特权)。 -
Virtualizes portions of procfs & sysfs inside the container.
在容器内虚拟化部分procfs和sysfs。 -
Hides host info inside the container.
在容器内隐藏主机信息。 -
Locks the container's initial mounts, and more.
锁定容器的初始挂载点等。
-
-
Enables containers to run same workloads as VMs
使容器能够运行与虚拟机相同的工作负载:-
With Sysbox, containers can run system-level software such as systemd, Docker, Kubernetes, K3s, buildx, legacy apps, and more seamlessly & securely.
使用Sysbox,容器可以无缝且安全地运行系统级软件,如systemd、Docker、Kubernetes、K3s、buildx、传统应用程序等。 -
This software can run inside Sysbox containers without modification and without using special versions of the software (e.g., rootless variants).
这些软件可以在Sysbox容器内无需修改运行,也无需使用特殊版本的软件(例如,非特权变体)。 -
No privileged containers, no complex images, no tricky entrypoints, no special volume mounts, etc.
无特权容器,无复杂的镜像,无需复杂的入口点,无需特殊的卷挂载等。
-
简单来说,它将容器变成了类似于虚拟机,但隔离性/安全性稍弱于虚拟机、强于普通容器的东西,虽然和虚拟机相比是“稍弱”,但事实上来说已经相当强大了,至少对于本次的需求来说是基本满足的。
Sysbox 的 github 页面上还提供了一个演示视频:"VM-like" containers with Docker + Sysbox
需要注意的是,安装 sysbox 对操作系统的内核版本有一定要求,具体请参考这里
验证
从文档和演示视频来看, sysbox 这个方案似乎可以满足我们的要求,接下来我们就可以着手验证了。
在此之前,需要更全面的盘点一下本次的需求:
- 让用户可以在 docker 容器中,再嵌套的运行 docker
- 我们可以通过对外层容器执行 docker commit,来保存外层容器和 inner docker 的所有内容(虽然我们要做一个 playground,但也必须是一个可以保存状态的 playground,关机就丢掉所有数据可不行)
- inner docker 可以分配 outter docker container 的 GPU 资源
- 用户在 outter docker container 中,无法通过
df -hT查看到宿主机的存储信息(非必须) - 限制 outter docker container 系统盘的大小(即 overlay 的大小)(非必须)
- 用户在 outter docker container 中,无法通过
top查看宿主机的内存、CPU等信息(非必须)
其中 123 是必须要求,456 如果实在做不到可以放弃。
事实上,在几乎要验证完的时候,我们才在 Sysbox 文档的旮旯拐角里发现了个问题: sysbox 的 inner container 目前不支持共享 outter container 的 GPU 资源,这其实已经宣告了这条路的死亡,因为我们本次需求核心就是为了共享 GPU 资源。虽然 issue 中有人尝试自行 diy,并且似乎已经实现了,但是说实话,这东西着实有点复杂,没有官方背书只靠我们自己确实有点难。
1. Docker + Sysbox
先从简单的来,在 docker 中安装 sysbox 非常简单,直接去 release page下载安装包,然后 apt-get install ./sysbox-ce_0.6.3-0.linux_amd64.deb 就可以了。
-
启动 outter container: 注意要加上
--runtime=sysbox-runcdocker run --runtime=sysbox-runc -itd nestybox/ubuntu-focal-docker:latest -
启动 inner docker
containerd > /var/log/containerd.log 2>&1 &
dockerd > /var/log/dockerd.log 2>&1 &
- 在 inner docker 中运行一个 container
outter docker 中是看不到这个 container 的,只能看到 outter container:
到这里就初步实现了 docker in docker 的特性,但是我们的最终目标是要在 k8s 中使用,否则没法做资源的调度工作。
接下来我们看看能否做到磁盘容量的限制:
a. 限制磁盘容量
通过 linux xfs 文件系统,可以限制磁盘空间的大小,达成存储隔离的需求,这要求磁盘必须是 xfs 文件系统:
- 打开文件系统的配额管理
vim /etc/default/grub
配置:GRUB_CMDLINE_LINUX_DEFAULT="rootflags=pquota"
grub-mkconfig -o /boot/grub/grub.cfg
配置完记得重启操作系统才能生效
- 启动容器,通过
--storage-opt参数,指定磁盘(overlay)大小
docker run --runtime=sysbox-runc -itd --storage-opt size=18G nestybox/ubuntu-focal-docker:latest
在 inner container 中执行 df -hT 即可看到,系统盘的大小就是上面指定的18G
- 我们尝试在 outter container 中创建一个 4G 的空文件,在 inner container 中创建一个 3G 的空文件
- 然后再主机上查找一下大文件,出现了一个有趣的结果,原来 inner docker 的数据是单独存储的,并未放在 outter container 的 overlay中,在 inner docker 中,不论存放多少数据,都不会触发 18G 的限制
总结一下:
- 能实现 docker in docker,且内外层 docker 无关联,互不影响
- docker in docker 的情况下,无法限制存储资源的大小
2. k8s + Sysbox
接下来是到 k8s 中验证,这个才是我们关注的重点,不过我电脑上的虚拟机实在是带不动了,没办法,去腾讯云上搞了个轻量级服务器继续测试
机器的 hostname 记得一定要用小写,不然安装过程中会出各种离奇的问题
sysbox 对操作系统内核有要求,具体可以看这里
sysbox 支持的 kubernetes 版本在这里
kubekey 支持的 kubernetes 版本在这里
a. 安装 k8s
使用 kubekey 安装 k8s 集群
通过 ./kk version --show-supported-k8s 命令,也可以查看 kubekey 支持的 kubernetes 版本
这里我选择了 kubernetes v1.26.5 和 kubesphere v3.4.1;其实检查一下 kubekey、sysbox 支持的版本就能发现,可选择的余地并不多。
export KKZONE=cn
./kk create cluster --with-kubernetes v1.26.5 --with-kubesphere v3.4.1 --container-manager containerd
输入完 yes 之后,这个命令会跑好一阵子下载各种资源,耐心等待吧。K8S 装好之后效果如图所示:
记得修改默认密码:
kubectl patch users admin -p '{"spec":{"password":"xxxxxxxxx"}}' --type='merge' && kubectl annotate users admin iam.kubesphere.io/password-encrypted-
b. 安装 sysbox
sysbox 仅支持 crio 作为容器运行时,这对我们(本次需求)来说,其实是一个重大的缺点,因为被 crio 所管理的容器,无法被 containerd 或其他 container runtime 看到,无法实现类似 docker commit 和 docker tag 的功能。
github 上有用户提问需要使用 commit、tag 这样的功能时,收到的回答是这样的:
I'm afraid not. We recommend you develop your images with podman and then use them in cri-o in production
CRIO 到底是什么?可以参考下面这张网图,更详细的知识请大家自行百度
我们开始安装,首先给 node 打上标签,表示要安装 sysbox:
kubectl label nodes <node-name> sysbox-install=yes
接着是执行下面的命令,这会在集群中启动一个 pod,自动完成安装任务,安装完之后会自动重启整个集群。但是,国内的机器可能下载不了,即便能下载也拉取不下来镜像
kubectl apply -f https://raw.githubusercontent.com/nestybox/sysbox/master/sysbox-k8s-manifests/sysbox-install.yaml
就像这样:
所以我在本地挂代理下载好镜像,然后手动传到服务器上,导入到 container runtime 中;上面安装 k8s 的时候指定的容器运行时是 containerd,所以导入的时候需要用命令:
ctr -n=k8s.io image import ./sysbox-k8s-installer.tar
然后手动下载 yaml 文件,修改里面的 imagePullPolicy 为 IfNotPresent,之后再执行下面的命令即可:
kubectl apply -f ./sysbox-install.yaml
这个命令执行的时间比较久,还会重启整个集群,运行起来之后,我们检查一下只要 sysbox 的 pod 状态是 running,就可以小憩一会儿了。等待整个集群重启完毕,我们再看 sysbox-installer pod 的日志,就可以看到已经安装成功了:
需要注意的是,sysbox 安装好之后,集群的 container runtime 就被修改成 cri-o 了,这时候它不再支持类似 docker load 这样的功能了,可以先 docker load(这时候加载的镜像对集群来说是不可见的),再通过下面的命令拷贝到 cri-o 中:
skopeo --debug copy docker-daemon:registry.nestybox.com/nestybox/sysbox-deploy-k8s:v0.6.3 containers-storage:registry.nestybox.com/nestybox/sysbox-deploy-k8s:v0.6.3
c. 验证 docker in docker
按照 github 上的示例,我们创建一个 pod,重点是 runtimeClassName: sysbox-runc 这个参数
apiVersion: v1
kind: Pod
metadata:
name: dind-test-centos
annotations:
io.kubernetes.cri-o.userns-mode: "auto:size=65536"
spec:
runtimeClassName: sysbox-runc
containers:
- name: dind-test-centos
image: docker.io/library/centos:latest
command: ["/sbin/init"]
restartPolicy: Never
outter container 中安装 docker 的过程省略,我们登录到 outter container 中,创建一个 inner container:
成功!
d. 继续测试
- 我们在 inner container 中用命令
fallocate -l 3G inner-3g-file创建一个 3G 的空文件 inner-3g-file - 再退回到 outter container 中用命令
fallocate -l 4G outter-4g-file创建一个 4G 的空文件 outter-4g-file - 再在宿主机上查找刚刚创建的大文件
可以看到 inner container 中的内容,被单独存储在了 /var/lib/sysbox 中,而 outter container 中的内容,被存储在了 /var/lib/containers/storage/overlay/ 下
e. 限制磁盘使用量
在 3 种方法限制 Pod 磁盘容量 这篇文章中,提供了 3 种方案:
- 从容器引擎限制:需要修改 docker 的配置,且无法影响那些挂载的卷;sysbox 场景下不使用 docker 而是 cri-o,没有这个特性。
- 从系统层限制:无法灵活应用于 sysbox 的应用场景,inner container 的内容单独存储,大小不可控
- 从编排层限制:存在两个问题,一是使用超过限制值时,不是提示无法写入,而是驱逐 pod;二是同样无法灵活应用于 sysbox 的应用场景
我们测试下方案 3 看看效果,高版本 k8s 自动开启了这个特性,我们可以直接测试,首先给容器加上 ephemeral-storage = 7GB 的限制:
然后手动创建两个大文件,可以看到直接就因为被驱逐而 exit 了(事实上 k8s 是定期去扫描磁盘占用的,并非实时检测):

这里我们可以看到确实是由于大小超限而被驱逐了,而不是我们期望的“无法写入”:

结论
到这一步,基本上对 sysbox 有一定的了解了,我们在看一下需求描述:
- 让用户可以在 docker 容器中,再嵌套的运行 docker:可以实现
- 我们可以通过对外层容器执行 docker commit,来保存外层容器和 inner docker 的所有内容(虽然我们要做一个 playground,但也必须是一个可以保存状态的 playground,关机就丢掉所有数据可不行):无法实现,sysbox 必须使用 cri-o 作为 container runtime,cri-o 不支持类似
docker commitdocker tag等命令 - inner docker 可以分配 outter docker container 的 GPU 资源:无法实现
- 用户在 outter docker container 中,无法通过
df -Ht查看到宿主机的存储信息(非必须):无法实现 - 限制 outter docker container 系统盘的大小(即 overlay 的大小)(非必须):无法实现
- 用户在 outter docker container 中,无法通过
top查看宿主机的内存、CPU等信息(非必须):无法实现
经过一系列调研,发现确实没法实现产品的要求,最终大家讨论决定放弃 docker in docker 的方案。事实上,sysbox 这东西设计的初衷,其实是为了跑类似 jenkins 流水线这类任务,并非是为了实现我们这种特殊的需求。












