news 2026/10/22 9:59:08

Linux内核namespace实战:PID/Mount/Network/User隔离与排障

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Linux内核namespace实战:PID/Mount/Network/User隔离与排障

这期继续写 Linux 内核容器技术里最容易被翻来覆去问的 namespace。上一篇已经把七类 namespace 的轮廓拉了一遍,如果只是把名字和 clone flag 背下来,那离“排障”还差得远。Part2 我打算直接从几个“看着简单、用起来全是坑”的类型下手:PID namespace 里那个 PID 1 为什么不按常理出牌,mount namespace 的挂载传播为什么能让宿主机和容器互相“传染”,network namespace 的 veth 到底是怎么把两个隔离的网络栈串起来的,还有 user namespace 里那个绕来绕去的 uid_map。写这些是因为我自己在排查容器问题的时候,这几个点每次都能绕进去一会儿。如果你是已经在用容器、想往内核底层钻一钻的开发者,这篇应该能对上胃口。全程不搞空谈,所有结论都配命令,你可以直接复制跑。

1. 先把 namespace 的家底盘一遍:Part2 要啃哪几块硬骨头

1.1 第一篇到第二篇的定位差异

上一篇是“面”,这一篇是“点”。面讲的是七类 namespace 分别隔离什么资源、什么时候用哪个 flag,点到为止。Part2 则默认你已经知道下面的对照关系:

namespace隔离对象clone flag
Mount挂载点、文件系统视图CLONE_NEWNS
UTShostname、NIS domain nameCLONE_NEWUTS
IPCSystem V IPC、POSIX 消息队列CLONE_NEWIPC
PID进程号空间、PID 1 语义CLONE_NEWPID
Network网络设备、协议栈、路由、iptablesCLONE_NEWNET
UserUID/GID 映射、capabilitiesCLONE_NEWUSER
Cgroupcgroup 根目录视图CLONE_NEWCGROUP

表里最容易被忽略的是 mount namespace 的 flag 为什么叫 CLONE_NEWNS 而不是 CLONE_NEWMOUNT。这是历史遗留,内核最早引入挂载隔离时叫“new namespace”,后来发现要隔离的东西越来越多,但 flag 名字已经改不了了。读老代码、看 strace 输出时如果遇到 CLONE_NEWNS,别觉得奇怪。

Part2 如果想真正吃透,不能只是看功能表,要看这些 namespace 对象在内核里到底怎么跟进程绑定、生命周期怎么管理。所以这篇不会急着讲“容器怎么用”,而是先把机制本身掰开。

1.2 namespace 对象是怎么挂在进程上的:nsproxy 与 proc/self/ns

内核里每个进程的 task_struct 上有一个 nsproxy 指针,它指向一组 namespace 对象的集合。这组对象包括 mnt_ns、uts_ns、ipc_ns、pid_ns_for_children、net_ns、cgroup_ns,各自对应一个内存对象。调用 clone 或 unshare 时,如果你传了 CLONE_NEWNET、CLONE_NEWPID 之类标记,内核就会创建对应的新 namespace 对象,然后用新对象替换 nsproxy 里对应位置的指针。如果不传标记,子进程直接共享父进程的 namespace 对象。

这里有个容易让人懵的点:CLONE_NEWPID 改的不是“当前进程”的 PID namespace,而是 nsproxy 里的 pid_ns_for_children,也就是将来 fork 出的子进程用什么 PID namespace。正因如此,用 unshare 命令创建 PID namespace 时,必须再带一个 --fork 参数,否则当前 shell 不会进入新的 PID namespace,只有它 fork 出来的子进程才会。这个坑后面会详细说。

每个 namespace 对象都能通过 /proc 下的 magic symlink 拿到一个文件描述符。比如打开 /proc/self/ns/net,返回的 fd 就指向当前进程的网络 namespace。这个机制非常重要:namespace 不一定非要有进程在里面才存活,只要有一个打开的 fd,内核里那个对象就会一直活着。后面说生命周期的时候,还会用这个特性来做“续命”。

2. PID namespace:容器里“第一个进程”的错觉

2.1 局部 PID 与全局 PID 是怎么映射的

