Contents

Docker容器网络命名空间不可见

1. 概述

在本教程中,我们将研究 Docker 容器的网络命名空间文件的问题。具体来说,我们将了解为什么网络命名空间文件对ip netns ls命令不可见。

2. 网络命名空间

**在 Docker 容器的最基础层是Linux cgroup 和命名空间机制。**这两种机制协同工作,在我们利用的 Docker 容器中提供进程和资源隔离。例如,cgroups 限制进程可以使用的资源。另一方面,命名空间控制着进程间资源的可见性。命名空间的示例之一是网络命名空间,通常称为net

**网络命名空间本质上是虚拟化和隔离进程的网络堆栈。**也就是说,不同的进程可以有自己独特的防火墙配置、私有IP地址和路由规则。通过这个网络命名空间,我们可以为每个 Docker 容器提供一个与主机网络隔离的网络堆栈。

在 Linux 中,管理网络名称空间的主要工具之一是ip netns 。此命令行工具是ip工具的扩展。它允许我们在不同的网络名称空间上执行ip 兼容命令。

3. 不可见的 Docker 网络命名空间

**每当我们创建 Docker 容器时,守护进程都会为容器进程创建命名空间伪文件。然后它将这些文件放在目录/proc/{pid}/ns 下,其中pid是容器的进程 ID。让我们看一个例子:

$ sudo docker run --rm -d ubuntu:latest sleep infinity
2545fdac9b41e463a29b4a61c201b789d567f88d54b6973bdcca9e69ba35ba92
$ sudo docker inspect -f '{{.State.Pid}}' 2545fdac9b41e463a29b4a61c201b789d567f88d54b6973bdcca9e69ba35ba92 
3357

在上面的命令中,我们首先创建一个运行ubuntu:latest镜像的 Docker 容器。然后我们通过运行sleep infinity来保持容器运行。最后,我们运行docker inspect 命令获取容器的进程id。

现在,查看 /proc/3357/ns目录,我们可以看到创建了所有不同类型的名称空间:

$ sudo ls -la /proc/3357/ns
total 0
dr-x--x--x 2 root root 0 Feb  5 04:24 .
dr-xr-xr-x 9 root root 0 Feb  5 04:24 ..
lrwxrwxrwx 1 root root 0 Feb  5 04:25 cgroup -> 'cgroup:[4026531835]'
lrwxrwxrwx 1 root root 0 Feb  5 04:25 ipc -> 'ipc:[4026532720]'
lrwxrwxrwx 1 root root 0 Feb  5 04:25 mnt -> 'mnt:[4026532718]'
lrwxrwxrwx 1 root root 0 Feb  5 04:24 net -> 'net:[4026532723]'
lrwxrwxrwx 1 root root 0 Feb  5 04:25 pid -> 'pid:[4026532721]'
lrwxrwxrwx 1 root root 0 Feb  5 04:25 pid_for_children -> 'pid:[4026532721]'
lrwxrwxrwx 1 root root 0 Feb  5 04:25 time -> 'time:[4026531834]'
lrwxrwxrwx 1 root root 0 Feb  5 04:25 time_for_children -> 'time:[4026531834]'
lrwxrwxrwx 1 root root 0 Feb  5 04:25 user -> 'user:[4026531837]'
lrwxrwxrwx 1 root root 0 Feb  5 04:25 uts -> 'uts:[4026532719]'

由于 Docker 守护进程以 root 身份运行,因此所有文件系统都由 root 拥有。因此,我们需要sudo来查看这些文件。

从命名空间伪文件列表中,我们可以看到此进程的net文件的存在。由于net文件对应于 Linux 网络名称空间,因此我们可以预期它会在我们列出所有网络名称空间时显示出来。但是,我们可以看到并非如此。例如,现在运行ip netns ls将显示 0 个结果:

$ ip netns ls
$

作为完整性检查,让我们手动创建一个网络名称空间。然后,验证它在我们运行ip netns时是否显示:

$ sudo ip netns add netA
$ ip netns ls
netA
$ 

如我们所见,它 按预期显示了netA 。那么为什么它不显示docker run 创建的net呢?

4. 丢失的文件参考

要理解这个问题,我们需要认识到ip netns ls 命令在*/var/run/netns目录中查找网络名称空间文件。但是, Docker 守护程序**在创建后不会在/var/run/netns*目录中创建网络命名空间文件的引用。**因此,ip netns ls无法解析网络命名空间文件。

*修复这种不一致的一种方法是在/var/run/netns目录中为net文件创建一个文件引用 。**具体来说,我们可以将net命名空间文件绑定挂载 到我们在/var/run/netns*目录中创建的空文件上。

首先,我们在目录中创建一个空文件,并使用命名空间文件关联的容器 ID 命名它:

$ mkdir -p /var/run/netns
$ touch /var/run/netns/$container_id

其中 $container_id是一个环境变量,用于评估我们创建的 Docker 容器的 ID。

随后,我们可以运行 mount -o bind命令来绑定挂载 net文件:

$ mount -o bind /proc/3357/ns/net /var/run/netns/$container_id

现在,再次运行相同的ip netns ls命令将按预期显示网络名称空间:

$ ip netns ls
ip netns ls
2545fdac9b41e463a29b4a61c201b789d567f88d54b6973bdcca9e69ba35ba92
netA

一旦我们建立了对网络命名空间文件的文件引用,我们就可以使用ip netns exec运行任何ip命令 。例如,我们可以使用ip addr list命令查看网络命名空间上的接口:

$ ip netns exec $container_id ip addr list
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: itcodingman@blogdemo: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default 
    link/ether 02:42:ac:11:00:02 brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet 172.17.0.2/16 brd 172.17.255.255 scope global eth0
       valid_lft forever preferred_lft forever

**要记住的一件事是我们应该始终使用绑定安装来创建文件引用。**使用符号链接可能很危险,因为 PID 值是可重用的。这将引入ip netns错误地解析到为其创建文件引用的错误网络命名空间的可能性。