当前位置:首页 > 文章列表 > 文章 > linux > 用 namespace 理解容器进程、网络与挂载隔离

用 namespace 理解容器进程、网络与挂载隔离

来源:17golang原创 2026-10-08 15:52:08 0浏览 收藏

第一次认真排查容器里的“进程明明存在,宿主机和容器看到的 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主要隔离对象容器内直观表现常见误解
PIDPID 编号空间、进程可见性容器主进程显示为 PID 1,只能看到本层和后代层可见进程误以为宿主机上不存在这些进程
network网卡、IPv4/IPv6 栈、路由、端口、防火墙规则容器有独立接口和路由,同一端口可在不同网络命名空间重复监听误以为创建 netns 后天然能访问外网
mount挂载点列表、挂载传播与文件系统视图容器内挂载或卸载可不影响宿主机视图误以为它自动复制了文件内容或改变磁盘权限
Linux 内核上的 PID、network 与 mount namespace 三种隔离视图
结构图:三类 namespace 共享同一个 Linux 内核,但分别为进程、网络与挂载资源提供独立视图。

这个表带来的一个重要变化是:排障时先问“当前进程处于哪个 namespace”,再问“资源是否存在”。例如端口冲突只在同一个 network namespace 的端口空间里成立;某个挂载点只对处于对应 mount namespace 的进程可见;同一个内核进程在不同 PID 层级中可能有不同编号。

旧认知风险:为什么只看进程号会误判

PID namespace 是分层的。新命名空间中的第一个进程会成为该层的 PID 1,它承担特殊职责,例如在该命名空间中处理孤儿进程。宿主机所在的祖先 PID namespace 能看到后代命名空间里的进程,但子层不能反过来看见父层的全部进程。

因此,同一个内核进程可以在容器里显示为 PID 1,在宿主机上显示为 PID 24860。它不是两个进程,也不是 PID 被“改掉”了,而是观察者位于不同 PID namespace,看到的是同一进程在各层的编号。

同一进程在宿主机与容器 PID namespace 中的不同编号
说明图:宿主机与子 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 再决定这些世界如何通信。

回归检查:确认隔离而不是猜测

当容器网络、进程或挂载出现异常时,可以按下面的证据顺序核对:

  1. 比较 namespace 句柄:读取两个进程的 /proc/PID/ns/pid、net、mnt。标识相同才表示位于同一视图。
  2. 从目标进程视角进入:用 nsenter 选择目标 PID 及具体 namespace,而不是只在宿主机执行同名命令。
  3. PID 检查:确认新 PID namespace 是否有正确的 PID 1,以及 procfs 是否从对应 PID 视图挂载。
  4. 网络检查:查看目标 netns 内的接口、地址、路由和监听端口;不要拿宿主机的 ip route 代替。
  5. 挂载检查:读取目标进程的 /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 1PID namespace进程层级、信号处理、僵尸进程回收、procfs 视图
独立网卡、路由和端口空间network namespaceveth/bridge、路由、DNS、转发、NAT、防火墙
独立挂载视图mount namespace只读挂载、传播属性、bind mount、敏感路径
限制 CPU、内存和 I/Ocgroup配额、压力、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/——先确认观察者站在哪个视图里,再解释它看到的世界。

版本声明
本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
把模板函数注册、解析与执行错误分别处理把模板函数注册、解析与执行错误分别处理
上一篇
把模板函数注册、解析与执行错误分别处理
模板执行时出现缺失字段,应报错还是输出空值
下一篇
模板执行时出现缺失字段,应报错还是输出空值
查看更多
最新文章
查看更多
课程推荐
  • 前端进阶之JavaScript设计模式
    前端进阶之JavaScript设计模式
    设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
    543次学习
  • GO语言核心编程课程
    GO语言核心编程课程
    本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
    516次学习
  • 简单聊聊mysql8与网络通信
    简单聊聊mysql8与网络通信
    如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
    500次学习
  • JavaScript正则表达式基础与实战
    JavaScript正则表达式基础与实战
    在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
    487次学习
  • 从零制作响应式网站—Grid布局
    从零制作响应式网站—Grid布局
    本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
    485次学习
查看更多
AI推荐
  • PubMedQA数据集详解:生物医学问答基准、功能与应用指南
    PubMedQA
    深入了解PubMedQA生物医学问答数据集,涵盖其核心功能、使用方法及在临床决策、药物研发等场景的应用,助力提升NLP模型性能。
    378次使用
  • H2O EvalGPT:开源LLM大模型评估与排行榜工具
    H2O EvalGPT
    H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
    449次使用
  • LMArena是什么?伯克利AI模型评估平台使用指南与功能解析
    LMArena
    LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
    457次使用
  • 斯坦福HELM:大语言模型Holistic Evaluation整体评估框架详解
    HELM
    深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
    400次使用
  • MMBench详解:多模态大模型基准测试、功能特点与使用指南
    MMBench
    MMBench是由上海人工智能实验室等机构联合推出的多模态基准测试平台,提供细粒度能力评估、大规模数据集及VLMEvalKit工具。本文详细介绍其核心功能、安装使用方法及应用场景,助力开发者全面评估多模态模型性能。
    227次使用
微信登录更方便
  • 密码登录
  • 注册账号
登录即同意 用户协议 和 隐私政策
返回登录
  • 重置密码