在一个 PID namespace 内部,所有进程的进程号都从 1 开始重新编。这个“局部 PID”和宿主机上的“全局 PID”是两套编号体系,内核通过进程的 upid 结构在不同层级的 PID namespace 里维护不同的编号。

举个例子:你在容器里看到 PID 1 的进程,从宿主机上看它可能是 23456。反过来,宿主机上有一堆 sshd、crond 进程,在容器内用 ps 完全看不到。原因很简单:容器内的 proc 文件系统挂载在容器自己的 PID namespace 视图下,而宿主机进程根本不在这个 namespace 里,自然就不会出现在这个视图里。

这个机制的底层逻辑是,内核在每次 fork 时给新进程在“它所属的每个祖先 PID namespace”里都分配一个编号。所以一个进程在不同层级会有不同编号,但只有最内层那个编号对进程自己可见。调试时最容易犯的错误是,在容器外看到某个进程 PID,直接进容器后按这个 PID 去找,找不到。不是进程没了,是两套编号体系之间没有直接换算关系,必须通过进程所属的 PID namespace 来理解。

2.2 用 unshare 手工做一个独立 PID 视图

先看最典型的做法:

sudo unshare --pid --fork --mount-proc bash

进入之后你就在一个新的 PID namespace 里。此时在 bash 里执行:

echo $$ ps aux

你会发现自己的 shell 是 PID 1,ps 看到的进程列表非常干净,只有当前 namespace 里的进程。

这里有两个细节必须说清楚。第一,为什么必须加 --fork。unshare 系统调用创建了新的 PID namespace 后,当前调用进程自己是不会自动变成新 namespace 成员的,它只是把“以后子进程要用的 namespace”换掉了。如果不 fork,bash 还是老 namespace 的成员,那你进入的其实是个空壳。加了 --fork 之后,unshare 会 fork 一个子进程,这个子进程才是新 namespace 里的第一个进程,也就是新 PID namespace 的 init。

第二,为什么还要 --mount-proc。进程的 PID namespace 换了,但 /proc 还是原来那个挂载,ps 读取 /proc 时依然会看到宿主机的完整进程表。--mount-proc 会自动为新 namespace 挂一份新的 procfs,让 ps 拿到正确的进程视图。如果忘了加,你会看到自己的 shell 在容器里是 PID 1,但 ps 里却有一堆不属于这个 namespace 的宿主进程,第一次看到会非常困惑。

2.3 容器里那个“杀不死的 PID 1”

PID namespace 里的 init 进程,也就是 PID 1,跟普通进程的差别很大。内核给它加了很多特殊语义,其中一个直接体现在信号处理上:来自同一个 PID namespace 内部的普通信号,如果 PID 1 没有显式注册 handler,内核会直接忽略掉,默认动作是终止的信号在它身上并不按常规执行。这就是为什么很多容器里执行 kill 1 没反应,因为业务进程直接当 PID 1 时,往往根本没写 SIGTERM 的 handler。

但注意,SIGKILL 和 SIGSTOP 是特例。这两种信号本来就无法被捕获或忽略,从 namespace 外部(比如宿主机 root)发送时,仍然可以把容器里的 PID 1 干掉。所以不存在“绝对杀不死”的进程,只是在 namespace 内部的信号投递规则跟普通进程不一样。

另一个容易被忽略的职责是孤儿进程回收。普通进程的父进程退出后,孤儿进程会被过继给最近的 subreaper,如果没有 subreaper,就过继给 PID namespace 的 init。如果 PID 1 不负责 wait,这些变成僵尸的子进程就一直挂在系统里。这也是很多生产容器喜欢用专门 init 程序当 PID 1 的原因:它负责信号转发和僵尸回收,业务进程反而可以专注干活。

实操里的一个心得是,别把 pid namespace 当成进程数限制器。一个 PID namespace 里能创建的进程号数量受 pid_max 限制,但一般我们担心的“容器里进程太多拖垮宿主机”要靠 cgroup 的 pids 控制器去限制,两码事。

3. Mount namespace:文件系统视图的隔离与“挂载传染”

3.1 挂载传播:为什么有人说 mount namespace 最微妙

