← 全部笔记

Docker · 7 底层原理

namespace:隔离了什么

同一个内核,为不同进程提供不同的资源视图。

约 15 分钟 · 截至 2026-11-03,Docker Engine 29.x · 实验未实测 · 本页讨论 Linux 容器

两个容器的进程使用不同资源视图,共享宿主内核 虚线边界中的进程 A 和 B 各有 pid、net、mnt、uts、ipc 视图。user 是否映射需检查配置。两条系统调用箭头到达同一个 Linux 内核。 容器 A · 进程容器 B · 进程 pid / net / mntpid / net / mnt uts / ipcuts / ipc user:检查配置user:检查配置 系统调用系统调用 同一个 Linux 内核

namespace 隔离“看到哪些资源”。

进程仍在宿主机上运行。内核按它所属的命名空间,决定进程编号、网络、挂载点等资源视图。

示意默认隔离模型;共享参数可以单独改变其中一项。user 不一定另建。

图 1 namespace 划分资源视图,容器进程仍向同一个宿主内核发起系统调用。

模型 / 从进程出发

共用内核

模块 1:容器是什么已经给出结论:容器是被隔离的进程。命名空间(namespace)是 Linux 内核提供的一种资源视图边界。同一边界内的进程看到同一组相应资源。[1]

一个进程同时属于多种 namespace。你可以只共享网络,保留进程编号和主机名的隔离。不要把图中的容器虚线框理解成一个不可拆分的开关。

容器 A、B 都有 PID 1。请用“同一进程”和“编号视图”解释:宿主机为什么没有 PID 冲突?

编号只在各自的 PID namespace 内唯一。宿主所在的祖先 PID namespace 为这两个进程分配不同编号。容器内的 PID 1 与宿主看到的对应 PID,指向同一个进程。没有再启动一套内核。

实验状态:未实测

本页执行环境没有 Docker,以下 Docker 命令均未实测。除明确注明的官方摘录外,不展示虚构终端输出;“预期”是根据文档推导的判读目标,待维护者补实测。

前六个探针在你的 Docker 客户端终端运行。使用 alpine:3.21,首次可能拉取镜像;--rm 在退出后删除容器。统一用 --network none,只保留容器回环接口,不创建 bridge 接线或端口映射。[3]

视图 A / 进程与网络

pid · net

pid:同一进程有不同编号

宿主和容器给同一进程不同的 PID外层宿主 PID 视图包含容器的子视图。同一个应用在宿主视图中为 PID p,在容器视图中为 PID 1。p 是待实测的编号。 宿主的 PID 视图 同一应用PID p 容器 PID 视图 同一应用PID 1 重新编号
图 2 两个编号描述同一进程;外层视图能看到内层进程。

进程标识符(Process ID,PID)由所在视图决定。新 PID namespace 的第一个进程是 PID 1。Docker 默认给容器单独的 PID namespace。[4]

docker run --rm --network none alpine:3.21 sh -c 'printf "shell PID=%s\n" "$$"; cat /proc/1/comm'

预期:先打印 shell 的 PID 为 1,随后读到 PID 1 的进程名称。这里没有启用 --init,也没有共享 PID namespace。实际名称以运行结果为准。

/proc 是内核提供进程等信息的虚拟文件系统。/proc/1/comm 读取当前视图中 PID 1 的进程名;$$ 是 shell 自己的 PID。

两个默认隔离的容器,其主进程都可以是容器内 PID 。

PID 只需在各自的 PID namespace 内唯一。宿主仍用不同的宿主 PID 标识这两个进程。

net:接口、路由与端口各自一份

两个独立网络 namespace 可以使用相同监听端口A 和 B 各有自己的回环接口 lo 与 TCP 80 端口。没有连线表示不自动互通。80 是端口示意,不是实测输出。 net Anet B lolo TCP :80TCP :80
图 3 相同端口在不同 net namespace 中可以分别使用;互通需要另行接线。

net 隔离网络接口、IP 协议栈、路由表、端口和防火墙规则等。容器的 127.0.0.1 指向它自己的回环接口。[5]

docker run --rm --network none alpine:3.21 ip link show

预期:接口列表只有 lo(loopback,回环接口)。这验证了 none 模式提供的网络视图;对照 namespace 句柄后,才能判断是否处于同一边界。

对照 Docker 官方输出摘录

官方 none 驱动文档使用 alpine:latest,给出以下原文。本页固定镜像标签,输出细节可能不同;这不是本机实测。

1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00

两个容器都有独立 net namespace,都在内部监听 TCP 80。会冲突吗?若又都发布到同一宿主地址的 8080 端口呢?

