Linux Namespace:容器隔离的底层原理,PID、网络、挂载隔离实战

Linux Namespace:容器隔离的底层原理,PID、网络、挂载隔离实战

当你执行 docker run 启动一个容器时,容器内的进程以为自己是系统上唯一运行的程序,它看到的网络接口是独立的,挂载点是独立的,主机名也是独立的。这一切魔法的背后,是 Linux 内核的 Namespace 机制。本文将深入讲解 Linux Namespace 的原理,并通过实际命令演示每种 Namespace 的效果。


服务器配置

Namespace 实验需要 Linux 内核 3.8+ 以上(现代发行版均满足),建议使用有 root 权限的服务器。推荐雨云服务器 rainyun-com 的 2 核 4GB 机型,注册填 2026off 领 5 折,性能充足且支持内核级操作。


Namespace 是什么

Namespace 是 Linux 内核提供的一种资源隔离机制。每个 Namespace 都是系统资源的一个独立视图------进程只能看到属于自己 Namespace 中的资源,对同一系统资源的其他 Namespace 中的内容一无所知。

Linux 目前支持 7 种 Namespace 类型:

Namespace 系统调用标志 隔离内容 引入版本
PID CLONE_NEWPID 进程 ID 空间 Linux 3.8
Network CLONE_NEWNET 网络接口、路由、端口 Linux 2.6.24
Mount CLONE_NEWNS 挂载点 Linux 2.4.19
IPC CLONE_NEWIPC System V IPC、POSIX 消息队列 Linux 2.6.19
UTS CLONE_NEWUTS 主机名、NIS 域名 Linux 2.6.19
User CLONE_NEWUSER 用户 ID、组 ID 映射 Linux 3.8
Time CLONE_NEWTIME 系统时钟偏移 Linux 5.6

实验工具安装

bash 复制代码
apt update
apt install util-linux iproute2 procps -y

主要工具说明:

  • unshare --- 以新 Namespace 启动程序
  • nsenter --- 进入已有进程的 Namespace
  • lsns --- 列出系统中所有 Namespace
  • ip netns --- 专门管理 Network Namespace

PID Namespace 实战

PID Namespace 让进程看到一个独立的进程 ID 空间,容器内的进程 1 不是系统真正的 init。

创建 PID 隔离环境

bash 复制代码
# 创建新 PID Namespace,同时挂载独立的 /proc
unshare --pid --fork --mount-proc /bin/bash

进入后执行:

bash 复制代码
# 在新 Namespace 中,只能看到自己的进程
ps aux

输出类似:

复制代码
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root         1  0.0  0.0   4624  3720 pts/0    S    10:00   0:00 /bin/bash
root        12  0.0  0.0   7480  3200 pts/0    R+   10:00   0:00 ps aux

当前 bash 进程在 Namespace 内的 PID 是 1,而在宿主机上它有一个完全不同的 PID。退出后在宿主机验证:

bash 复制代码
# 宿主机可以看到所有进程
ps aux | wc -l

关键点

  • --fork 是必须的:让 unshare 先 fork 子进程再 exec,确保子进程是新 Namespace 中的 PID 1
  • --mount-proc 在新 Namespace 中重新挂载 /proc,否则 ps 仍会读宿主机数据

UTS Namespace 实战

UTS (UNIX Time-sharing System) Namespace 隔离主机名和 NIS 域名,是最简单的 Namespace 类型。

bash 复制代码
# 创建新 UTS Namespace
unshare --uts /bin/bash

# 在新 Namespace 中修改主机名
hostname container-node-1
hostname
# 输出:container-node-1

# 退出后查看宿主机主机名(不受影响)
exit
hostname
# 输出:your-original-hostname

Docker 正是利用 UTS Namespace 给每个容器设置独立的 hostname(默认使用容器 ID 的前 12 位)。


Network Namespace 实战

