Linux network namespace(网络命名空间)认知

不必太纠结于当下,也不必太忧虑未来,当你经历过一些事情的时候,眼前的风景已经和从前不一样了。——村上春树

写在前面


  • 整理K8s网络相关笔记
  • 博文内容涉及 Linux network namespace 认知以及彼此通信Demo,实际中的应用
  • 理解不足小伙伴帮忙指正

不必太纠结于当下,也不必太忧虑未来,当你经历过一些事情的时候,眼前的风景已经和从前不一样了。——村上春树


network namespace 是什么?

可以通过 ip-netns 帮助文档简单了解

1
2
┌──[liruilong@liruilongs.github.io]-[~]
└─$man ip-netns | cat

网络命名空间在逻辑上是网络堆栈的另一个副本,具有自己的路由、防火墙规则和网络设备

默认情况下,进程从其父进程继承其网络名称空间。 最初,所有进程共享来自 init 进程的相同默认网络命名空间。即 Linux 进程处在和主机相同的 namespace,即初始的根 namespace 里,默认享有全局系统资源。

按照约定,命名网络命名空间是位于 /var/run/netns/NAME 的可以打开的对象。 打开 /var/run/netns/NAME 产生的文件描述符引用指定的网络名称空间。 保持文件描述符打开可以使网络命名空间保持活动状态。 文件描述符可以与 setns(2) 系统调用一起使用来更改与任务关联的网络命名空间。

对于了解网络命名空间的应用程序,惯例是首先在 /etc/netns/NAME/ 中查找全局网络配置文件,然后在 /etc/ 中查找。 例如,如果您想要不同的用于隔离 VPN 的网络命名空间的 /etc/resolv.conf 版本,您可以将其命名为 /etc/netns/myvpn/resolv.conf

ip netns exec 通过创建安装命名空间并绑定安装所有每个网络命名空间,自动处理此配置、网络命名空间不感知应用程序的文件约定将文件配置到 /etc 中的传统位置。

network namespace 可以说是整个 Linux 网络虚拟化技术的基石,其作用就是隔离内核资源

Linux 内核自2.4.19 版本接纳第一个 namespace:Mount namespace(用于隔离文件系统挂载点)起,到 3.8 版本的user namespace(用于隔离用户权限),总共实现了 6 种不同类型的 namespace (Mount namespace、UTS namespace(主机名)、IPC namespace(进程通信mq)、PID namespace、network namespace和user namespace)

默认情况下 network namespace 在 Linux 内核 2.6 版本引入,作用是隔离 Linux 系统的设备,以及 IP 地址、端口、路由表、防火墙规则等网络资源。因此,每个网络 namespace 里都有自己的网络设备(如 IP 地址、路由表、端口范围、/proc/net 目录等)。

初识 network namespace

network namespace 可以通过系统调用来创建, 当前 network namespace 的增删改查功能已经集成到 Linux 的 ip 工具的 netns 子命令中。

帮助文档中的 Demo

1
2
3
4
5
ip netns add vpn
创建名为vpn的网络名称空间

ip netns exec vpn ip link set lo up
在vpn网络名称空间中激活回环接口。

创建一个名为 ns_lrlnetwork namespace

1
2
3
4
5
6
7
┌──[root@liruilongs.github.io]-[~]
└─$ip netns list
┌──[root@liruilongs.github.io]-[~]
└─$ip netns add ns_lrl
┌──[root@liruilongs.github.io]-[~]
└─$ip netns list
ns_lrl

创建了一个 network namespace 时,系统会在 /var/run/netns 路径下面生成一个挂载点

1
2
3
4
5
┌──[root@vms105.liruilongs.github.io]-[~]
└─$ls /var/run/netns/ns_lrl
/var/run/netns/ns_lrl
┌──[root@vms105.liruilongs.github.io]-[~]
└─$