内部监听不会冲突,因为属于不同网络视图。若用 -p 8080:80 都占同一个宿主地址和端口,第二次发布会冲突:宿主这一端仍是同一个网络视图。

容器里访问 127.0.0.1:80 失败,宿主却在监听 80。容器使用独立 net namespace,最合理的解释是什么?

两个独立 net namespace 内的监听端口可以相同;占用关系要在同一网络视图中判断。

pid 管进程编号;回环接口属于 net 的资源视图。

回环流量留在当前网络视图中。访问宿主服务需要选择可达的宿主地址、正确的监听地址和访问规则,不能把容器内的 localhost 当成宿主。

视图 B / 挂载与主机名

mnt · uts

mnt:路径接到哪些文件系统

不同挂载视图可把路径接到不同文件系统容器 A 的 /probe 挂接 tmpfs。下方共享数据通过两条标为挂载的连线进入不同 mnt 视图,说明隔离挂载视图不等于复制数据。 mnt Amnt B /probe → tmpfs无此挂载 /data/data 同一份共享数据 挂载挂载
图 4 mnt 隔离挂载视图;两份视图仍可挂载同一份数据。

挂载(mount)把文件系统接到一个目录。mnt 隔离进程看到的挂载列表。容器运行时还会准备根文件系统;独立 mnt 不等于自动复制磁盘内容。[6]

docker run --rm --network none --tmpfs /probe alpine:3.21 awk '$5 == "/probe" {print $5}' /proc/self/mountinfo

预期:筛出挂载点 /probe。/proc/self 指向读取它的进程,mountinfo 描述其挂载视图,awk 的 $5 选择第五列。

影响:创建容器内的临时内存文件系统(tmpfs),使用宿主内存;容器退出即销毁。不会把宿主的 /probe 目录挂进去,也不需要 --privileged。

A、B 的 mnt namespace 不同,但都读写同一个数据卷。A 写入的文件,B 能读到吗?请说出判断条件。

可以,只要两边挂载的是同一份数据,且路径、读写权限正确。mnt 隔离的是挂载列表,不会为每次挂载复制一份文件。

uts:主机名可以不同

同一内核上的进程可以读到不同主机名uts A 的 hostname 是 ns-a,uts B 的 hostname 是 ns-b。这些是人为指定的示意名称,不代表不同内核。 uts Auts B hostnamehostname ns-ans-b
图 5 主机名来自 uts 视图,名称不同不能证明内核不同。

UTS(UNIX Time-sharing System,历史名称)隔离主机名(hostname)与 NIS 域名。NIS 是一种网络身份目录服务;这里的域名不等同于 DNS 解析配置。[7]

docker run --rm --network none --hostname ns-a alpine:3.21 hostname

预期:读到你指定的主机名 ns-a。这是容器自己的 UTS 设置,不修改宿主机主机名。--name 是 Docker 管理容器的名字,与 --hostname 的作用不同。[2]

想把容器内 hostname 读到的值设为 ns-a,应补哪个参数?docker run --rm --network none alpine:3.21 hostname

--hostname ns-a 设置 UTS 主机名。--name ns-a 给 Docker 管理对象命名,不能代替这个参数。

视图 C / 通信对象与身份

ipc · user

ipc:按名字或编号查通信对象

System V IPC 的对象查找限定在各自视图进程 A 在 IPC A 查找 key K 并找到共享内存段。进程 B 在独立 IPC B 用同一个 key 查找,不能因此找到 A 的对象。 ipc A · 进程 Aipc B · 进程 B 查找 key K查找 key K 找不到 A 的对象 共享内存段
图 6 相同 IPC 键值在不同 ipc namespace 中不指向同一个对象。

进程间通信(Interprocess Communication,IPC)让进程交换数据。ipc namespace 隔离其中的 System V 共享内存、信号量、消息队列,以及 POSIX 消息队列。共享内存让进程访问同一内存区;信号量用于协调访问次序。[8]

docker run --rm --network none --ipc private alpine:3.21 sh -c 'readlink /proc/self/ns/ipc; cat /proc/sysvipc/shm'

预期:先看到 ipc namespace 句柄,再看到 System V 共享内存表;新容器未创建此类对象时只有表头。句柄数字随环境变化。空表本身不足以证明隔离,下一节会比较句柄。

深入一点:ipc 是否隔离所有通信?

不包括所有通信。TCP 连接由 net 管理,文件读写受挂载与权限影响。POSIX 共享内存通常经 /dev/shm 的文件实现,也要看挂载安排。Docker 的 IPC 模式还涉及 /dev/shm,不能只凭目录名判断内核 IPC 对象是否共享。

