Docker网络

188 阅读33分钟

Network Namespace介绍

  1. Network Namespace 是 Linux 内核提供的用于实现网络虚拟化的重要功能,它能创建多个隔离的网络空间,每个独立的网络空间内的防火墙、网卡、路由表、邻居表、协议栈都是独立的。不管是虚拟机还是容器,当运行在独立的命名空间时,就像是一台单独的主机一样。

    • ip netns是ip命令的子命令,和Network Namespace 有关的操作都是在ip netns下进行的
    [root@dawn dockerfile]# 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
    
    • 创建一个network namespase,如果已经存在则会抛错Cannot create namespace。
    • ip netns 命令创建的Network Namespace 会出现在 /var/run/netns/ 目录下,如果需要管理其他不是 ip netns 创建的 network namespace,只要在这个目录下创建一个指向对应 network namespace 文件的链接即可。 image.png
  2. 对于每个 network namespace 来说,都会有自己独立的网卡、路由表、ARP 表、iptables 等和网络相关的资源。ip netns exec 命令可以在对应的 network namespace 中执行命令,比如查看指定network namespace 中有哪些网卡。 image.png

    • 注意:每个 namespace 在创建的时候会自动创建一个 lo 的 interface,它的作用和 linux 系统中看到的 lo 一样,都是为了实现 loopback 通信。如果希望 lo 能工作就必须启用loopback interface。
    • 启用loopback interface image.png

两个network namespace之间的通信

Network Namespace实现了网络的隔离,默认情况下,Network Namespace 是不能和主机网络或者其他 Network Namespace 通信的。

linux 提供了veth pair将一对网络接口分别置于两个命名空间中。 veth pair 可以当做是双向的 pipe(管道),从一端数据可直接被另外一端接收到;或者也可以想象成两个 namespace 直接通过一个特殊的虚拟网卡连接起来从而实现了两个Network Namespace 之间的通信。

使用 ip link add type veth 可以创建一对 veth pair ,需要注意的是 veth pair 无法单独存在,删除其中一个,另一个也会自动消失。

image.png

通过veth pair实现两个网络之间通信的步骤:

  1. 创建两个network namespace分别命名为 dawn0, dawn1 image.png

  2. 使用ip link add type veth命令创建一对 "veth pair": veth0和veth1 image.png

    • 我们也可以指定即将生成这一对veth的名称ip link add veth-00 type veth peer name veth-01 image.png
  3. 使用ip link set DEV netns NAME命令,把这对 veth pair 分别放到两个network namespace里面 image.png

  4. 为veth pair 配置ip 地址,并启用 image.png

  5. 通过ping命令测试两个network namespace的连通性 image.png

多个network namespace 之间通信

通过veth pair可以实现两个network namespace之间的通信,但是当多个 namespace 需要通信的时候,就需要借助交换机和路由器了。因为这里要考虑的只是同一个网络,所以只用到交换机的功能。

linux 提供了虚拟交换机的功能,linux中的bridge是一个虚拟网络设备,具有网络设备的特征,可以配置IP、MAC地址等,同时,它也是一个虚拟交换机,和物理交换机有类似的功能。

对于普通的网络设备来说,只有两端,从一端进来的数据会从另一端出去,如物理网卡从外面网络中收到的数据会转发给内核协议栈,而从协议栈过来的数据会转发到外面的物理网络中。而bridge不同,bridge有多个端口,数据可以从任何端口进来,进来之后从哪个口出去和物理交换机的原理差不多,要看mac地址。

image.png

