摘要:Docker网桥Linux路由网关

问题复现

网上这个问题出现的挺多的,我也总结一下,因为里面涉及到的网络基础知识比较多的,正好我一直不懂这一块,需要大补
问题:openvpn挂vpn连接远程主机,发现无法ping通远程主机IP:172.20.2.52,以前一直可以远程登录


问题排查

route一波查看一下路由表,看一下主机访问172.20.2.52的路由路径

root@ubuntu:/etc/openvpn# route
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
default         bogon           0.0.0.0         UG    600    0        0 wlp2s0
link-local      0.0.0.0         255.255.0.0     U     1000   0        0 br-3b2a750ddc03
172.17.0.0      0.0.0.0         255.255.0.0     U     0      0        0 docker0
172.18.0.0      0.0.0.0         255.255.0.0     U     0      0        0 br-e2cde023b4a7
172.19.0.0      0.0.0.0         255.255.0.0     U     0      0        0 br-94d6396c6c12
172.20.0.0      0.0.0.0         255.255.0.0     U     0      0        0 br-3b2a750ddc03
192.168.43.0    0.0.0.0         255.255.255.0   U     600    0        0 wlp2s0

发现和目标IP 172.20.2.52相似的目标网段为172.20.0.0,看后面的接口为br-3b2a750ddc03,也即是说数据被转发到这个接口下,这貌似是个docker创建的网桥,怀疑是docker网桥占用了172.20网段,查看以下docker网络

root@ubuntu:/etc/openvpn# docker network ls
NETWORK ID     NAME            DRIVER    SCOPE
22021490da82   bridge          bridge    local
e2cde023b4a7   harbor_harbor   bridge    local
e4aa16aea5df   host            host      local
3b2a750ddc03   milvus          bridge    local
2d89edc384b1   none            null      local
94d6396c6c12   test_default    bridge    local

看到3b2a750ddc03为命名为milvus的docker网络,原来是milvus的容器创建的网桥,进一步检查网络内部信息

root@ubuntu:/etc/openvpn# docker network inspect 3b2a750ddc03

可以看到所属的容器id和容器名

"Containers": {
            "22b638b8d2f05f3f8b17c62c63c00cd67ec60887fe20be19cc8f6bfb278c123b": {
                "Name": "milvus-standalone",
                "EndpointID": "e14a0676adba99908ab113a866650752e0e3ca21381d54ea43f6f4a33aca0fe0",
                "MacAddress": "02:42:ac:19:00:04",
                "IPv4Address": "172.20.0.4/16",
                "IPv6Address": ""
            },
            "39ef60cd098c20efc62f6668375988786d1874a0f1711c15b947e46bd4da80be": {
                "Name": "milvus-etcd",
                "EndpointID": "34104d54ae9d3e4834fafb8d7c475d5690acff08955e7d08686999c78ccb220f",
                "MacAddress": "02:42:ac:19:00:03",
                "IPv4Address": "172.20.0.3/16",
                "IPv6Address": ""
            },
            "d55dc8c8e4cce63ceb386e24ee05e27099826ec0c892aa77bb4998a99e9a67a6": {
                "Name": "milvus-minio",
                "EndpointID": "4eb0c415c175b7780a94ed72d09a574bad9797039a122a942bcac0e391186cfa",
                "MacAddress": "02:42:ac:19:00:02",
                "IPv4Address": "172.20.0.2/16",
                "IPv6Address": ""
            }
        },

可见这个网络接口下目标主机只有172.20.0.2,172.20.0.3,172.20.0.4三台主机IP,以及172.20.0.1网关,因此ping 172.20.2.52根本ping不通
milvus的三个容器虚拟IP以及网关服务占用了172.20网段,openvpn创建的网段也是172.20.0.0,因此在路由中产生网段冲突,无法转发到openvpn的对应网络接口,同理从172.17、18、19、20都是docker创建出来的网络接口和网段,如果有其他网络服务处在相同网段可能导致无法路由到


