Linux Network Namespace

Linux Network Namespace

Linux Network Namespace
跳至正文

同一台 Linux 主机上,两个 Web 服务都想监听 0.0.0.0:8080。如果它们处于同一个网络命名空间,通常会遇到端口占用冲突;把它们分别放进两个网络命名空间后,就能各自监听这个地址和端口。

Linux 会为每个网络命名空间分别管理网络接口、IP 地址和端口占用情况。检查 8080 是否被占用时,系统只看当前服务所在的网络命名空间。一个命名空间里的 8080 被占用,不影响另一个命名空间使用自己的 8080。Namespace(命名空间)就是 Linux 实现这类隔离的机制;网络命名空间负责网络,其他类型则负责进程编号、挂载点等。

Namespace 类型:分别隔离什么

不同类型负责不同资源。以容器为例,隔离的效果可以具体到一个端口号、一个挂载点,或者一个主机名:

类型具体作用与例子
Network(网络)各自管理网络接口、IP 地址、路由和端口。例如,两个容器中的服务可以分别监听 TCP 8080 端口。
PID(进程编号)同一个进程在容器内外可以有不同的 PID。两个容器都能有自己的 PID 1,宿主机仍使用另一套编号识别这些进程。
Mount(挂载)各自维护挂载点列表。例如,两个容器的 /data 可以挂载不同的目录;挂载隔离本身不会复制底层文件。
UTS(主机名)各自设置主机名。例如,两个容器可以分别叫 web 和 db。
User(用户与组)分别映射用户和组 ID。例如,容器内的 UID 0(root)可以对应宿主机上的普通用户 ID。
IPC(进程间通信)隔离 System V IPC 和 POSIX 消息队列。例如,两个进程组可以用相同的 System V 共享内存键,访问各自的内存区域。
Cgroup(控制组视图)改变进程看到的 cgroup 路径。例如,让容器所属的控制组在容器内显示为根路径 /。
Time(时钟)分别设置单调时钟、启动时钟的偏移,例如让容器看到不同的「系统已启动多久」;不改变系统日期和时间。

其中,Cgroup Namespace 隔离的是控制组的路径视图。CPU 配额、内存上限由 cgroup 控制器管理,创建这个 Namespace 不会自动施加资源限额。

这些类型可以组合使用,也可以单独使用。本文接下来只讨论 Network Namespace:仅隔离网络资源,并不会同时隔离进程编号、主机名或文件系统挂载。

先弄清命令在哪个命名空间执行

在终端运行 ip addr 时,看到的是当前进程所在网络命名空间的接口和地址。在宿主机上直接打开的终端里,这通常是系统启动时创建的初始网络命名空间;在容器里运行同一条命令,看到的可能就是容器自己的网络。

因此,后面的每一步都要先确认:这条命令是在操作宿主机,还是某个新建的命名空间?sudo 用于提升权限,选择命名空间则要用 ip 的相应参数。

实验准备

以下命令在 Linux 中执行,需要 iproute2(提供 ip 和 bridge)以及 ping。Debian / Ubuntu 缺少这些工具时,可以用 sudo apt install iproute2 iputils-ping 安装。macOS 上需要在 Linux 虚拟机中完成实验;下面的「宿主机」指用于实验的 Linux 系统。

示例中的命名空间和接口都是新建对象,请使用尚未占用的名称。示例网段为 192.168.20.0/24 和 192.168.30.0/24;如果与现有网络重叠,需要一起替换相关地址。文末有本次实验的清理命令。

实验一:用 veth 连接两个命名空间

目标是让 ns1 和 ns2 各有一个网络接口,并通过这两个接口互相发送数据。最终连接如下,所有对象都在同一台 Linux 系统中:

网络命名空间 ns1网络命名空间 ns2veth 对veth_ns1192.168.20.1/24veth_ns2192.168.20.2/24

1. 创建两个空的网络环境

sudo ip netns add ns1sudo ip netns add ns2ip netns list

ns1、ns2 是我们起的名字。ip netns list 用于列出这类有名字的网络命名空间。