A、B 的 ipc namespace 独立。B 使用 A 的 System V IPC 键值,能直接取得 A 的共享内存吗?这是否意味着 A、B 不能用 TCP 通信?

不能仅凭相同键值取得 A 的对象,查找发生在 B 自己的 ipc 视图中。TCP 是否互通取决于 net、网络接线与访问规则;ipc 隔离不等于切断全部通信。

user:内外身份由映射决定

userns-remap 将容器 UID 0 映射到宿主普通 UID启用映射时,容器 UID 0 可以对应宿主 UID 231072,这是 Docker 文档中的示例。未启用映射的普通 rootful 配置中,UID 0 仍对应宿主 UID 0。 容器 user 视图UID 0 已启用映射 宿主身份UID 231072 映射 未启用映射的 rootful: 容器 UID 0 → 宿主 UID 0
图 7 UID 231072 是 Docker 文档的映射示例;你的映射必须实查,不能照抄。

用户标识符(User ID,UID)和组标识符(Group ID,GID)用于表示身份。user namespace 允许同一进程的内外 UID/GID 不同,权限也受 namespace 范围约束。[9]

docker run --rm --network none alpine:3.21 sh -c 'id -u; cat /proc/self/uid_map; cat /proc/self/gid_map'

预期:先读到容器内 UID,再读映射表。每行三列依次是“内侧起点、外侧起点、连续 ID 数量”。只看到 UID 0 不足以判断宿主身份。

普通 rootful(守护进程以 root 运行)、未启用 userns-remap 时,通常不单独隔离 user,UID 0 仍是宿主 UID 0,但其他权限限制仍生效。--user 1000 是换进程身份,不是开启映射。[10]

你只拿到容器中 id -u 为 0 的记录。能断定它在宿主也是 UID 0 吗?还要查什么?

还不能断定。应查 user namespace、uid_map 与 Docker 的运行模式。可能是未映射的 rootful,也可能是启用映射的容器;容器内的同一个数字不能替代内外身份对照。

深入一点:userns-remap 与 rootless

userns-remap 重映射容器的 UID/GID,守护进程仍以 root 运行。Rootless 模式把守护进程和容器都放在 user namespace 内,以非 root 身份运行。映射可能有多层,直接父 namespace 的“外侧 UID”未必就是最外层宿主 UID。[11]

本页只检查现状。不要为了获得某张输出表就修改守护进程配置:启用 remap 会改变 Docker 对原有数据的可见性,需按官方文档单独规划。

实验 / 查资源视图的身份

实测边界

比较 namespace 身份,比比较主机名或进程名更可靠。/proc/<pid>/ns/ 中的符号链接指向进程所属 namespace。readlink 读取链接目标;同一 Linux 内核中,同一类型的目标相同,表示处于同一个 namespace。数字是内核分配的身份编号,不是容器 ID。[1]

适用条件:下面的宿主对照须在运行 Docker Engine 的原生 Linux 主机上执行,使用普通 rootful、未启用 remap 的配置。需要可访问 Docker,并可用 sudo 读取进程信息。Docker Desktop 的容器位于 Linux 虚拟机内;远程 context 的 PID 属于远端,不能拿本机的 /proc 去查。Rootless 的内外视图也不同。[12]

① 在宿主的同一个 shell 启动一个临时容器。它最多运行 600 秒;记下变量,后续命令接着执行。inspect 的 .State.Pid 给出主进程在该 Engine 宿主中的 PID。[13]

ns_cid=$(docker run --rm -d --network none --ipc private --hostname ns-lab alpine:3.21 sleep 600)
ns_pid=$(docker inspect --format '{{.State.Pid}}' "$ns_cid")
printf 'container=%s host-pid=%s\n' "$ns_cid" "$ns_pid"

② 比较宿主 shell 与容器主进程。/proc/$$ 指宿主 shell;不使用 /proc/1 代替你的 shell,以免把不同运行环境混在一起。

for ns_kind in pid net mnt uts ipc user; do
  printf '\n%s\n' "$ns_kind"
  readlink "/proc/$$/ns/$ns_kind"
  sudo readlink "/proc/$ns_pid/ns/$ns_kind"
done

预期:在上述配置下,pid、net、mnt、uts、ipc 两行目标不同;user 通常相同。若不符合,先查运行模式、共享参数和命令所在的主机,不要把某组固定编号当标准答案。