挂载点的作用一方面是方便对 namespace 的管理,另一方面是使 namespace 即使没有进程运行也能继续存在。

命令空间通过下面的方式激活回环接口后

1
2
┌──[root@liruilongs.github.io]-[~]
└─$ip netns exec ns_lrl ip link set lo up

可以看到像默认 Linux namespace 一样的本地回环地址

1
2
3
4
5
6
7
8
┌──[root@liruilongs.github.io]-[~]
└─$ip netns exec ns_lrl ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
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

做简单的 ping 测试

1
2
3
4
5
6
7
8
9
10
11
12
┌──[root@vms105.liruilongs.github.io]-[~]
└─$ip netns exec ns_lrl ping 127.0.0.1 -c 3
PING 127.0.0.1 (127.0.0.1) 56(84) bytes of data.
64 bytes from 127.0.0.1: icmp_seq=1 ttl=64 time=0.044 ms
64 bytes from 127.0.0.1: icmp_seq=2 ttl=64 time=0.033 ms
64 bytes from 127.0.0.1: icmp_seq=3 ttl=64 time=0.031 ms

--- 127.0.0.1 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2024ms
rtt min/avg/max/mdev = 0.031/0.036/0.044/0.005 ms
┌──[root@vms105.liruilongs.github.io]-[~]
└─$

配置 network namespace 之间通信

虚拟以太网对(veth pair)

我们来看一个 Demo,两个网络命名空间如何通信,在这之前,先看一点理论,

veth pair是Virtual Ethernet(虚拟以太网)接口在Linux中的一种实现方式。veth接口具有以下特点:

veth是一对接口,分别位于两个不同的network namespace中。这一对接口通过内核实现软链接,可将不同namespace中的数据连接起来。数据可以直接在这一对接口间进行传输,实现了namespace间的数据通信。

创建一个网络命名空间

1
2
3
┌──[liruilong@liruilongs.github.io]-[~]
└─$ip netns add net1
mkdir /var/run/netns failed: Permission denied

需要 root 才行,切一下 root,这里需要说明

  • 非 root 进程被分配到 network namespace 后只能访问和配置已经存在于该 network namespace 的设备
  • root 进程可以在 network namespace 里创建新的网络设备
  • network namespace 里的 root 进程还能把本 network namespace 的虚拟网络设备分配到其他 network namespac
1
2
3
┌──[liruilong@liruilongs.github.io]-[~]
└─$sudo -i
[sudo] password for liruilong:

创建两个 网络命名空间

1
2
3
4
5
6
7
8
┌──[root@liruilongs.github.io]-[~]
└─$ip netns add net1
┌──[root@liruilongs.github.io]-[~]
└─$ip netns add net2
┌──[root@liruilongs.github.io]-[~]
└─$ip netns list
net2
net1

仅有一个本地回环设备是没法与外界通信的。如果我们想与外界(比如主机上的网卡)进行通信,就需要在 namespace 里再 创建一对虚拟的以太网卡,即所谓的 veth pair。顾名思义,veth pair 总是成对出现且相互连接,它就像 Linux 的双向管道(pipe),报文从 veth pair 一端进去就会由另一端收到.

1
2
3
4
5
6
7
8
9
┌──[root@liruilongs.github.io]-[~]
└─$ip link add veth1 netns net1 type veth peer name veth2 netns net2
┌──[root@liruilongs.github.io]-[~]
└─$ip link
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
2: ens160: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP mode DEFAULT group default qlen 1000
link/ether 00:0c:29:93:51:67 brd ff:ff:ff:ff:ff:ff
altname enp3s0
  • ip link add: 创建一对接口
  • veth1: 设置本地端接口名
  • netns net1: 将 veth1 移动到名为 net1 的命名空间
  • type veth: 指定接口类型为veth虚拟接口,veth是virtual ethernet的缩写,即虚拟以太网接口。
  • peer name veth2: 设置远端接口名
  • netns net2: 将veth2移动到名为 net2 的命名空间