mount namespace 隔离的是整个挂载点视图,听起来简单,但内核在挂载点之间引入了一套“传播关系”,常把新手搞得头疼。传播关系分几种:shared、slave、private、unbindable。

  • shared:同组挂载点之间,一个挂载或卸载事件会互相传播。
  • slave:能接收 master 的传播事件,但自己产生的事件不反向传播。
  • private:完全独立,不传播任何事件。
  • unbindable:不仅不传播,连 bind mount 都不允许。

你可能会问,为什么容器里的 mount namespace 会跟宿主机的挂载互相“传染”?根源在于现代发行版的初始化进程会把根挂载点设成 shared。这样一来,如果创建新的 mount namespace 后不调整传播类型,新 namespace 里的挂载点也继承 shared 关系,你在容器内 mount 一块 tmpfs,宿主机上可能同步看到;宿主机新插的 U 盘自动挂载,容器里也可能会冒出来。

这个问题的正确解法是在进入新的 mount namespace 之后、执行真正的业务挂载之前,先把传播关系断开:

unshare --mount --fork bash mount --make-rprivate /

--make-rprivate 会递归地把当前 namespace 里的所有挂载点都改成 private,彻底切断双向传染。容器运行时在启动容器时一般也会做类似处理,只是它们更喜欢设成 slave 而不是 private。slave 的好处是,宿主机上本来就有的文件系统挂载在容器里仍然可见,但容器内新 mount 不会反向影响宿主。这符合大部分业务需求:机器上的 /etc/xxx 要能看到,但容器内的临时挂载不能漏回宿主机。

3.2 用 pivot_root 做一次真正的“换根”

chroot 只是把当前进程的根目录换到一个路径,但进程如果有能力拥有旧根目录的文件描述符,理论上还有越狱的可能。pivot_root 要彻底得多:它会把当前 mount namespace 的根挂载点切换成新 rootfs,同时把原来的根挂载点挂到新根下面的一个目录里,之后还能卸载掉。真正把进程锁在隔离的文件系统视图里。

手动演示一版核心流程:

# 先准备好一个最小 rootfs,至少要有静态编译的 shell 和基本命令 mkdir -p /tmp/newroot/bin /tmp/newroot/proc /tmp/newroot/oldroot # 关键:把 newroot 变成一个挂载点 mount --bind /tmp/newroot /tmp/newroot cd /tmp/newroot pivot_root . oldroot exec chroot . /bin/sh mount -t proc proc /proc umount /oldroot

这个流程里有几个硬规矩。第一,pivot_root 的 new_root 参数必须是一个挂载点。如果你直接拿一个普通目录当 new_root,内核会返回 EINVAL。所以上面先 mount --bind 把目录绑定到自己身上,让它成为挂载点。第二,put_old 必须位于 new_root 之下,这样内核才能把旧根移动进去。第三,pivot_root 之后要尽快把旧根卸载掉,否则只要旧根路径还在,等于给进程留了一个回程通道。

这里也顺带解释一个常见疑问:为什么容器运行时不直接用 chroot 而是用 pivot_root。除了安全考虑之外,pivot_root 能干净地把根目录从宿主机的挂载树里剥离开,让容器内看到的文件系统拓扑跟宿主机完全无关。chroot 只是改变了路径解析起点,挂载树层面的牵连还在。

3.3 实操心法:如何防止挂载泄漏

我见过不少手工做容器实验的人,在 unshare 出 mount namespace 后直接 mount 了一个 tmpfs,然后发现宿主机也多了一个挂载点。一开始还以为是命令写错了,其实是传播关系没断开。

排查方法很简单,看 /proc/self/mountinfo 里挂载点的可选字段有没有 shared 标记:

cat /proc/self/mountinfo | head -1

如果最右边出现 shared:123 之类的内容,说明当前挂载点处于 shared peer group 中。想确认整个树的状态,可以:

findmnt -o TARGET,PROPAGATION -R /

一旦发现有很多 shared,就不要犹豫,在创建新 mount namespace 后立刻执行 mount --make-rprivate /,把所有挂载点拍扁成 private。顺序很重要:必须是先改传播类型,再挂载新文件系统。如果先 mount 再改,已经进入 peer group 的传播事件可能已经把挂载传出去了。