通过bridge来实现多个network namespace之间的通信步骤:

  1. 创建一个bridge br0 并开启。
    image.png
    [root@dawn ~]# ip link add br0 type bridge
    [root@dawn ~]# ip link set dev br0 up
    
    • 安装bridge-utils,这个工具包提供了管理和配置网络桥接的功能yum install bridge-utils
    • 新创建br0的interface为空
    [root@dawn ~]# brctl show
    bridge name     bridge id               STP enabled     interfaces
    br0             8000.000000000000       no
    docker0         8000.024203374061       no
    
  2. 创建3个network namespace,分别命名为net0,net1,net2
    [root@dawn ~]# ip netns add net0
    [root@dawn ~]# ip netns add net1
    [root@dawn ~]# ip netns add net2
    [root@dawn ~]# ip netns list
    net2
    net1
    net0
    
  3. 创建veth pair,将其一端放入net0,为其设置ip地址,并开启,另一端放入br0,开启;
    [root@dawn ~]# ip link add type veth
    [root@dawn ~]# ip link set dev veth1 netns net0
    [root@dawn ~]# ip netns exec net0 ip link set dev veth1 name eth0
    [root@dawn ~]# ip netns exec net0 ip addr add 10.0.1.1/24 dev eth0
    [root@dawn ~]# ip netns exec net0 ip link set dev eth0 up
    [root@dawn ~]#
    [root@dawn ~]# ip link set dev veth0 master br0
    [root@dawn ~]# ip link set dev veth0 up
    [root@dawn ~]# bridge link
    6: veth0 state UP @(null): <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 master br0 state forwarding priority 32 cost 2
    
  4. 对net1,net2做同样的操作
    [root@dawn ~]# ip link add type veth
    [root@dawn ~]# ip link set dev veth2 netns net1
    [root@dawn ~]# ip netns exec net1 ip link set dev veth2 name eth0
    [root@dawn ~]# ip netns exec net1 ip addr add 10.0.1.2/24 dev eth0
    [root@dawn ~]# ip netns exec net1 ip link set dev eth0 up
    [root@dawn ~]#
    [root@dawn ~]# ip link set dev veth1 master br0
    [root@dawn ~]# ip link set dev veth1 up
    [root@dawn ~]# bridge link
    6: veth0 state UP @(null): <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 master br0 state forwarding priority 32 cost 2
    8: veth1 state UP @(null): <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 master br0 state forwarding priority 32 cost 2
    [root@dawn ~]#
    [root@dawn ~]# ip link add type veth
    [root@dawn ~]# ip link set dev veth3 netns net2
    [root@dawn ~]# ip netns exec net2 ip link set dev veth3 name eth0
    [root@dawn ~]# ip netns exec net2 ip addr add 10.0.1.3/24 dev eth0
    [root@dawn ~]# ip netns exec net2 ip link set dev eth0 up
    [root@dawn ~]#
    [root@dawn ~]# ip link set dev veth2 master br0
    [root@dawn ~]# ip link set dev veth2 up
    [root@dawn ~]# bridge link
    6: veth0 state UP @(null): <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 master br0 state forwarding priority 32 cost 2
    8: veth1 state UP @(null): <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 master br0 state forwarding priority 32 cost 2
    10: veth2 state UP @(null): <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 master br0 state forwarding priority 32 cost 2
    
    [root@dawn ~]# brctl show
    bridge name     bridge id               STP enabled     interfaces
    br0             8000.462dde136df6       no              veth0
                                                            veth1
                                                            veth2
    docker0         8000.024203374061       no
    [root@dawn ~]#
    
  5. 通过ping命令测试network namespace的连通性
    [root@dawn ~]# ip netns exec net0 ping 10.0.1.2
    PING 10.0.1.2 (10.0.1.2) 56(84) bytes of data.
    64 bytes from 10.0.1.2: icmp_seq=1 ttl=64 time=0.078 ms
    64 bytes from 10.0.1.2: icmp_seq=2 ttl=64 time=0.108 ms
    ^C
    --- 10.0.1.2 ping statistics ---
    2 packets transmitted, 2 received, 0% packet loss, time 1145ms
    rtt min/avg/max/mdev = 0.078/0.093/0.108/0.015 ms
    [root@dawn ~]# ip netns exec net2 ping 10.0.1.1
    PING 10.0.1.1 (10.0.1.1) 56(84) bytes of data.
    64 bytes from 10.0.1.1: icmp_seq=1 ttl=64 time=0.088 ms
    64 bytes from 10.0.1.1: icmp_seq=2 ttl=64 time=0.115 ms
    ^C
    --- 10.0.1.1 ping statistics ---
    2 packets transmitted, 2 received, 0% packet loss, time 1001ms
    rtt min/avg/max/mdev = 0.088/0.101/0.115/0.016 ms
    
    

Docker中的网络

Docker 网络架构由三个主要部分构成:CNM、Libnetwork 与 Driver。

CNM 是规范,Libnetwork 是 CNM 规范的实现,Driver 是 Libnetwork 中不同网络模式的实现。

  • bridge:容器具有独立的namespace、独立的网络接口与IP,默认的网络模式
  • none:容器具有独立的 namespace,但没有连接外网的网络接口,也就不可能会有IP
  • host:容器没有独立的 namespace,没有独立的网络接口与 IP,全部与宿主机共享,加入网络的容器无需再暴露端口号,其端口号直接就在宿主机的 namespce中

image.png

  • CNM,Container Network Model,容器网络模型,其是一种网络连接的解决方案,是一种设计规范、设计标准,其规定了 Docker 网络的基础组成要素。

    • CNM 中定义了三个基本要素:沙盒 Sandbox,终端 Endpoint 与网络 Network。
      • 沙盒:一个独立的网络栈,其中包括以太网接口、端口号、路由表、DNS 配置等。Linux Network Namespace 是沙盒的标准实现。
      • 终端:虚拟网络接口,主要负责创建连接,即将沙盒连接到网络上。一个终端只能接入某一个网络。
      • 网络:802.1d 网桥的软件实现,是需要交互的终端的集合。
  • Libnetwork:CNM 是设计规范,而 Libnetwork 是开源的、由 Go 语言编写的、跨平台的 CNM 的标准实现。Libnetwork 除了实现了 CNM 的三个组件,还实现了本地服务发现、容器负载均衡,以及网络控制层与管理层功能。

  • Driver:每种不同的网络类型都有对应的不同的底层 Driver,这些 Driver 负责在主机上真正实现需要的网络功能,例如创建 veth pair 设备等。不过,无论哪种网络类型,其工作方式都是类似的。通过调用 Docker 引擎的 API 发出请求,然后由 Libnetwork 做出框架性的处理,然后将请求转发给相应的 Driver。

通过 docker network ls 命令可以查看当前主机所连接的网络及网络类型。

    [root@dawn ~]# docker network ls
    NETWORK ID     NAME      DRIVER    SCOPE
    4547c2396f6d   bridge    bridge    local
    991375c15ff9   host      host      local
    99c601b6d0b5   none      null      local

查看docker中某一个网络的详情信息 docker network inspect 网络ID

