
网络命名空间是什么?网络虚拟化浅析
网络命名空间到底是什么?
命名空间是什么?
在 Linux 系统中,Namespace 是一种机制,用来隔离内核的资源。而 Network Namespace 允许防火墙、网卡、路由表、邻居表、协议栈等相互隔离在不同的命名空间中,从而实现网络资源的隔离。
不管是虚拟机还是容器,当运行在独立的命名空间时,就像是一台单独的物理主机。由于每个容器都有自己的网络服务, 在 Network namespace 的作用下,这就使得一个主机内运行两个同时监听 80 端口的 Nginx 服务成为可能(当然,外部访问还需宿主机 NAT,如VMware 的 NAT)
Linux 系统其实提供了 8 种资源的隔离:Cgroup、IPC、Network、Mount、PID、User、UTS、Time
K8s 创建的命名空间
在部署了 K8s 的 Node 节点上(宿主机),我们可以在 /var/run/netns
目录下看到一系列的网络命名空间
cjj@cjj-workspace ~> ll /var/run/netns
total 0
-r--r--r-- 1 root root 0 Aug 1 17:34 cni-00942302-xxxx-xxxx-xxxx-f08a053fd4db
-r--r--r-- 1 root root 0 Aug 1 17:38 cni-01bdd4ae-xxxx-xxxx-xxxx-69f34e7f1b0a
-r--r--r-- 1 root root 0 Aug 1 17:34 cni-0276affe-xxxx-xxxx-xxxx-13034821084b
但 /var/run/netns
目录下的这一系列网络命名空间,并不是真正的 Network Namespace(后续简称 netns)的存储位置,Namespace 实际上是在内核中。Linux 内核为方便管理,将 netns 挂载到了这个目录下而已。Linux 内核为每一个 netns 维护了一组隔离的网络资源,这些资源在内核的数据结构中存储
也就是说,netns 是一种内核级别的抽象,而不是一个用户空间中的文件或目录。网络命名空间所管理的资源是不能通过文件系统来访问到的
当我们使用 ip netns add my-ns-01
时,会创建一个关于网络命名空间的数据结构来存储这个命名空间的相关信息,为了方便用户管理,Linux 还会在 /var/run/netns
目录下创建一个符号链接,这个链接指向了内核中的此 Namespace,意思是这个目录下的符号链接只是一个便于用户访问的入口而已
Network Namespace 实践
我们可以通过 Linux 的 ip 工具的子命令 netns 来对 Network Namespace 进行增删改查
创建新的 netns。当我们用 ip
命令创建 netns 时,会在 /var/run/netns
生成一个挂载点
sudo ip netns add aaa-netns-01
ll /var/run/netns | head -n 5
# output:
cjj@cjj-workspace ~> ll /var/run/netns | head -n 5
total 0home/cjj
-r--r--r-- 1 root root 0 Aug 13 01:10 aaa-netns-01
-r--r--r-- 1 root root 0 Aug 1 17:34 cni-00942302-xxxx-xxxx-xxxx-f08a053fd4db
-r--r--r-- 1 root root 0 Aug 1 17:38 cni-01bdd4ae-xxxx-xxxx-xxxx-69f34e7f1b0a
-r--r--r-- 1 root root 0 Aug 1 17:34 cni-0276affe-xxxx-xxxx-xxxx-13034821084b
查询该命名空间基本信息,由于没有进行任何配置,该命名空间下就只有一块状态为 DOWN 的本地回环设备 lo
sudo ip netns exec aaa-netns-01 ip a
# output:
cjj@cjj-workspace ~> sudo ip netns exec aaa-netns-01 ip a
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
继续查看该命名空间下的 iptables 规则配置,由于是一个初始化的命名空间,所以也没有任何规则在里面
- -L 表示查看当前表的所有规则,默认查看的是 filter 表,如果要查看 nat 表,可以加上 -t nat 参数
- -n 表示不对 IP 地址进行反查,加上这个参数显示速度将会加快
- -v 表示输出详细信息,包含通过该规则的数据包数量、总字节数以及相应的网络接口
sudo ip netns exec aaa-netns-01 iptables -L -n
# output:
cjj@cjj-workspace ~> sudo ip netns exec aaa-netns-01 iptables -L -n
Chain INPUT (policy ACCEPT)
target prot opt source destination
Chain FORWARD (policy ACCEPT)
target prot opt source destination
Chain OUTPUT (policy ACCEPT)
target prot opt source destination
总结:新建的 Namespace 其实就相当于是一台裸机(因为与宿主机的 Namespace 隔离了),所以 Namespace 和 Namespace 之间并不能直接相互通信。如果想让他们通信,就得在 “裸机” 上添加虚拟网卡(veth)、并连接到交换机(bridge),如同我们两台物理机配置一个局域网一样,做好了这些配置,才能相互通信
验证 inode 是否相同
我们看看 /proc/<pid>/ns
下的 net 命名空间,与 /var/run/netns
下的 netns 的 inode 是否一致?因为 inode 是内核的一个标识,一样则是同一个入口
先用 lsns
命令拿出该命名空间的 PID
lsns
# output:
cjj@cjj-workspace ~> lsns
NS TYPE NPROCS PID USER COMMAND
4026531835 cgroup 50 598 cjj /lib/systemd/systemd --user
4026531837 user 50 598 cjj /lib/systemd/systemd --user
......
4026544674 net 15 1266123 cjj bash
4026545229 uts 15 1266123 cjj bash
根据 PID 查出 netns:ip netns identify <pid>
ip netns identify 1266123
# output:
cjj@cjj-workspace ~> ip netns identify 1266123
cni-2180b3ea-73e3-5960-40a5-0e5f792f7530
对比 inode 值
ll /proc/1266123/ns
# output:
cjj@cjj-workspace ~> ll /proc/1266123/ns
total 0
lrwxrwxrwx 1 cjj cjj 0 Aug 12 01:13 cgroup -> 'cgroup:[4026531835]'
lrwxrwxrwx 1 cjj cjj 0 Aug 12 01:13 ipc -> 'ipc:[4026545230]'
lrwxrwxrwx 1 cjj cjj 0 Aug 12 01:13 mnt -> 'mnt:[4026545234]'
lrwxrwxrwx 1 cjj cjj 0 Aug 12 01:12 net -> 'net:[4026544674]'
lrwxrwxrwx 1 cjj cjj 0 Aug 11 19:00 pid -> 'pid:[4026545235]'
lrwxrwxrwx 1 cjj cjj 0 Aug 13 01:35 pid_for_children -> 'pid:[4026545235]'
lrwxrwxrwx 1 cjj cjj 0 Aug 12 01:13 user -> 'user:[4026531837]'
lrwxrwxrwx 1 cjj cjj 0 Aug 12 01:13 uts -> 'uts:[4026545229]'
ll -i /var/run/netns | grep cni-2180b3ea-73e3-5960-40a5-0e5f792f7530
# output:
cjj@cjj-workspace ~> ll -i /var/run/netns | grep cni-2180b3ea-73e3-5960-40a5-0e5f792f7530
4026544674 -r--r--r-- 1 root root 0 Aug 2 14:04 cni-2180b3ea-73e3-5960-40a5-0e5f792f7530
发现 inode 都是 4026544674
这个值,证明他们是同一个命名空间,同一个符号引用标识
Netns 隔离机制内核实现
前文提及:Linux 内核为每一个 netns 维护了一组隔离的网络资源,这些资源在内核的数据结构中存储
那在这里我们可以进一步看看,是通过哪些数据结构来实现,这些在内核的数据结构到底是什么,比如用什么结构体来表示
net
include/net/net_namespace.h
网络系统在初始化的时候,会初始化一个初始网络命名空间,即 init_net
命名空间(也是一个 net 结构体)。后续创建的 netns 都会和 init_net
像链表一样串起来。并且每一个网络设备都对应一个命名空间,同一个命名空间下的网络设备通过 dev_base_head
串起来
net 结构体包含了各种网络资源的指针,比如网络设备、路由表、套接字、IP 地址、Netfilter 表(iptables)、连接跟踪表等
net_device
这是 Linux 内核中用于表示网络接口的结构体。每个网络接口都由一个 net_device
实例表示。
当你在一个命名空间中创建或配置网络接口时,内核会将这个接口与该命名空间绑定。操作这个接口的任何网络操作(如发送、接收数据包)都会局限在这个命名空间内部,其他命名空间无法访问或影响这个接口
net
与 net_device
的关联关系:net
中包含有 net_device
结构体的链表字段,而通过 net_device
找到 net
可以通过以下方式
可通过此字段来查找网络设备对应的命名空间
@nd_net: Network namespace this network device is inside
struct net *dev_net(const struct net_device *dev)
{
return read_pnet(&dev->nd_net);
}
网络设备通常是在驱动初始化时或显式创建时被注册到命名空间的。这个注册过程会将 net_device 添加到当前命名空间的设备链表中
int register_netdevice(struct net_device *dev)
{
......
}
路由表隔离
路由表相关的数据通常通过 struct netns_ipv4
和 struct netns_ipv6
结构体进行管理,这些结构体包含了与 IPv4 和 IPv6 相关的路由信息,这样每个网络命名空间就可以有自己的 IPv4 和 IPv6 路由表
套接字(Sockets)隔离
每个 netns 有自己独立的套接字空间。
sock 结构体:Linux 内核使用 sock 结构体来表示一个网络连接的套接字。这个结构体中包含一个指向 net 结构体的指针,指向该套接字所属的 netns
隔离机制:当一个进程创建套接字时,内核会根据进程当前所在的 netns,初始化套接字的命名空间指针。这意味着该套接字只能在该命名空间内通信,无法跨命名空间传输数据。
Netfilter 和防火墙规则隔离
Netfilter 是 Linux 内核中用于处理网络数据包的框架,iptables 是它在用户空间工具
nf_conntrack
:这是内核用于连接跟踪的结构体,每个 netns 都有独立的连接跟踪表,用于跟踪在该命名空间内建立的连接
隔离机制:每个 netns 有自己独立的 Netfilter 表和规则集。这些规则集也存储在对应的 net 结构体中,确保不同命名空间中的防火墙规则彼此隔离
nf_conntrack
是 Netfilter 框架的一部分,用于连接跟踪(Connection Tracking),即跟踪通过网络的数据流状态
nf_conntrack
也不是直接存储在 struct net
中。连接跟踪表通常由 struct netns_ct
来管理,netns_ct
是一个包含连接跟踪相关数据的结构体
struct netns_ct
也是 struct net
的成员,通过它,网络命名空间可以拥有独立的连接跟踪表,确保每个命名空间的连接状态是独立的,不会互相干扰
系统调用和命名空间
系统调用是用户空间和内核空间交互的接口。当涉及网络资源的系统调用(如 socket()
、bind()
、connect()
等)被调用时,内核会根据进程当前的 netns 上下文来决定操作的目标资源。
task_struct
结构体:每个进程在内核中都对应一个 task_struct
结构体。这个结构体中包含一个指向 nsproxy
结构体的指针,nsproxy
中包含了该进程所属的各类命名空间(包括 netns)
上下文切换:当进程执行网络操作时,内核会通过 task_struct
--> nsproxy
--> net
的路径找到进程所属的网络命名空间,并在该命名空间的上下文中执行网络操作
