为什么要在意网络命名空间
很多人第一次听说 network namespace(网络命名空间,后面简称 netns)是在 Docker 的文档里,觉得这是容器引擎的内部实现,跟自己维护一两台 VPS 的个人站长没关系。其实恰恰相反:netns 是 Linux 内核从 2.6.24 就带进来的原生能力,它把「一套独立的网络协议栈」变成了一个可以随时创建、随时销毁的对象。你不需要装 Docker、不需要跑 K8s,只要内核支持,就能用它做出一台「虚拟路由器」「独立测试网段」或者「隔离出口 IP 的代理容器」。
我自己的使用场景很典型:站点要迁移,新老服务器之间需要长时间互传数据,但我不想让测试流量和线上流量互相污染;同时还要用 iperf3 测新线路的真实带宽。如果直接在本机加 iptables 规则,规则一多就会跟线上防火墙打架,回滚还容易漏。用 netns 就干净得多——新建一个命名空间,里面的网卡、路由表、iptables 规则全是独立的,测完删除命名空间,所有东西瞬间消失,一行都不残留在主机上。
核心概念:netns 隔离了什么
netns 隔离的对象是「网络协议栈实例」。具体来说,下面这些东西在每个 netns 里都是各自独立的一份:
- 网卡:每张网卡(包括虚拟网卡 veth、bridge、tun/tap)在任一时刻只属于一个 netns。用
ip link set dev xxx netns NAME可以在命名空间之间搬移。 - IP 地址与路由表:同一个 IP 可以同时存在于两个 netns 而互不冲突。
- iptables / nftables 规则:每个 netns 有自己独立的规则集,这正是隔离的意义所在。
- 连接跟踪表、socket 列表:
ss -tulnp在 netns 里看到的只是该命名空间的监听端口。 - 本地回环 lo:注意,新建的 netns 里 lo 默认是 down 状态,需要手动
ip link set lo up,这是新手最常踩的坑。
但它不隔离的东西也要清楚:文件系统、进程 PID、用户、主机名都还是共享的(那些是 mount / pid / user / uts ns 的职责)。所以你会在 ip netns exec 出来的 shell 里看到和主机一样的 /etc/hosts,这是正常的。
最小可用示例:手搓一条隔离通道
下面这套命令在两台机器之间(或者同一台机器的两个 netns 之间)建立一条点对点链路。假设主机上网卡是 eth0,我们用命名空间 lab 模拟一台「独立的小机器」。
# 1) 创建命名空间
ip netns add lab
# 2) 创建一对 veth 虚拟网卡,一端留在主机(veth-host),一端塞进 lab(veth-lab)
ip link add veth-host type veth peer name veth-lab
ip link set veth-lab netns lab
# 3) 两端都启用,并配置地址(/30 的网段只有两个可用地址,正好点对点)
ip addr add 10.200.0.1/30 dev veth-host
ip link set veth-host up
ip netns exec lab ip addr add 10.200.0.2/30 dev veth-lab
ip netns exec lab ip link set veth-lab up
ip netns exec lab ip link set lo up # 关键!别忘了 lo
# 4) 验证
ping -c 2 10.200.0.2
ip netns exec lab ping -c 2 10.200.0.1
ip netns exec lab ss -tulnp
到这一步,你就得到了一个完全独立的网络栈。ip netns exec lab 后面跟的命令,等价于「在这台虚拟机器里执行」。想进去交互式操作,可以用 ip netns exec lab bash,注意 shell 提示符没有任何变化,很容易忘记自己在哪个命名空间里——建议进之前 echo $PS1 记一下,或者给每个 netns 起 ip netns exec lab bash --rcfile /root/.labrc 这样带自定义提示符的 shell。
让 lab 能访问外网:把主机当路由器
默认情况下 lab 只能 ping 通 10.200.0.1,出不去。要让它借主机的出口上网,需要三步:
# 1) 在 lab 里加默认路由,指向主机那一端
ip netns exec lab ip route add default via 10.200.0.1
# 2) 主机开启 IPv4 转发(当路由器)
sysctl -w net.ipv4.ip_forward=1
# 3) 做 SNAT,把 lab 出来的流量伪装成主机的地址
iptables -t nat -A POSTROUTING -s 10.200.0.0/30 -o eth0 -j MASQUERADE
# 顺带放开 FORWARD 链(很多云主机的 FORWARD 默认是 DROP)
iptables -A FORWARD -i veth-host -o eth0 -j ACCEPT
iptables -A FORWARD -i eth0 -o veth-host -m state --state RELATED,ESTABLISHED -j ACCEPT
# 验证
ip netns exec lab ping -c 2 1.1.1.1
ip netns exec lab curl -s https://ifconfig.me
最后那条 curl 特别有用:如果返回的是主机的外网 IP,说明 SNAT 生效了。整个过程中,lab 里跑的进程用的是自己的路由表和连接跟踪表,和主机上其他服务完全隔离,不会在 ss 里串味。
实战场景一:用 netns 做带宽与线路测试
测带宽最怕的是把线上业务带的卡顿。把测试流量塞进独立 netns,就能设定明确的出口路径,还不会污染主机的路由表和 iptables 计数。
# lab 侧起 iperf3 服务端
ip netns exec lab iperf3 -s -p 5201 &
# 从另一台机器打流,测的就是 veth 这条路(本机环回,用来验证隔离是否成功)
iperf3 -c 10.200.0.2 -p 5201 -t 10
更常见的用法是反过来:主机上有多个出口 IP / 多张网卡(比如一块走 CN2、一块走普通线路),想固定某条线路跑批量下载或 rsync。可以建一个 netns,把特定网卡搬进去,然后在里面跑任务:
ip netns add line2
ip link set eth1 netns line2
ip netns exec line2 ip addr add 203.0.113.7/24 dev eth1
ip netns exec line2 ip link set eth1 up
ip netns exec line2 ip route add default via 203.0.113.1
ip netns exec line2 rsync -avP /data/ user@target:/backup/
好处是这台机器上其它进程完全不知道 eth1 去哪了,测试任务结束后 ip link set eth1 netns 1(netns 1 是主机默认命名空间)就能把网卡还回去,路由表和地址都还保留着,非常方便。
实战场景二:临时给某个进程换出口 IP,不动主机配置
有时候只是想临时用某个 IP 发一次请求做验证(比如确认某地域的 CDN 节点是否生效),又不想改主机默认路由。可以借用 netns 配合 setns 的思路——最省事的做法是在 netns 里跑一个 socks5 代理,本机按需走代理:
ip netns exec line2 ss -tlnp # 确认代理端口在 netns 里监听
# 用 socat 之类做转发,或者直接 ip netns exec line2 curl https://example.com
因为 ip netns exec 是这个进程的「一次性外衣」,进程退出后命名空间里的状态该留的留、该清的清,对主机没有任何副作用。
排查三件套:怎么知道当前在哪个 netns
运维里最常见的问题不是「怎么建」,而是「这个进程到底在哪个命名空间里」。三个命令解决:
# 1) 列出所有命名空间
ip netns list
# 2) 查看某个进程所属的 netns(inode 号是唯一标识)
ls -l /proc/<PID>/ns/net
# 输出形如 net:[4026532520],方括号里的数字就是 netns 的 inode
# 3) 反查这个 inode 对应哪个命名空间的 veth
ip -n lab link show
ip link show veth-lab # 在主机查会报 "Device does not exist",说明它在别的 ns 里
如果 ip netns list 输出为空但 /proc/*/ns/net 里能看到多个 inode,说明有命名空间是被「进程持有」但没在 /var/run/netns/ 里挂载(Docker 就是这样),这种看不到名字,只能靠 inode 对比。
五个必踩的坑与防范
- lo 没 up。新建 netns 后 lo 是 down 的,任何依赖 127.0.0.1 的程序都会失败,表现为「端口明明监听了却连不上」。记住每个 netns 都要
ip link set lo up。 - 删掉 netns 后网卡不回来。如果你把一张物理网卡搬进 netns,然后
ip netns delete,这张网卡会连同命名空间一起消失,本质上是回到主机默认 ns 但处于 down 且无地址的状态。跑ip link能找到它,重新ip link set eth1 up并配地址即可,但如果它承载着你的管理 IP,就会失联——所以永远不要把管理网卡搬进临时 netns。 - FORWARD 链默认 DROP。云主机(尤其是带安全组的)默认
iptables -P FORWARD DROP,只加 MASQUERADE 是不够的,必须显式放行 FORWARD,否则 ping 通网关但出不去。 - 命名空间里的服务监听 0.0.0.0 也访问不到。这不是 bug,是隔离生效了。要在主机访问 netns 里的服务,必须靠 veth 的对端地址,或者用
ip netns exec进去访问。 - 命名空间会随进程一起消失。只有挂载在
/var/run/netns/NAME上的命名空间才会在无进程引用时保留。ip netns add会自动做这个挂载,所以手动建的一般不会丢;但如果是通过unshare -n临时的,进程一退就没了,别指望它持久。
清理:一定要有删除脚本
测试完不清理会累积垃圾 veth 和 iptables 规则。建议写成一个可反复执行的清理脚本,先删规则再删命名空间(顺序反了规则会残留):
#!/bin/bash
NS=lab
# 先撤掉 iptables 规则(不存在就忽略)
iptables -t nat -D POSTROUTING -s 10.200.0.0/30 -o eth0 -j MASQUERADE 2>/dev/null
iptables -D FORWARD -i veth-host -o eth0 -j ACCEPT 2>/dev/null
iptables -D FORWARD -i eth0 -o veth-host -m state --state RELATED,ESTABLISHED -j ACCEPT 2>/dev/null
# 再删命名空间(对端 veth 会自动消失)
ip netns delete $NS 2>/dev/null
echo "cleaned. remaining:"
ip netns list
ip link show type veth 2>/dev/null | grep -c veth
脚本里用 -D 而不是 -F,是为了只删自己加的规则,不影响线上其它规则。2>/dev/null 让重复执行不报错,这对无人值守的 cron 清理任务很重要。
常见问题
Q:netns 和 Docker 的 network 是什么关系?
A:Docker 的每个容器默认就活在一个 netns 里,docker network 这一层是在 netns 之间加 bridge 和 veth 的编排。理解了裸 netns,再看 Docker 网络就是「富余的一层封装」;反过来,用 docker inspect 拿到容器 PID 后 nsenter -t <PID> -n ss -tulnp 直接钻进容器的网络栈,排查容器网络问题时比 exec 更直接。
Q:性能损耗大吗?
A:netns 本身只是内核里一个数据结构,几乎没有额外开销。真正有成本的是 veth 对——每包要过一次软件网络栈。在同机测环回场景下,veth 大致能跑到 20~40 Gbps,对个人站点的带宽来说完全不是瓶颈。
Q:重启后还在吗?
A:不在。netns 是运行时对象,重启即消失。要自动化持久化,得写 systemd unit 用 ExecStart 里跑创建脚本,或用 ip netns add 配合 systemd 的 NetworkNamespacePath=。
netns 的价值不在于「高级」,而在于它把「隔离」这件事变得可逆——出问题的第一反应永远是删掉命名空间重来,而不是在大堆 iptables 规则里找一个写错的 -s。对个人站长来说,这是一种成本极低但收益明显的运维工具,值得花半小时摸熟。