Docker 是使用 Linux 的 Namespace 技术实现各种资源隔离的。

什么是 Namespace?

下面是 Namespace 的维基百科定义:

Namespace 是 Linux 内核的一项功能,该功能对内核资源进行分区,以使一组进程看到一组资源,而另一组进程看到另一组资源。Namespace 的工作方式通过为一组资源和进程设置相同的 Namespace 而起作用,但是这些 Namespace 引用了不同的资源。资源可能存在于多个 Namespace 中。这些资源可以是进程 ID、主机名、用户 ID、文件名、与网络访问相关的名称和进程间通信。

简单来说,Namespace 是 Linux 内核的一个特性,该特性可以实现在同一主机系统中,对进程 ID、主机名、用户 ID、文件名、网络和进程间通信等资源的隔离。Docker 利用 Linux 内核的 Namespace 特性,实现了每个容器的资源相互隔离,从而保证容器内部只能访问到自己 Namespace 的资源。

Namespace 类型 隔离资源 内核版本 Docker 应用场景
Mount 文件系统挂载点 2.4.19 容器独立文件视图
PID 进程 ID 空间 2.6.24 容器内 1 号进程
Net 网络设备/IP/端口/路由表 2.6.29 容器独立网络栈
IPC System V IPC/POSIX 消息队列 2.6.19 进程间通信隔离
UTS 主机名与域名 2.6.19 容器自定义 hostname
User 用户/组 ID 映射 3.8 容器 root≠宿主机 root
Cgroup Cgroups 根目录 4.6 容器独立 cgroup 视图
Time 系统时钟 5.6 ⚠️ Docker 暂未支持

虽然 Linux 内核提供了8种 Namespace,但是最新版本的 Docker 只使用了其中的前6 种,分别为Mount Namespace、PID Namespace、Net Namespace、IPC Namespace、UTS Namespace、User Namespace。

下面,我们详细了解下 Docker 使用的 6 种 Namespace的作用分别是什么。

各种 Namespace 的作用?

1. Mount Namespace (mnt)

作用:隔离文件系统挂载点,让每个容器拥有独立的文件视图

底层实现原理

  • 内核标志:CLONE_NEWNS
  • 进程看到的挂载点由其 /proc/[pid]/mountinfo 决定
  • 使用 MS_REC|MS_PRIVATE 防止挂载传播到主机

命令行实践

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 创建新的 Mount Namespace
sudo unshare --mount --fork /bin/bash

# 在隔离环境中挂载目录
mkdir /tmp/isolated_fs
mount -t tmpfs tmpfs /tmp/isolated_fs -o size=100M

# 验证挂载
df -h | grep isolated_fs
# tmpfs 100M 0 100M 0% /tmp/isolated_fs

# 主机查看(看不到此挂载)
新终端执行:
df -h | grep isolated_fs # 无输出

2. PID Namespace (pid)

作用:隔离进程 ID,使容器内进程从 1 开始编号

底层实现原理

  • 内核标志:CLONE_NEWPID
  • 父 Namespace 能看到所有子 Namespace 的进程
  • /proc 文件系统需要重新挂载才能显示正确的 PID 视图

命令行实践

1
2
3
4
5
6
7
8
9
10
11
12
13
# 创建 PID Namespace 并重挂 /proc
sudo unshare --pid --fork --mount-proc /bin/bash

# 在隔离环境中查看进程
ps aux
# USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
# root 1 0.0 0.0 115544 2004 pts/0 S 10:57 0:00 bash
# root 10 0.0 0.0 155444 1764 pts/0 R+ 10:59 0:00 ps aux

# 在主机上查看实际进程
新终端执行:
pstree -p | grep $$
# |-bash(10000)---sudo(10001)---unshare(10002)---bash(10003)

3. UTS Namespace (uts)

作用:隔离主机名和域名(NIS domain name)

底层实现原理

  • 内核标志:CLONE_NEWUTS
  • 修改 struct new_utsname 中的 nodenamedomainname
  • 容器内设置主机名使用 sethostname() 系统调用

命令行实践

1
2
3
4
5
6
7
8
9
# 创建 UTS Namespace
sudo unshare --uts --fork /bin/bash

# 在隔离环境中修改主机名
hostname container-host
echo "新主机名: $(hostname)" # container-host

# 主机查看:
hostname # 显示原始主机名不变

查看源码实现

Linux 内核中的 UTS 相关结构:

1
2
3
4
5
struct uts_namespace {
struct kref kref;
struct new_utsname name;
struct user_namespace *user_ns;
};

4. IPC Namespace (ipc)

作用:隔离 System V IPC 和 POSIX 消息队列,主要是用来隔离进程间通信的

底层实现原理

  • 内核标志:CLONE_NEWIPC
  • 隔离对象:信号量(sem)、消息队列(msg)、共享内存(shm)
  • /proc/sysvipc 在容器内只显示本命名空间的资源

命令行实践

1
2
3
4
5
6
7
8
9
10
11
12
# 主机创建原始IPC资源
ipcmk -M 1024 # 创建共享内存
ipcs -m # 查看到新创建的共享内存

# 创建 IPC Namespace
sudo unshare --ipc --fork /bin/bash

# 创建隔离的IPC资源
ipcmk -Q # 创建消息队列
ipcs -q # 只显示新创建的队列