[root@dawn ~]# docker network inspect 4547c2396f6d
[
    {
        "Name": "bridge",
        "Id": "4547c2396f6db07397d476dcb9a672e3e72b306e4a5a15e99b795b16e0bcd68b",
        "Created": "2024-06-30T22:11:38.070017934Z",
        "Scope": "local",
        "Driver": "bridge",
        "EnableIPv6": false,
        "IPAM": {
            "Driver": "default",
            "Options": null,
            "Config": [
                {
                    "Subnet": "172.17.0.0/16",
                    "Gateway": "172.17.0.1"
                }
            ]
        },
        "Internal": false,
        "Attachable": false,
        "Ingress": false,
        "ConfigFrom": {
            "Network": ""
        },
        "ConfigOnly": false,
        "Containers": {},
        "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 run创建Docker容器的同时,系统会自动为容器创建一个和主机隔离开的network namespace, 以实现网络的隔离.

Docker中的网络模式主要有4种:Bridge Network,  Host Network,  None Network,  以及Overlay Network.  其中,前三种网络模式用于单机网络, Overlay用于多机网络

在创建Docker容器时,可以用参数"--net=网络模式" 来指定Docker容器间的网络模式,默认的网络通信模式为Bridge模式

Bridge Network

bridge network是默认Docker容器间的网络模式,也称为单机桥接网络。其实现原理就是通过一个bridge实现的,在安装Docker后,Docker主机中会多出一个名为 "docker0" 的bridge,以用于docker容器间的网络通信

[root@dawn ~]# 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: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
    link/ether 52:54:00:4d:77:d3 brd ff:ff:ff:ff:ff:ff
    inet 10.0.2.15/24 brd 10.0.2.255 scope global noprefixroute dynamic eth0
       valid_lft 83521sec preferred_lft 83521sec
    inet6 fe80::5054:ff:fe4d:77d3/64 scope link
       valid_lft forever preferred_lft forever
3: eth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
    link/ether 08:00:27:a6:0d:56 brd ff:ff:ff:ff:ff:ff
    inet 192.168.10.200/24 brd 192.168.10.255 scope global noprefixroute eth1
       valid_lft forever preferred_lft forever
    inet6 fe80::a00:27ff:fea6:d56/64 scope link
       valid_lft forever preferred_lft forever
4: docker0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN group default
    link/ether 02:42:03:37:40:61 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

单机中docker容器之间通信的原理

以bridge模式创建一个具有网络操作的docker容器时,系统会通过一对veth pair将docker容器和docker0连接起来,以便实现单机中docker容器之间的通信,其拓扑结构如图:

image.png

查看网络路由,得知即将发送到172.17.x.x范围内的数据包都会使用docker0网络接口。

  • 当安装 Docker 后,Docker 会创建一个默认的网络桥接接口 docker0,并为其分配一个子网(如 172.17.0.0/16)。
  • 所有通过 Docker 创建的容器都会获取 172.17.x.x 范围内的 IP 地址,并且能够通过 docker0 接口与其他容器进行通信。
  • Docker 内部的网络路由设置使得这些容器可以相互访问,而不需要通过主机的网络接口或外部网关。
[root@dawn vagrant]# route -n
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
0.0.0.0         10.0.2.2        0.0.0.0         UG    100    0        0 eth0
10.0.2.0        0.0.0.0         255.255.255.0   U     100    0        0 eth0
172.17.0.0      0.0.0.0         255.255.0.0     U     0      0        0 docker0
192.168.10.0    0.0.0.0         255.255.255.0   U     101    0        0 eth1

docker容器如何访问外网

单机中的docker容器要访问到外面的公网,则是通过NAT技术实现的,docker主机可以通过eth0访问外网,通过NAT将内部ip转换的成eth0上即可实现容器的外网访问.

NAT(Network Address Translation)允许一个整体机构以一个公用IP(Internet Protocol)地址出现在Internet上。它是一种把内部私有网络地址(IP地址)翻译成合法网络IP地址的技术,NAT在一定程度上,能够有效的解决公网地址不足的问题.

image.png

docker容器之间的 link 

容器之间的ping除了可以直接 ping Ip,还可以直接 ping 容器名称,ping名称的方式在生产中非常重要。因为容器的 IP 可能会发生变化,但容器名称一般是不会变的。如果服务通过 IP 与容器相连接,那么一旦容器 IP 变化,则该服务将连接不上容器。但如果是通过容器名称相连接,那么无论容器 IP 如何变化,都将不影响服务与容器的连接。

容器间的访问必须知道ip和端口,一般服务的端口号是固定的,但ip是不确定的.这时就可通过link机制实现容器间的访问。

对于自定义的 bridge 网络,其具有一个特性:该网络上的容器可以通过容器名互 ping。但默认的 bridge 网络是不行的。如果在默认的 bridge 网络上实现通过容器名进行的连接,则需要创建容器时通过--link 选项指定。

-- 创建一个名为test1的容器
docker run -d --name test1 busybox /bin/sh -c "while true; do sleep 3600; done"
 
-- 创建一个名为test2的容器,并将其link到test1上
docker run -d --name test2 --link test1 busybox /bin/sh -c "while true; do sleep 3600; done"
 
-- 此时,test2可以直接通过名字"test1"来访问test1
docker exec -it test2 /bin/sh 
ping test1
 
注意:通过ip地址,test1和test2是可以互相访问的,因为它们均连接到了docker0上,
			但通过名字,只能是test2访问test1,link带有方向性的
                        
[root@dawn ~]# docker exec -it test2 ping test1
PING test1 (172.17.0.2): 56 data bytes
64 bytes from 172.17.0.2: seq=0 ttl=64 time=0.064 ms
64 bytes from 172.17.0.2: seq=1 ttl=64 time=0.185 ms                       
[root@dawn ~]# docker exec -it test1 ping test2
ping: bad address 'test2'

除了默认的"docker0"这个docker bridge外,我们也可以手动自己为docker容器创建bridge docker network create -d bridge bridgename

[root@dawn ~]# docker network create -d bridge bridge0
90549f893f5f6c09e6f8fd4450bba362b8c0560323f18ae79f4868d93cda8ef6
[root@dawn ~]# docker network ls
NETWORK ID     NAME      DRIVER    SCOPE
4547c2396f6d   bridge    bridge    local
90549f893f5f   bridge0   bridge    local
991375c15ff9   host      host      local
99c601b6d0b5   none      null      local
[root@dawn ~]#

此时,我们可以看到除了docker自带的bridge, host, none三个网络外,多出一个我们自己创建的网络.在创建docker容器时,我们可以通过参数 --network 指定容器连接到某个网络上,如果未指定,则默认连接到自带的bridge(docker0)上

docker run -d --name test3 --network bridge0 busybox /bin/sh "while true; do sleep 3600; done"

也可以通过命令将已经连接在某个网络的容器,再连接到另一个网络上,将该容器连接到了两个网络上

docker network connect bridge0 test2

值得注意的是,连接到用户自定义的bridge(非docker0)上的docker容器是可以通过名字访问对方


docker exec -it test3 ping test2
 
docker exec -it test2 ping test3

在Docker中创建一个bridge网络

  1. 创建 driver 为 bridge 的网络
docker network create -d bridge my-bridge

#查看docker网络
docker network ls
或
brctl show
  1. 将容器连接到指定的网络中
创建容器指定网络为新创建的网络
docker run -d --name test3 --network my-bridge busybox /bin/sh -c "while true; do sleep 3600; done"

#查看创建的网络是否有新的interfaces
brctl show  
或
docker network inspect my-bridge
  1. 将test1 和 test2 link 到 my-bridge 上
#连接语法
docker network connect [network_name] [container_name]

docker network connect my-bridge test1

查看是否连接上
docker network inspect my-bridge     查看里面的 containers 是否包含 test1 和 test3
docker network inspect bridge     查看到container 内 也包含了 test1 和 test2

此时在 test1 容器内是ping通test2和test3的ip地址
而在用户创建的 network (eg:my-bridge) 内是可以使用 ping [container_name]的

创建共享网络

在创建容器时可以指定其与某已经存在的容器共享 Network Namespace,但要求该已经存在的容器采用的是 bridge 网络模式。

创建了一个的容器test.1,并共享 test.0 容器的 Network Namespace。

[root@dawn ~]# docker run -d --name test.0 --network bridge0 busybox /bin/sh -c "while true;do sleep 3600; done"
a394d62ece181fdd150cc64ac4aace4a5cbc885dc7014a384e1419eb917b0742
[root@dawn ~]# docker run -d --name test.1 --network container:test.0 busybox /bin/sh -c "while true; do sleep 3600; done"
d9f5cdee8ea11b10589f26ebf944dbfe8a2116e0d0846de424fd1bc001a02346

查看两个容器的接口情况,发现完全相同。

[root@dawn ~]# docker exec test.0 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
    inet6 ::1/128 scope host
       valid_lft forever preferred_lft forever
30: eth0@if31: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue
    link/ether 02:42:ac:12:00:04 brd ff:ff:ff:ff:ff:ff
    inet 172.18.0.4/16 brd 172.18.255.255 scope global eth0
       valid_lft forever preferred_lft forever
[root@dawn ~]# docker exec test.1 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
    inet6 ::1/128 scope host
       valid_lft forever preferred_lft forever
30: eth0@if31: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue
    link/ether 02:42:ac:12:00:04 brd ff:ff:ff:ff:ff:ff
    inet 172.18.0.4/16 brd 172.18.255.255 scope global eth0
       valid_lft forever preferred_lft forever
[root@dawn ~]#

查看容器 test.1 的详情,可以发现,其没有自身的网络设置。因为其共享的 test.0 容器的网络设置。

docker inspect test.1

image.png

docker inspect test.0 image.png

docker端口映射

将docker容器中某个服务的端口映射到其docker主机的某个端口上,则可以通过docker主机的端口获得容器提供的服务

docker run -d -p 80:80 ngixn

Host Network

将容器连接到host网络,则该容器没有自己独立的network namespace。其network namespace与其docker主机共享,容器的网络接口将和其docker主机的网络接口保持一致。

创建一个Host Network

# 创建一个host网络的container test1 
docker run -d --name test1 --network host busybox /bin/sh -c "while true; do sleep 3600; done" 
# 查看host网络的详情可以看到containers中含有test1 (发现没有mac地址和 IP 地址)
docker network inspect host
#查看test1的网络情况发现使用的接口和虚拟机的网络是一样的(意味着端口会冲突)
docker exec test1 ip a
  • 端口映射:由于容器与宿主机共用一个 Network Namespace,所以无论是 IP 还是应用程序的 Port,容器与宿主机的都是相同的,所以对于容器中应用程序的 Port 不存在映射的问题,host 中的 Port 与容器中的 Port 相同。

    image.png 上面的tomcat容器由于指定了网络模式为host,在启动时指定的端口映射不会起作用。系统给出的 WARNING 指出,当使用 host 网络模式时,已发布的端口号被丢弃。此时,通过仍需通过 8080 端口访问。

None Network

将容器连接到none网络,即意味着该容器是孤立的,即外部其它容器无法访问该容器,访问该容器的方法只有通过命令docker exec -it dockerName /bin/sh实现。这种容器应用一般只用于本地访问安全存储密码等等

image.png

创建一个None Network

# 创建一个 none 网络的 container test.2 
docker run --name test.2 --network none busybox /bin/sh -c "while true; do sleep 3600; done" 
# 查看none网络的详可以看到 containers 中含有 test.2
docker network inspect none
# 查看test1 的网络情况可以看到没有veth的接口 (表示该容器是一个孤立的 namespace)
docker exec test.2 ip a

image.png

image.png

Overlay Network

overlay 实现原理介绍

overlay是基于VXLAN技术可以实现在一个局域网中的多个docker主机上的容器之间通信。主要解决 Bridge 网络模式下的容器只能和同一台主机上的容器通信,无法和其他主机上的容器通信的问题。

网络模型:

image.png

image.png

实现原理: image.png

  1. VTEP 设备的创建:在每个安装 Docker 的宿主机上,创建一个 VXLAN 封装点(VTEP,Virtual Tunnel Endpoint)设备。VTEP 负责对数据包进行 VXLAN 封装和解封装操作。

  2. 监听端口:在每个宿主机上,启动一个进程来监听 UDP 端口 4789。这是 VXLAN 标准使用的端口,用于接收和发送 VXLAN 封装的数据包。

  3. 数据包流向

    • 容器到网桥:Docker 容器生成的数据包首先会经过宿主机上的网桥(Bridge)。网桥负责将容器网络流量转发到宿主机的网络接口。
    • 网桥到 VTEP:网桥将数据包转发到 VTEP 设备。VTEP 设备从网桥接收到原始数据包。
  4. 封装

    • VXLAN 封装:VTEP 设备对接收到的数据包进行 VXLAN 封装。这包括在原始数据包的外层添加一个 VXLAN 头部,然后将 VXLAN 数据包封装在 UDP 数据包中。VXLAN 头部包含了 VXLAN 网络标识符(VNI)和其他相关信息。
    • UDP 封装:封装后的数据包通过 UDP 协议发送。VXLAN 数据包的外层是 UDP 数据包,目标端口是 4789。
  5. 传输

    • 发送到目标宿主机:封装后的 UDP 数据包通过物理网络发送到目标宿主机上的 VTEP 设备。
  6. 解封装

    • 目标宿主机的 VTEP:目标宿主机上的 VTEP 设备接收到 UDP 数据包后,去除外层的 VXLAN 头部和 UDP 封装,恢复原始数据包。
    • 数据包到网桥/容器:恢复后的数据包将被传递到目标宿主机上的网桥或容器接口,以便到达目标 Docker 容器。

这样,通过 VTEP 设备和 VXLAN 封装技术,数据包可以在不同宿主机上的 Docker 容器之间进行透明的跨主机通信,就像它们在同一个局域网内一样。

docker swarm 集群的overlay网络

image.png

当我们装好swarm集群后,会创建两个网络 Ingressdocker_gwbridge;此外,还会创建两个网络命名空间(Network Namespace)ingress_sbox namespaceIngress namespace 。这两个网络命名空间不直接关联任何容器,而是用于处理Overlay网络以及数据的转发。

  • Ingress namespace image.png
    • vxlan0:vxlan0就是VTEP设备用来封装接收到的数据并处理成外部宿主机的ip+端口,然后,将数据包发送到指定外部主机。
    • veth1:连接 ingress_sbox namespace的veth,负责接收ingress_sbox转发的数据,并将数据通过vxlan0 发送给外部网络
  • ingress_sbox namespace image.png 主要用于连接网桥并配置IPVS(IP Virtual Server)的负载均衡策略。该命名空间包含两个虚拟以太网对接口(veth pairs)。一个veth接口连接到docker_gwbridge网络,用于接收来自docker_gwbridge的流量。接收到流量后,ingress_sbox使用IPVS的负载均衡算法(例如vs rr轮询策略)来选择目标容器的地址,并将数据包通过另一个veth接口发送到ingress网络中的网桥。这种方式实现了Swarm集群中Ingress流量的负载均衡和流量转发。
  • container namespace:正常容器使用bridge网络,container namespace默认只会有一对veth设备与docker0网桥连接。如果容器使用Ingress网络,container namespace中有两对veth设备,一端连接到docker_gwbridge网桥中(容器可直接通过宿主机访问外部网络),另一端连接到 Ingress namespace 中(容器到容器之间的访问)。

当运行docker swarm init 后我们发现多如下两种网络类型

  • ingress:是 Docker Swarm 集群中使用的一种内置 overlay 网络,使加入到ingress的容器处于同一个网络之中,并且ingress控制了流量入口,提供了服务发现和负载均衡功能,使得服务能够在不同节点之间通过一个虚拟网络进行通信,并确保外部请求能够被均匀地分配到服务的各个副本上。
  • docker_gwbridge:在 Swarm 模式下用来连接 Overlay 网络(如 ingress 网络)和主机网络的桥接网络。它负责将来自 Overlay 网络的流量转发到主机网络,充当容器网络与主机网络之间的网关。这使得 Swarm 集群中的容器能够访问外部网络(如互联网),同时也支持主机与容器之间的网络通信。

swarm manager 节点的作用 image.png

image.png

docker inspect ingress

[root@dawn-80 vagrant]# docker inspect ingress
[
    {
        "Name": "ingress",
        "Id": "emr2wsf54530zfb81fgku0itf",
        "Created": "2024-08-21T14:04:09.097221263Z",
        "Scope": "swarm",
        "Driver": "overlay",
        "EnableIPv6": false,
        "IPAM": {
            "Driver": "default",
            "Options": null,
            "Config": [
                {
                    "Subnet": "10.0.0.0/24",
                    "Gateway": "10.0.0.1"
                }
            ]
        },
        "Internal": false,
        "Attachable": false,
        "Ingress": true,
        "ConfigFrom": {
            "Network": ""
        },
        "ConfigOnly": false,
        // 连接到该网络的所有容器
        "Containers": {
            "18b0b18756505d614245eae8a2841eb97241e198486fc1487040f89f270c6ff9": {
                "Name": "nginx.1.26u764lec0xf9pxfa9inw0yk4",
                "EndpointID": "8adae66526eee389ea692c5cef678a90d848dffec8a3f07d447365868bfca5b4",
                "MacAddress": "02:42:0a:00:00:33",
                "IPv4Address": "10.0.0.51/24",
                "IPv6Address": ""
            },
            "26e647cd036a26fd2285e82145b79cb7e82337f89504c06ee1859162113b01bb": {
                "Name": "nginx.3.q2f8r9en7s4r3dkwb6jss9dlu",
                "EndpointID": "dc1ea61b8506cbeb4efd56207f13a33a8e4ac305cdb5e3fcfa245c8d8bc278ab",
                "MacAddress": "02:42:0a:00:00:34",
                "IPv4Address": "10.0.0.52/24",
                "IPv6Address": ""
            },
            "778afac209fa1a28cfbf32d02329de5da017dbb6f98f014157552b48cd7192ea": {
                "Name": "nginx.4.blhmq1cyy066hjvd356sef1sr",
                "EndpointID": "a8b5c9bba023d5712362948438b40e03b4bd31aeba58b95e79b338867f082373",
                "MacAddress": "02:42:0a:00:00:32",
                "IPv4Address": "10.0.0.50/24",
                "IPv6Address": ""
            },
            // 并非一个容器,而是一个用于处理集群中的流量路由。
            "ingress-sbox": {
                "Name": "ingress-endpoint",
                "EndpointID": "d27c8cf1d728f3b7f8824fe9abbf9279e0645203007a0603403a47bfa925a3d3",
                "MacAddress": "02:42:0a:00:00:02",
                "IPv4Address": "10.0.0.2/24",
                "IPv6Address": ""
            }
        },
        "Options": {
            "com.docker.network.driver.overlay.vxlanid_list": "4096"
        },
        "Labels": {},
        // 集群中的其他节点
        "Peers": [
            {
                "Name": "5c6be17c6d16",
                "IP": "192.168.10.80"
            },
            {
                "Name": "cc51e316feda",
                "IP": "192.168.10.81"
            },
            {
                "Name": "b92d7dfb7190",
                "IP": "192.168.10.82"
            }
        ]
    }
]

docker inspect docker_gwbridge

[root@dawn-80 vagrant]# docker inspect docker_gwbridge
[
    {
        "Name": "docker_gwbridge",
        "Id": "8f9c081e17eebcadc569da886978902d0b105e0ee72f64f61998f6144f27e361",
        "Created": "2024-08-20T23:27:00.982774232Z",
        // 作用于本机,不可被其他 Docker 主机所访问
        "Scope": "local",
        "Driver": "bridge",
        "EnableIPv6": false,
        "IPAM": {
            "Driver": "default",
            "Options": null,
            "Config": [
                {
                    "Subnet": "172.18.0.0/16",
                    "Gateway": "172.18.0.1"
                }
            ]
        },
        "Internal": false,
        "Attachable": false,
        "Ingress": false,
        "ConfigFrom": {
            "Network": ""
        },
        "ConfigOnly": false,
        "Containers": {
            "18b0b18756505d614245eae8a2841eb97241e198486fc1487040f89f270c6ff9": {
                "Name": "gateway_f4adb5b14384",
                "EndpointID": "61c7808ca6bec808db3d51b4a7e077fe9734d6af73aa42995b05ca0d6b8831ab",
                "MacAddress": "02:42:ac:12:00:05",
                "IPv4Address": "172.18.0.5/16",
                "IPv6Address": ""
            },
            "26e647cd036a26fd2285e82145b79cb7e82337f89504c06ee1859162113b01bb": {
                "Name": "gateway_48ad67e025c0",
                "EndpointID": "7dd141d64d5bbd0e94b0f012d45e9ac07851c7c107df239b5a22b211570a7c40",
                "MacAddress": "02:42:ac:12:00:04",
                "IPv4Address": "172.18.0.4/16",
                "IPv6Address": ""
            },
            "778afac209fa1a28cfbf32d02329de5da017dbb6f98f014157552b48cd7192ea": {
                "Name": "gateway_e7c6c27d44ab",
                "EndpointID": "b2c636363e0af6891106f558b8ca45562ee4df39174751ee41109b99a2c81fce",
                "MacAddress": "02:42:ac:12:00:03",
                "IPv4Address": "172.18.0.3/16",
                "IPv6Address": ""
            },
            // IP 地址是 `172.18.0.2`,用于处理 Docker 的 Ingress 网络流量
            "ingress-sbox": {
                "Name": "gateway_ingress-sbox",
                "EndpointID": "6d4b239732cc3aaf5a28e7d7b727748e6442107981bc0b7d4b0dc7d19569e66f",
                "MacAddress": "02:42:ac:12:00:02",
                "IPv4Address": "172.18.0.2/16",
                "IPv6Address": ""
            }
        },
        "Options": {
            // 表示网络不允许容器之间的互相通信
            "com.docker.network.bridge.enable_icc": "false",
            // 表示启用 IP 伪装,使得容器可以通过主机的 IP 地址访问外部网络。
            "com.docker.network.bridge.enable_ip_masquerade": "true",
            // 网络的桥接设备名称为 `docker_gwbridge`。
            "com.docker.network.bridge.name": "docker_gwbridge"
        },
        "Labels": {}
    }
]

查看网络命名空间


# 1-emr2wsf545 就是 ingress namepace
# 48ad67e025c0  e7c6c27d44ab  f4adb5b14384 每个容器的的namespace
[root@dawn-80 vagrant]# ls /var/run/docker/netns/
1-emr2wsf545  48ad67e025c0  e7c6c27d44ab  f4adb5b14384  ingress_sbox
  • swarm集群规划
nodeip
master192.168.10.80
worker192.168.10.81
worker192.168.10.82
  • 初始化集群
[root@dawn-80 vagrant]# docker swarm init --advertise-addr 192.168.10.80
Swarm initialized: current node (cjwp8i1o8gvy7dl54s1xsswbp) is now a manager.

To add a worker to this swarm, run the following command:

    docker swarm join --token SWMTKN-1-1p2yr9a9987p8goit14lmksb7z039qt7gid2kelss2x5mjc8fd-39mnquvgxdemyyf3e9ogdxh0v 192.168.10.80:2377

To add a manager to this swarm, run 'docker swarm join-token manager' and follow the instructions.
  • 加入集群
[root@dawn-81 vagrant]# docker swarm join --token SWMTKN-1-1p2yr9a9987p8goit14lmksb7z039qt7gid2kelss2x5mjc8fd-39mnquvgxdemyyf3e9ogdxh0v 192.168.10.80:2377
This node joined a swarm as a worker.
[root@dawn-82 vagrant]# docker swarm join --token SWMTKN-1-1p2yr9a9987p8goit14lmksb7z039qt7gid2kelss2x5mjc8fd-39mnquvgxdemyyf3e9ogdxh0v 192.168.10.80:2377
This node joined a swarm as a worker.
[root@dawn-80 vagrant]# docker node list
ID                            HOSTNAME   STATUS    AVAILABILITY   MANAGER STATUS   ENGINE VERSION
cjwp8i1o8gvy7dl54s1xsswbp *   dawn-80    Ready     Active         Leader           26.1.4
p9smbbvoczwyp0a337acof7sm     dawn-81    Ready     Active                          26.1.4
rv9lbvdyaf2bjbvup735v2j59     dawn-82    Ready     Active                          26.1.4

创建、查看服务

[root@dawn-80 vagrant]# docker service create --name nginx -p 8888:80 --replicas 5 nginx
iolweo8hyrl99zbmxdtvzx11x

[root@dawn-80 vagrant]# docker service ps nginx
ID             NAME      IMAGE          NODE      DESIRED STATE   CURRENT STATE            ERROR     PORTS
1c1ssidtubmy   nginx.1   nginx:latest   dawn-82   Running         Running 34 seconds ago
syq6s4yzuhxw   nginx.2   nginx:latest   dawn-81   Running         Running 20 seconds ago
n3456jx9zhhj   nginx.3   nginx:latest   dawn-82   Running         Running 34 seconds ago
blhmq1cyy066   nginx.4   nginx:latest   dawn-80   Running         Running 34 seconds ago
y0trll636e1s   nginx.5   nginx:latest   dawn-81   Running         Running 20 seconds ago

查看ingress网络,每个节点都有ingress

[root@dawn-80 vagrant]# docker network inspect ingress
[
    {
        "Name": "ingress",
        "Id": "emr2wsf54530zfb81fgku0itf",
        "Created": "2024-08-21T14:04:09.097221263Z",
        "Scope": "swarm",
        "Driver": "overlay",
        "EnableIPv6": false,
        "IPAM": {
            "Driver": "default",
            "Options": null,
            "Config": [
                {
                    "Subnet": "10.0.0.0/24",
                    "Gateway": "10.0.0.1"
                }
            ]
        },
        "Internal": false,
        "Attachable": false,
        "Ingress": true,
        "ConfigFrom": {
            "Network": ""
        },
        "ConfigOnly": false,
        "Containers": {
            "778afac209fa1a28cfbf32d02329de5da017dbb6f98f014157552b48cd7192ea": {
                "Name": "nginx.4.blhmq1cyy066hjvd356sef1sr",
                "EndpointID": "a8b5c9bba023d5712362948438b40e03b4bd31aeba58b95e79b338867f082373",
                "MacAddress": "02:42:0a:00:00:32",
                "IPv4Address": "10.0.0.50/24",
                "IPv6Address": ""
            },
            "ingress-sbox": {
                "Name": "ingress-endpoint",
                "EndpointID": "d27c8cf1d728f3b7f8824fe9abbf9279e0645203007a0603403a47bfa925a3d3",
                "MacAddress": "02:42:0a:00:00:02",
                "IPv4Address": "10.0.0.2/24",
                "IPv6Address": ""
            }
        },
        "Options": {
            "com.docker.network.driver.overlay.vxlanid_list": "4096"
        },
        "Labels": {},
        "Peers": [
            {
                "Name": "5c6be17c6d16",
                "IP": "192.168.10.80"
            },
            {
                "Name": "b92d7dfb7190",
                "IP": "192.168.10.82"
            },
            {
                "Name": "cc51e316feda",
                "IP": "192.168.10.81"
            }
        ]
    }
]

查看docker_gwbridge网络

[root@dawn-80 vagrant]# docker network inspect docker_gwbridge
[
    {
        "Name": "docker_gwbridge",
        "Id": "8f9c081e17eebcadc569da886978902d0b105e0ee72f64f61998f6144f27e361",
        "Created": "2024-08-20T23:27:00.982774232Z",
        "Scope": "local",
        "Driver": "bridge",
        "EnableIPv6": false,
        "IPAM": {
            "Driver": "default",
            "Options": null,
            "Config": [
                {
                    "Subnet": "172.18.0.0/16",
                    "Gateway": "172.18.0.1"
                }
            ]
        },
        "Internal": false,
        "Attachable": false,
        "Ingress": false,
        "ConfigFrom": {
            "Network": ""
        },
        "ConfigOnly": false,
        "Containers": {
            "778afac209fa1a28cfbf32d02329de5da017dbb6f98f014157552b48cd7192ea": {
                "Name": "gateway_e7c6c27d44ab",
                "EndpointID": "b2c636363e0af6891106f558b8ca45562ee4df39174751ee41109b99a2c81fce",
                "MacAddress": "02:42:ac:12:00:03",
                "IPv4Address": "172.18.0.3/16",
                "IPv6Address": ""
            },
            "ingress-sbox": {
                "Name": "gateway_ingress-sbox",
                "EndpointID": "6d4b239732cc3aaf5a28e7d7b727748e6442107981bc0b7d4b0dc7d19569e66f",
                "MacAddress": "02:42:ac:12:00:02",
                "IPv4Address": "172.18.0.2/16",
                "IPv6Address": ""
            }
        },
        "Options": {
            "com.docker.network.bridge.enable_icc": "false",
            "com.docker.network.bridge.enable_ip_masquerade": "true",
            "com.docker.network.bridge.name": "docker_gwbridge"
        },
        "Labels": {}
    }
]
容器到外部、外部到容器的访问

image.png

访问 http://192.168.10.81:8888 时,路由表显示端口 8888 的请求被转发到 172.18.0.2。这个 IP 地址是 docker_gwbridge 网络桥接的 IP,因此请求会被传递到 docker_gwbridge 网络桥接。

image.png

image.png image.png 查看ingress_sbox网络路由,发现最终请求会发送到10.0.0.3这个地址,而这个地址是ingress_sbox 的一个veth所绑定的地址,它的另外一端会连接到ingress网络命名空间。 image.png

image.png

# 安装工具
[root@dawn-81 vagrant]# yum install ipvsadm -y

首先宿主机定位到了172网段,172网段进入到ingress_sbox网络命名空间,而sbox通过ipvs负载到了10.0.0.5地址,我们又看到目的地以10开头的全部会通过ipvs转发到了10.0.0.3中。而10.0.0.3连接的另一端就是ingress网络中的一个veth设备,然后通过网桥br0,通过vxlan0设备进行封装最终将请求发送出去。 image.png image.png image.png

从ingress_sbox的ipvs我们得知最终会经这个请求发送到绑定了10.0.0.5这个容器,我们查看一下容器的地址,最终发现最终访问的就是dawn-80的这台主机上的nginx容器 image.png

我们还看到容器中还有一个veth设备绑定了172.18.0.3/16这个IP地址,而这个设备实际是为了容器可以对外访问。 image.png

创建一个容器同样也会连接到ingress网络,我们查看一下ingress中有没有新增veth设备连接 image.png

查看ingress网络空间的arp地址表,10.0.0.3 和 10.0.0.4 就是其他两个宿主机上 ingress_sbox 网络空间的虚拟设备地址。 image.png

查看ingress网络空间中的fdb表,fdb表是由网桥维护的,记录了MAC地址与接口的映射关系,告诉网桥MAC地址对应的接口在哪里

image.png

arp记录了容器对应的IP地址,而fdb记录了MAC地址对应的具体主机的IP地址,vxlan最终就会找到对应的主机将请求转发出去 image.png

容器到容器之间的访问

image.png

自定义网络的数据流向

image.png 创建自定义的overlay网络

image.png

查看自定义的网络命名空间,发现并没有,实际上是因为,这个自定义的命名空间会在创建服务的时候自动在容器所部署的节点去创建 image.png

image.png

我们查看自定义的容器网络

  • 10.0.0.15:连接ingress网络
  • 172.18.0.3:连接网桥,对外访问
  • 10.0.1.12:自定义网络,容器间的访问

image.png

将服务扩展为两个,并查看nginx服务的virtualIp image.png image.png

部署一个busybox容器,在容器中查看nginx的dns域名,发现对应的地址为10.0.1.11 image.png image.png

image.png

image.png