如何使用OverlayFS保护根文件系统?

如何使用OverlayFS保护根文件系统?

如何正确使用 OverlayFS 来保护我的根文件系统?

我有一个从 SD 卡启动和运行的嵌入式系统。由于会突然断电,所以我想保护根文件系统。覆盖文件系统似乎是最简单的解决方案,但我发现的示例通常不涉及根文件系统和/或使用 tmpfs,这对我来说不好,因为我的内存很少。

我使用的是CONFIG_OVERLAY_FS=y已启用的 Linux 内核 4.4.0。我的文件系统是xenial-base-armhf.tar.gz并且我已经完成了apt install -y overlayroot

我的 SD 卡如下所示:

# fdisk -l /dev/mmcblk1
Disk /dev/mmcblk1: 29 GiB, 31104958464 bytes, 60751872 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disklabel type: dos
Disk identifier: 0x7f56a0ab

Device         Boot    Start      End  Sectors  Size Id Type
/dev/mmcblk1p1 *        2048  1050623  1048576  512M  c W95 FAT32 (LBA)
/dev/mmcblk1p2       1050624  1052671     2048    1M da Non-FS data
/dev/mmcblk1p3       1052672  7344127  6291456    3G 83 Linux
/dev/mmcblk1p4       7344128 60751871 53407744 25.5G  5 Extended
/dev/mmcblk1p5       7346176 13637631  6291456    3G 83 Linux
/dev/mmcblk1p6      13639680 60751871 47112192 22.5G 83 Linux

在创建 OverlayFS 之前,所有内容都安装为:

# mount
/dev/mmcblk1p3 on / type ext4 (rw,noatime,data=ordered)
devtmpfs on /dev type devtmpfs (rw,relatime,size=170440k,nr_inodes=42610,mode=755)
sysfs on /sys type sysfs (rw,nosuid,nodev,noexec,relatime)
proc on /proc type proc (rw,nosuid,nodev,noexec,relatime)
tmpfs on /dev/shm type tmpfs (rw,nosuid,nodev)
devpts on /dev/pts type devpts (rw,nosuid,noexec,relatime,gid=5,mode=620)
tmpfs on /run type tmpfs (rw,nosuid,nodev,mode=755)
tmpfs on /run/lock type tmpfs (rw,nosuid,nodev,noexec,relatime,size=5120k)
tmpfs on /sys/fs/cgroup type tmpfs (ro,nosuid,nodev,noexec,mode=755)
cgroup on /sys/fs/cgroup/systemd type cgroup (rw,nosuid,nodev,noexec,relatime,xattr,release_agent=/lib/systemd/systemd-    cgroups-agent,name=systemd)
configfs on /sys/kernel/config type configfs (rw,relatime)
tmpfs on /run/user/0 type tmpfs (rw,nosuid,nodev,relatime,size=35752k,mode=700)
/dev/mmcblk1p6 on /opt type ext4 (rw,noatime,data=ordered)
/dev/mmcblk1p5 on /overlay type ext4 (rw,noatime,data=ordered)

我的计划是用作/dev/mmcblk1p5安装在 的覆盖文件系统/overlay

# tree /overlay
/overlay
├── lost+found
├── root-fs
└── work

要么我做错了,要么我有一些配置问题,因为:

# mount -t overlay overlay -o lowerdir=/,upperdir=/overlay/root-fs,workdir=/overlay/work /
# mount
/dev/mmcblk1p3 on / type ext4 (rw,noatime,data=ordered)
devtmpfs on /dev type devtmpfs (rw,relatime,size=170440k,nr_inodes=42610,mode=755)
sysfs on /sys type sysfs (rw,nosuid,nodev,noexec,relatime)
proc on /proc type proc (rw,nosuid,nodev,noexec,relatime)
tmpfs on /dev/shm type tmpfs (rw,nosuid,nodev)
devpts on /dev/pts type devpts (rw,nosuid,noexec,relatime,gid=5,mode=620)
tmpfs on /run type tmpfs (rw,nosuid,nodev,mode=755)
tmpfs on /run/lock type tmpfs (rw,nosuid,nodev,noexec,relatime,size=5120k)
tmpfs on /sys/fs/cgroup type tmpfs (ro,nosuid,nodev,noexec,mode=755)
cgroup on /sys/fs/cgroup/systemd type cgroup (rw,nosuid,nodev,noexec,relatime,xattr,release_agent=/lib/systemd/systemd-cgroups-agent,name=systemd)
configfs on /sys/kernel/config type configfs (rw,relatime)
tmpfs on /run/user/0 type tmpfs (rw,nosuid,nodev,relatime,size=35752k,mode=700)
/dev/mmcblk1p6 on /opt type ext4 (rw,noatime,data=ordered)
/dev/mmcblk1p5 on /overlay type ext4 (rw,noatime,data=ordered)
overlay on / type overlay (rw,relatime,lowerdir=/,upperdir=/overlay/root-fs,workdir=/overlay/work)

