如何正确使用 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 执行此操作的一种可能方法是在安装根文件系统后简单地劫持/init
initramfs 内的脚本。例如,假设您希望拥有一个覆盖根目录,但还希望在系统启动并运行后/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.squashfs
VFAT 分区上,这可能并不理想(您需要在上面指定为 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。
或者,如果不使用 ARM 设备,则位于Ubuntuoverlayroot
软件包
答案3
为目录覆盖只读“保护”
我无法让它适用于我的整个根文件系统或使用/boot/inird
解决方案。但我可以让它适用于特定的文件夹。
一旦您的电路板/系统准备好“生产”,下面的代码就必须运行。您可以对其进行调整以满足您的需求。我的案例是运行一个 SOC 板网络录像机(网络录像机)。
此代码可用于 '保护' 系统上的任何目录。您知道目录有大量 I/O。而且你不关心上面写了什么,比如日志等。
“保护”/var
文件夹示例
我使用/dev/shm
ramdisk 文件夹,如果您的发行版没有它,您必须自己安装 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 卡损坏。