# 此时主机上的 ipcs 仍只显示原始资源

5. User Namespace (user)

作用:隔离用户和组 ID,提供权限映射

底层实现原理

  • 内核标志:CLONE_NEWUSER
  • 通过 /proc/[pid]/uid_map/proc/[pid]/gid_map 实现 ID 映射
  • 允许无特权用户创建容器

命令行实践

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 确保启用用户命名空间
echo 10000 > /proc/sys/user/max_user_namespaces

# 以普通用户创建 User Namespace
unshare --user -r /bin/bash

# 在隔离环境中查看用户
id
# uid=0(root) gid=0(root) groups=0(root)

# 尝试特权操作(应该失败)
mount -t tmpfs tmpfs /mnt
# mount: only root can use "--types" option

# 查看实际权限
cat /proc/self/uid_map
# 0 1000 1 # 表示容器 root (0) 映射到主机用户 1000

6. Network Namespace (net)

作用:隔离网络资源(设备、IP 地址、端口、路由表)

底层实现原理

  • 内核标志:CLONE_NEWNET
  • 每个网络命名空间有独立的网络设备列表、路由表、Iptables 规则
  • 使用 veth 接口对连接不同 Namespace

命令行实践

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 创建 Network Namespace
sudo ip netns add mynet

# 创建虚拟网卡对
sudo ip link add veth0 type veth peer name veth1

# 将 veth1 放入新命名空间
sudo ip link set veth1 netns mynet

# 配置 IP 地址
sudo ip netns exec mynet ip addr add 192.168.1.101/24 dev veth1
sudo ip netns exec mynet ip link set veth1 up
sudo ip netns exec mynet ip link set lo up

# 主机端配置
sudo ip addr add 192.168.1.1/24 dev veth0
sudo ip link set veth0 up

# 测试连通性
sudo ip netns exec mynet ping 192.168.1.1

复杂网络实践:创建两个容器互通

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
# 创建两个命名空间
sudo ip netns add web-net
sudo ip netns add db-net

# 创建桥接设备
sudo ip link add br0 type bridge
sudo ip link set br0 up

# 为每个命名空间创建 veth 对
sudo ip link add web-eth type veth peer name web-br
sudo ip link add db-eth type veth peer name db-br

# 连接命名空间
sudo ip link set web-eth netns web-net
sudo ip link set db-eth netns db-net

# 连接桥接
sudo ip link set web-br master br0
sudo ip link set db-br master br0
sudo ip link set web-br up
sudo ip link set db-br up

# 配置 IP 地址
sudo ip netns exec web-net ip addr add 10.1.1.10/24 dev web-eth
sudo ip netns exec web-net ip link set web-eth up
sudo ip netns exec web-net ip link set lo up
sudo ip netns exec web-net ip route add default via 10.1.1.1

sudo ip netns exec db-net ip addr add 10.1.1.20/24 dev db-eth
sudo ip netns exec db-net ip link set db-eth up
sudo ip netns exec db-net ip link set lo up
sudo ip netns exec db-net ip route add default via 10.1.1.1

# 配置桥接 IP
sudo ip addr add 10.1.1.1/24 dev br0

# 测试互通
sudo ip netns exec web-net ping 10.1.1.20

为什么 Docker 需要 Namespace?

  1. 资源隔离:防止容器间相互影响
  2. 安全性:通过 User Namespace 减少权限溢出
  3. 环境一致性:确保容器运行不受主机环境影响
  4. 进程独立:允许不同容器使用相同的进程 ID
  5. 网络隔离:避免端口冲突,支持多容器网络拓扑
  6. 轻量化:比硬件虚拟化更高效(如 KVM)

关键进阶操作:结合多个 Namespace

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 创建完整隔离环境
sudo unshare \
--pid \
--mount \
--net \
--ipc \
--uts \
--user -r \
--fork \
--mount-proc \
/bin/bash

# 验证所有隔离
echo "PID 空间:" && ps aux | head -3
echo "主机名:" && hostname isolated && hostname
echo "用户ID:" && id
echo "网络设备:" && ip addr
echo "IPC资源:" && ipcs
echo "挂载点:" && df -Th

排查 Namespace 问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 查看进程所属 Namespace
ls -l /proc/$PID/ns

# 进入目标进程 Namespace
sudo nsenter -t $PID -a

# 比较主机和容器的 Namespace ID
# 容器中:
echo "容器PID的Namespace信息:"
ls -l /proc/self/ns

# 主机上查找同名进程:
PID_ON_HOST=$(pgrep -f "container-process")
echo "主机上相同进程的Namespace信息:"
sudo ls -l /proc/$PID_ON_HOST/ns

生产环境建议

  1. Always enable User Namespace: 减少 root 权限风险
    dockerd --userns-remap=default

  2. Mount propagation settings: 使用 :slave:private
    docker run -v /host-path:/container-path:slave

  3. Limit container capabilities:
    docker run --cap-drop ALL --cap-add NET_BIND_SERVICE

  4. Use cgroups v2 with cgroup namespaces (内核 ≥5.2):
    docker run --cgroupns=private

通过以上底层实践,可以深入理解 Docker 如何利用 Linux Namespace 实现容器化隔离,这些知识对于容器排错、性能优化和安全加固都至关重要。