另外一个容易忽略的细节是,bind mount 也会参与传播。所以有些时候不是“临时挂载”漏出去了,而是 bind 宿主目录时把事件带出去了。问题不在 bind 本身,而在 bind 之前挂载点还挂在 shared peer group 里。把传播属性理清楚,mount namespace 的很多“灵异事件”都能解释通。

4. Network namespace:容器网络栈独立背后的机制

4.1 一个 network namespace 里到底装着什么

网络 namespace 隔离的不是“一块网卡”这么简单,它拥有完整的网络栈:独立的网络设备列表、IPv4/IPv6 协议栈、路由表、netfilter 规则集、还有已经创建的 socket。你可以把它理解成一台没有显示器的小型“虚拟机”,只不过它跟宿主共享同一个内核,只是网络状态完全隔离。

刚创建一个新的网络 namespace 时,里面只有一个 loopback 设备,而且这个 lo 默认还是 down 的。所以很多人在容器里 ping localhost 不通,问题就在这:lo 都没 up。手动操作一下:

ip netns add ns1 ip netns exec ns1 ip link set lo up

另外要记住,网络 namespace 并不负责 DNS 配置。/etc/resolv.conf 是文件系统里的东西,归 mount namespace 管。很多容器里的 DNS 问题排查到最后发现是挂载视图和网络视图不一致,而不是网络不通。

还有一个容易踩的点:一个 socket 属于创建它时的那个网络 namespace。也就是说,已经打开的 socket 不能通过 setns 从 A namespace “搬”到 B namespace,只能销毁后重建。这一点在排查长连接问题时很重要,你以为切了 namespace 就能继承已有 socket,实际上是做不到的。

4.2 亲手搓一对 veth,把两个 namespace 连起来

veth 是成对出现的虚拟网卡,像一根交叉线插在两个 namespace 之间。数据从一端发出,会以内核收包的形式出现在另一端。很多人把它理解成“隧道”,其实更准确地说,它就是把一对网卡的内核路径直接连起来。

先看两个网络 namespace 直连的场景:

ip netns add ns1 ip netns add ns2 ip link add veth1 type veth peer name veth2 ip link set veth1 netns ns1 ip link set veth2 netns ns2 ip netns exec ns1 ip addr add 192.168.15.1/24 dev veth1 ip netns exec ns1 ip link set veth1 up ip netns exec ns2 ip addr add 192.168.15.2/24 dev veth2 ip netns exec ns2 ip link set veth2 up ip netns exec ns1 ping -c3 192.168.15.2

这套命令跑下来,两个本来看不见对方的 namespace 就能互相 ping 通。但要注意,veth 设备创建后默认还是挂在当前 namespace 里的,必须用 ip link set ... netns 指定归属。名字也容易混,veth1 和 veth2 两个名字只是端点的代号,不是自动表明某个端点属于哪个 namespace。

真正的容器网络通常不是点对点,而是所有容器接到同一个 Linux bridge 上。大致流程:

ip link add br0 type bridge ip link set br0 up ip link add veth-ns1 type veth peer name veth-ns1-peer ip link add veth-ns2 type veth peer name veth-ns2-peer ip link set veth-ns1-peer netns ns1 ip link set veth-ns2-peer netns ns2 ip link set veth-ns1 master br0 up ip link set veth-ns2 master br0 up ip netns exec ns1 ip addr add 10.0.0.1/24 dev veth-ns1-peer ip netns exec ns1 ip link set veth-ns1-peer up ip netns exec ns2 ip addr add 10.0.0.2/24 dev veth-ns2-peer ip netns exec ns2 ip link set veth-ns2-peer up

原理上,每个 namespace 的“对端网卡”插进了宿主的 bridge,bridge 像一个二层交换机,负责在 namespace 之间转发帧。加了 bridge 之后,同一网段内多个 namespace 就能互相访问,这也是很多容器网络插件最基层的模型。

4.3 网络 namespace 清障:三板斧定位不通

网络不通时,第一件事不是怀疑内核,而是先看链路状态:

ip netns exec ns1 ip link show

确认端口 state 是不是 UP,MAC 地址有没有到手,有没有 HUP 之类的异常。

第二板斧是查 ARP 和路由。ping 不通但链路正常时,往往是对端 MAC 解析不出来:

ip netns exec ns1 ip neigh show ip netns exec ns1 ip route

如果 ARP 表是空的,优先检查对端接口有没有配置 IP、有没有 up。veth 的收包路径走的是完整协议栈,对端接口 down 的话,包根本进不了协议栈。

第三板斧是检查宿主机的 iptables 规则和 rp_filter。特别是加了 bridge 之后,宿主机的 FORWARD 链可能默认 DROP,导致跨 namespace 的包在二层转发后被三层拦掉。有些发行版还会开反向路径过滤,源地址不具备对称路由的包会被直接丢。排查命令:

sysctl net.ipv4.conf.all.rp_filter iptables -L FORWARD -n -v

另外有个细节:veth 的 MTU 默认 1500,如果在隧道或 overlay 场景下报文被再封装,容易出现 ping 通但大包不通的情况。可以调小 MTU 验证,也可以直接用ip link set ... mtu 1400规避。

5. User namespace 和剩下那几位:权限隔离是门玄学

5.1 uid_map 和 gid_map:容器里 root 为什么不是真 root

user namespace 的核心是 UID/GID 映射。每个进程的 /proc/PID/uid_map 文件里存着“namespace 内 UID”到“父 namespace UID”的映射关系,格式是三列:起始 ID、起始长度、范围长度。例如一行0 1000 1,表示这个 user namespace 里的 UID 0 对应父 namespace 里的 UID 1000。

最常见的验证姿势:

unshare -U -r bash id -u cat /proc/self/uid_map

如果当前登录用户是 UID 1000,上述命令跑完,你会发现自己在新 namespace 里是 UID 0,也就是 root,但 uid_map 显示0 1000 1。也就是说,你这个“root”只是映射到宿主机普通用户的 root,在宿主机上没有任何特权。这就是 rootless 容器的核心基础:在隔离的权限空间里当 root,而不是直接获得宿主机的 root。

gid_map 比 uid_map 多一个坑:写 gid_map 之前,必须先往 /proc/PID/setgroups 里写deny,否则内核会拒绝修改 gid 映射。原因很直接,setgroups 系统调用允许普通进程把附加组切换到任意组,如果不先禁止,gid_map 的权限检查就没有意义。

映射还有一个不可逆性:一旦写入 uid_map,就不能再改了。所以那些需要给容器分配多段 UID 映射的场景,必须在子进程启动前用专门的 setuid helper 一次性写好。

5.2 capabilities 在 user namespace 里的“加减法”

user namespace 之所以能实现“非特权用户变成 root 但不危害宿主机”,关键在于 capabilities 的作用范围被限定在 namespace 树里。创建新的 user namespace 时,当前进程会获得这个新 namespace 中完整的 capabilities 集合,包括 CAP_SYS_ADMIN、CAP_NET_ADMIN 这些敏感权限。但它只对当前 user namespace 及后续子 namespace 中的对象有效,一旦要操作系统外层资源,就原形毕露。

举个实际例子,普通用户直接mount -t tmpfs tmpfs /mnt大概率被拒绝,但在新的 user namespace 叠加 mount namespace 之后就可以:

unshare -Urn bash mount -t tmpfs tmpfs /mnt mount | grep mnt

-U创建 user namespace,-r把当前用户映射成 0,-n创建新的 network namespace。进入之后,你在这个 user namespace 里拥有挂载权限,tmpfs 又是白名单里的可挂载文件系统,所以能成功。但如果你试图把宿主机的物理磁盘或某些敏感文件系统挂进来,内核会拒绝,因为权限作用域不够。

理解了这个“权限作用域”,就能解释很多安全问题:容器里的 root 能不能破坏宿主机,取决于它有没有在对应的父 user namespace 里的 capabilities。user namespace 的嵌套层数也被内核限制,上限是 32 层。写递归脚本时如果不小心,很容易踩到 EUSERS 类错误。

5.3 UTS 和 IPC namespace:最常被忽略的两个小透明

UTS namespace 隔离的是 hostname 和 NIS domain name。它是所有 namespace 里最轻量的一类,甚至很多人觉得它“没用”。但容器没有自己的 hostname,日志、监控、网络标识全都会混在一起。手动体验一下:

unshare --uts bash hostname container-demo hostname

执行后新 namespace 里看到的是 container-demo,宿主机的 hostname 不会变。这里有个细节:某些应用除了读 hostname,还会读 /etc/hosts 和 /etc/hostname,而这些是文件系统内容,需要 mount namespace 配合才能真正做到“内外各叫各的”。所以很多容器里改了 hostname 但程序还是显示旧值,不是 UTS 没隔离,是文件视图没跟上。

IPC namespace 隔离的是 System V IPC 对象,也就是消息队列、信号量、共享内存,也包含 POSIX 消息队列。对单机业务影响不大,但如果你的应用重度依赖共享内存做进程间通信,跨 namespace 的进程就访问不到同一块共享内存。这个机制本来是隔离,但有时候会成为排障盲区:应用从宿主机移进容器后,日志里全是“共享内存不存在”,第一反应往往是权限问题,其实是 IPC namespace 换了,IPC 对象的 namespace 归属对不上。

cgroup namespace 则是 Linux 4.6 之后加的一类 namespace,隔离的是 cgroup 根目录视图。它让容器内看到的是相对路径,不会把宿主机整套 cgroup 树暴露给容器。这个特性在 cgroup v2 下尤其重要,但由于涉及 cgroup 本身的面太广,这篇文章不展开,等后面单独聊 cgroup 控制器时再细说。

6. namespace 的生命周期、调试实战与容器运行时串联

6.1 让 namespace“多活一秒”的 fd 魔法

namespace 对象的生命周期不跟随“创建它的进程”走,而是跟着引用计数走。只要还有进程在使用某个 namespace,或者还有打开的文件描述符指向它,它就不会被销毁。最常见的续命手法,是把 /proc/PID/ns/ 下的符号链接 bind mount 到固定路径:

mkdir -p /tmp/ns touch /tmp/ns/net mount --bind /proc/$$/ns/net /tmp/ns/net

执行完后,哪怕之前那个进程退出,/tmp/ns/net 这个 bind mount 仍然会让网络 namespace 存活。这个特性非常有用,尤其是手工调试后不想让 namespace 随进程消失时,可以先续命,再做后续分析。

反过来,如果 namespace 里没有进程、也没有 fd 引用它,内核会直接回收。很多“网络 namespace 消失”的现象,本质就是引用计数清零了。看到这里,你应该能明白为什么 ip netns add 创建的 namespace 能一直存在:ip 命令为它在 /var/run/netns/ 下放了一个绑定文件,相当于一直持有引用。

6.2 nsenter 和 lsns:调试 namespace 的实用三板斧

光会创建 namespace 还不够,生产环境里更多是“进入一个已经存在的 namespace”去排查问题。util-linux 里的 lsns 和 nsenter 是干这个事的利器。

lsns 可以列出当前系统里的 namespace:

lsns -t net -o NS,PROC,PATH,COMMAND

这样能看到每个网络 namespace 的编号、里面的代表进程、proc 路径和对应命令,快速定位容器对应的 namespace 从属。

如果要进入某个进程的网络 namespace,用 nsenter 指定目标进程 ID,只切网络部分:

nsenter -t 1234 -n bash ip addr

这里的-n只切 network namespace。默认情况下 nsenter 如果不加参数,会把目标进程的所有 namespace 都切过去,但实际排障时我们往往只希望切某一个维度。比如只看网络配置,就不要切 mount namespace,否则你连宿主机的 /etc 都看不到了。

需要注意,进入 PID namespace 的场景更微妙,因为 PID namespace 对“调用进程是否能直接切换”有额外限制。日常使用中,如果只是要进入容器的网络、挂载或 UTS namespace,nsenter 足够顺手。

6.3 容器运行时是怎么把 namespace 全部串起来的

理解了单个 namespace 的创建和切换,再把容器运行时启动一个容器的流程拼起来就顺理成章了。

容器进程在创建时,运行时会在 clone 阶段传入一串 CLONE_NEWNS、CLONE_NEWPID、CLONE_NEWNET 等标志。新进程由此出生在一套全新的 namespace 集合里。紧接着,运行时会在这个新进程里完成 rootfs 准备,通常是挂载 rootfs、调整传播类型、pivot_root 换根,最后 exec 用户指定的程序。网络 namespace 创建出来后可能是空的,需要再配置接口和路由。这一整套动作的顺序是有讲究的:先隔离,再配置,最后启动业务。