Network Namespace 是最复杂也最强大的 Namespace 类型,它隔离网络接口、IP 地址、路由表、iptables 规则、端口空间。

创建独立网络 Namespace

bash 复制代码
# 创建名为 myns 的 Network Namespace
ip netns add myns

# 列出已有的 Network Namespace
ip netns list

# 在 myns 中执行命令
ip netns exec myns ip link list
# 只能看到 lo 接口,且默认是 DOWN 状态

用 veth pair 连通两个 Namespace

veth (virtual ethernet) 是一对虚拟网卡,数据从一端进必从另一端出,常用于连接两个 Namespace。

bash 复制代码
# 创建 veth pair:veth0 和 veth1
ip link add veth0 type veth peer name veth1

# 将 veth1 移入 myns
ip link set veth1 netns myns

# 为宿主机端的 veth0 配置 IP
ip addr add 10.0.0.1/24 dev veth0
ip link set veth0 up

# 为 myns 内的 veth1 配置 IP
ip netns exec myns ip addr add 10.0.0.2/24 dev veth1
ip netns exec myns ip link set veth1 up
ip netns exec myns ip link set lo up

# 验证连通性
ping -c 3 10.0.0.2

# 从 myns ping 宿主机
ip netns exec myns ping -c 3 10.0.0.1

清理实验环境

bash 复制代码
ip netns del myns
ip link del veth0

Mount Namespace 实战

Mount Namespace 让每个容器拥有独立的挂载点树,容器内的挂载操作不会影响宿主机。

bash 复制代码
# 创建新 Mount Namespace
unshare --mount /bin/bash

# 在新 Namespace 中创建挂载
mkdir -p /tmp/testmount
mount -t tmpfs tmpfs /tmp/testmount
df -h /tmp/testmount
# 可以看到 tmpfs 挂载

# 退出后在宿主机验证
exit
df -h /tmp/testmount
# mount: /tmp/testmount: not mounted  ------ 宿主机不受影响

bind mount 隔离

bash 复制代码
unshare --mount /bin/bash

# bind mount 一个目录
mkdir /tmp/hidden
mount --bind /etc /tmp/hidden
ls /tmp/hidden  # 可以看到 /etc 的内容

exit
ls /tmp/hidden  # 宿主机 /tmp/hidden 是空的

User Namespace 实战

User Namespace 允许在 Namespace 内部"成为 root",但在宿主机上仍是普通用户,是实现无需特权运行容器的关键。

bash 复制代码
# 以普通用户身份(不需要 sudo)创建 User Namespace
unshare --user --map-root-user /bin/bash

# 在 Namespace 内查看当前用户
id
# uid=0(root) gid=0(root) groups=0(root) ------ 看起来是 root

# 但实际上在宿主机看,这只是普通用户
# 在另一个终端查看
ps aux | grep unshare
# 进程所属用户仍然是你的普通用户

查看 UID/GID 映射关系

bash 复制代码
# 在 User Namespace 内查看映射
cat /proc/self/uid_map
# 0    1000    1
# Namespace内UID 0 对应宿主机UID 1000

使用 lsns 查看所有 Namespace

bash 复制代码
# 列出系统中所有 Namespace
lsns

# 输出示例
NS TYPE   NPROCS   PID USER  COMMAND
4026531835 cgroup    120     1 root  /sbin/init
4026531836 pid       120     1 root  /sbin/init
4026531837 user      120     1 root  /sbin/init
4026531838 uts       120     1 root  /sbin/init
4026531839 ipc       120     1 root  /sbin/init
4026531840 mnt       118     1 root  /sbin/init
4026531992 net       120     1 root  /sbin/init
4026532198 mnt         1   823 root  /lib/systemd/systemd-udevd

每个 Namespace 都有一个唯一的 inode 编号,可以通过 /proc/<PID>/ns/ 目录查看:

bash 复制代码
ls -la /proc/self/ns/
# lrwxrwxrwx 1 root root 0 net -> 'net:[4026531992]'
# lrwxrwxrwx 1 root root 0 pid -> 'pid:[4026531836]'
# ...

nsenter:进入容器 Namespace 调试

nsenter 是运维的利器,可以进入正在运行的容器的 Namespace 进行调试,无需通过 docker exec

进入 Docker 容器的 Namespace

bash 复制代码
# 获取容器 PID
docker inspect --format '{{.State.Pid}}' my-container

# 假设 PID 是 12345
# 进入容器的网络和 PID Namespace
nsenter -t 12345 -n -p -- ip addr

# 进入容器的所有 Namespace(相当于 docker exec -it)
nsenter -t 12345 --mount --uts --ipc --net --pid -- /bin/bash

# 只进入网络 Namespace(宿主机文件系统依然可用)
nsenter -t 12345 -n -- ss -tlnp

nsenter vs docker exec 的区别

  • docker exec 进入容器的所有 Namespace,且受 Docker 安全策略限制
  • nsenter 可以选择性地进入特定 Namespace,且不受 Docker 策略限制,适合深度调试

例如,容器内没有安装 tcpdump,但你想抓包:

bash 复制代码
# 只进入容器的网络 Namespace,使用宿主机的 tcpdump
nsenter -t 12345 -n -- tcpdump -i eth0 -w /tmp/container.pcap

Docker 与 Namespace 的对应关系

Docker 功能 Namespace 类型 作用
容器进程隔离 PID 容器内看不到宿主机进程
容器网络 Network 独立 IP、独立端口空间
容器文件系统 Mount 独立的 rootfs
容器主机名 UTS 独立的 hostname
容器 IPC IPC 独立的 SHM/消息队列
普通用户运行(rootless) User 非 root 用户运行容器
bash 复制代码
# 验证:查看一个运行中容器使用的 Namespace
docker run -d --name test nginx
PID=$(docker inspect --format '{{.State.Pid}}' test)
ls -la /proc/$PID/ns/

安全意义

Namespace 提供了视图隔离,但不是完整的安全边界:

  1. Namespace 不限制资源使用(CPU、内存需要 cgroup)
  2. 内核漏洞可能突破 Namespace(需要 seccomp、AppArmor 加固)
  3. PID 1 问题:容器内 PID 1 需要正确处理信号(推荐使用 tini)
  4. User Namespace + 其他 Namespace:非 root 用户可以安全地创建完整隔离环境(Podman rootless 的基础)

总结

Linux Namespace 是理解容器技术的基础。掌握了 Namespace,你就理解了为什么 Docker 里的进程 PID 是 1、为什么容器有独立的 IP、为什么修改容器内的主机名不影响宿主机。更进一步,结合 cgroup(资源限制)和 seccomp(系统调用过滤),就构成了完整的容器隔离体系。

相关推荐
todoitbo1 小时前
一台 2C2G 服务器上的 KingbaseES 安装记录
运维·服务器·数据库·国产数据库
Demon1_Coder1 小时前
智能体的自定义工具
java·linux·前端
轻帆向远1 小时前
Debian 旧版源配置指南:国内镜像加速与 archive.debian.org 替代方案
网络·debian·apt
gf13211111 小时前
【精确查找python脚本是否在运行】
linux·前端·python
allnlei1 小时前
分层 B 帧(Hierarchical B-frames)详解
网络
Sunny Boy 0011 小时前
linux环境编译Pro*C 源文件(.pc文件)
linux·c语言·oracle
Gong-Yu1 小时前
MySQL数据库运维(1)
运维·数据库·mysql·慢查询
Yang96111 小时前
宽频高精度!鼎讯信通 OM-T 台式频谱分析仪风电实验室专用
大数据·运维·网络
用户937855808702 小时前
Linux 基础教程(二)】系统目录结构、用户与用户组管理(useradd/usermod/passwd/sudo)
linux