Network Namespace 到底是个啥,别人再问你告诉他

36 阅读7分钟

在 Linux 系统中,每个进程的命名空间信息都暴露在 /proc/<PID>/ns/ 目录下

核心底层技术是 Linux 的 Network Namespace(网络命名空间) 和 Pause 容器。

在 Kubernetes 中,一个 Pod 并不是一个单一的实体,而是由一组共享资源的容器组成的。具体实现过程如下:

  1. Pause 容器(基础设施容器):当你创建一个 Pod 时,K8s 会首先启动一个极其轻量的 pause 容器。这个容器的唯一作用就是占用并维持一个 Network Namespace。
  2. 共享网络栈:Pod 内的其他业务容器(比如 Nginx、Java 应用等)在启动时,会被加入到这个 pause 容器创建的同一个 Network Namespace 中。
  3. 效果:因为它们都在同一个网络命名空间里,所以它们看到的网卡(如 eth0)、IP 地址、路由表、端口号都是完全一样的。这就好比几台电脑插在了同一个交换机上,甚至更亲密——它们共用同一个虚拟网卡。

一个 namespace 下可以有多个虚拟网卡吗?

完全可以。

一个 net namespace 就像一台独立的“虚拟主机”,拥有自己独立的:

  1. 网卡列表(lo、eth0、br0、veth 等)
  2. 路由表
  3. iptables 规则
  4. 端口空间

在 Linux 操作系统中,网络栈(Network Stack)指的是操作系统内核处理网络数据的一整套架构。它不仅包含 IP 地址,还包含了以下整套家当:

  1. 网络接口(网卡/Network Interfaces): 比如宿主机的物理网卡 eth0,或者虚拟网卡。
  2. 路由表(Routing Table): 决定数据包从哪个网卡发出去,下一步去哪里。
  3. 防火墙规则(iptables/netfilter): 决定哪些数据包可以放行,哪些要丢弃或转发。
  4. 协议实现: TCP/UDP 等协议的底层实现。
  5. 端口(Ports): 0 到 65535 的端口资源,用来区分不同的应用程序。

创建一个独立的网络命名空间 Linux Namespace(网络命名空间)

这是容器技术的基石。Linux 允许你把网络资源隔离开来。每个 Namespace 都有自己独立的网卡、路由表 ,也就是一个命名空间有自己的网络栈。

# 创建一个名为 pod_ns 的网络命名空间
sudo ip netns add pod_ns 
# 查看是否创建成功 
ip netns list

可以进入这个命名空间,查看网络

ip netns exec pod_ns ip addr

意思是“请让我钻进 pod_ns 这个独立空间里,去执行后面的命令”,

你会发现屏幕上极其干净!之前的物理网卡、宿主机 IP 统统消失了。里面只有孤零零的一张 lo(本地环回虚拟网卡),而且状态还是 DOWN(关闭的)。

这就是“网络栈隔离”的铁证! 这个小房间现在是一片绝对的白纸,它不知道外面有 eth0,也没有任何 IP 地址。

image.png

ip netns exec pod_ns ip link set lo up

进入到pod_ns 空间,启动lo网卡

ip netns del pod_ns

删除pod_ns 网络命名空间

sudo ip link add br1 type bridge

内核创建了一个名为 br1 的虚拟网卡。但此时它并没有启动,也没有分配 IP,里面也没有插任何网线

ip link show br1

image.png

目前还是处于down的状态,还没有通电,还没有ip,暂时不能通信

# 1. 先删掉 br1 上的旧 IP ,可选 如果配置错了,然后重新添加
sudo ip addr del 192.168.100.1/24 dev br1 
# 2. 给 br1 添加新 IP(举例改成 192.168.200.1) 
sudo ip addr add 192.168.200.1/24 dev br1

查询目前状态

ip addr show br1

image.png

接下来启动这个br1

sudo ip link set br1 up

image.png

已经启动了 目前是21号设备 如上图编号。

现在网桥创建好了,接下来就是把虚拟网线(veth pair)插上去

创建veth pair

sudo ip link add veth_host type veth peer name veth_ns

如果已经存在,同一个名称会报错,

具体效果与原因: 系统会返回类似 RTNETLINK answers: File exists 的错误提示5。这是因为 veth_host 这个网络接口名称在你的系统中已经存在。在 Linux 网络命名空间中,网络接口(网卡)的名称必须是唯一的,系统不允许创建同名接口

我们可以先删除,在重建

sudo ip link delete veth_host 

p link show veth_host  确保不存在
[root@test64 _data]# ip link show veth_host
Device "veth_host" does not exist.

