avatar🌌
TangentTangent Blog

技术博客 | Go & Kubernetes & Linux

技术博客 | Go & Kubernetes & Linux

Linux unshare 命令详解:从命名空间到手工容器

unshare 是 Linux 系统中一个底层且强大的命令行工具。它的作用是让一个程序在脱离父进程某些共享环境的情况下运行,是现代容器技术(Docker、Podman)的核心基石。

核心概念:Linux 命名空间(Namespaces) ​

unshare 本质上是对 Linux 内核 unshare() 系统调用的封装,操作的核心概念是 Linux 命名空间。

在传统 Linux 系统中,所有进程共享同一套全局资源(网络栈、进程号列表、文件系统挂载树等)。Namespaces 机制允许将这些全局资源进行分割和隔离,使不同进程组看到不同的资源视图。

Linux 内核支持以下七种主要命名空间:

命名空间隔离内容
Mount (mnt)文件系统挂载点。新空间内的挂载/卸载不影响宿主机
PID (pid)进程 ID。新空间内第一个进程 PID 为 1,看不到宿主机进程
Network (net)网络设备、IP 地址、路由表、防火墙规则等
IPC (ipc)System V IPC 和 POSIX 消息队列
UTS (uts)主机名和域名
User (user)用户和用户组 ID。普通用户可在新空间内映射为 root
Cgroup (cgroup)Cgroup 根目录视图

unshare 的主要参数 ​

命名空间参数 ​

参数对应命名空间作用
-m, --mountMount隔离挂载点视图
-u, --utsUTS隔离主机名
-i, --ipcIPC隔离进程间通信
-n, --netNetwork隔离网络,新环境默认只有 lo 且处于 down 状态
-p, --pidPID隔离进程号
-U, --userUser隔离用户 ID
-C, --cgroupCgroup隔离 cgroup 视图

关键辅助参数 ​

仅指定命名空间参数还不够,以下三个辅助参数让隔离环境真正可用:

  • --fork (-f):配合 PID 隔离使用。Linux 内核规定创建新 PID 命名空间的进程本身 PID 不变,只有其子进程才会成为新空间的 PID 1。因此使用 -p 时必须带上 -f。
  • --mount-proc:配合 PID 隔离使用。ps、top 等命令通过读取 /proc 获取进程信息。如果不重新挂载 /proc,这些命令仍会显示宿主机进程。此参数会在新的 Mount 命名空间中自动挂载全新的 /proc。
  • --map-root-user (-r):配合 User 隔离使用。将当前宿主机用户在新的用户命名空间中映射为 root,获得该空间内的管理员权限。

常见应用场景 ​

1. 隔离主机名(UTS 隔离) ​

临时修改主机名做测试,不影响宿主机:

bash
sudo unshare -u /bin/bash
hostname test-container
hostname  # 显示 test-container

2. 干净的网络测试环境(Network 隔离) ​

bash
sudo unshare -n /bin/bash
ip a  # 只有 lo 网卡,完全断网

3. 模拟容器环境(PID + Mount 综合隔离) ​

bash
sudo unshare -p -f --mount-proc /bin/bash
ps aux  # 只有 bash 和 ps,bash 的 PID 为 1

4. 完全隔离的命名空间 ​

一次性隔离全部七种命名空间:

bash
sudo unshare --mount --uts --ipc --net --pid --user --cgroup \
    --fork --map-root-user --mount-proc /bin/bash

缩写形式:

bash
sudo unshare -muinpUC -f -r --mount-proc /bin/bash

验证隔离效果:

bash
ps aux          # 只有 bash 和 ps,PID 从 1 开始
ip a            # 只有 lo,处于 DOWN 状态
hostname box    # 修改主机名不影响宿主机
ls -l /proc/$/ns  # 对比宿主机,inode 编号完全不同

深入理解:Mount 隔离 ≠ 文件系统隔离 ​

这是一个容易混淆的关键点:unshare -m 隔离的是挂载点列表的视图,而不是文件系统本身。

执行 unshare -m 时,内核将宿主机的整棵挂载树复制一份给新空间。新空间内的挂载/卸载操作不会影响宿主机,但根目录 / 仍然指向宿主机的磁盘分区。

这意味着:

  • 修改文件会影响宿主机:echo "hello" > /root/test.txt 会直接写入宿主机磁盘
  • 挂载操作不影响宿主机:mount -t tmpfs none /mnt 只在新空间内生效

/proc 为什么是隔离的? ​

--mount-proc 参数会在新空间内重新挂载 /proc 伪文件系统。/proc 不是硬盘上的真实文件,而是内核在内存中动态生成的接口。当在隔离的 PID 命名空间内挂载 proc 时,内核只暴露属于该命名空间的进程。

结合 chroot 构建真正的隔离容器 ​

要实现像 Docker 一样的文件系统隔离,需要配合 chroot 或 pivot_root 切换根目录。

准备独立的根文件系统 ​

使用轻量级的 Alpine Linux(约 5MB):

bash
mkdir -p /tmp/my-container && cd /tmp/my-container
curl -O https://dl-cdn.alpinelinux.org/alpine/v3.19/releases/x86_64/alpine-minirootfs-3.19.1-x86_64.tar.gz
tar -xvf alpine-minirootfs-3.19.1-x86_64.tar.gz
rm alpine-minirootfs-3.19.1-x86_64.tar.gz

启动隔离容器 ​

bash
sudo unshare --mount --uts --ipc --net --pid --user --cgroup \
    --fork --map-root-user \
    chroot /tmp/my-container /bin/sh

进入后手动挂载 /proc:

bash
mount -t proc proc /proc
ps aux   # 只有 sh 进程
ls /     # Alpine 的文件系统,不再是宿主机的

此时在容器内创建的文件只存在于 /tmp/my-container 目录中,不会污染宿主机的根文件系统。

User Namespace 的权限陷阱 ​

使用 --user --map-root-user 时可能遇到 Permission denied,即使文件属于当前用户。

原因:--map-root-user 将你映射为容器内的 UID 0(root),但文件系统检查时,UID 0 与宿主机上文件属主的 UID(如 1000)不匹配,导致权限被拒。

解决方案:

bash
sudo chown -R $(whoami) /tmp/my-container

sudo unshare --mount --uts --ipc --net --pid --fork --mount-proc \
    chroot /tmp/my-container /bin/sh

总结 ​

unshare 是操纵 Linux 内核 Namespaces 机制的核心工具,也是理解容器技术的钥匙。一个完整的容器可以概括为:

容器=Namespace(资源隔离)+Chroot(文件隔离)+Cgroups(资源限额)容器 = Namespace(资源隔离)+ Chroot(文件隔离)+ Cgroups(资源限额)

  • unshare 解决"你看得到谁"和"你能用哪个网卡"
  • chroot / pivot_root 解决"你能改哪个文件"
  • cgroups 解决"你能用多少 CPU 和内存"
runc 源码解读(一):从 SPEC.md 看容器命名空间
Linux mount 指令详解:从基础挂载到容器存储
由 Valaxy v1.2.2 驱动|主题-Yunv1.2.2