Network-Namespace 是 Linux 内核提供的用于实现网络虚拟化的重要功能,它能创建多个隔离的网络空间,该网络空间内的防火墙、网卡、路由表、邻居表、协议栈与外部都是独立的。不管是虚拟机还是容器,当运行在独立的命名空间时,就像是一台单独的主机一样。**
下面会通过一些例子来说明网络命名空间,以加深理解,会用到 iproute2 工具包的 ip
命令,需先自行安装,并且使用 root 权限操作。
- 在 Centos 下执行如下命令:
yum install iproute2
- 验证安装完成:
[root@worker3 ~]# ip help
Usage: ip [ OPTIONS ] OBJECT { COMMAND | help }
ip [ -force ] -batch filename
where OBJECT := { link | address | addrlabel | route | rule | neigh | ntable | tunnel | tuntap | maddress | mroute | mrule | monitor | xfrm | netns | l2tp | fou | macsec | tcp_metrics | token | netconf | ila | vrf }
OPTIONS := { -V[ersion] | -s[tatistics] | -d[etails] | -r[esolve] |
-h[uman-readable] | -iec |
-f[amily] { inet | inet6 | ipx | dnet | mpls | bridge | link } |
-4 | -6 | -I | -D | -B | -0 |
-l[oops] { maximum-addr-flush-attempts } | -br[ief] |
-o[neline] | -t[imestamp] | -ts[hort] | -b[atch] [filename] |
-rc[vbuf] [size] | -n[etns] name | -a[ll] | -c[olor]}
创建网络命名空间
ip
命令中用于操作网络命名空间的命令是 ip netns
,用 help
来查看一下子命令有哪些
[root@worker3 ~]# ip netns help
Usage: ip netns list
ip netns add NAME
ip netns set NAME NETNSID
ip [-all] netns delete [NAME]
ip netns identify [PID]
ip netns pids NAME
ip [-all] netns exec [NAME] cmd ...
ip netns monitor
ip netns list-id
常用的也就是增删查命令,先来创建一个网络空间空间 ns1
ip netns add ns1
查看当前所有的网络命名空间
[root@worker3 ~]# ip netns list
ns1
在这有些人可能会很困惑,我主机上明明运行中好几个 docker 容器,按理说每个容器都运行在独立的网络命名空间,怎么这里没有列出来?不要着急,下面会提到。
先来感觉一下什么叫独立的网卡,独立的路由表,要查看 ns1 命名空间的网卡,iproute2 工具提供了命令 ip netns exec ns1
,跟在这个命令后面的命令都会在这个网络命名空间中执行。
先查看一下主机的网卡和路由表
[root@worker3 ~]# 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: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP mode DEFAULT group default qlen 1000
link/ether 00:50:56:bb:ab:df brd ff:ff:ff:ff:ff:ff
[root@worker3 ~]# ip route
default via 10.57.4.1 dev eth0
10.1.2.0/24 dev br0 proto kernel scope link src 10.1.2.1
再看看 ns1 中的网卡和路由表
[root@worker3 ~]# ip netns exec ns1 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
[root@worker3 ~]# ip netns exec ns3 ip route
这样执行命令有点麻烦,也可以简单一点:
[root@worker3 ~]#ip netns exec ns1 bash
//这个命令后执行的命令就都是在ns1中执行了
[root@worker3 ~]#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
[root@worker3 ~]#ip route
用 exit
可以回到主机的默认空间。
ip netns add的原理
当我们在主机上执行 ip netns add ns1
后 ,实际是在 /var/run/netns
下创建了一个 ns1 的文件
[root@worker3 ~]# ls /var/run/netns
ns1
下面的命令可以模拟 ip netns add ns2
&& ip netns exec ns2 bash
:
[root@worker3 ~]# touch /var/run/netns/ns2
[root@worker3 ~]# unshare --net bash
[root@worker3 ~]# mount --bin /proc/self/ns/net /var/run/netns/ns2
//上面的过程实际就是执行了ip netns add ns2 && ip netns exec ns2 bash
[root@worker3 ~]# 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
[root@worker3 ~]# exit
//退出,回到主机默认命名空间。用ip netns list查看,已经可以看到ns2
[root@worker3 ~]# ip netns list
ns2
ns1
//如果想再次进入ns2,还有一个方法:
[root@worker3 ~]# nsenter --net=/var/run/netns/ns2
从上面的示例可以看出,创建命名的 Network Namespace 其实就是创建一个文件,然后通过绑定挂载的方式将新创建的 Network Namespace 文件和进程的 /proc/self/ns/net
文件绑定。
查看容器的网络命名空间
接下来该回答上面的遗留问题,为什么当我在主机上 ip netns list
的时候看不到 docker 的网络命名空间?因为 ip netns list
的时候只会显示在 /var/run/netns
下的文件,而 docker 的文件默认是创建在 /var/run/docker/netns
下的。所以我们可以通过 ls /var/run/docker/netns
来显示当前的所有容器的网络命名空间,并且通过 nsenter --net=/var/run/docker/xxx
来进入容器的网络命名空间。
[root@worker3 ~]# ls /var/run/docker/netns
5bbd5f99d403 a2eabf9acccb b63ec59b3d9e d6e4ff961713 default
[root@worker3 ~]# nsenter --net=/var/run/docker/netns/b63ec59b3d9e
[root@worker3 ~]# ip addr
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
4: eth0@if3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
link/ether fa:a7:8d:05:03:a6 brd ff:ff:ff:ff:ff:ff link-netnsid 0
inet 10.244.0.11/32 scope global eth0
valid_lft forever preferred_lft forever
如果想查看具体某个 docker 容器对应的文件,可以用:
docker inspect $CONTAINER_ID$|grep SandboxKey
注意如果是 K8S 拉起来的 docker,要拿非 hostNetwork=true 的 pause 容器来看。如果hostNetwork=true
,那么下面的值为 /var/run/docker/netns/default
,这是主机的默认网络命名空间。如果不是 pause 容器,那么下面的值为空,因为只有 pause 容器会创建一个新的网络命名空间,其它 container 都只是加入这个网络命名空间。
[root@worker3 ~]# docker inspect ebd6855901ef|grep SandboxKey
"SandboxKey": "/var/run/docker/netns/b63ec59b3d9e",
还有另一个办法:
[root@worker3 ~]# docker inspect nginx|grep Pid
"Pid": 31817,
"PidMode": "",
"PidsLimit": null,
[root@worker3 ~]# mkdir -p /var/run/netns/
[root@worker3 ~]# ln -s /proc/31817/ns/net /var/run/netns/ns100
[root@worker3 ~]# ip netns ls
ns100
ns2
ns1
[root@worker3 ~]# ip netns exec ns100 bash
[root@worker3 ~]# //这时候已经在容器网络里了,比nsenter还方便
这个小技巧在我们调试 Pod 的网络时非常有用,大多数时候 Pod 里面自带的工具非常少,没有 curl
没有 telnet
,这时候用这个技巧先进入空器的网络空间,再执行命令就行了,因为只是切了网络命名空间,其它还在主机上,所以用的工具也全是主机的工具。