看起来它有效,但如果我创建一个如下文件:

# touch /root/test_file_write

然后,关闭电源并查看桌面上的 SD 卡,我看到的/dev/mmcblk1p3/root/test_file_write不是我所期望的/dev/mmcblk1p5/root-fs/root/test_file_write

这应该有效吗?

答案1

这个答案是基于我自己的经验,但不适用于嵌入式设备。也许这对那些偶然发现这个设置的人很有用——你应该根据你的具体情况调整它,或者至少希望能从中学到一两件事。

一个简单的方法是在挂载根文件系统时劫持该位置(在 initramfs 中,否则劫持进程init),并在那里挂载覆盖层,然后照常继续。如果您在没有 initramfs 的情况下挂载它,请不要忘记在将控制/proc/sys传递给普通init.

如果你想要一个 initramfs,那么 Mathieu Maret 的有关如何在树莓派中完成它的链接解决方案应该可以工作。基本上,您可以init通过内核命令行用自己的进程/脚本覆盖进程/脚本。假设您在根文件系统中创建了脚本/sbin/init-overlay,然后您需要将其添加init=/sbin/init-overlay到引导加载程序配置的内核引导参数中。

init-overlay脚本在将控制权传递给 之前可以执行任何操作init,在本例中,它将覆盖层安装到另一个目录,然后再chroot装入其中。


使用 initramfs 执行此操作的一种可能方法是在安装根文件系统后简单地劫持/initinitramfs 内的脚本。例如,假设您希望拥有一个覆盖根目录,但还希望在系统启动并运行后/run/rootfs/ro访问原始挂载点/run/rootfs/rw(前者是只读根目录,后者是已完成的修改) upperdir。我还假设启动系统的驱动器有一个root.squashfs包含只读根文件系统的文件,并且您想要挂载它。假设您还想/media/drive在系统启动并运行后再次访问此驱动器,以方便起见。

我们劫持 initramfs 脚本的原因是,与使用一些最终运行默认脚本命令的预制参数相比,这为我们提供了更大的灵活性。因此,您需要编辑引导配置以向内核传递“根”文件系统实际上是找到的文件系统的信息root.squashfs,因为我们稍后将挂载它。

VFAT 分区上的典型命令syslinux.cfg是(如果需要,更改 UUID):

label linux
    linux vmlinuz
    append root=UUID=ABCD-1234 rootfstype=vfat rootflags=ro,umask=022,quiet ro quiet splash
    initrd initrd.img

这假设您将把 放在root.squashfsVFAT 分区上,这可能并不理想(您需要在上面指定为 root= 的是包含root.squashfs或类似的分区或文件系统,例如真正的根文件系统,如果您不想要它被压缩了)。但是,我解释它时假设您将其放置在启动分区本身上。我不知道你运行的是哪种嵌入式系统,所以你必须在这里使用你自己的判断。

首先,您必须将 initramfs 提取到 /tmp 中的某个位置,以便可以修改其/init脚本。在我们将其打包回来之前,不要忘记以root(超级用户)身份执行此操作,以正确保留所有权。在了解如何完成之后,您可能可以编写整个事情的脚本。例如,将其解压以/tmp/initramfs进行编辑:

mkdir /tmp/initramfs 2>/dev/null; (cd /tmp/initramfs && zcat /initrd.img | sudo cpio -idmv)

现在我们需要找到默认脚本挂载根目录的位置。在/tmp/initramfs/init(以 root 身份编辑)中查找类似的内容:

maybe_break mount
log_begin_msg "Mounting root file system"
. /scripts/${BOOT}
parse_numeric ${ROOT}
maybe_break mountroot
mountroot
log_end_msg

您不必了解这是如何工作的。您需要了解的是,这会将包含您的普通文件系统安装root.squashfs到显然是通过${rootmnt}shell 变量给出的安装点。

换句话说,我们现在拥有的${rootmnt}是 VFAT 分区(或者我们通过root=命令行参数指定的任何分区)。该脚本现在会执行其他操作,例如将所有虚拟文件系统移动到${rootmnt}安装点,因此我们需要确保执行所有自定义操作上面的代码。

