使用 overlayfs 和 chroot 搭建临时环境

最近在写一个小工具,依赖的库比较多,但是这些依赖都不常用,不想为了开发而装在常用的机器上。想用以前用 chroot 搞的一个开发环境,可惜更新源验证总出问题,搜索了几个方案后还是没解决。我只是想搞个隔离的测试环境,不需要限制资源,docker 又庞大配置又麻烦(其实根本原因是我讨厌 golang),又不想重新配置一份环境,于是搜了下 docker 相关的底层技术,最后发现 overlayfs+chroot 就能完美解决我的需求,在这里记录一下,内核版本是 5.6。

overlayfs 介绍

顾名思义,overlayfs 就是把多个目录组合在一起形成一个新的文件系统,在 2014 年被合并进 kernel v3.18。先来看下使用时的命令行参数:

mount -t overlay overlay -o lowerdir=<lowerdir1>:<lowerdir2>:<...>,upperdir=<upperdir>,workdir=<workdir> <mountpoint>

大概介绍下这里的三个参数:

  • lowerdir:只读目录,多个目录用 ":" 分隔。
  • upperdir:可读可写目录。如果 upperdirlowerdir 中有同名的文件,会优先展示 upperdir 中的文件(换言之就是 lowerdir 中的文件被屏蔽了)。
  • workdir:必须是空目录,作用未知。

例如下面的例子:

mkdir low1 low2 low3 upper work overlaydir
touch low1/1.txt low2/2.txt low3/3.txt

mount -t overlay overlay -o lowerdir=./low1:./low2:./low3,upperdir=./upper,workdir=./work overlaydir

执行完上面的命令后:

# ls overlaydir
1.txt 2.txt 3.txt

可以看到三个 lower 目录下的内容被合并到 overlaydir 中。如果这时在 overlaydir 中创建一个新文件:

touch overlaydir/4.txt

ls 查看三个 lower 目录都没有改变,但是在 upper 中出现了新建的文件。

接着修改下 3.txt 的内容:

echo "hello, world!" > overlaydir/3.txt

cat overlaydir/3.txt 可以发现的确写入了内容,但是 cat low3/3.txt 可以发现这个文件还是空的,而 upper 目录下多了个 3.txt,里面的内容正是我们刚才写入的内容。

upper 中新建一个文件(根据 参考资料 [1],在目录被挂载的时候修改被挂载的目录可能导致不可预知的问题,这里只是做个实验):

touch upper/5.txt

新建的文件也出现在了 overlaydir 中。接着往新建的文件中写入内容:

echo "ouonline" > overlaydir/5.txt

通过查看可以发现在 upper/5.txt 中保存了我们写入的内容。

overlayfs 的简单用法就这样,知道这些已经可以很好地满足我的需求了,实现细节就没有去深究。

overlayfs+chroot

具体到我的需求,就是先把宿主的环境挂载为只读(不污染宿主环境):

# 创建工作用的目录
mkdir upper work overlaydir

# 把根目录挂载为只读,overlaydir 是最终的挂载点
mount -t overlay overlay -o lowerdir=/,upperdir=./upper,workdir=./work overlaydir

这里将根目录作为 lowerdir。由于我的 /home 在另一个分区,虽然直接把根目录作为 lowerdir,但是进入 overlaydir/home 是看不到 /home 目录下的内容的。如果需要在 chroot 后访问 home 需要重新手动挂载:

mount -t <fs_type> <dev_of_home> overlaydir/home

或者想共享某个目录:

mount --bind /path/to/shared/dir overlaydir/path/to/mount/point

准备好环境后就切换到 overlaydir

chroot overlaydir /bin/bash

# 这里是 chroot 到 overlaydir 之后的一些环境初始化
mount -t devpts devpts /dev/pts
mount -t proc proc /proc
mount -t sysfs sysfs /sys

然后像宿主一样正常使用就可以了。做完实验后

umount /sys
umount /proc
umount /dev/pts
exit
umount overlaydir

然后把 upper 中的内容都删掉,重新挂载后就跟实验前的环境一样了。当然也可以新建一个 upperdir 然后挂载到另一个目录中,就可以同时拥有多个不同的环境了,但是存储空间不会线性增长,因为只有被修改过的内容会保存下来。同时内核和其它基础工具都是用的宿主系统的环境,不需要单独维护。

缺点

使用这个方案的好处是轻量而且不污染宿主环境,但是缺点也是很明显的,就是当宿主环境有改动,例如更新了某个包,实验环境可能因为依赖法生变化导致不可用,这种情况下就需要删掉 upper 的内容重新来一遍,挺麻烦的。

要解决这个问题,要么把当前系统打个包(参考 使用 tar 备份 linux)然后解压到某个目录下,或者用 debootstrap 生成一个基础系统,相当于一个固定的 lower 环境,后面宿主怎么改都不影响,但是这样占用空间会多一些;要么是用一些支持快照的文件系统如 bcachefs 和 btrfs,对根目录做个快照,这样会方便些。不过到目前(内核版本 6.11)为止,bcachefs 还没稳定,而 btrfs 总有这样那样的小问题。至于 zfs 每次更新内核都要重新编译,而且用在根目录上还需要一番折腾,就更不推荐了。

其它

由于 overlayfs 不能嵌套,如果需要在 overlayfs 中创建另一个 overlayfs,要把 upper 和 work 放到外部存储(例如上面映射的 /path/to/shared/dir)或者放到 tmpfs 上(例如放到 /dev/shm 里)。

这样在 overlay 环境内就可以执行

mount -t overlay overlay -o lowerdir=/,upperdir=/path/to/mount/point/upper,workdir=/path/to/mount/point/work /path/to/merged

挂载点 merged 可以位于任意位置。

mount 一般都需要 root 权限才可以执行。如果我们访问挂载的目录不需要 root 权限(例如 lower 是我们有权限读写的一个目录),那么可以用 linux 的 unshare 命令 来切换到一个新的 namespace,然后再执行需要的 mount 操作。

参考资料

[1] Overlay Filesystem

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注