用 namespace 理解容器进程、网络与挂载隔离
第一次认真排查容器里的“进程明明存在,宿主机和容器看到的 PID 却不同”时,我才意识到,把容器理解成轻量虚拟机很容易把问题带偏。更准确的模型是:容器中的进程仍由宿主机同一个 Linux 内核运行,只是多个 namespace 为它组合出不同的资源视图。PID namespace 改变进程号与进程可见范围,network namespace 隔离网卡、路由、端口等网络资源,mount namespace 隔离挂载点列表和文件系统层级视图。
这三类隔离可以单独存在,也可以组合使用。它们不负责限制 CPU 和内存,也不能单独构成完整安全沙箱;资源配额要看 cgroup,权限收敛还要结合 capabilities、seccomp 和 LSM 等机制。
Linux namespace 参考手册:https://man7.org/linux/man-pages/man7/namespaces.7.html
升级范围:从“轻量虚拟机”改成“多视图组合”
以前我习惯用虚拟机类比容器:似乎每个容器都有自己的一套系统。这个类比适合解释使用感受,却不适合定位底层问题。虚拟机通常运行独立内核,而基于 Linux namespace 的容器共享宿主机内核。被“复制”的不是内核本身,而是进程能看到和操作的若干资源视图。
namespace 将原本全局的系统资源包装为抽象,让某组进程看起来拥有独立实例。一个进程属于多种 namespace,因此容器边界不是一个总开关,而是 PID、mount、network、UTS、IPC、user、cgroup、time 等边界的组合。本文只聚焦最容易在日常排障中碰到的三种:
- PID namespace:控制进程号空间、进程可见范围和命名空间内的 PID 1。
- network namespace:控制网络设备、协议栈、路由表、防火墙规则和端口空间等。
- mount namespace:控制进程看到的挂载点集合与目录层级视图。
变更表:PID、network、mount 各自隔离什么
| namespace | 主要隔离对象 | 容器内直观表现 | 常见误解 |
|---|---|---|---|
| PID | PID 编号空间、进程可见性 | 容器主进程显示为 PID 1,只能看到本层和后代层可见进程 | 误以为宿主机上不存在这些进程 |
| network | 网卡、IPv4/IPv6 栈、路由、端口、防火墙规则 | 容器有独立接口和路由,同一端口可在不同网络命名空间重复监听 | 误以为创建 netns 后天然能访问外网 |
| mount | 挂载点列表、挂载传播与文件系统视图 | 容器内挂载或卸载可不影响宿主机视图 | 误以为它自动复制了文件内容或改变磁盘权限 |

这个表带来的一个重要变化是:排障时先问“当前进程处于哪个 namespace”,再问“资源是否存在”。例如端口冲突只在同一个 network namespace 的端口空间里成立;某个挂载点只对处于对应 mount namespace 的进程可见;同一个内核进程在不同 PID 层级中可能有不同编号。
旧认知风险:为什么只看进程号会误判
PID namespace 是分层的。新命名空间中的第一个进程会成为该层的 PID 1,它承担特殊职责,例如在该命名空间中处理孤儿进程。宿主机所在的祖先 PID namespace 能看到后代命名空间里的进程,但子层不能反过来看见父层的全部进程。
因此,同一个内核进程可以在容器里显示为 PID 1,在宿主机上显示为 PID 24860。它不是两个进程,也不是 PID 被“改掉”了,而是观察者位于不同 PID namespace,看到的是同一进程在各层的编号。