解决方案

(1)路由表添加细路由

在路由寻址时会根据目标IP在路由表中相同网络ID(网段)的记录,根据该记录将数据转发给下一个路由器,如果路由表中存在多条相同网络ID的记录,根据最长匹配算则最吻合的一个,因此增加一个172.20.2.0/24的目标网段即可,使用route add进行添加,添加目的地网段为172.20.2.0/24,网络接口为tun0

root@ubuntu:/etc/openvpn# route add -net 172.20.2.0/24 dev tun0

tun0是openvpn创建的网络接口,使用ifconfig查看

root@ubuntu:/etc/openvpn# ifconfig
tun0: flags=4305<UP,POINTOPOINT,RUNNING,NOARP,MULTICAST>  mtu 1500
        inet 10.0.1.65  netmask 255.255.255.255  destination 10.0.1.66
        inet6 fe80::7778:a3ae:badc:d759  prefixlen 64  scopeid 0x20<link>
        unspec 00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00  txqueuelen 100  (UNSPEC)
        RX packets 0  bytes 0 (0.0 B)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 1  bytes 48 (48.0 B)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

再看一下路由已经成功添加了一条细路由

root@ubuntu:/etc/openvpn# route
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
default         bogon           0.0.0.0         UG    600    0        0 wlp2s0
link-local      0.0.0.0         255.255.0.0     U     1000   0        0 br-3b2a750ddc03
172.17.0.0      0.0.0.0         255.255.0.0     U     0      0        0 docker0
172.18.0.0      0.0.0.0         255.255.0.0     U     0      0        0 br-e2cde023b4a7
172.19.0.0      0.0.0.0         255.255.0.0     U     0      0        0 br-94d6396c6c12
172.20.0.0      0.0.0.0         255.255.0.0     U     0      0        0 br-3b2a750ddc03
172.20.2.0      0.0.0.0         255.255.0.0     U     0      0        0 tun0
192.168.43.0    0.0.0.0         255.255.255.0   U     600    0        0 wlp2s0

此时再ping 172.20.2.52已经可以ping通

root@ubuntu:/etc/openvpn# ping 172.20.2.52
PING 172.20.2.52 (172.20.2.52) 56(84) bytes of data.
64 bytes from 172.20.2.52: icmp_seq=1 ttl=126 time=45.2 ms
64 bytes from 172.20.2.52: icmp_seq=2 ttl=126 time=43.1 ms
(2)删除docker-compose网桥路由

另一种方法是找到docker网桥对应的容器应用之后直接删除容器,则对应的网络接口和路由都会删除,进入milvus工程目录使用docker-compose停止和删除容器即可

root@ubuntu:~/docker/compose/milvus# docker-compose down
Removing milvus-standalone ... done
Removing milvus-minio      ... done
Removing milvus-etcd       ... done
Removing network milvus

查看route路由表172.20.0.0网段已经消失,ifconfig,docker network ls都已经查不到对应的网络接口,此时再重启openvpn即可

(3)修改docker-compose.yml配置网络

docker-compose默认会给每个应用从172.18.0.0依次往后匹配网段,只要容器没有被删除则一直占用网络,如果删除后重启则依次采用新的网段之前的不再使用,因此很容易造成路由冲突,可以在单个docker-compose.yml文件中增加networks配置,设置网段为10.103.0.0/16

networks:
  default:
    name: milvus
    ipam: 
      driver: default
      config:
        - subnet: 10.103.0.0/16

重启docker-compose,此时路由和docker网络都切换为了10.103.0.0/16网段