您所要做的就是在 initramfs 中的上述代码后面插入类似的内容/init

# create some temporary directories under the initramfs's /run
# they will be our mountpoints and such, which will get moved
# by the default script to the actual root filesystem...
mkdir -m 755 /run/rootfs
mount -t tmpfs -o size=90%,mode=755,suid,exec tmpfs /run/rootfs
mkdir -m 755 /run/rootfs/drive /run/rootfs/ro /run/rootfs/rw /run/rootfs/.workdir

# move the original root that was mounted, temporarily
mount -n -o move "${rootmnt}" /run/rootfs/drive

# mount the squashfs and then the overlay to our designated locations
mount -t squashfs -o defaults,ro /run/rootfs/drive/root.squashfs /run/rootfs/ro
mount -t overlay -o lowerdir=/run/rootfs/ro,upperdir=/run/rootfs/rw,workdir=/run/rootfs/.workdir root "${rootmnt}"

# at this point we have our overlay root at ${rootmnt}!
# however, move the drive's filesystem mount to the new root
# this allows it to be accessed afterwards from /media/drive
# NOTE: this assumes you have the /media/drive dir in the root squashfs
mount -n -o move /run/rootfs/drive "${rootmnt}/media/drive"
rm -d /run/rootfs/drive

就是这样。该脚本将像往常一样继续,但根文件系统是覆盖层,并且随后可以轻松访问其所有部分。请注意,它确实没有错误检查,这取决于您自行决定添加上述命令或确保模块overlay已加载。

现在只需打包 initramfs:

sudo sh -c 'cd /tmp/initramfs && find . -print0 | cpio --null -ov --format=newc' | gzip -9 > /tmp/initrd.img

并将 /tmp/initrd.img 复制到您的 SD 卡或任何地方。不要忘记放置root.squashfs在 VFAT 分区的根目录中,尽管这显然很容易定制,而且您不必这样做。这只是 syslinux(甚至 UEFI 启动)的“最简单”方式,绝不是最好的方式。

抱歉,我知道您要求使用嵌入式设备,但我不知道引导过程是如何工作的,我只是使用 x86 的示例,但这部分不太重要(仅部分syslinux.cfg)。

请注意overlayfs这将使as的可写部分成为tmpfs您想要的。然而它是容易改变,如果你看上面的内容,只需安装其他东西/run/rootfs而不是 tmpfs,无论你想要写入到哪里。

答案2

一种解决方案是使用 initramfs 来挂载 rootfs 和 overlayfs。

你可以看看它是如何在 Raspberry Pi 上完成的

或者,如果不使用 ARM 设备,则位于Ubuntuoverlayroot软件包

答案3

为目录覆盖只读“保护”

我无法让它适用于我的整个根文件系统或使用/boot/inird解决方案。但我可以让它适用于特定的文件夹。

一旦您的电路板/系统准备好“生产”,下面的代码就必须运行。您可以对其进行调整以满足您的需求。我的案例是运行一个 SOC 板网络录像机(网络录像机)。

此代码可用于 '保护' 系统上的任何目录。您知道目录有大量 I/O。而且你不关心上面写了什么,比如日志等。

“保护”/var文件夹示例

我使用/dev/shmramdisk 文件夹,如果您的发行版没有它,您必须自己安装 RAM 磁盘(tmpfs 等)。

# create folders needed by overlay filesystem on /dev/shm (RAM)
mkdir -p /dev/shm/var_upper /dev/shm/var_workdir /dev/shm/var_overlay
mkdir -p /var_
# since /var will be 'hidden' by overlay mouting 'over' it
# we need it 'visible' somewhere to be usable as lowerdir 
# mount --bind does that to /var_
mount --bind /var /var_
sudo mount -t overlay overlay -o lowerdir=/var_,upperdir=/dev/shm/var_upper,workdir=/dev/shm/var_workdir /var

一些解释

Overlay 安装一个联合文件系统(或目录树),其中上层目录受到保护。此示例使/var只读。写入仅在 RAM 上完成,/dev/shm因此日志等在重新启动时会丢失。重新启动是安全的,因为解除绑定将自动完成,并且所有内容都会返回到默认位置。可以复制到任何其他需要只读“保护”的文件夹。

我最初的搜索是寻找一种解决方案,以防止 SOC 板上断电时 SD 卡损坏。

相关内容