
聊到 Linux 网络十个人里有八个会提到容器而容器技术背后躲不开的一个概念就是 Network Namespace。我最早接触这个词是在排查 Docker 容器网络异常的时候当时只记得每个容器都有自己的网络栈但对它到底隔离了啥、怎么隔离、怎么让两个 namespace 通信完全是一头雾水。后来自己动手用ip netns折腾了一下午把这层窗户纸捅破之后再回头看 Docker、Kubernetes 的网络模型基本就不需要死记硬背了。这篇文章我不打算讲太虚的原理而是用一个你能直接在机器上敲的命令流程把 Network Namespace 从概念名词变成看得见摸得着的东西。适合刚接触容器网络的开发者、运维同学也适合那些被面试官问过容器网络是怎么隔离的但没答利索的人。读完之后别人再问你 Network Namespace 是啥你不需要背定义直接讲一遍你做过的那几个实验就行。1. namespace 在 Linux 里到底是个什么角色1.1 Linux namespace 家族不止一个先说个容易混淆的点Network Namespace 只是 Linux namespace 家族里的一个成员。这个家族里还有 Mount namespace、PID namespace、UTS namespace、IPC namespace、User namespace、Cgroup namespace 等好几类。它们做的事情是同一件事隔离。只不过隔离的对象不同。Mount namespace 隔离文件系统的挂载点容器里看到的/和宿主机不一样改挂载也不影响宿主机。PID namespace 隔离进程号容器里看到的 PID 1 在宿主机上可能是个两位数的大号进程。UTS namespace 隔离主机名和域名。IPC namespace 隔离进程间通信的资源比如 System V 信号量、消息队列。User namespace 隔离用户 ID 和组 ID。Network namespace 隔离网络相关的资源。把这几个组合起来再配合 cgroup 做资源限制就构成了容器技术的底座。Docker 创建容器时实际上就是一次性新建了多个 namespace然后把进程扔进去。所以你想理解 Docker 网络绕不开 Network Namespace 这个核心。1.2 Network Namespace 具体隔离了什么Network Namespace 隔离的关键词是网络栈。它给里面住着的进程一套完全独立的网络世界包括下面这些东西网络接口网卡每个 namespace 自己有一份网卡列表你在一个 namespace 里看到的网卡在别的 namespace 里看不到。IP 地址、路由表每个 namespace 有独立的 IP 分配和路由规则A 空间里配的默认路由不会污染 B 空间。iptables 规则过滤规则、NAT 规则都是隔离的容器里的防火墙规则不会影响宿主机。网络栈相关的内核参数比如net.ipv4.ip_forward这类每个 namespace 可以有自己的值。socket 连接表、连接跟踪表conntrack不同 namespace 之间互不干扰。做个生活化的类比每个 Network Namespace 就像一套独立的公寓有自己的门牌号IP、自己的小区路牌路由表、自己的物业规定防火墙规则。公寓之间默认是断开的你想让 A 公寓和 B 公寓的人互相串门必须专门拉一根网线veth pair或者装一个交换机bridge接上。这个默认完全隔离是最关键的点。如果你理解了这一点后面所有操作本质上都在做一件事打破隔离建立通路。2. 动手玩创建你的第一个 Network Namespace2.1 核心命令就三条Linux 里操作 Network Namespace 最常用的工具是ip netns它来自iproute2这个包绝大多数发行版都默认装了。如果没有装一下即可。创建、查看、删除的命令如下# 创建名为 ns1 的 network namespace ip netns add ns1 # 查看当前有哪些 network namespace ip netns list # 进入 ns1 里执行命令 ip netns exec ns1 bash # 删除 ns1 ip netns delete ns1这里有个细节值得留意ip netns add其实是在/var/run/netns/目录下创建一个挂载点把新的 Network Namespace 绑定在里面。你可以用ls /var/run/netns/看一下每个 namespace 对应一个文件。这个机制的意义在于内核里 namespace 本身只靠一个 inode 标识没有任何名字ip netns用文件系统给它起了个名字方便用户态管理。2.2 新建的 namespace 里有什么创建完 ns1 之后进入里面看一眼它的网络世界ip netns exec ns1 ip addr你会看到输出非常干净1: lo: LOOPBACK mtu 65536 qdisc noop state DOWN group default qlen 1000 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00也就是说这时候 ns1 里只有一个回环接口 lo而且状态是 DOWN连 IP 都没有。为什么 lo 是 DOWN这是新手最容易踩的第一个坑。因为 namespace 是全新的内核不会自动帮你把 lo 起来你需要手动操作ip netns exec ns1 ip link set lo up这时候再执行ip addr你会看到 lo 的状态变成了 UNKNOWN而且它有了 IPv6 的::1地址。至于 IPv4 的127.0.0.1默认也会出现在 lo 上可以理解为内核给回环接口分配的本地地址。到这里你就有了一个完全孤立的网络小空间。它没有对外网卡、没有路由表条目、没有任何 iptables 规则就像一个刚装修完、还没通网的新房子。3. 打通 namespaceveth pair 和 bridge 实战3.1 veth pair 是根虚拟网线要让两个 Network Namespace 互通最常见的方式是用 veth pair。veth 的全称是 Virtual Ethernet它天生就是一对一端叫 veth0另一端叫 veth1数据从一端进去就会从另一端出来效果跟一根网线连了两台机器一样。我习惯叫它虚拟网线。为什么不直接用一张网卡因为物理网卡是硬件资源不能同时属于两个 namespace。veth 是纯软件设备你可以在内核里无限创建然后把这对虚拟网卡的两端分别塞进不同的 namespace网络就通了。创建 veth pair 的命令ip link add veth0 type veth peer name veth1这条命令会在当前所在的 namespace通常是默认的宿主 namespace里创建一对虚拟网卡。你可以用ip link show看到它们两个接口的状态默认都是 DOWN。3.2 让两个 namespace 直接互通现在做一个小实验创建一个 ns2然后用 veth pair 把 ns1 和 ns2 连起来。# 创建第二个 namespace ip netns add ns2 # 创建 veth pair ip link add veth0 type veth peer name veth1 # 把 veth0 塞进 ns1把 veth1 塞进 ns2 ip link set veth0 netns ns1 ip link set veth1 netns ns2 # 在 ns1 里配置 veth0 ip netns exec ns1 ip addr add 10.0.0.1/24 dev veth0 ip netns exec ns1 ip link set veth0 up # 在 ns2 里配置 veth1 ip netns exec ns2 ip addr add 10.0.0.2/24 dev veth1 ip netns exec ns2 ip link set veth1 up操作完之后测试连通性ip netns exec ns1 ping 10.0.0.2正常情况下ping 会通。你可能会好奇为什么我用10.0.0.1/24和10.0.0.2/24而不是其他网段因为ip addr add后面带/24表示子网掩码两端要在同一个网段内路由才会认为目标地址在直连网段里直接通过 veth0 发出去。如果不带掩码内核不知道目标在哪ping 就会判定为不可达。这一步是整个 Network Namespace 理解的关键节点两个完全隔离的房间用一根虚拟网线连起来就能互相通信了。Docker 里单机容器互访的底层逻辑本质就是这套东西只不过它把网线换成了 bridge veth 的组合。3.3 多个 namespace 互通引入 bridge如果只有两个 namespaceveth pair 就够用了。但真实场景里往往是十几个容器这时候就要用 Linux bridge。你可以把 bridge 理解成一台虚拟交换机所有 namespace 的网线都插到这台交换机上它们就在同一个二层网络里了。创建 bridge 并接入多个 namespace# 创建名叫 br0 的 bridge ip link add br0 type bridge ip link set br0 up # 创建第一对 veth一端在 ns1一端插到 br0 ip link add veth1 type veth peer name veth1-br ip link set veth1 netns ns1 ip link set veth1-br master br0 ip link set veth1-br up # 创建第二对 veth一端在 ns2一端插到 br0 ip link add veth2 type veth peer name veth2-br ip link set veth2 netns ns2 ip link set veth2-br master br0 ip link set veth2-br up # 给 namespace 里的接口配 IP ip netns exec ns1 ip addr add 10.0.1.1/24 dev veth1 ip netns exec ns1 ip link set veth1 up ip netns exec ns2 ip addr add 10.0.1.2/24 dev veth2 ip netns exec ns2 ip link set veth2 up这里的关键命令是ip link set veth1-br master br0它的意思是把 veth1-br 这个接口加入 br0 这个 master 设备等价于把网线插进交换机。配好之后ns1 和 ns2 就能通过 br0 互相 ping 通而不需要像之前那样一根线直连。有个特别容易踩的坑给 bridge 本身也要配 IP 并设置 UP否则你从宿主机这边没法访问 namespace 内的地址。Docker 就是这么做的docker0网桥拥有一个网段地址比如172.17.0.1容器里的 IP 都在这个网段里宿主机通过 docker0 访问容器。如果你想复刻这个效果ip addr add 10.0.1.254/24 dev br0 ip link set br0 up这时在宿主机上直接ping 10.0.1.1就能通到 ns1 里的接口。这一步做完你已经能理解 Docker 默认 bridge 网络模式的大部分原理了。4. 让 namespace 访问外网路由与 NAT4.1 从 namespace 访问宿主机前面建立的网络都在同一个二层网段里那如果要访问宿主机自己的 IP或者访问外网该怎么办先看最简单的场景从 ns1 里访问宿主机 IP。假设宿主机有一块物理网卡 ens33IP 是192.168.1.100。你从 ns1 里ping 192.168.1.100会发现不通原因很简单ns1 的路由表中没有通往192.168.1.0/24网段的路由包发出去之后不知道往哪里走。解决办法是给 ns1 加一条默认路由指向 bridge 或 veth 对端的地址。以刚才的拓扑为例ns1 的网关就是 br0 的地址10.0.1.254ip netns exec ns1 ip route add default via 10.0.1.254加完之后ns1 里访问192.168.1.100时包会先发给10.0.1.254也就是到达宿主机的 br0再由宿主机的内核网络栈决定怎么转发。如果宿主机和目标地址在同一网段直接就通了。4.2 从 namespace 访问外部网络如果要从 ns1 访问公网光有默认路由还不够还有两个关键动作开启内核转发和配置 NAT。先确认宿主机开启了 IP 转发sysctl net.ipv4.ip_forward如果输出是0需要临时开启echo 1 /proc/sys/net/ipv4/ip_forward这个参数的含义说白了就是这台 Linux 要不要像路由器一样把从一个网卡收到的包转到另一个网卡。默认是关闭的这是出于安全考虑毕竟不是每台机器都想当路由器。然后配置 SNAT 或 MASQUERADE。原因是 ns1 里的10.0.1.1是私有地址路由器不会把这样的包转发到公网。你需要让宿主机把包的源地址改成自己物理网卡的地址再发出去这样外部服务器回包时才能回到宿主机。最简单的命令iptables -t nat -A POSTROUTING -s 10.0.1.0/24 -o ens33 -j MASQUERADE这条规则的意思是从10.0.1.0/24网段来、从 ens33 出去的包都做一次源地址伪装。MASQUERADE 和 SNAT 的区别是MASQUERADE 会自动读取出口网卡的当前 IP适合 IP 不固定的场景比如 PPPoE 拨号SNAT 需要你明确指定--to-source 192.168.1.100性能略好一点点。家用路由器里的上网共享功能底层就是这套逻辑。配完之后在 ns1 里执行ip netns exec ns1 ping 8.8.8.8不出意外就能通了。如果这一步通了你就完成了一个迷你版容器访问外网的全链路Docker 的 bridge 模式 NAT 也就是这个套路。5. 实战中踩到的坑与排查技巧5.1 常见问题速查表我见过太多人卡在命令都执行了就是不连通这种状态这里把最常见的几类问题整理成一张表方便你排查现象可能原因处理方式namespace 里 ping 不通对端接口没 UPip link set把两端都设置 UP加了 IP 还是不通两端不在同一网段检查掩码确保同网段ping 外网不通但 ping 网关通没开 ip_forwardsysctl net.ipv4.ip_forward1ping 外网仍然不通没做 NAT检查 POSTROUTING 链的 MASQUERADE 规则宿主机访问不到 namespacebr0 没配 IP 或没 UP给 br0 配一个同网段 IP重启后 namespace 和配置全没了ip netns是临时配置用 systemd 或脚本开机自动重建Docker 启动容器报网络冲突自定义网段和 docker0 重叠使用不同网段或修改 docker0 的网段其中接口没 UP是最常见的。veth 接口创建出来默认是 DOWN两边都得手动 UP漏一个都不通。我早期调试的时候每次都是ip addr看一遍所有接口状态养成了习惯之后踩坑率直线下降。5.2 排查命令清单排查 Network Namespace 问题时这几条命令基本够用# 查看所有 namespace 是否创建成功 ip netns list # 查看某个 namespace 里的所有接口和地址 ip netns exec ns1 ip addr # 查看某个 namespace 里的路由表 ip netns exec ns1 ip route # 查看 veth 对端的配对关系后面的数字是 peer index ip -d link show veth1 # 查看 bridge 里挂了多少接口 ip link show master br0 # 查看 NAT 规则状态 iptables -t nat -L -n -v调试时我习惯按顺序检查接口状态 - 地址配置 - 路由 - 转发开关 - NAT 规则。绝大多数问题都能在这个顺序里定位到。你如果问怎么确认 veth pair 的对应关系重点看ip -d link show veth1输出里的veth段它会显示对端接口的 index 编号只要在宿主机上找到相同 index 的接口就是同一根网线的另一端。5.3 生产环境里的几个小经验最后分享几个我在真实场景里攒下的经验。第一不要把iptables规则全写到默认链里。namespace 里和宿主机上的 iptables 规则是分开的你操作的时候一定要确认当前在哪个 namespace。用iptables -L看到的是当前命名空间的规则容易搞混。建议把规则写进脚本或配置管理工具方便复现。第二ip netns创建的 namespace 是临时的机器重启就没了。如果你需要持久化配置可以用 systemd 的systemd-networkd或者写一个开机执行的初始化脚本。生产环境里我更推荐用容器 runtime 管理这些 namespace让工具帮你兜底而不是手动维护。第三调试时别急着怀疑内核。我遇到过一次 namespace 内 ping 外网一直失败查了半天最后发现是那个 veth 接口的 MTU 和物理网卡不一致导致分片问题。改成同样的 MTU 就通了。遇到网络不通先把两端 MTU、接口状态、路由这三样确认一遍再往下深挖。我个人实际测试下来的体验是Network Namespace 这个概念光看文档十遍都不如自己从头到尾搭一遍通透。你花半小时把 veth、bridge、NAT 这条链路走通之后再回头看 Docker 的--network参数、Kubernetes 的 CNI 插件脑子里会自然浮现出哦原来底层就是这套东西的感觉。下次有人问你 Network Namespace 是啥你就把他拉到终端前敲一遍ip netns add比任何口头解释都管用。