sudo ip link add veth_host type veth peer name veth_ns

执行 ip addr 会有一对吗?

22: veth_ns@veth_host: <BROADCAST,MULTICAST,M-DOWN> mtu 1500 qdisc noop state DOWN group default qlen 1000
    link/ether ca:0e:10:13:fe:37 brd ff:ff:ff:ff:ff:ff
23: veth_host@veth_ns: <BROADCAST,MULTICAST,M-DOWN> mtu 1500 qdisc noop state DOWN group default qlen 1000
    link/ether 5e:55:f9:5f:ab:10 brd ff:ff:ff:ff:ff:ff

@ 符号的关联:

veth_ns@veth_host 表示 veth_ns 的配对端是 veth_host。

veth_host@veth_ns 表示 veth_host 的配对端是 veth_ns。

这种相互指向正是 veth(Virtual Ethernet)设备对的典型特征,它们就像一根虚拟网线的两端

把网线一头插进命名空间,一头插上网桥

把 veth_ns 塞进 pod_ns 命名空间: veth_ns 移到一个网络命名空间里

sudo ip link set veth_ns netns pod_ns

执行完毕后 通过ip addr观察,成对的消失了:变成了 veth_host@if22

23: veth_host@if22: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN group default qlen 1000
    link/ether 5e:55:f9:5f:ab:10 brd ff:ff:ff:ff:ff:ff link-netnsid 0
  • if22:这里的 22 是 veth_ns 在目标命名空间里的接口索引号(Index)。因为 veth_host 还在宿主机,为了让你知道它的配对端在哪,内核加上了这个后缀。

  • link-netnsid 0:这表示配对端所在的命名空间在当前视角下的 ID。

插一段:

ip netns exec pod_ns bash   

这个命令可以进入对应的网络空间执行bash命令。

接下来 把 veth_host 这头插到我们的网桥 br1 上,将 veth_host 这个虚拟网卡接入(挂载)到名为 br1的虚拟网桥(Bridge)上

sudo ip link set veth_host master br1

如何解除绑定 如果你想把 veth_host 从网桥上拔下来,可以使用 nomaster 参数:

sudo ip link set veth_host nomaster

进入到 pod_ns网络空间,给veth_ns 分配 IP,注意要和网桥是同一个网段

# 给 veth_ns 分配 IP
sudo ip netns exec pod_ns ip addr add 192.168.200.2/24 dev veth_ns
sudo ip netns exec pod_ns ip link set veth_ns up

可以发现 ip netns exec pod_ns ip addr,veth_ns网卡状态已经是up

[root@test64 _data]# ip netns exec pod_ns ip addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default 
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host 
       valid_lft forever preferred_lft forever
22: veth_ns@if23: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
    link/ether ca:0e:10:13:fe:37 brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet 192.168.200.2/24 scope global veth_ns
       valid_lft forever preferred_lft forever
    inet6 fe80::c80e:10ff:fe13:fe37/64 scope link 
       valid_lft forever preferred_lft forever

路由表

当给网桥分配IP的时候,内核会自动内核自动生成直连路由

[root@test64 _data]# route -n
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
0.0.0.0         10.10.30.254    0.0.0.0         UG    100    0        0 eth0
10.10.30.0      0.0.0.0         255.255.255.0   U     100    0        0 eth0
172.17.0.0      0.0.0.0         255.255.0.0     U     0      0        0 docker0
192.168.100.0   0.0.0.0         255.255.255.0   U     0      0        0 br0
192.168.122.0   0.0.0.0         255.255.255.0   U     0      0        0 virbr0
192.168.200.0   0.0.0.0         255.255.255.0   U     0      0        0 br1

在 route -n 的输出中,0.0.0.0 代表“无网关”。这说明 192.168.200.0/24 这个网段是直接连接在 br1 上的。内核不需要把数据包交给任何外部路由器,直接通过 br1 这个接口就能送达

验证

宿主机网桥 (192.168.200.1) <--- veth pair ---> 命名空间容器 (192.168.200.2)

1.从宿主机 ping 容器:

ping 192.168.200.2

image.png

2.从容器 ping 宿主机网关:

sudo ip netns exec pod_ns  ping 192.168.200.1

image.png

3.在容器里启动一个 Web 服务,宿主机访问:

sudo ip netns exec pod_ns python2 -m SimpleHTTPServer 8080


# 终端2:在宿主机访问
curl http://192.168.200.2:8080

发现能正常访问服务。至此我们的网络已经通了。