root@ubuntu:~/docker/compose/milvus# docker network ls|grep milvus
4a772f696084   milvus          bridge    local
root@ubuntu:~/docker/compose/milvus# route |grep br-4a772f696084
10.103.0.0      0.0.0.0         255.255.0.0     U     0      0        0 br-4a772f696084
root@ubuntu:~/docker/compose/milvus# docker network inspect 4a772f696084
"Containers": {
            "71248b9185a5279bfd6a1c67d2a318554217beb94b8f5c0d9f837fc8480911dc": {
                "Name": "milvus-minio",
                "EndpointID": "2c39a39329eca0389eb6204e64f67869dedd787ea17d067fc23c9dd0f179dafa",
                "MacAddress": "02:42:0a:67:00:03",
                "IPv4Address": "10.103.0.3/16",
                "IPv6Address": ""
            },
            "78adbd5868226361033c74532ea57d4fcbb5ef13f81fdd4e4fec531c19a9b629": {
                "Name": "milvus-standalone",
                "EndpointID": "711bb663135135189852e1ba39ab7c58b2acf84f5a5bb53fca00425efd08a234",
                "MacAddress": "02:42:0a:67:00:04",
                "IPv4Address": "10.103.0.4/16",
                "IPv6Address": ""
            },
            "9d221d4fb00ff8a1dd8d95e3ed8198f134f2177ccccbb32f393595a29e1d884b": {
                "Name": "milvus-etcd",
                "EndpointID": "79bc5d0ffacb5a124ee5a2452c33346c871f6564bc446c21ba59516d080cfd19",
                "MacAddress": "02:42:0a:67:00:02",
                "IPv4Address": "10.103.0.2/16",
                "IPv6Address": ""
            }
(4)修改docker全局配置文件

修改全局docker网络配置,docker自动分配的网段使用12.11.0.0/16,每个子网掩码划分为 255.255.255.0

root@ubuntu:~/docker/compose/milvus# cat /etc/docker/daemon.json 
{
    "insecure-registries": ["http://192.168.1.28:80"],
    "default-address-pools" : [
    {
      "base" : "12.11.0.0/16",
      "size" : 24
    }
  ]
}

重启docker

root@ubuntu:~/docker/compose/milvus# systemctl daemon-reload
root@ubuntu:~/docker/compose/milvus# systemctl restart docker

再重新启动docker-compose,项目切换为指定的网段

root@ubuntu:~/docker/compose/milvus# docker-compose up -d
Creating network "milvus" with the default driver
Creating milvus-etcd  ... done
Creating milvus-minio ... done
Creating milvus-standalone ... done
root@ubuntu:~/docker/compose/milvus# docker network inspect 
            "Config": [
                {
                    "Subnet": "12.11.2.0/24",
                    "Gateway": "12.11.2.1"
                }
            ]
        },
        "ConfigOnly": false,
        "Containers": {
            "335d4fb5fb97e3daaf4246dd76c6a90809ac841c36c05360baeab2e20b0ef0a1": {
                "Name": "milvus-standalone",
                "EndpointID": "5c51dd328f3baa31183512b40872ae3e281b6a339c702ac6ccdda893320299df",
                "MacAddress": "02:42:0c:0b:02:04",
                "IPv4Address": "12.11.2.4/24",
                "IPv6Address": ""
            },
            "7311706aaaa23143297632afcb482f0585afcf5de8abc3af33e737c29af2b659": {
                "Name": "milvus-minio",
                "EndpointID": "10068465e847919b212089d0057ea3b860ffc00df817768ebe1e7f7305e9180f",
                "MacAddress": "02:42:0c:0b:02:03",
                "IPv4Address": "12.11.2.3/24",
                "IPv6Address": ""
            },
            "a3e69162650f63b12e491e44fc8268768bb6ef252cc80ccb9636cc7f621980da": {
                "Name": "milvus-etcd",
                "EndpointID": "610ebfc3e0f090edea8b1d076b5930ce25770e227d1db4071a26d21ee5715e50",
                "MacAddress": "02:42:0c:0b:02:02",
                "IPv4Address": "12.11.2.2/24",
                "IPv6Address": ""
            }
        },

