深入理解 Namespace
什么是Namespace
-
先来看看那种比较正式的定义。Namespace是将内核的全局资源做封装,使得每个Namespace都有一份独立的资源,因此不同的进程在各自的Namespace内对同一种资源的使用不会互相干扰。
-
这样的解释可能不清楚。举个例子:在Linux系统上,你想要改变系统的主机名,这个主机名就是一个内核的全局资源。但是内核通过实现UTS Namespace,可以将不同的进程分隔在不同的UTS Namespace中,在某个Namespace修改主机名时,另一个Namespace的主机名还是保持不变。
-
所以说Namespace是用来隔离进程对系统资源的访问。每一个 Namespace 都有一套自己的系统资源(例如进程ID、主机名、网络接口等),进程只能看到和操作同一个 Namespace 中的资源。这样就可以在逻辑上隔离不同的进程,让它们在各自的 “空间” 中运行,互不干扰。
-
在 Linux 系统中,每个进程都与一组 Namespace 关联。这些 Namespace 决定了进程可以看到和影响哪些系统资源。例如,进程的网络 Namespace 决定了进程可以看到和使用哪些网络接口。
-
当一个进程被创建时,它会继承其父进程所在的所有 Namespace。但是,也可以在创建新进程时指定其所在的 Namespace,或者将已有的进程移动到新的Namespace。在 Linux 的 /proc 文件系统中,可以查看进程的 Namespace 信息。例如,/proc/[pid]/ns/ 目录下的每个文件都代表了一个 Namespace,这些文件是该进程所在 Namespace 的符号链接。通过查看和比较这些文件,可以确定进程的 Namespace 信息。
![image-20230914194056001](https://i-blog.csdnimg.cn/blog_migrate/91e1e6ade84fc8c1f872b3fedf737616.png)
看完上面5段话,对于Namespace就会有一个基本的认识。
Namespace当中的 Network Namespace
目前Linux内核总共实现了6种 Namespace。
- IPC:隔离System V IPC和 POSIX消息队列。
- Network:隔离网络资源。
- Mount:隔离文件系统挂载点。
- PID:隔离进程ID。
- UTS:隔离主机名和域名。
- User:隔离用户ID和组ID。
Network Namespace 对网络相关的系统资源进行隔离,每个Network Namespace都有自己的网络设备、IP地址、路由表、/proc/net目录、端口号等。网络隔离的必要性是很明显的举一个例子,在没有隔离的情况下,如果两个不同的容器都想运行同一个Web应用,而这个应用又需要使用80端口,那就会有冲突了。
Libcontainer
Libnetwork是由Go语⾔编写的一个开源工具,该工具具有跨平台性,主要处理Docker网络相关的工作。下图是Libnetwork的整体结构。
![image-20230914205054633](https://i-blog.csdnimg.cn/blog_migrate/d61f2d4412e5a6f6b41bfb6acdfeec01.png)
- 沙盒:沙盒是一个隔离的网络运行环境,保存了容器网络栈的配置,包括了对网络接口、路由表和 DNS配置的管理。在 Linux平台上,沙盒是用Linux NetworkNamespace实现的。一个沙盒可以包括来自多个网络的多个Endpoint(网络接口)。
- 网络接口:Endpoint(网络接口)将沙盒加入一个网络,Endpoint 的实现可以是一对 veth pair或者OVS内部端口,当前的Libnetwork 使用的是veth pair。一个 Endpoint 只能属于一个沙盒及一个网络。通过给沙盒增加多个Endpoint可以将一个沙盒加入多个网络。
- 网络:网络包括一组能互相通信的Endpoint。网络的实现可以是bridge ,host,none等。
- 可拔插接口:可拔插接口指的是允许开发者将自定义的网络驱动接入到 Libnetwork 中,使得 Docker 容器可以使用这些自定义的网络驱动进行网络通信。这种接口的设计使得 Libnetwork 具备了极高的灵活性和可扩展性。
- 服务发现:Libnetwork 的网络控制层和管理层负责创建和管理网络以及网络接口。这些网络接口可以被绑定Docker 容器上,使得这些容器可以在同一个网络中相互通信。通过这种方式,容器可以实现本地服务发现,即在同一个网络中的容器可以通过容器名称或者其他标识来找到其他容器,从而实现相互通信。
- 负载均衡:Libnetwork 的网络控制层和管理层可以创建和管理网络,这些网络可以配置为支持负载均衡。例如,可以使用 Overlay 网络驱动创建一个跨多个 Docker 宿主机的虚拟网络,然后在这个虚拟网络中的容器之间实现负载均衡。另外,Libnetwork 还可以与 Docker Swarm 集群管理工具集成,通过 Docker Swarm,可以实现在集群范围内的容器负载均衡。
docker 网络基础
有了上面的一些基础知识做铺垫,理解docker 网络的底层原理可能会轻松一点。下面就用一些linux命令创建两个命名空间,最后让这两个命名空间之间能相互通信。
创建两个命名空间
分别创建两个命名空间 namespace1 与 namespace2。
因为每个网络空间都是独立的,所以每个 Network Namespace 都具有一个回环网络适配器 lo。可以看到这个接口的最大传输单元(MTU)是65536字节,其中MTU是网络接口可以接受的最大数据包大小。并且还可以看到state DOWN表示这个接口目前没有启用。而qlen 1000则表示这个接口的发送队列的长度,即接口可以缓存的待发送数据包的数量。
创建网络接口 veth pair
如果要让两个命名空间连通,则需要用到虚拟设备接口技术 veth pair。该技术需要一对网络接口分别置于两个命名空间中。
以下命令用于创建一对网络接口 veth-ns1 与 veth-ns2。
对于ip link add veth-ns1 type veth peer name veth-ns2 这个命令的解释如下:
ip link add
:这是用于创建网络设备的命令。veth-ns1
:这是你要创建的第一个veth设备的名称。type veth
:这表示你要创建的设备的类型是 veth。peer name veth-ns2
:这表示你要创建的第二个veth设备的名称。peer
关键字表示这两个设备是一对,互为对方的端点。
而 ip link show veth-ns1命令和 ip link show veth-ns2 命令展示了两个虚拟网络接口的信息。通过 veth-ns1@veth-ns2 和 veth-ns2@veth-ns1 可以发现这两个网络接口是一对接口。除此之外,还能看到网络接口的状态和MAC地址等等信息。
命名空间添加 veth 接口
通过 ip link set 命令,将这两个网络接口分别分配给两个命名空间。
为 veth 接口分配 IP
前面创建的两个网络接口是没有 IP 的。下面要通过 ip netns exec 命令,为每个指定的命名空间执行 IP 添加命令 ip addr add [ip] dev [网络接口]。为 namespace1 的 veth-ns1 网络接口分配的 IP 为 192.168.1.1,掩码为 24;为 namespace2 的 veth-ns2 网络接口分配的 IP 为 192.168.1.2,掩码为 24。
启动 veth 接口
以上两个命名空间中的 veth 接口已经具有了 IP,但其状态仍为 DOWN,还没有开启。下面要通过 ip link set dev [接口] up 来启动指定的网络接口。
相互 ping
此时可以通过在两个命名空间中执行 ping 命令来与对方进行连通性测试了。
bridge 网络
通过 docker network ls 命令可以查看当前主机所连接的网络及网络类型。
bridge 网络,也称为单机桥接网络,是 Docker 默认的网络模式。该网络模式只能存在于单个 Docker 主机上,其只能用于连接所在 Docker 主机上的容器。
搭建网络环境
需求:搭建如下的网络环境。
查看docker0 网桥
![image-20230915102335813](https://i-blog.csdnimg.cn/blog_migrate/24d201b6942169576e77a78d75402203.png)
- 网络接口名称(docker0)及其状态(UP,表示启动并运行)。
- docker0 的 MAC 地址(02:42:36:20:cc:8b)。
- docker0 的 IP 地址(172.17.0.1)和子网掩码(/16,表示子网的地址范围是 172.17.0.0 到 172.17.255.255)。
- docker0 的广播地址(172.17.255.255)。
- docker0 的 IPv6 地址(fe80::42:36ff:fe20:cc8b/64)。
- docker0 的 MTU(Maximum Transmission Unit,最大传输单元)是 1500。这是 Ethernet 的标准 MTU。
创建网桥 bridge2
![image-20230915103033257](https://i-blog.csdnimg.cn/blog_migrate/df41ca5e8082336139aec16327c662fc.png)
-d 选项用于指定要创建网络时所使用的驱动,即创建的网络类型。最后的 bridge2 则是新创建网络的名称。查看bridge2网桥的相关信息:
![image-20230915111942451](https://i-blog.csdnimg.cn/blog_migrate/a571fc6b886eef09a4400eb91fa2795f.png)
可以看到bridge2网桥的IP地址为172.18.0.1。
创建容器并连接网络
创建busybox1容器并连接bridge网桥:--network bridge
告诉 Docker 使用 bridge
网桥来运行 busybox1
容器。
可以看到busybox1的IP地址为172.17.0.2。现在创建busybox2容器并连接bridge网桥。
可以看到busybox2容器的IP地址为172.17.0.3。现在要将busybox2连接到bridge2网桥上。
使用docker network connect命令就可以让busybox2容器连接到bridge2网桥上。可以发现busybox2的IP地址又多了一个172.18.0.2/16。说明这个IP地址在172.18.0.0这个网段上,也就是bridge2网桥所在的网段。现在创建busybox3容器并连接到bridge2网桥上。
可以看到busybox3的IP地址为172.18.0.3。
查看容器详情
这里以busybox2容器为例,查看该容器的网络详情。
docker inspect busybox2
# 查看容器的网络配置信息:NetworkSettings
{
"Bridge": "",
"SandboxID": "145b13df0a94364eda70a3dd93a10b171f23159ddff200313f4a0c558505b9e6",
"HairpinMode": false,
"LinkLocalIPv6Address": "",
"LinkLocalIPv6PrefixLen": 0,
"Ports": { },
"SandboxKey": "/var/run/docker/netns/145b13df0a94",
"SecondaryIPAddresses": null,
"SecondaryIPv6Addresses": null,
"EndpointID": "2af54e966620633d28dfc91e54816e4a85a4c105c0213a7ef41d2c9fdcf2f3df",
"Gateway": "172.17.0.1",
"GlobalIPv6Address": "",
"GlobalIPv6PrefixLen": 0,
"IPAddress": "172.17.0.3",
"IPPrefixLen": 16,
"IPv6Gateway": "",
"MacAddress": "02:42:ac:11:00:03",
"Networks": {
"bridge": {
"IPAMConfig": null,
"Links": null,
"Aliases": null,
"NetworkID": "5f417d52f8fdae77a94202c473b03e1998de09f7509363e03826c01a3f669f14",
"EndpointID": "2af54e966620633d28dfc91e54816e4a85a4c105c0213a7ef41d2c9fdcf2f3df",
"Gateway": "172.17.0.1",
"IPAddress": "172.17.0.3",
"IPPrefixLen": 16,
"IPv6Gateway": "",
"GlobalIPv6Address": "",
"GlobalIPv6PrefixLen": 0,
"MacAddress": "02:42:ac:11:00:03",
"DriverOpts": null
},
"bridge2": {
"IPAMConfig": { },
"Links": null,
"Aliases": [
"8be8f43ccd8d"
],
"NetworkID": "5f4893ee055e6a11841ff89b5a6b8d088d8d80362deac9f4c1ff0ee9d6974b96",
"EndpointID": "c29ec9f1b1a75010c70a5f908175101bcbfc21b0159b3475fe7d7e50f86c3741",
"Gateway": "172.18.0.1",
"IPAddress": "172.18.0.2",
"IPPrefixLen": 16,
"IPv6Gateway": "",
"GlobalIPv6Address": "",
"GlobalIPv6PrefixLen": 0,
"MacAddress": "02:42:ac:12:00:02",
"DriverOpts": { }
}
}
}
可以看到busybox2这个容器有两个网段,分别是172.17.0.1和172.18.0.1;并且busybox2这个容器有两个IP地址,分别是172.17.0.3和172.18.0.2。
查看网络详情
# 查看网桥的相关信息
docker network inspect bridge2
"Containers": {
"8be8f43ccd8d1177bab90f81f3499f2448bfdc046c06db298f3e39f3943a5147": {
"Name": "busybox2",
"EndpointID": "a7ecc7cf1092e7c7601ac14b560a43b648cde38d02ffe770ba9ec77a33b0de95",
"MacAddress": "02:42:ac:12:00:02",
"IPv4Address": "172.18.0.2/16",
"IPv6Address": ""
},
"97cbf3344e7b5587ce84ac0141af9a4b40692dd14407fc2ac40f20bf5f02456c": {
"Name": "busybox3",
"EndpointID": "29cc4737c14c54b1b8a4698c66faa833e6262b4163152ee50b3ef6c7969228c0",
"MacAddress": "02:42:ac:12:00:03",
"IPv4Address": "172.18.0.3/16",
"IPv6Address": ""
}
}
查看 bridge2 的网络详情中的容器情况,发现 busybox2 与 busybox3 都在该网络上。
容器之间相互ping
发现busybox1能ping通busybox2;busybox2能Ping通busybox3;而busybox1不能ping通busybox3。说明只有在同一段网络下的容器之间才能相互ping通。
ping 容器名
该方式在生产中非常重要。因为生产中容器的 IP 可能会发生变化,但容器名称一般是不会变的。如果某服务总是直接通过 IP 与容器相连接,那么一旦容器 IP 变化,则该服务将连接不上容器。但如果是通过容器名称相连接的,那么无论容器 IP 如何变化,都将不影响服务与容器的连接。
创建定向连接容器
对于自定义的 bridge 网络,其具有一个特性:该网络上的容器可以通过容器名互 ping。但默认的 bridge 网络是不行的。如果在默认的 bridge 网络上实现通过容器名进行的连接,则需要创建容器时通过–link 选项指定。
但是容器 busybox4 是无法通过容器名称来连接 busybox2 的。然后 busybox1 也无法通过容器名称连接 busybox4。所以,–link 指定的连接是一种定向连接,是带有指向性与方向性的。
创建共享网络命名空间容器
在创建容器时可以指定其与某已经存在的容器共享 Network Namespace,但要求该已经存在的容器采用的是 bridge 网络模式。
docker run -it --name busybox-1 --network container:busybox1 busybox /bin/sh
上面的命令创建了一个 busybox-1 的容器,其共享了 busybox1 容器的 Network Namespace。查看两个容器的接口情况,发现完全相同。
![image-20230915153824101](https://i-blog.csdnimg.cn/blog_migrate/3eecf0b35290863d4aacbb806c22b780.png)
查看容器 busybox-1 的详情,可以发现,其没有自身的网络设置。因为其共享的 bb1 容器的网络设置。
none 网络
none 网络,即没有网络。容器仍是一个独立的 Network Namespace,但没有网络接口,没有 IP。
创建 none 网络容器
在 docker run 命令中,通过–network none 选项指定创建的容器没有网络功能。
docker run -it --name busybox5 --network none busybox /bin/sh
查看容器详情
# 查看容器详情的命令
docker inspect busybox5
# 下面是busybox5关于网络部分的信息
"NetworkSettings": {
"Bridge": "",
"SandboxID": "400b397e12ca82bc791f5fd4d194a05289445e489a121e2fce06ca54817f2f86",
"HairpinMode": false,
"LinkLocalIPv6Address": "",
"LinkLocalIPv6PrefixLen": 0,
"Ports": {
},
"SandboxKey": "/var/run/docker/netns/400b397e12ca",
"SecondaryIPAddresses": null,
"SecondaryIPv6Addresses": null,
"EndpointID": "",
"Gateway": "",
"GlobalIPv6Address": "",
"GlobalIPv6PrefixLen": 0,
"IPAddress": "",
"IPPrefixLen": 0,
"IPv6Gateway": "",
"MacAddress": "",
"Networks": {
"none": {
"IPAMConfig": null,
"Links": null,
"Aliases": null,
"NetworkID": "3b0eaed4d1d8ff72e62cc883d3a97639284fbd8eec96d6919322acbb4acd8d17",
"EndpointID": "61ed43896cfc51adf35120c16f335ba58dcb124103cb0bb6f17b7b9eee2ca355",
"Gateway": "",
"IPAddress": "",
"IPPrefixLen": 0,
"IPv6Gateway": "",
"GlobalIPv6Address": "",
"GlobalIPv6PrefixLen": 0,
"MacAddress": "",
"DriverOpts": null
}
}
}
通过 docker inspect 命令查看该容器的详情,发现其没有 IP,没有网关,没有 MAC 地址。这种none网络的主要用途是为那些不需要或不希望有任何网络通信功能的容器提供一个安全的运行环境,例如一些只需要进行本地计算或处理本地数据的应用。
host 网络
host 网络,即与宿主机 host 共用一个 Network Namespace。该网络类型的容器没有独立的网络空间,没有独立的 IP,全部与 host 共用。
创建 host 网络容器
在 docker run 命令中,通过–network host 选项指定创建的容器为 host 网络。
docker run -it --name busybox6 --network host busybox /bin/sh
查看容器详情
# 查看容器详情的命令
docker inspect busybox6
# 下面是busybox5关于网络部分的信息
"NetworkSettings": {
"Bridge": "",
"SandboxID": "d16a68fa18c745642fe4a7833e89bb2ee7351c06291cb5441dc3c35f7c350321",
"HairpinMode": false,
"LinkLocalIPv6Address": "",
"LinkLocalIPv6PrefixLen": 0,
"Ports": {
},
"SandboxKey": "/var/run/docker/netns/default",
"SecondaryIPAddresses": null,
"SecondaryIPv6Addresses": null,
"EndpointID": "",
"Gateway": "",
"GlobalIPv6Address": "",
"GlobalIPv6PrefixLen": 0,
"IPAddress": "",
"IPPrefixLen": 0,
"IPv6Gateway": "",
"MacAddress": "",
"Networks": {
"host": {
"IPAMConfig": null,
"Links": null,
"Aliases": null,
"NetworkID": "16501fad09abfb197b94593aa27568c6c91cfbebb2c521553da481552fa3ad1f",
"EndpointID": "7cdf57ebf08f15fe7069b7f03f46a711b6b283ae8aff85ed614b6b5524dc9035",
"Gateway": "",
"IPAddress": "",
"IPPrefixLen": 0,
"IPv6Gateway": "",
"GlobalIPv6Address": "",
"GlobalIPv6PrefixLen": 0,
"MacAddress": "",
"DriverOpts": null
}
}
}
通过 docker inspect 命令查看该容器的详情,发现其没有 IP,没有网关,没有 MAC 地址。
查看容器的IP信息
通过 docker exec busybox6 ip addr 命令查看容器的IP信息可以发现该容器的IP与宿主机的IP信息一模一样。所以在host模式下,容器直接使用宿主机的网络接口、IP和路由规则。这意味着在host网络模式下运行的容器可以直接监听宿主机上的所有网络端口,也正是因为容器与宿主机共用网络端口,所以在host模式下的运行容器不需要进行端口映射就能直接对外提供服务。
这种模式的主要用途是为那些需要直接访问宿主机网络环境或者需要高性能网络通信的容器提供一个高效的运行环境。但是,由于它会让容器直接暴露在宿主机的网络环境中,所以在某些需要隔离或安全的场景中可能不适用。