③ 从容器里读同一进程的 PID namespace。接着在宿主用 util-linux 提供的 lsns 汇总容器主进程的 namespace,显式指定输出列。[14]

docker exec "$ns_cid" readlink /proc/1/ns/pid
sudo readlink "/proc/$ns_pid/ns/pid"
sudo lsns --list --task "$ns_pid" --output NS,TYPE,NPROCS,PID,COMMAND

预期:前两条命令的链接目标相同,因为它们指向同一个主进程的 PID namespace;两次使用的进程编号却不同。lsns 的 NS 是身份编号,TYPE 是类型,NPROCS 是当前可见的成员数,PID/COMMAND 描述代表进程,不是每种 namespace 各运行一个进程。

④ 观察完即清理。只删除本次变量记录的实验容器;若已经超时退出,--rm 已完成清理。

docker rm -f "$ns_cid"

为什么宿主用 /proc/$ns_pid、容器用 /proc/1,却能读到相同的 PID namespace 身份?用两句话解释。

两条路径按各自视图选择同一个容器主进程。进程编号随观察视图改变,但该进程所属的 PID namespace 没有改变。

  • 区分了进程编号与 namespace 身份。
  • 说明了同一进程仍在同一个内核中运行。
没有原生 Linux 宿主,或查询失败怎么办?

Docker Desktop 可先做前面的六个容器内探针,以及 docker exec 的读取;宿主对照实验请换到 Linux Engine 主机,不用特权容器去绕过边界。

查不到进程时,先确认容器仍在运行、ns_pid 不是 0,且 CLI 没连远端。权限不足时,在 Engine 宿主使用允许的管理员权限。lsns 只能列当前可访问的信息;不是整台物理机的全知视图。

你可能还看到 cgroup、time、pid_for_children 等条目。Linux 的 namespace 不止本页六种;cgroup namespace 是控制组路径视图,资源额度则由 cgroups(控制组)机制控制,下一页再学。

边界 / 逐项判断

共享边界

共享某一种 namespace,只会改变相应资源视图。下面是参数的判读表,不是要求执行的实验清单。host 指 Docker Engine 所在的 Linux 环境。[2]

共享时,哪条边界发生变化?
配置改变的视图判断与影响
--pid hostpid可看到宿主进程编号;操作仍受权限约束。
--network hostnet共用网络接口与端口;监听可能占用宿主端口。不会自动共享 uts。
--uts hostuts读宿主主机名;具备相应权限的改名操作也会影响宿主。
--ipc hostipc进入宿主 IPC 对象视图,访问仍受权限约束。
共享数据卷文件数据不等于共享 mnt namespace,两份挂载列表可以指向同一数据。
--user 1000进程身份切换 UID;不自动启用 user namespace 映射。

同一台原生 Linux Engine 主机上,容器仅加 --network host,没有其他共享参数。哪项判断成立?

每类 namespace 分别选择,网络参数不会连带打开全部视图。

主机名归 uts 管理。两个主机名相同也可能只是值碰巧一致,须比较句柄。

Docker 文档明确说明,即使 --network host,容器默认仍拥有自己的 UTS namespace。pid 也有独立参数。判断共享应按类型比较 namespace 身份,而不是从名字推断。

离开本页前,记住三条

来源

核对日期:2026-11-03;命令与参数以 Docker 官方文档为准。Linux man-pages 补充内核语义。Docker 实验未实测,维护者需补运行记录。

  1. Docker:Running containers;namespaces(7):进程模型、类型与 /proc/pid/ns 身份比较。
  2. Docker:docker container run:pid、uts、ipc、userns、hostname、tmpfs 参数。
  3. Docker:None network driver:只创建回环接口;图 3 下的官方输出摘录来源。
  4. pid_namespaces(7):PID 1、嵌套可见性与进程编号。
  5. network_namespaces(7):接口、路由、端口等隔离范围。
  6. mount_namespaces(7):挂载列表;Docker:tmpfs mounts:临时挂载与内存。
  7. uts_namespaces(7):主机名、NIS 域名。
  8. ipc_namespaces(7):System V IPC、POSIX 消息队列。
  9. user_namespaces(7):UID/GID 映射及其三列含义。
  10. Docker:Isolate containers with a user namespace:remap 配置与 UID 231072 示例。
  11. Docker:Rootless mode:守护进程与容器的运行身份。
  12. Docker Desktop:General FAQs:Linux 虚拟机边界。
  13. Docker:docker inspect:格式化读取;Engine API v1.52 定义:ContainerState.Pid 字段。
  14. lsns(8):namespace 汇总、指定列与可见性限制。