这也解释了为什么容器里的 ps 结果与宿主机不同。更细一点说,/proc 展示哪些进程,还与这个 procfs 实例是从哪个 PID namespace 挂载出来的有关。只创建 PID namespace、却继续沿用旧的 /proc,会让观察结果显得混乱。
新写法一:用 /proc 与 unshare 观察 PID 和挂载
每个进程的 /proc/PID/ns/ 目录都保存了它所属 namespace 的句柄。符号链接内容包含 namespace 类型和 inode 标识;两个进程对应条目的设备号与 inode 相同时,说明它们处于同一个该类 namespace。先观察当前 shell:
# 查看当前 shell 所属的 PID、网络和挂载 namespace for ns in pid net mnt; do readlink "/proc/$$/ns/$ns" done # 列出当前进程的全部 namespace 句柄 ls -l "/proc/$$/ns"
接着用 unshare 创建一次性 PID 实验环境。以下命令需要 Linux,并通常需要相应 capability 或 sudo 权限。--pid 为后续子进程建立新 PID namespace,--fork 确保启动一个子进程,--mount-proc 为新视图挂载匹配的 procfs:
# 创建新的 PID namespace,并挂载与其匹配的 /proc sudo unshare --fork --pid --mount-proc /bin/bash # 在新 shell 内查看进程;当前 bash 通常显示为 PID 1 ps -ef # 确认当前 shell 的 PID namespace 句柄 readlink "/proc/$$/ns/pid" # 退出后一次性 namespace 随最后一个进程结束 exit
如果只想理解 mount namespace,可以再做一个独立实验。新的 mount namespace 初始会复制调用者的挂载列表,但后续挂载变化可以只保留在新视图中。显式将传播设为递归 private,可以避免实验挂载传播到其他命名空间:
# 新建 mount namespace,并在其中启动 shell sudo unshare --mount /bin/bash # 将根挂载传播设为递归 private,限制实验影响范围 mount --make-rprivate / # 只在当前 mount namespace 内挂载一个 16 MiB 的 tmpfs mkdir -p /mnt/ns-lab mount -t tmpfs -o size=16m tmpfs /mnt/ns-lab # 查看当前视图中的挂载信息 findmnt /mnt/ns-lab # 退出后临时挂载随 namespace 生命周期结束 exit
mount namespace 隔离的是“挂载视图”,不是文件权限万能开关。多个命名空间仍可挂载同一底层文件系统,也可能看到相同文件内容;是否允许读写仍受 UID/GID、capability、只读挂载、LSM 策略等共同影响。
新写法二:用 netns 和 veth 观察网络边界
network namespace 会隔离网络设备、路由表、端口、协议栈状态与多类网络配置。新建命名空间后,它不会凭空获得与宿主机相连的网卡。常见做法是创建一对 veth:两端像一根虚拟网线,一端留在宿主机,另一端移动到目标 network namespace。
# 创建名为 lab 的 network namespace sudo ip netns add lab # 创建一对相连的 veth 虚拟网卡 sudo ip link add veth-host type veth peer name veth-lab # 将 veth-lab 移入 lab namespace sudo ip link set veth-lab netns lab # 为宿主机一端配置文档专用地址并启用接口 sudo ip address add 192.0.2.1/24 dev veth-host sudo ip link set veth-host up # 在 lab 内启用回环和 veth,并配置另一端地址 sudo ip netns exec lab ip link set lo up sudo ip netns exec lab ip address add 192.0.2.2/24 dev veth-lab sudo ip netns exec lab ip link set veth-lab up # 从 lab 内验证与宿主机 veth 端点的连通性 sudo ip netns exec lab ping -c 1 192.0.2.1
这个实验只建立了点对点连接,不等于已经配置外网访问。要访问其他网络,还需根据环境配置路由、转发和 NAT,并谨慎处理防火墙。实验结束后删除命名空间和宿主机 veth:
# 删除 namespace;其中的 veth-lab 会随之销毁 sudo ip netns delete lab # 删除宿主机剩余的 veth 端,避免遗留测试接口 sudo ip link delete veth-host 2>/dev/null || true
我觉得 veth 实验最有价值的地方,不是记住几条命令,而是亲眼看到“网络隔离”和“网络连通”是两件事。namespace 先划出独立网络世界,veth、bridge、路由与 NAT 再决定这些世界如何通信。
回归检查:确认隔离而不是猜测
当容器网络、进程或挂载出现异常时,可以按下面的证据顺序核对:
- 比较 namespace 句柄:读取两个进程的
/proc/PID/ns/pid、net、mnt。标识相同才表示位于同一视图。 - 从目标进程视角进入:用
nsenter选择目标 PID 及具体 namespace,而不是只在宿主机执行同名命令。 - PID 检查:确认新 PID namespace 是否有正确的 PID 1,以及 procfs 是否从对应 PID 视图挂载。
- 网络检查:查看目标 netns 内的接口、地址、路由和监听端口;不要拿宿主机的
ip route代替。 - 挂载检查:读取目标进程的
/proc/PID/mountinfo,同时关注 shared、master 等传播标记。
# 将 TARGET_PID 替换为目标容器主进程的宿主机 PID TARGET_PID=24860 # 比较当前 shell 与目标进程的三类 namespace 标识 for ns in pid net mnt; do readlink "/proc/$$/ns/$ns" readlink "/proc/$TARGET_PID/ns/$ns" done # 进入目标进程的 PID、网络和挂载视图后启动诊断 shell sudo nsenter --target "$TARGET_PID" --pid --net --mount /bin/bash
nsenter 需要相应权限,生产环境中也不应把它当作绕过容器边界的日常入口。它适合受控排障:先记录目标 PID、进入了哪些 namespace、执行了什么只读检查,再退出。
迁移清单:把 namespace 放回容器全栈
理解 namespace 后,我对容器配置的判断也发生了变化:不再问“容器有没有隔离”,而是逐项确认隔离层是否完整。
| 目标 | 主要机制 | 检查重点 |
|---|---|---|
| 隐藏其他进程并提供容器 PID 1 | PID namespace | 进程层级、信号处理、僵尸进程回收、procfs 视图 |
| 独立网卡、路由和端口空间 | network namespace | veth/bridge、路由、DNS、转发、NAT、防火墙 |
| 独立挂载视图 | mount namespace | 只读挂载、传播属性、bind mount、敏感路径 |
| 限制 CPU、内存和 I/O | cgroup | 配额、压力、OOM 行为、统计与层级 |
| 减少特权操作 | capabilities | 按需授予,避免保留不必要的 CAP_SYS_ADMIN |
| 限制系统调用 | seccomp | 默认策略、必需调用、异常行为 |
| 强化访问控制 | SELinux/AppArmor 等 LSM | 策略标签、拒绝日志、最小权限 |
- 不要把 namespace 当成虚拟机边界:它共享宿主机内核。
- 不要把 namespace 当成资源配额:CPU 和内存限制属于 cgroup 的职责。
- 不要只检查容器内输出:同时从宿主机和目标 namespace 两个视角取证。
- 不要忽略 PID 1:应用作为 PID 1 时,信号转发与子进程回收行为会影响优雅退出。
- 不要默认 mount 变化绝不传播:检查挂载传播属性,尤其是宿主机与容器共享路径。
- 不要默认 netns 自动联网:接口、路由、DNS、转发和 NAT 都要分别配置。
常见问题
namespace 和 cgroup 有什么区别?
namespace 主要改变进程“看见什么”,cgroup 主要控制和统计进程“能使用多少资源”。容器通常同时使用两者:前者提供资源视图隔离,后者提供 CPU、内存、I/O 等控制。
为什么容器内 PID 1 在宿主机不是 1?
PID namespace 是分层的,同一进程在每个可见层级中可以有不同 PID。它在容器子层是 PID 1,在宿主机祖先层有另一个普通 PID。
mount namespace 会复制整个文件系统吗?
不会。创建 mount namespace 时获得的是挂载列表视图,后续可独立调整挂载。底层文件系统和文件数据仍可能共享,权限与读写控制也需要其他机制配合。
两个容器为什么都能监听 80 端口?
如果它们位于不同 network namespace,就拥有独立的端口空间和网络栈,因此都可以在各自视图中监听 80。宿主机如何把外部流量送到它们,则由端口映射、代理、路由或负载均衡决定。
把容器从“缩小版虚拟机”升级成“多个 namespace 组合出的资源视图”后,很多看似矛盾的现象都会变得直观:PID 不同不是两个进程,端口相同不一定冲突,挂载存在也不代表所有进程都能看到。对日常排障来说,最实用的起点就是 /proc/PID/ns/——先确认观察者站在哪个视图里,再解释它看到的世界。
把模板函数注册、解析与执行错误分别处理
- 上一篇
- 把模板函数注册、解析与执行错误分别处理
- 下一篇
- 模板执行时出现缺失字段,应报错还是输出空值
-
- 文章 · linux | 3小时前 | Linux · 文件描述符 ·
- 日志轮转后服务仍写旧文件,文件描述符发生了什么
- 490浏览 收藏
-
- 文章 · linux | 6小时前 | 运维 · SSH Linux安全 sshd_config 服务加固 密钥登录
- SSH 只允许密钥登录后还要收紧哪些服务边界
- 297浏览 收藏
-
- 文章 · linux | 1天前 | Linux ·
- Linux 负载高但 CPU 空闲,怎样区分 I/O 等待与锁等待
- 398浏览 收藏
-
- 文章 · linux | 1天前 | 磁盘配额 Linux磁盘空间 No space left on device inode耗尽 ext4保留块
- 磁盘空间没满却无法写入:inode、配额与保留块检查
- 326浏览 收藏
-
- 文章 · linux | 1天前 | Linux ·
- nftables 为容器主机建立最小入站规则
- 373浏览 收藏
-
- 文章 · linux | 1天前 | linux运维 · 故障排查 · 服务管理 · systemctl journalctl RestartSec StartLimitBurst Restart systemd 服务
- systemd 服务反复重启:从退出码到速率限制排查
- 432浏览 收藏
-
- 文章 · linux | 1天前 | Linux ·
- Linux cgroup v2 限制服务 CPU 与内存的完整思路
- 150浏览 收藏
-
- 文章 · linux | 1天前 |
- systemd 服务里的 LimitNOFILE 为什么和 shell ulimit 不同
- 258浏览 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 485次学习
-
- PubMedQA
- 深入了解PubMedQA生物医学问答数据集,涵盖其核心功能、使用方法及在临床决策、药物研发等场景的应用,助力提升NLP模型性能。
- 378次使用
-
- H2O EvalGPT
- H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
- 449次使用
-
- LMArena
- LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
- 457次使用
-
- HELM
- 深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
- 400次使用
-
- MMBench
- MMBench是由上海人工智能实验室等机构联合推出的多模态基准测试平台,提供细粒度能力评估、大规模数据集及VLMEvalKit工具。本文详细介绍其核心功能、安装使用方法及应用场景,助力开发者全面评估多模态模型性能。
- 227次使用
-
- 详解golang执行Linuxshell命令完整场景下的使用方法
- 2023-01-07 426浏览
-
- go程序部署到linux上运行的实现方法
- 2023-01-02 387浏览
-
- Linux系统下Go语言开发环境搭建
- 2023-01-07 242浏览
-
- Go 容器遍历的实现示例
- 2022-12-23 133浏览
-
- Golang: 内建容器的用法
- 2022-12-30 496浏览