先看看 ns1 里有什么:

sudo ip -n ns1 -br addr

-n ns1 指定操作 ns1,-br 让输出简短一些。刚创建时,通常只有一个未启用的回环接口:

lo               DOWN

lo 用于本机内部通信,常见的 127.0.0.1 就通过它访问本机服务。新命名空间不会自动复制宿主机的网卡和 IP 配置,所以此时既没有通向 ns2 的接口,也没有通向外网的连接。

后面会交替使用两种命令形式:

  • sudo ip -n ns1 addr:让 ip 查询 ns1 的地址。
  • sudo ip netns exec ns1 ip addr:在 ns1 中运行后面的程序。这里运行的是 ip addr,也可以换成 ping、Shell 或 Web 服务。

这两条查询的网络相同。区别在于,ip -n 用于执行 ip 的操作,ip netns exec 可以启动其他程序。

2. 创建一对 veth,并把两端分别放进去

可以把 veth 对想成一根两端自带网卡的虚拟网线:数据从一端发出,就会到达另一端。两端在创建时就连在一起,之后可以分别放进不同的命名空间。

sudo ip link add veth_ns1 type veth peer name veth_ns2sudo ip link set veth_ns1 netns ns1sudo ip link set veth_ns2 netns ns2

第一条命令创建两个相连的接口,后两条分别移动它们。移动后,在宿主机上直接运行 ip link 就看不到这两个接口了,需要指定命名空间:

sudo ip -n ns1 -br linksudo ip -n ns2 -br link

此时能分别看到 veth_ns1 和 veth_ns2,但它们还未启用。创建接口、配置地址和启用接口,是三个不同的步骤。

3. 配置 IP 地址并启用接口

给两端分配同一网段内的地址:

sudo ip -n ns1 addr add 192.168.20.1/24 dev veth_ns1sudo ip -n ns2 addr add 192.168.20.2/24 dev veth_ns2sudo ip -n ns1 link set lo upsudo ip -n ns1 link set veth_ns1 upsudo ip -n ns2 link set lo upsudo ip -n ns2 link set veth_ns2 up

dev 后面指定接口。这里的 /24 表示地址的前 24 位属于网段,也就是前三段 192.168.20;两端因此都属于 192.168.20.0/24。这个实验中,我们让它们通过 veth 直接通信。

再检查地址:

sudo ip -n ns1 -br addrsudo ip -n ns2 -br addr

重点确认接口上的 IPv4 地址,以及 veth 接口是否为 UP。输出还可能包含自动生成的 IPv6 地址,这次实验先看 IPv4 即可。

如果查看完整的 ip link 输出,尖括号中的 UP 表示接口已被启用,LOWER_UP 表示驱动报告底层链路已就绪。veth 对要两端都启用,链路才能起来。回环接口 lo 的状态可能显示为 UNKNOWN,这本身不表示故障。

4. 先看路由,再测试能否通信

配置好地址并启用接口后,查看 ns1 的路由:

sudo ip -n ns1 route

输出应包含这样一条记录:

192.168.20.0/24 dev veth_ns1 proto kernel scope link src 192.168.20.1

它的意思是:发往 192.168.20.0/24 的数据,直接从 veth_ns1 发出,源地址使用 192.168.20.1。 这是内核根据接口地址自动添加的直连路由。

现在从 ns1 测试 ns2:

sudo ip netns exec ns1 ping -c 3 192.168.20.2

-c 3 表示发送三次探测后退出。若收到三次回复,说明请求和回复都能通过这对 veth 接口。

这里不需要配置默认网关。两端处在同一个直连网段,已有路由足够。默认路由用于没有更具体路由匹配的目标;它的下一跳应是能继续转发数据的网关,不能把本接口自己的地址填成网关。

5. IP 地址怎样对应到对端网卡

知道从哪个接口发出后,以太网还需要知道对端的 MAC 地址。在这个 IPv4 实验里,ARP 会询问「谁拥有 192.168.20.2」,对端回复自己的 MAC 地址,系统把结果记入邻居表。

在 ping 之后查看:

sudo ip -n ns1 neigh

会看到类似的记录,MAC 地址和末尾状态会随运行变化:

192.168.20.2 dev veth_ns1 lladdr 02:00:00:00:00:02 REACHABLE

这表示可以通过 veth_ns1,把发往 192.168.20.2 的以太网帧交给这个 MAC 地址。REACHABLE 表示近期确认过邻居可达;如果稍后变成 STALE,只是这条记录需要重新确认,不等于连接已经断开。

于是,一次 ping 的过程就串起来了:先查路由选择接口,再通过邻居表找到对端 MAC;没有记录时先做 ARP 查询,随后沿 veth 发送请求,对端再回复。

ns1 和 ns2 虽然能互相通信,仍各自管理网络接口、路由和端口。veth 提供了一条连接通道,并没有把两个命名空间合并。

实验二:用网桥连接多个命名空间

如果三个命名空间都要互通,逐对连接就需要多对 veth。更常见的做法是创建一个 Linux 网桥,让每个命名空间各接一根「虚拟网线」。

可以把网桥理解成软件实现的以太网交换机:它记录某个 MAC 地址位于哪个端口,再据此转发数据。Linux 网桥也支持 VLAN 等功能;理解这里的基本通信,先不需要展开这些配置。

下面创建 h1、h2、h3,把它们接到宿主机网络命名空间里的网桥 s1。p1、p2、p3 是三对 veth 留在宿主机一侧的接口名:

h1h2h3宿主机的网络命名空间veth1192.168.30.11/24veth2192.168.30.12/24veth3192.168.30.13/24p1p2p3s1 网桥

1. 创建命名空间和网桥

sudo ip netns add h1sudo ip netns add h2sudo ip netns add h3sudo ip link add s1 type bridge

type bridge 指定创建网桥,s1 是它的名字。此时网桥还没有接入任何接口。

2. 给每个命名空间接一对 veth

先连接 h1:

sudo ip link add veth1 type veth peer name p1sudo ip link set veth1 netns h1sudo ip link set p1 master s1

veth1 放进 h1;p1 留在宿主机,并通过 master s1 加入网桥。可以把最后一步理解成把网线的宿主机这一端插进交换机。

另外两组按同样的方法连接:

sudo ip link add veth2 type veth peer name p2sudo ip link set veth2 netns h2sudo ip link set p2 master s1sudo ip link add veth3 type veth peer name p3sudo ip link set veth3 netns h3sudo ip link set p3 master s1

3. 配置地址,启用连接两端和网桥

三个命名空间都使用 192.168.30.0/24 网段:

sudo ip -n h1 addr add 192.168.30.11/24 dev veth1sudo ip -n h2 addr add 192.168.30.12/24 dev veth2sudo ip -n h3 addr add 192.168.30.13/24 dev veth3sudo ip -n h1 link set lo upsudo ip -n h1 link set veth1 upsudo ip -n h2 link set lo upsudo ip -n h2 link set veth2 upsudo ip -n h3 link set lo upsudo ip -n h3 link set veth3 upsudo ip link set p1 upsudo ip link set p2 upsudo ip link set p3 upsudo ip link set s1 up

注意,这里只给三个命名空间内的 veth 接口配置了 IPv4 地址。p1、p2、p3 和 s1 都没有配置 IPv4 地址,仍然能帮助这三个命名空间通信。

原因是网桥在这里负责根据 MAC 地址转发以太网帧,不需要作为 IP 通信的起点或终点。只有当宿主机自己也要通过 s1 与这些命名空间通信,或者充当它们的网关时,才需要在 s1 上配置相应的 IP 地址。

4. 验证连接,并观察网桥学到了什么

先看网桥端口:

bridge link show master s1

应能看到 p1、p2、p3 都属于 s1。在本例的默认网桥配置下,接口启用后应进入 forwarding 状态,表示允许转发。

然后从 h1 分别访问另外两个命名空间:

sudo ip netns exec h1 ping -c 3 192.168.30.12sudo ip netns exec h1 ping -c 3 192.168.30.13