不仅是docker-compose,docker run启动的容器的虚拟IP也变更为指定的网段


相关知识

(1)网络基础知识(网关、路由器)

网关:网关是一个逻辑概念,网关是一个结点,是一个网络连接到另一个网络的关口,实质上是一个网络通向其他网络的IP地址。两个不同网段的网络结点之间通信需要使用网关,如果网络A的主机发现目标主机不在本地网络中就会把数据包发送到A的网关,再转发给网络B的网关,再转发给网络B。

路由器:路由器是物理设备,路由器可以作为网关使用,通常指的网关就是路由器的IP。主机1.1要发送数据包给主机4.1,因为IP地址不再同一网段,主机会将数据包发送给本网段的网关路由器A。路由器A接收到数据包,查看数据包IP中的目标IP地址,在查找自己的路由表,数据包的目标IP地址是4.1,属于4.0网段,路由器A在路由表中查到4.0网段转发的接口是SO接口。于是,路由表A将数据包从SO接口转发出去。到达了网关路由器B,用同样的转发方法,从EO口转发出去,4.1主机接收发这个数据包。

docker 容器bridge模式改用路由转发模式 docker route_IP

(2)docker网桥模式
(3)docker-compose网络模式

docker-compose中可以设置网络,如果不显示设置网络这些容器都会被加入app_default网络,比如工程目录为test,则启动后的网络名为test_default,使用docker network ls可以查看网络列表,docker network inspect <network id>可以查看对应网络的配置。如果想要工程有特有的网段以及容器有特有的IP则需要在docker-compose.yml中设置自定义要网络,例子如下

version: '3'
services:
  mysql:
    image: mysql
    restart: always
    volumes:
      - ./mysql:/home
    environment:
      - MYSQL_ROOT_PASSWORD=gp123456
    networks:
      mynet1:
        ipv4_address: 172.100.0.5
  flask:
    build: ./flask
    volumes:
      - ./flask:/home
    links:
      - mysql:mysql
    environment:
      - MYSQL_USER=xiaogp
      - MYSQL_PASSWORD=gp123456
      - MYSQL_DB=pira
    ports:
      - "5000:5000"
    networks:
      mynet1:
        ipv4_address: 172.100.0.6

networks:
  mynet1:
    ipam:
      config:
        - subnet: 172.100.0.0/16

具体的写法是在最下面设置一个工程全局的网络,并且在各个容器服务的最下面引用,在全局下使用subnet设置网段,各容器使用ipv4_address设置IP。重新启动后docker网络已经固定为172.100.0.0/16网段,各容器IP也改为自定义IP

root@ubuntu:~/docker/docker-compose/test_1# docker network inspect 393d21db53d0
 "IPAM": {
            "Driver": "default",
            "Options": null,
            "Config": [
                {
                    "Subnet": "172.100.0.0/16"
                }
            ]
        },
        "Internal": false,
        "Attachable": true,
        "Ingress": false,
        "ConfigFrom": {
            "Network": ""
        },
        "ConfigOnly": false,
        "Containers": {
            "b2a5571aadaca0e52812b2016967b20e79d02dea6d70c422c8746ee510256c64": {
                "Name": "test_1_flask_1",
                "EndpointID": "e9d73b5841b24d01fd03e74dbbb5b40e9c9b884b2497073fd1b482d08aa4c89c",
                "MacAddress": "02:42:ac:64:00:06",
                "IPv4Address": "172.100.0.6/16",
                "IPv6Address": ""
            },
            "e57d400253c9e97c5f8816a1832e4c105329c72611b691d2d4bd0ce1e8626cb3": {
                "Name": "test_1_mysql_1",
                "EndpointID": "f306029d9ab4c67014cbfd665523735ed968cca984b62a0bb00946635ee82d27",
                "MacAddress": "02:42:ac:64:00:05",
                "IPv4Address": "172.100.0.5/16",
                "IPv6Address": ""
            }
        },