如果只是给一个正在运行的容器追加“执行命令”,比如进入容器里跑 ps,底层就用到 setns 系统调用。运行时拿到目标容器进程的 namespace fd,然后用 setns 把当前辅助进程切换进那组 namespace,再 fork 出最终执行命令的子进程。不过 PID namespace 的 setns 相比其它类型要多绕一步,因为它涉及“当前进程知道自己是否属于某个 PID namespace”的内核语义,所以常用做法是 fork 一次,让子进程成为目标空间里的新成员。

从这个角度看,容器不过是一组 namespace 对象加一套文件系统视图的集合。进程可以自由地“属于多个 namespace”,但每类资源的隔离边界完全由内核对象决定。排障时如果先把“这个进程到底在哪个 net namespace、哪个 pid namespace”理清楚,问题基本就解决一半了。

最后分享一个小技巧:我自己做实验时,经常先手动 bind mount 一个 namespace fd,防止 namespace 被回收。比如想保留某个容器的网络 namespace,就先把 /proc/PID/ns/net 绑到 /tmp/ns/net,之后不管容器怎么重启,我都能用 nsenter 进去看残留的 socket、路由和 iptables 规则。调试完再 umount,干净利落。踩了几次“namespace 消失”的坑之后,你就会明白,一个 fd 的价值很多时候比一堆内存分析工具都管用。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/22 9:58:15

大模型三层架构:输入层、输出处理层才是AI应用创新的主战场

1. 先跳出一层:为什么90%的人都在错误地理解大模型 先说个我自己的经历。早年做推荐系统,接触过各种"智能"模块,老觉得里面一定藏着什么高深莫测的核心。后来真正上手大模型相关的应用,把架构一层层拆开,才发…

作者头像 李华
网站建设 2026/10/22 9:57:53

Claude Opus 5.5生成视频真相:五步代码视频流水线拆解

1. 先厘清一个被标题带偏的认知:模型不产视频,产的是“可渲染的代码” 看到“Claude Opus 5.5 生成视频”这种说法,我第一反应是:又有人把“模型输出代码”和“模型输出视频”混为一谈了。这两件事在工程上差着十万八千里。模型本…

作者头像 李华
网站建设 2026/10/22 9:57:10

PCB表面缺陷检测实战:基于YOLO的训练与部署全流程

做机器视觉这些年,我越来越觉得PCB印刷电路板表面缺陷检测是一个被严重低估的方向。表面上看,它不过是"找瑕疵"三个字,但真正深入进去,你会发现它同时踩中了制造业质检的三大痛点:缺陷种类杂、误检代价高、人…

作者头像 李华
网站建设 2026/10/22 9:57:07

TensorFlow实现局部上下文归一化:解决图像光照不均的预处理层

做图像相关的项目,最怕的就是光照不均匀。之前我在一个模拟项目X里做文档分类,采集回来的图片经常出现一半亮一半暗、某个角落被阴影盖住的情况,模型训练集上跑得挺好,一到验证集就被这些局部亮度波动打穿,卷积核全被绝…

作者头像 李华
网站建设 2026/10/22 9:56:53

Linux 下 Android 命令行工具链安装与 CI 构建实战

简介:本资源为 Android 命令行工具包 commandlinetools-linux-10406996-latest.zip,面向 Linux 平台下进行 Android 开发、构建与自动化部署的工程师及学习者。若无需安装完整的 Android Studio,可通过该工具集直接获取 sdkmanager 等核心组件…

作者头像 李华
网站建设 2026/10/22 9:56:24

企业级Agent平台深度解析:权限、编排与审计如何驱动生产级应用

直接切入。最近一段时期,身边不少团队都在讨论同一个话题:个人用的 Agent 工具已经跑得挺顺了,怎么一搬到企业环境就各种别扭?权限管不住、流程接不上、出了问题没人能解释。正好看到某云厂商发布企业级 Agent 平台 AgentCore&…

作者头像 李华