以访问 h2 为例,路径是:h1 的 veth1 → p1 → 网桥 s1 → p2 → h2 的 veth2。

第一次通信时,如果还不知道对端的 MAC 地址,h1 会发出 ARP 广播。网桥把广播转发到其他端口,h2 收到后回复。在这个过程中,网桥也会从收到的数据中学习源 MAC 地址与入口端口的对应关系。

可以查看网桥的转发表,也叫 FDB(Forwarding Database):

bridge fdb show br s1

关注其中学到的动态记录:它们说明某个 MAC 地址位于 p1、p2 还是 p3。表里也会有标记为 self、permanent 的条目,它们不全是从对端流量中学到的记录。

这里要分清两张表:命名空间的邻居表记录「这个 IP 对应哪个 MAC」,网桥的转发表记录「这个 MAC 应该从哪个端口转发」。 前者帮助发送方找到下一跳,后者帮助网桥选择出口。

为什么互相能 ping 通,仍然不能上网

目前这些接口只组成了一张本地网络,还没有连接到能转发外网流量的网关。

可以先让宿主机加入刚才的网段,并让 h1 把发往其他网段的数据交给它:

sudo ip addr add 192.168.30.254/24 dev s1sudo ip -n h1 route add default via 192.168.30.254sudo ip netns exec h1 ping -c 3 192.168.30.254

这里的网关是宿主机网桥上的 192.168.30.254,h1 自己的地址是 192.168.30.11。最后一条命令只验证 h1 能否到达网关。

真正访问外网,还需要把后面的路径接通:

  1. 宿主机能够继续转发。 它要有可用的外网路由,启用 IPv4 转发(net.ipv4.ip_forward),并允许相应流量通过防火墙的转发规则。
  2. 回复能够回来。 上游路由器需要知道如何返回 192.168.30.0/24。如果不能配置这条返回路由,常见做法是在宿主机上使用 SNAT / MASQUERADE,把源地址改成宿主机对外接口的地址。
  3. 使用域名时能解析 DNS。 网络命名空间不会自动生成专用的 DNS 配置。ip netns exec 支持通过 /etc/netns/h1/resolv.conf 为 h1 提供配置,但指定的 DNS 服务器也必须能从 h1 到达。

这些设置取决于宿主机的出口接口、上游网络和已有防火墙。前面的直连与网桥实验只建立了本地通信,不能用「已经能 ping 到网关」判断外网一定可用。

连不通时,从哪里开始查

先检查配置是否加在了正确的命名空间里,再沿着数据经过的路径检查:

要确认什么查询示例重点看什么
接口是否启用、地址是否正确sudo ip -n ns1 -br addr是否能看到目标接口,地址和 /24 是否与实验一致
数据准备从哪里发出sudo ip -n ns1 route get 192.168.20.2dev 是否为预期接口,源地址是否正确;这条命令不会实际发送探测包
是否能找到同一链路上的对端sudo ip -n ns1 neigh持续出现 INCOMPLETE 或 FAILED 时,检查对端、veth 两端状态及网桥连接
网桥端口是否允许转发bridge link show master s1两端对应的端口是否都加入网桥,并处于 forwarding 状态

网络配置正确后仍然失败,还要检查测试环境已有的防火墙规则是否允许这次通信。

清理实验

先退出通过 ip netns exec 启动的 Shell 或服务,再删除本次创建的对象:

sudo ip netns del ns1sudo ip netns del ns2sudo ip netns del h1sudo ip netns del h2sudo ip netns del h3sudo ip link del s1

没有进程等继续使用命名空间时,删除命名空间会释放其中的接口;销毁 veth 的一端,也会销毁配对的另一端。删除 s1 会一并移除后来加在它上面的 IP 地址。若还有进程留在命名空间里,单纯删除名字并不会立刻释放整个网络环境。

这两个实验对应两种连接方式:veth 把两个网络接口连起来,网桥把多条这样的连接接到一起。无论怎样连接,每个命名空间仍有自己的地址、路由和端口占用情况;要理解一次通信,就沿着接口、路由和下一跳逐段查看。

参考资料