(3)路由寻址规则

当TCP/IP需要向某个IP地址发起通信时,它会对路由表进行评估,以确定如何发送数据包。评估过程如下:
1.目的IP地址和路由表中每一个路由项的网络掩码进行相与计算,如果相与后的结果匹配对应路由项的网络地址,则记录下此路由项
2.当计算完路由表中所有的路由项后,择优选择其中一条路由规则

最长匹配路由:TCP/IP选择记录下的路由项中的最长匹配路由(网络掩码中具有最多“1”位的路由项)来和此目的IP地址进行通信。
最低跃点数:如果存在多个最长匹配路由,那么选择具有最低跃点数的路由项。
优先级高低:如果存在多个具有最低跃点数的最长匹配路由,那么:均根据最长匹配路由所对应的网络接口在网络连接的高级设置中的绑定优先级来决定(一般有线(eth0) > 无线 (wlan0) > 移动信号(4G))

(4)Linux route路由操作

每一个linux系统中都具有IP路由表,它存储了本地计算机可以到达的网络目的地址范围和如何到达的路由信息,本地计算机上的任何TCP/IP通信都受到路由表的控制,linux通过route 命令查看 Linux 内核的路由表。

字段

操作符

Destination

目标网络或目标主机。Destination 为 default(0.0.0.0)时,表示这个是默认网关,所有数据都发到这个网关

Gateway

网关地址,0.0.0.0 表示当前记录对应的 Destination 跟本机在同一个网段,通信时不需要经过网关。如果没有就显示星号(*)

Genmask

U: 该路由可以使用。

H: 该路由是到一个主机

G: 该路由是到一个网关(路由器)。如果没有设置该标志,说明目的地 是直接相连的

R: 恢复动态路由产生的表项

D: 该路由是由改变路由(redirect)报文创建的

M: 该路由已被改变路由报文修改

!:这个路由将不会被接受。

Metric

路由距离,到达指定网络所需的中转数

Ref

路由项引用次数

Use

此路由项被路由软件查找的次数

Iface

网卡名字,例如 eth0

(5)docker网络操作
  • docker network ls :查看docker网络列表
  • docker network inspect <network id/name>:查看某个docker网络详情
  • docker network rm:删除docker网络
  • docker network disconnect:断开docker网络
  • docker network prune:清楚无用的网络

使用案例:
(1)查看所有driver=bridge的docker网络

root@ubuntu:~# docker network ls --filter driver=bridge
NETWORK ID     NAME             DRIVER    SCOPE
33868f4f5f1c   bridge           bridge    local
e2cde023b4a7   harbor_harbor    bridge    local
4fab000d4ed4   milvus           bridge    local
64634f55cf1b   test_1_default   bridge    local
393d21db53d0   test_1_mynet1    bridge    local

(2)删除已经无效的网络

root@ubuntu:~# docker network rm 64634f55cf1b
64634f55cf1b

如果哟啊删除正在使用的网络,需要先断开连接再删除网络

docker network disconnect <network> <container>
# 先使用inspect查到网络名和容器名
root@ubuntu:~# docker network disconnect test_1_mynet1 test_1_mysql_1
root@ubuntu:~# docker network ls
NETWORK ID     NAME            DRIVER    SCOPE
33868f4f5f1c   bridge          bridge    local
e2cde023b4a7   harbor_harbor   bridge    local
e4aa16aea5df   host            host      local
4fab000d4ed4   milvus          bridge    local
2d89edc384b1   none            null      local
393d21db53d0   test_1_mynet1   bridge    local
root@ubuntu:~# docker network rm test_1_mynet1
test_1_mynet1

(3)清理无效网络,想通过这种方法清除历史曾经使用过的网段,但是没有达到预期效果,以后再研究

root@ubuntu:~# docker network prune -f