 Docker网络
Docker网络
  # 一、Docker网络模式
Docker网络模式有四种模式(其实也可以说5种):
- bridge(默认):容器使用独立net namespace,会为每一个容器分配,设置IP等,并将容器连接到一个docker0 的虚拟网桥,通过docker 0 网桥以及iptables nat 表配置与宿主机通信。
- host: 和宿主机公用同一个net namespace,容器中的网络环境和宿主机一模一样。
- container: 创建的容器不会创建自己的网卡,配置自己的IP,而是和一个指定的容器共享IP,端口范围。
- none: :对于此容器,禁用所有联网。通常与自定义网络驱动程序一起使用。
- 自定义(Macvlan):Macvlan网络允许您为容器分配MAC地址,使其在网络上显示为物理设备。Docker守护程序通过其MAC地址将流量路由到容器。在处理希望直接连接到物理网络而不是通过Docker主机的网络堆栈进行路由的旧应用程序时,使用macvlan驱动程序有时是最佳选择。
总结
其实,Docker这几种网络模式也可以理解为,是否跟宿主机或者是否跟其它容器共享Network namespace而延伸出来的网络模式。
# 二、网络模式应用场景
# 2.1 bridge 模式
bridge 模式是Docker默认网络驱动模式,容器使用独立的NetWork NameSpace。 Docker进程启动时,会在宿主机上创建一个名为docker0的虚拟网卡,同一个宿主机上的容器,都会连接到这个虚拟网卡上面,通过此网卡进行与外部或者与其它容器通信。
启动的容器都会从 docker0 子网中分配一个 IP 给容器使用,并设置 docker0 的 IP 地址为容器的默认网关,在主机上创建一堆虚拟网卡 veth pair 设备,veth pair 是一种成对出现的特殊网络设备,可以把他们想象成由一根虚拟网线连接起来的一对网卡,网卡的一头(eth0)在容器中,另一头(veth**)挂在网桥 docker0 上。具体的原理参考下面网络通信原理。
拓扑

示例
# 启动三个容器 默认就是bridge模式
[root@localhost ~]# docker run -d --name nginx-b1 nginx:latest
[root@localhost ~]# docker run -d --name nginx-b2 nginx:latest
[root@localhost ~]# docker run -d --name nginx-b3 nginx:latest
# 通过ip a可以看到创建的veth虚拟网络设备
[root@localhost ~]# 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: ens192: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000
    link/ether 00:50:56:a4:78:54 brd ff:ff:ff:ff:ff:ff
    inet 10.66.31.130/24 brd 10.66.31.255 scope global noprefixroute ens192
       valid_lft forever preferred_lft forever
    inet6 fe80::1a49:7ad8:da9b:d0b4/64 scope link tentative noprefixroute dadfailed 
       valid_lft forever preferred_lft forever
    inet6 fe80::f3f0:e911:6ed:35db/64 scope link noprefixroute 
       valid_lft forever preferred_lft forever
3: docker0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default 
    link/ether 02:42:e1:fc:20:ef brd ff:ff:ff:ff:ff:ff
    inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0
       valid_lft forever preferred_lft forever
    inet6 fe80::42:e1ff:fefc:20ef/64 scope link 
       valid_lft forever preferred_lft forever
21: vethaad56ba@if20: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP group default 
    link/ether 9a:b1:5b:f5:ee:e4 brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet6 fe80::98b1:5bff:fef5:eee4/64 scope link 
       valid_lft forever preferred_lft forever
23: veth8dd5b8a@if22: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP group default 
    link/ether ee:12:83:d0:7a:34 brd ff:ff:ff:ff:ff:ff link-netnsid 1
    inet6 fe80::ec12:83ff:fed0:7a34/64 scope link 
       valid_lft forever preferred_lft forever
25: veth90ba14f@if24: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP group default 
    link/ether 2a:27:bb:f2:3c:b1 brd ff:ff:ff:ff:ff:ff link-netnsid 2
    inet6 fe80::2827:bbff:fef2:3cb1/64 scope link 
       valid_lft forever preferred_lft forever
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
30
31
32
33
34
35
36
37
38
# 2.2 host 模式
host模式,指的是跟宿主机共享同一个Network Namespace,使用宿主机的IP地址,和宿主机共享端口范围,使用参数--net=host指定。
示例:
# 以host模式启动容器
[root@localhost ~]# docker run -d --name nginx-h1 --net=host nginx:latest
1a3f2958b84d3987106c3ea1ada3c2cd29f83f51e4c85f531080768f3c80b829
# 通过查看运行的容器,可以看到PORTS也是没有端口映射的
[root@localhost ~]# docker ps
CONTAINER ID   IMAGE          COMMAND                  CREATED         STATUS        PORTS     NAMES
1a3f2958b84d   nginx:latest   "/docker-entrypoint.…"   2 seconds ago   Up 1 second             nginx-h1
2
3
4
5
6
7
访问Nginx:

注意
host模式虽然网络性能比较好(不用docker0转发),使用起来比较方便,但是因为与宿主机共享IP、端口,所以如果运行容器较多,没有合理规范,会导致端口冲突,隔离性不好,也不安全。
# 2.3 container 模式
container模式指的是新创建的容器和已经存在的一个容器共享一个Network Namespace,Docker不会给容器分配IP,两个容器除了网络方面一样,其它诸如文件系统,pid等都是隔离的,两个容器网络通信直接是lo网卡通信。
拓扑:

示例:
# 先启动Container01 
[root@localhost ~]# docker run -d -it --name busybox-c1  busybox:latest /bin/sh
# 查看容器IP
[root@localhost ~]# docker exec busybox-c1 ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue 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
18: eth0@if19: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue 
    link/ether 02:42:ac:11:00:02 brd ff:ff:ff:ff:ff:ff
    inet 172.17.0.2/16 brd 172.17.255.255 scope global eth0
       valid_lft forever preferred_lft forever
# 启动Container02
[root@localhost ~]# docker run -d -it --name busybox-c2 --net=container:busybox-c1  busybox:latest /bin/sh
# 查看Container02容器IP
[root@localhost ~]# docker exec busybox-c2 ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue 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
18: eth0@if19: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue 
    link/ether 02:42:ac:11:00:02 brd ff:ff:ff:ff:ff:ff
    inet 172.17.0.2/16 brd 172.17.255.255 scope global eth0
       valid_lft forever preferred_lft forever
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
可以看到两个容器IP是一样的,其实就类似于在宿主机直接启动2个应用,我们还可以通过查看容器的network namespace是否一样

注意
这种模式比host模式其实多了一层转发,但是隔离性比较好,有一个问题就是跟host模式一样,端口是公用的所以,使用的时候,需要提前规范化应用端口,避免端口冲突。
# 2.4 none 模式
--net=none指定
none模式下,Docker 容器拥有自己的 Network Namespace,但是,并不为 Docker 容器进行任何网络配置。也就是说,这个 Docker 容器没有网卡、IP、路由等信息。容器只有回环地址lo,因为无法联网与外部通信,所以此模式安全性比较好,一般不会使用。
# 2.5 自定义模式
我们知道,对于Docker默认的网络模式bridge,是没有办法指定容器IP的,只能有docker0虚拟网卡随机分配,要想自己定义容器网段,指定容器IP,并且在同一个宿主机下面,容器之间通过主机名也是无法ping通。对于自定义网络模式这些都是可以实现。
创建自定义网络
# 创建自定义网络
[root@localhost ~]# docker network create -d bridge --gateway 172.10.0.1 --subnet 172.10.0.0/16 mynet
# 查看创建的网卡
[root@localhost ~]# docker network ls
NETWORK ID     NAME      DRIVER    SCOPE
310f2cda0917   bridge    bridge    local
6da2e1705162   host      host      local
39b86b0c1cbf   mynet     bridge    local
a64d2d84fda0   none      null      local
2
3
4
5
6
7
8
9
10
- -d: 指网络桥接模式 默认也是bridge
- --gateway: 网关
- --subnet: 网络网段
- mynet: 自定义网络名称

使用自定义网络创建容器
# 创建容器busybox-c1 指定IP 为172.10.0.10
[root@localhost ~]# docker run -d -it --net mynet --ip 172.10.0.10 --name busybox-c1 --hostname busybox-c1 busybox:latest /bin/sh
# 查看容器busybox-c1 IP
# ## 容器IP为我们配置的IP地址
[root@localhost ~]# docker inspect busybox-c1 -f '{{ .NetworkSettings.Networks.mynet.IPAddress }}'
172.10.0.10
# 创建容器busybox-c2
[root@localhost ~]# docker run -d -it --net mynet --name busybox-c2 --hostname busybox-c2 busybox:latest /bin/sh
# 查看容器busybox-c2 IP
# ## 如果不知道IP,则会随机分配
[root@localhost ~]# docker inspect busybox-c2 -f '{{ .NetworkSettings.Networks.mynet.IPAddress }}'
172.10.0.2
2
3
4
5
6
7
8
9
10
11
12
13
14
测试主机名通信