上面的操作创建了 veth1veth2 这么一对虚拟以太网卡。在默认情况下,它们都在主机的根 network namespce 中,将其中一块虚拟网卡 veth1 通过 netns net1 命令移动到 net1 network namespace,另一块网卡通过 netns net2 命令移动到 命令移动到 net2 network namespace`

执行这个命令后,会在两个不同的命名空间net1和net2内各自创建一根接口:

  • 在 net1 命名空间内创建接口veth1
  • 在 net2 命名空间内创建接口veth2

这两根接口通过 peer 自动连接成一对,形成跨命名空间的虚拟链路

在每个命名空间执行 ip link 命令,可以看到详细的信息

1
2
3
4
5
6
┌──[root@liruilongs.github.io]-[~]
└─$ip netns exec net1 ip link
1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN mode DEFAULT group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
2: veth1@if2: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
link/ether b2:ae:39:9e:50:4b brd ff:ff:ff:ff:ff:ff link-netns net2

net1 命名空间虚拟网卡 veth1 ,与名称为 net2 的命名空间相关联

1
2
3
4
5
6
┌──[root@liruilongs.github.io]-[~]
└─$ip netns exec net2 ip link
1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN mode DEFAULT group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
2: veth2@if2: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
link/ether 7a:1b:8e:91:41:79 brd ff:ff:ff:ff:ff:ff link-netns net1

net2 命名空间虚拟网卡 veth2 ,与名称为 net1 的命名空间相关联

通过 ip netns exec net1 bash 这个命令进入指定命名空间的 shell 环境,在当前 shell 中执行的命名对当前命名空间生效

1
2
┌──[root@liruilongs.github.io]-[~]
└─$ip netns exec net1 bash

分配 IP 地址以及配置掩码,指定对应的虚拟网卡, 这里分配IP 192.168.20.1/24

1
2
┌──[root@liruilongs.github.io]-[~]
└─$ip address add 192.168.20.1/24 dev veth1

激活本地回环网卡

1
2
┌──[root@liruilongs.github.io]-[~]
└─$ip link set dev lo up

激活虚拟网卡

1
2
┌──[root@liruilongs.github.io]-[~]
└─$ip link set dev veth1 up

因为另一端 veth1 还没有打开,所以链接状态仍然显示为关闭 state DOWN

1
2
3
4
5
6
┌──[root@liruilongs.github.io]-[~]
└─$ip link
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
2: veth1@if2: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN mode DEFAULT group default qlen 1000
link/ether b2:ae:39:9e:50:4b brd ff:ff:ff:ff:ff:ff link-netns net2

查看路由信息,可以发现,命令空间路由相互独立,但是由于接口当前 down,这条路由实际不可用

1
2
3
┌──[root@liruilongs.github.io]-[~]
└─$ip route
192.168.20.0/24 dev veth1 proto kernel scope link src 192.168.20.1 linkdown

查看命名空间IP信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
┌──[root@liruilongs.github.io]-[~]
└─$ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
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
2: veth1@if2: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN group default qlen 1000
link/ether b2:ae:39:9e:50:4b brd ff:ff:ff:ff:ff:ff link-netns net2
inet 192.168.20.1/24 scope global veth1
valid_lft forever preferred_lft forever
┌──[root@liruilongs.github.io]-[~]
└─$exit
exit

退出第一个命名空间的 shell 环境,我们进入第二个命名空间的 shell 环境,做相同的配置 这里分配IP 192.168.20.2/24

1
2
3
4
5
6
7
8
┌──[root@liruilongs.github.io]-[~]
└─$ ip netns exec net2 bash
┌──[root@liruilongs.github.io]-[~]
└─$ip address add 192.168.20.2/24 dev veth2
┌──[root@liruilongs.github.io]-[~]
└─$ip link set dev veth2 up
┌──[root@liruilongs.github.io]-[~]
└─$ip link set dev lo up

这个时候,我们在看链接,状态,会发现,veth2 虚拟网卡状态为 UP 状态 state UP

1
2
3
4
5
6
┌──[root@liruilongs.github.io]-[~]
└─$ ip link
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
2: veth2@if2: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP mode DEFAULT group default qlen 1000
link/ether 7a:1b:8e:91:41:79 brd ff:ff:ff:ff:ff:ff link-netns net1

查看分配IP的虚拟网卡也为 UP 状态

1
2
3
4
5
6
7
8
9
10
11
12
13
14
┌──[root@liruilongs.github.io]-[~]
└─$ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
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
2: veth2@if2: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
link/ether 7a:1b:8e:91:41:79 brd ff:ff:ff:ff:ff:ff link-netns net1
inet 192.168.20.2/24 scope global veth2
valid_lft forever preferred_lft forever
inet6 fe80::781b:8eff:fe91:4179/64 scope link
valid_lft forever preferred_lft forever

独立的路由信息

1
2
3
┌──[root@liruilongs.github.io]-[~]
└─$ip route
192.168.20.0/24 dev veth2 proto kernel scope link src 192.168.20.2

回到 net1,net1名称空间中veth1的链接状态也显示UP (state UP)

1
2
3
4
5
6
7
8
9
┌──[root@liruilongs.github.io]-[~]
└─$ip netns exec net1 ip link
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
2: veth1@if2: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP mode DEFAULT group default qlen 1000
link/ether b2:ae:39:9e:50:4b brd ff:ff:ff:ff:ff:ff link-netns net2
┌──[root@liruilongs.github.io]-[~]
└─$exit
exit

命名空间不知道net1net2命名空间的IP配置,三者彼此隔离。

1
2
3
4
5
6
7
┌──[root@liruilongs.github.io]-[~]
└─$ip link
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
2: ens160: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP mode DEFAULT group default qlen 1000
link/ether 00:0c:29:93:51:67 brd ff:ff:ff:ff:ff:ff
altname enp3s0

路由信息也为独立的路由信息

1
2
3
4
┌──[root@liruilongs.github.io]-[~]
└─$ip route
default via 192.168.26.2 dev ens160 proto dhcp src 192.168.26.149 metric 100
192.168.26.0/24 dev ens160 proto kernel scope link src 192.168.26.149 metric 100

从根网络命名空间 ping 测试到veth1 IP失败。这是因为IP 192.168.20.1 属于独立的网络命名空间 net1。

1
2
3
4
5
6
┌──[root@liruilongs.github.io]-[~]
└─$ping 192.168.20.1 -c 1
PING 192.168.20.1 (192.168.20.1) 56(84) bytes of data.

--- 192.168.20.1 ping statistics ---
1 packets transmitted, 0 received, 100% packet loss, time 0ms

net1net2 测试网络通信,命名空间使用 ping 命令。

1
2
3
4
5
6
7
8
┌──[root@liruilongs.github.io]-[~]
└─$ip netns exec net1 ping -c 1 192.168.20.2
PING 192.168.20.2 (192.168.20.2) 56(84) bytes of data.
64 bytes from 192.168.20.2: icmp_seq=1 ttl=64 time=0.060 ms

--- 192.168.20.2 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 0.060/0.060/0.060/0.000 ms

输出确认 net1 名称空间中的 veth1 接口能够成功地与 net2 名称空间中的 veth2 接口通信。

1
2
3
4
5
6
7
8
9
10
┌──[root@liruilongs.github.io]-[~]
└─$ip netns exec net2 ping -c 1 192.168.20.1
PING 192.168.20.1 (192.168.20.1) 56(84) bytes of data.
64 bytes from 192.168.20.1: icmp_seq=1 ttl=64 time=0.027 ms

--- 192.168.20.1 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 0.027/0.027/0.027/0.000 ms
┌──[root@liruilongs.github.io]-[~]
└─$

在实际使用中,更多的是 当前的根网络命名空间和 某个网络命名空间组成 veth pair 进行通信。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# 创建一个网络命名空间
┌──[root@liruilongs.github.io]-[~]
└─$ip netns add pod_ns
# 在根网络命名空间和"pod_ns"命名空间之间创建一个veth pair,并将其中一个端口放入"pod_ns"命名空间:
┌──[root@liruilongs.github.io]-[~]
└─$ip link add veth0 type veth peer name veth1
┌──[root@liruilongs.github.io]-[~]
└─$ip link set veth1 netns pod_ns
# 在根网络命名空间中配置veth0的IP地址和其他网络参数
┌──[root@liruilongs.github.io]-[~]
└─$ip addr add 192.168.1.1/24 dev veth0
#激活虚拟网卡
┌──[root@liruilongs.github.io]-[~]
└─$ip link set veth0 up
# 查看连接信息 link-netns pod_ns
┌──[root@liruilongs.github.io]-[~]
└─$ip link
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
2: ens160: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP mode DEFAULT group default qlen 1000
link/ether 00:0c:29:93:51:67 brd ff:ff:ff:ff:ff:ff
altname enp3s0
4: veth0@if3: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state LOWERLAYERDOWN mode DEFAULT group default qlen 1000
link/ether 6e:62:83:49:71:90 brd ff:ff:ff:ff:ff:ff link-netns pod_ns
# 在"pod_ns"命名空间中配置veth1的IP地址和其他网络参数,激活网卡
┌──[root@liruilongs.github.io]-[~]
└─$ip netns exec pod_ns ip addr add 192.168.1.2/24 dev veth1
┌──[root@liruilongs.github.io]-[~]
└─$ip netns exec pod_ns ip link set veth1 up

查看链接状态

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
┌──[root@liruilongs.github.io]-[~]
└─$ip link
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
2: ens160: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP mode DEFAULT group default qlen 1000
link/ether 00:0c:29:93:51:67 brd ff:ff:ff:ff:ff:ff
altname enp3s0
4: veth0@if3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP mode DEFAULT group default qlen 1000
link/ether 6e:62:83:49:71:90 brd ff:ff:ff:ff:ff:ff link-netns pod_ns
┌──[root@liruilongs.github.io]-[~]
└─$ip netns exec pod_ns ip link
1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN mode DEFAULT group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
3: veth1@if4: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP mode DEFAULT group default qlen 1000
link/ether 7e:60:b2:6e:d8:55 brd ff:ff:ff:ff:ff:ff link-netnsid 0
┌──[root@liruilongs.github.io]-[~]
└─$ip netns exec pod_ns 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
3: veth1@if4: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
link/ether 7e:60:b2:6e:d8:55 brd ff:ff:ff:ff:ff:ff link-netnsid 0
inet 192.168.1.2/24 scope global veth1
valid_lft forever preferred_lft forever
inet6 fe80::7c60:b2ff:fe6e:d855/64 scope link
valid_lft forever preferred_lft forever

在根网络命名空间对 pod_ns 网络命名空间分配IP进行ping 测试

1
2
3
4
5
6
7
8
9
10
11
12
┌──[root@liruilongs.github.io]-[~]
└─$ping -c 3 192.168.1.2
PING 192.168.1.2 (192.168.1.2) 56(84) bytes of data.
64 bytes from 192.168.1.2: icmp_seq=1 ttl=64 time=0.380 ms
64 bytes from 192.168.1.2: icmp_seq=2 ttl=64 time=0.043 ms
64 bytes from 192.168.1.2: icmp_seq=3 ttl=64 time=0.117 ms

--- 192.168.1.2 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2047ms
rtt min/avg/max/mdev = 0.043/0.180/0.380/0.144 ms
┌──[root@liruilongs.github.io]-[~]
└─$

network namespace 实际中的应用

接触过 k8s 的小伙伴通过ip a 命令打印网络接口信息的时候(CNI使用Calico),经常会看到下面的一些接口信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
┌──[root@vms100.liruilongs.github.io]-[~/ansible]
└─$ansible etcd -m shell -a "ip a | grep -A 4 cali"
192.168.26.102 | CHANGED | rc=0 >>
5: cali6f956c2ada9@if4: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP
link/ether 6a:65:54:1a:19:e6 brd ff:ff:ff:ff:ff:ff link-netnsid 0
inet6 fe80::6865:54ff:fe1a:19e6/64 scope link
valid_lft forever preferred_lft forever
192.168.26.100 | CHANGED | rc=0 >>
5: cali0b7f49da20a@if4: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP
link/ether 9e:da:0e:cc:b3:7e brd ff:ff:ff:ff:ff:ff link-netnsid 0
inet6 fe80::9cda:eff:fecc:b37e/64 scope link
valid_lft forever preferred_lft forever
192.168.26.101 | CHANGED | rc=0 >>
5: calib6f7ddae7e3@if4: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP
link/ether 1e:e6:16:ae:f0:91 brd ff:ff:ff:ff:ff:ff link-netnsid 0
inet6 fe80::1ce6:16ff:feae:f091/64 scope link
valid_lft forever preferred_lft forever
┌──[root@vms81.liruilongs.github.io]-[~/ansible]
└─$

实际上,上面的接口信息即为生成的 虚拟以太网对veth pair,也就是刚刚做的Demo,一端在根网络命名空间,一端在 Pod对应的网络命名空间中。从而实现 k8s Pod 之间的通信,当然其中还要涉及工作节点的路由信息。

容器与 host veth pair 的关系

经典容器组网模型就是 veth pair+bridge 的模式。容器中的 eth0 实际上和外面根网络命名空间上的某个 veth 是成对的(pair)关系.(这里的 bridge 主要用于 和因特网通信)

可以通过下面两种方式来获取对应关系

方法一

容器里面看 /sys/class/net/eth0/iflink

1
2
3
4
5
6
7
┌──[root@liruilongs.github.io]-[/]
└─$ docker exec -it 6471704fd03a sh
/ # cat /sys/class/net/eth0/if
ifalias ifindex iflink
/ # cat /sys/class/net/eth0/iflink
95
/ # exit

然后,在主机上遍历 /sys/claas/net 下面的全部目录,查看子目录 ifindex 的值和容器里查出来的 iflink 值相当的 veth 名字,这样就找到了容器和主机的 veth pair 关系。

1
2
3
┌──[root@liruilongs.github.io]-[/]
└─$ grep -c 95 /sys/class/net/*/ifindex | grep 1
/sys/class/net/veth2e08884/ifindex:1

方法二

在目标容器里执行以下命令,获取网卡索引为 94,其中 94 是 eth0 接口的index,95 是和它成对的veth的index。

1
2
3
4
5
6
┌──[root@liruilongs.github.io]-[/]
└─$ docker exec -it 6471704fd03a sh
/ # ip link show eth0
94: eth0@if95: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue
link/ether 02:42:ac:11:00:03 brd ff:ff:ff:ff:ff:ff
/ # exit

通过 95 index 来定位主机上对应的虚拟网卡

1
2
3
4
5
┌──[root@liruilongs.github.io]-[/]
└─$ ip link show | grep 95
95: veth2e08884@if94: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP mode DEFAULT
┌──[root@liruilongs.github.io]-[/]
└─$

network namespace API 的使用

  1. 创建 namespace 的黑科技:clone 系统调用

用户可以使用 clone()系统调用创建一个 namespace。

  1. 维持 namespace 存在:/proc/PID/ns 目录的奥秘

每个 Linux 进程都拥有一个属于自己的/proc/PID/ns,这个目录下的每个文件都代表一个类型的 namespace。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
┌──[root@liruilongs.github.io]-[~]
└─$ls -l /proc/$$/ns
total 0
lrwxrwxrwx. 1 root root 0 Feb 11 02:27 cgroup -> 'cgroup:[4026531835]'
lrwxrwxrwx. 1 root root 0 Feb 11 02:27 ipc -> 'ipc:[4026531839]'
lrwxrwxrwx. 1 root root 0 Feb 11 02:27 mnt -> 'mnt:[4026531841]'
lrwxrwxrwx. 1 root root 0 Feb 11 02:27 net -> 'net:[4026531840]'
lrwxrwxrwx. 1 root root 0 Feb 11 02:27 pid -> 'pid:[4026531836]'
lrwxrwxrwx. 1 root root 0 Feb 11 02:27 pid_for_children -> 'pid:[4026531836]'
lrwxrwxrwx. 1 root root 0 Feb 11 02:27 time -> 'time:[4026531834]'
lrwxrwxrwx. 1 root root 0 Feb 11 02:27 time_for_children -> 'time:[4026531834]'
lrwxrwxrwx. 1 root root 0 Feb 11 02:27 user -> 'user:[4026531837]'
lrwxrwxrwx. 1 root root 0 Feb 11 02:27 uts -> 'uts:[4026531838]'
┌──[root@liruilongs.github.io]-[~]
└─$

Linux 内核 3.8 版本以前,/proc/PID/ns 目录下的文件都是硬链接(hard link),而且只有 ipc、net 和 uts 这三个文件,从 Linux 内核 3.8 版本开始,每个文件都是一个特殊的符号链接文件

符号链接的其中一个用途是确定某两个进程是否属于同一个 namespace。如果两个进程在同一个 namespace 中,那么这两个进程/proc/PID/ns 目录下对应符号链接文件的 inode 数字(即上文例子中[]内的数字,例如 4026531839 会是一样的。

/proc/PID/ns 目录下的文件还有一个作用当我们打开这些文件时,只要文件描述符保持 open 状态,对应的 namespace 就会一直存在,哪怕这个 namespace 里的所有进程都终止运行了

  1. 往 namespace setns() 系统调用

Linux 系统调用 setns() 把一个进程加入一个已经存在的 namespace 中。

  1. 帮助进程逃离 namespace:unshare 系统调用

通过 Linux 的 network namespace 技术可以自定义一个独立的网络栈,简单到只有 loopback 设备,复杂到具备系统完整的网络能力,这就使得 network namespace 成为 Linux 网络虚拟化技术的基石——不论是虚拟机还是容器时代。

network namespace 的另一个隔离功能在于,系统管理员一旦禁用 namespace 中的网络设备,即使里面的进程拿到了一些系统特权,也无法和外界通信。

1
2
# NETNSNAME 是命名空间的名称,DEVNAME 是要禁用的网络设备的名称。
ip netns exec NETNSNAME ip link set DEVNAME down

最后,网络对安全较为敏感,即使 network namespace 能够提供网络资源隔离的机制,用户还是会结合其他类型的 namespace 一起使用,以提供更好的安全隔离能力。

博文部分内容参考

© 文中涉及参考链接内容版权归原作者所有,如有侵权请告知 :)


https://man7.org/linux/man-pages/man7/network_namespaces.7.html

https://ramesh-sahoo.medium.com/linux-network-namespace-and-five-use-cases-using-various-methods-f45b1ec5db8f

《Kubernetes 网络权威指南:基础、原理与实践》


© 2018-至今 liruilonger@gmail.com, All rights reserved. 保持署名-非商用-相同方式共享(CC BY-NC-SA 4.0)

发布于

2024-02-09

更新于

2024-11-22

许可协议

评论
Your browser is out-of-date!

Update your browser to view this website correctly.&npsb;Update my browser now

×