如何让挂载在mount namespace中的文件系统仅对该命名空间可见?
我通过unshare(CLONE_NEWNS)创建mount namespace,之后用mount系统调用(mountflags=0)将overlay文件系统挂载到/path/to/my/mount。但挂载后,不仅新namespace内的shell能看到,root mount namespace的普通shell也能看到该挂载,这不符合我“仅在新namespace内可见”的预期。
我的Go代码如下:
// lock this goroutine to a single OS thread because namespaces are thread-local runtime.LockOSThread() defer runtime.UnlockOSThread() // switch to a new mount namespace err := unix.Unshare(unix.CLONE_FS) if err != nil { return fmt.Errorf("error entering new mount namespace: %w", err) } mountopts := fmt.Sprintf("lowerdir=%s,upperdir=%s,workdir=%s", lower, upper, work) err = unix.Mount("overlay", target, "overlay", 0, mountopts) if err != nil { return fmt.Errorf("error mounting overlay filesystem: %w", err) }
我尝试过在unshare和mount之间加入以下代码,把新namespace的/设为私有挂载,但没效果:
// make the root filesystem in this new namespace private err = unix.Mount("ignored", "/", "ignored", unix.MS_PRIVATE, "ignored") if err != nil { return fmt.Errorf("error making root filesystem private") }
虽然新namespace里的/确实变成了私有(查看/proc/self/mountinfo确认),但root namespace的/也变成了私有,而且目标挂载依然在两个namespace都可见。
解决方法与原因分析
核心问题:挂载传播的继承特性
调用unshare(CLONE_NEWNS)时,新的mount namespace会完整复制当前namespace的所有挂载点及其传播属性。默认情况下,多数系统的根挂载/处于shared模式——这种模式下,挂载点的变更会在共享该挂载的所有namespace之间双向传播。你之前修改根挂载属性时,因为新namespace的根挂载还和原root namespace的根挂载处于共享关系,所以修改操作同步到了root namespace,同时未彻底切断传播链,导致后续挂载依然可见。
正确操作步骤
要实现挂载仅在新namespace内可见,需在unshare后、挂载前彻底切断与原namespace的挂载传播链,具体步骤:
- 调用
unshare(CLONE_NEWNS)创建新的mount namespace - 使用
MS_REC | MS_PRIVATE标志递归将根挂载/及其所有子挂载设置为私有,确保整个挂载树脱离与原namespace的共享关系 - 执行overlay挂载操作
修改后的Go代码示例
// lock this goroutine to a single OS thread because namespaces are thread-local runtime.LockOSThread() defer runtime.UnlockOSThread() // switch to a new mount namespace err := unix.Unshare(unix.CLONE_NEWNS) if err != nil { return fmt.Errorf("error entering new mount namespace: %w", err) } // 递归将根挂载及其所有子挂载设为私有,彻底切断与原namespace的传播链 err = unix.Mount("ignored", "/", "ignored", unix.MS_REC|unix.MS_PRIVATE, "ignored") if err != nil { return fmt.Errorf("error making root filesystem recursively private: %w", err) } mountopts := fmt.Sprintf("lowerdir=%s,upperdir=%s,workdir=%s", lower, upper, work) err = unix.Mount("overlay", target, "overlay", 0, mountopts) if err != nil { return fmt.Errorf("error mounting overlay filesystem: %w", err) }
为何之前的操作无效?
你之前仅使用MS_PRIVATE而未加MS_REC,只会修改根挂载本身的属性,但子挂载可能仍处于shared模式;同时,修改时根挂载与原namespace的根挂载仍共享,导致修改同步到了root namespace。加上MS_REC后,会递归处理整个挂载树,彻底切断传播关系,后续挂载就不会影响到root namespace了。
内容的提问来源于stack exchange,提问作者Alex Flint