# 2.6 Docker网络命令
# 查看docker网络列表
[root@localhost ~]# docker network ls
NETWORK ID     NAME      DRIVER    SCOPE
310f2cda0917   bridge    bridge    local
6da2e1705162   host      host      local
39b86b0c1cbf   mynet     bridge    local
a64d2d84fda0   none      null      local
# 查看网络详细
[root@localhost ~]# docker network inspect 39b86b0c1cbf
[
    {
        "Name": "mynet",
        "Id": "39b86b0c1cbffe813c5b290942eb12a27ba3f71aaaa5488d1bffe3e7db94e424",
        "Created": "2023-02-16T15:09:23.879290158+08:00",
        "Scope": "local",
        "Driver": "bridge",
        "EnableIPv6": false,
        "IPAM": {
            "Driver": "default",
            "Options": {},
            "Config": [
                {
                    "Subnet": "172.10.0.0/16",
                    "Gateway": "172.10.0.1"
                }
            ]
        },
        "Internal": false,
        "Attachable": false,
        "Ingress": false,
        "ConfigFrom": {
            "Network": ""
        },
        "ConfigOnly": false,
        "Containers": {
            "86c20e829811ec3a87c6b665cac9e9220775a610b4c4b4275411afcd52384370": {
                "Name": "busybox-c1",
                "EndpointID": "05cdb7011c4eda1907cb8aa21bb55fe8c3e844d20dd211ab9084897fd952c986",
                "MacAddress": "02:42:ac:0a:00:0a",
                "IPv4Address": "172.10.0.10/16",
                "IPv6Address": ""
            },
            "8e1bee9cbd3c6f7acbbc5409b133f9bfcd551ae3456f1b559d196e181d30e167": {
                "Name": "busybox-c2",
                "EndpointID": "c85b84ee6bc9bc3d88edaac95651e3bd9ef44fa689e1603dd3a0681271aa4170",
                "MacAddress": "02:42:ac:0a:00:02",
                "IPv4Address": "172.10.0.2/16",
                "IPv6Address": ""
            }
        },
        "Options": {},
        "Labels": {}
    }
]
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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
# 三、网络通信原理{#index}
说明
上面介绍了Docker的几种网络模式,这几种模式其实归类起来,就两种情况,一种是容器共享同一个网络命名空间(host、container),另一种是容器独立网络空间(bridge、none)。
# 3.1 同网络命名空间
这种模式其实很好理解,因为不同的容器都属于一个网络命名空间,等于网络、端口都是共享使用,看到的网络设备是一样的,都可以看到Loopback设备可以理解为在同一个host主机模式上,不同应用之间访问,直接使用lo地址即可。
# 3.2 不同网络命名空间
Docker在启动时,会在宿主机创建一个虚拟的网桥docker0,Docker创建容器时,会根据docker0定义的网段为容器随机分配一个IP地址。该docker0网桥也是这些容器的网关,由于同一个宿主机内的容器IP都是由docker0分配IP,并且都接入到同一个网桥,因此,这些容器之间通信直接使用容器IP即可。
查看默认网桥
[root@localhost ~]# ifconfig 
docker0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 172.17.0.1  netmask 255.255.0.0  broadcast 172.17.255.255
        inet6 fe80::42:e1ff:fefc:20ef  prefixlen 64  scopeid 0x20<link>
        ether 02:42:e1:fc:20:ef  txqueuelen 0  (Ethernet)
        RX packets 5901  bytes 319886 (312.3 KiB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 6256  bytes 9299137 (8.8 MiB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0
ens192: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 10.66.31.130  netmask 255.255.255.0  broadcast 10.66.31.255
        inet6 fe80::1a49:7ad8:da9b:d0b4  prefixlen 64  scopeid 0x20<link>
        inet6 fe80::f3f0:e911:6ed:35db  prefixlen 64  scopeid 0x20<link>
        ether 00:50:56:a4:78:54  txqueuelen 1000  (Ethernet)
        RX packets 176158  bytes 215680246 (205.6 MiB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 132485  bytes 14988051 (14.2 MiB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0
lo: flags=73<UP,LOOPBACK,RUNNING>  mtu 65536
        inet 127.0.0.1  netmask 255.0.0.0
        inet6 ::1  prefixlen 128  scopeid 0x10<host>
        loop  txqueuelen 1000  (Local Loopback)
        RX packets 44  bytes 5381 (5.2 KiB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 44  bytes 5381 (5.2 KiB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0
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
与外部通信
# 因为docker网桥是虚拟网络设备,所以对于外部来说,Docker容器IP对外是不可见的,如果想要被外部访问,可以把应用端口通过-p的方式映射到宿主机上
# ## 这样我们直接访问宿主机的80端口也就可以访问到容器中的内容
# ## -P 如果使用大P则,无需知道端口映射,会随机映射到宿主机的一个端口
[root@localhost ~]# docker run -d --name nginx-b1 -p 80:80 nginx:latest
2
3
4
端口映射,背后的原理其实就是DNAT转换,我们可以看下iptable规则

总结

笔记
- Docker将 veth pair设备的一端放在新创建的容器中,并命名为eth0 (容器的网卡),另一端放在主机中,以veth*这样类似的名字命名,并将这个网络设备映射加入到docker0 网桥中。 
- 对于同一个容器中docker创建的这对网络设备,无论在哪一端发送消息对端都可以收到,因此eth0与veth可以无障碍通信 
- 对于容器之间通信(也就是不同veth之间通信),通过虚拟网桥转发通信,通过arp协议得到目标IP地址的mac地址,通过mac地址进行转发 
- 对于容器与外部,通过docker0与宿主机eth0通过DNAT进行转发通信 
