1、扩展知识:Linux Namespace
Namespace(命名空间、名字空间)是 Linux 内核用来隔离内核资源的方式。通过 Namespace 可以让一些进程只能看到与自己相关的一部分资源,而另外一些进程也只能看到与他们自己相关的资源,这两拨进程根本就感觉不到对方的存在。具体实现方式是把一个或者多个进程的相关资源指定在同一个 Namespace 中。
Linux Namespace 是对全局系统资源的一种封装隔离。使得处于不同 Namespace 的进程拥有独立的全局系统资源,改变一个 Namespace 中的系统资源只会影响当前 Namespace 里的进程,对其他 Namespace 中的进程没有影。
可能绝大多数的使用者和我一样,是在使用 docker 后才开始了解 linux 的 namespace 技术的。实际上,Linux 内核实现 namespace 的一个主要目的就是实现轻量级虚拟化(容器)服务。在同一个 namespace 下的进程可以感知彼此的变化,而对外界的进程一无所知。这样就可以让容器中的进程产生错觉,认为自己置身于一个独立的系统中,从而达到隔离的目的。也就是说 linux 内核提供的 namespace 技术为 docker 等容器技术的出现和发展提供了基础条件。
目前,Linux内核里面实现了7种不同类型的namespace。
名称 宏定义 隔离内容
Cgroup CLONE_NEWCGROUP Cgroup root directory (since Linux 4.6)
IPC CLONE_NEWIPC System V IPC, POSIX message queues (since Linux 2.6.19)
Network CLONE_NEWNET Network devices, stacks, ports, etc. (since Linux 2.6.24)
Mount CLONE_NEWNS Mount points (since Linux 2.4.19)
PID CLONE_NEWPID Process IDs (since Linux 2.6.24)
User CLONE_NEWUSER User and group IDs (started in Linux 2.6.23 and completed in Linux 3.8)
UTS CLONE_NEWUTS Hostname and NIS domain name (since Linux 2.6.19)
下面简要介绍一个以上不同类型的命名空间的作用:
- IPC:用于隔离进程间通讯所需的资源( System V IPC, POSIX message queues),PID命名空间和IPC命名空间可以组合起来用,同一个IPC名字空间内的进程可以彼此看见,允许进行交互,不同空间进程无法交互
- Network:Network Namespace为进程提供了一个完全独立的网络协议栈的视图。包括网络设备接口,IPv4和IPv6协议栈,IP路由表,防火墙规则,sockets等等。一个Network Namespace提供了一份独立的网络环境,就跟一个独立的系统一样。
- Mount:每个进程都存在于一个mount Namespace里面,mount Namespace为进程提供了一个文件层次视图。如果不设定这个flag,子进程和父进程将共享一个mount Namespace,其后子进程调用mount或umount将会影响到所有该Namespace内的进程。如果子进程在一个独立的mount Namespace里面,就可以调用mount或umount建立一份新的文件层次视图。
- PID::linux通过命名空间管理进程号,同一个进程,在不同的命名空间进程号不同!进程命名空间是一个父子结构,子空间对于父空间可见。
- User:用于隔离用户
- UTS:用于隔离主机名
2、Docker 网络概述
Docker 容器之间的隔离是通过 Namespace 实现的,Docker 容器之间的网络隔离便是通过 Network Namespace 来实现的,每一份 Network Namespace 都提供了一份独立的 网络系统,包括网卡、路由、防火墙规则等与其他 Network Namespace 相互隔离。
Docker 启动后默认分配一个独立的 Network Namespace ,类型为 bridge ,可使用如下命令查看:
$ docker network ls
NETWORK ID NAME DRIVER SCOPE
fb7b7a0e2541 bridge bridge local
这个 bridge 的名称默认为 docker0,可使用 ifconfig 或者 ip address 来查看详细信息
$ ifconfig docker0
docker0: flags=4163 mtu 1500
inet 172.18.0.1 netmask 255.255.0.0 broadcast 172.18.255.255
inet6 fe80::42:53ff:fe55:43d1 prefixlen 64 scopeid 0x20
ether 02:42:53:55:43:d1 txqueuelen 0 (Ethernet)
RX packets 6345 bytes 393570 (384.3 KiB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 6868 bytes 41194953 (39.2 MiB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
Docker 还有两种网络类型,分别为 Host 和 None,如果启动 Docker 时,指定了网络类型为 Host 模式,则不会分配一个独立的 Network Namespace ,而是和主机共享一个共同的 Network Namespace;如果指定为 None 模式,则Docker 不会与外界进行通信。
使用 bridge 模式的网络类型时,通过端口映射的方法,来实现网络通信,你可以使用 docker network inspect bridge
来查看这个 bridge 的详细信息。
这里需要介绍两个 docker run
时的参数,-p
和 -P
-p,--publish list,Publish容器的一个指定的端口到主机端口
-P,--publish-all,Publish容器的所有公开的端口到主机的随机端口
你可以分别使用这两个参数进行测试,当你使用 docker run -P
时,你可以使用 docker ps
或者其他命令来查看随机映射的端口,下面以 -p 进行演示:
docker run --name test1_nginx -d -p 8080:80 nginx:latest
强调一下,-p 前面的端口是主机的端口,后端的端口对应容器的端口,不要搞错了,这个我经常搞错,♂️
3、自定义 Docker 网桥
前文提到,启动 Docker 后,默认创建一个名为 docker0 的网桥,但是官方不建议使用默认的这个 docker0 网桥,而是建议使用自定义(自创建)网络。
创建自定义网络使用命令 docker create
,具体参数如下
[root@small ~]# docker network create --help
Usage: docker network create [OPTIONS] NETWORK
Create a network
Options:
--attachable Enable manual container attachment
--aux-address map Auxiliary IPv4 or IPv6 addresses used by Network driver (default map[])
--config-from string The network from which copying the configuration
--config-only Create a configuration only network
-d, --driver string Driver to manage the Network (default "bridge")
--gateway strings IPv4 or IPv6 Gateway for the master subnet
--ingress Create swarm routing-mesh network
--internal Restrict external access to the network
--ip-range strings Allocate container ip from a sub-range
--ipam-driver string IP Address Management Driver (default "default")
--ipam-opt map Set IPAM driver specific options (default map[])
--ipv6 Enable IPv6 networking
--label list Set metadata on a network
-o, --opt map Set driver specific options (default map[])
--scope string Control the network's scope
--subnet strings Subnet in CIDR format that represents a network segment
3.1、创建自定义网络
# 创建网络
$ docker network create \
--driver=bridge \
--subnet=172.28.0.0/16 \
--ip-range=172.28.5.0/24 \
--gateway=172.28.5.254 \
my-net
# 解释
--driver= # 网络类型,默认为bridge
--subnet= # 子网
--ip-range= # ip地址范围
--gateway= # 网关
my-net # 网络名称
查看 Docker 网络
# 查看所有网络
$ docker network ls
NETWORK ID NAME DRIVER SCOPE
fb7b7a0e2541 bridge bridge local
93d7965ac414 host host local
3c6f04a1e42c my-net bridge local
69be969894e3 none null local
# 查看创建的网络的情况
$ docker network inspect my-net
...
{
"Name": "my-net",
"Config": [
{
"Subnet": "172.28.0.0/16",
"IPRange": "172.28.5.0/24",
"Gateway": "172.28.5.254"
}
...
# 查看默认网桥的详细信息
$ docker network inspect bridge
[
{
"Name": "bridge",
"Id": "fb7b7a0e25417a2946bd879bd05992be067eb15e51bf8658808b4225c97647da",
"Created": "2019-12-21T10:06:43.475651149+08:00",
"Scope": "local",
"Driver": "bridge",
"EnableIPv6": false,
"IPAM": {
"Driver": "default",
"Options": null,
"Config": [
{
"Subnet": "172.18.0.0/16"
}
]
},
"Internal": false,
"Attachable": false,
"Ingress": false,
"ConfigFrom": {
"Network": ""
},
"ConfigOnly": false,
"Containers": {
"4e28d1aa47d7fcf66d3f02af108f57271edba2dae7780634138d6cc5b56e8245": {
"Name": "test1_nginx",
"EndpointID": "c3122141281a2a05f392b132e4c9df0e9afd223a1bdac4e193f352ce53055c1b",
"MacAddress": "02:42:ac:12:00:02",
"IPv4Address": "172.18.0.2/16",
"IPv6Address": ""
},
"596d8d06c84fdc87ffa471b38925406bc1a1dd563438281a4351fb8554a4d075": {
"Name": "test2_nginx",
"EndpointID": "4b9ad904506c942d10df946411445022b432df45975ef69f4aa4225c0472e777",
"MacAddress": "02:42:ac:12:00:03",
"IPv4Address": "172.18.0.3/16",
"IPv6Address": ""
},
"d2429364fbe0c5524e5852b561c29a547b7bff2f01eb37ae45caee42a5c89a69": {
"Name": "test3_nginx",
"EndpointID": "ac281704c974f979c1d07b843ede61e911d558f33dc0be639a089f0fad72c191",
"MacAddress": "02:42:ac:12:00:04",
"IPv4Address": "172.18.0.4/16",
"IPv6Address": ""
}
},
"Options": {
"com.docker.network.bridge.default_bridge": "true",
"com.docker.network.bridge.enable_icc": "true",
"com.docker.network.bridge.enable_ip_masquerade": "true",
"com.docker.network.bridge.host_binding_ipv4": "0.0.0.0",
"com.docker.network.bridge.name": "docker0",
"com.docker.network.driver.mtu": "1500"
},
"Labels": {}
}
]
之前我启动了三个 docker 容器,用来跑 Nginx web 服务,接下来我们看一下这三个容器具体的网络情况,因为之前没有刚才创建的my-net网络,所有所有容器是跑在名称为 bridge 的网络下的
# 查看已经启动的容器
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
d2429364fbe0 nginx "nginx -g 'daemon of…" 5 minutes ago Up 5 minutes 0.0.0.0:8082->80/tcp test3_nginx
596d8d06c84f nginx "nginx -g 'daemon of…" 6 minutes ago Up 6 minutes 0.0.0.0:8081->80/tcp test2_nginx
4e28d1aa47d7 nginx:latest "nginx -g 'daemon of…" About an hour ago Up About an hour 0.0.0.0:8080->80/tcp test1_nginx
# 查看某个容器的情况,你可以在输出的信息中看到如下网络相关信息
$ docker inspect test1_nginx
...
"Gateway": "172.18.0.1",
"GlobalIPv6Address": "",
"GlobalIPv6PrefixLen": 0,
"IPAddress": "172.18.0.2",
...
3.2、将正在运行的容器连接到网络 my-net
# 将正在运行的容器连接到网络 my-net
$ docker network connect my-net test1_nginx
# 查看切换后的容器网络状况,可见,这种方式不会影响默认的bredeg网络
$ docker inspect test1_nginx
...
"Networks": {
"bridge": {
"Gateway": "172.18.0.1",
"IPAddress": "172.18.0.2",
"IPPrefixLen": 16,
"IPv6Gateway": "",
"GlobalIPv6Address": "",
"GlobalIPv6PrefixLen": 0,
"MacAddress": "02:42:ac:12:00:02",
"DriverOpts": null
},
"my-net": {
"Gateway": "172.28.5.254",
"IPAddress": "172.28.5.0",
"IPPrefixLen": 16,
"IPv6Gateway": "",
"GlobalIPv6Address": "",
"GlobalIPv6PrefixLen": 0,
"MacAddress": "02:42:ac:1c:05:00",
"DriverOpts": {}
}
...
注意:以上方式并没有因为给容器配置了新的my-net
网络而影响到旧的bridge
网络,你可以进入容器查看具体情况,此时这个test1_nginx
容器是有两块网卡的。
3.3、启动容器时将其连接到某个网络
启动新的容器时,指定其运行的网络。
# --ip 指定一个具体的IP地址
$ docker run --name=test4_nginx -itd --network=my-net --ip=172.28.5.10 nginx
# 查看容器情况,你可以在输出信息中找到网络相关内容
$ docker inspect test4_nginx
"Networks": {
"my-net": {
"Gateway": "172.28.5.254",
"IPAddress": "172.28.5.10",
"IPPrefixLen": 16,
"IPv6Gateway": "",
"GlobalIPv6Address": "",
"GlobalIPv6PrefixLen": 0,
"MacAddress": "02:42:ac:1c:05:0a",
"DriverOpts": null
}
你也可以进入容器安装ping
或者curl
等命令检查各个容器之间的网络连接情况,这时,test1_nginx 和 test2_nginx、test3_nginx 是相通的,test4_nginx 仅与 test1_nginx 相通。
3.4、断开容器的某个网络
断开容器名为 bridge 的网络,容器必须是运行中的
# 断开容器名为 test1_nginx 的 bridge 网络
$ docker network disconnect bridge test1_nginx
# 查看断开后的容器的网络情况
docker inspect test1_nginx
参考资料1:
https://www.cnblogs.com/sparkdev/p/9365405.html
https://www.jianshu.com/p/2a14fe583cdf
https://www.cnblogs.com/bakari/p/10613710.html
参考资料2:
https://www.imooc.com/video/14623
参考链接3:
https://docs.docker.com/engine/reference/commandline/network_create/#specify-advanced-options