跨主机互联是说 A 宿主机的容器可以访问 B 主机上的容器,但是前提是保证各宿主机之间的网络是可以相互通信的, 然后各容器才可以通过宿主机访问到对方的容器, 实现原理是在宿主机做一个网络路由就可以实现 A 宿主机的容器访问 B主机的容器的目的, 复杂的网络或者大型的网络可以使用 google 开源的 k8s 进行互联。本文之后将详细介绍docker网络配置,并演示容器跨主机通信的实现。

docker网络基础

  之前我们说过,当我们安装完docker应用后,就会自动添加一块虚拟的docker0网卡,并基于docker0网卡,提供了3种可选网络类型供创建的容器使用,分别是bridge(桥接),host(主机),none(无外部网络)。其中默认是采用桥接模式,容器中的网卡桥接在docker的网桥上,且通过DHCP自动分配IP,与docker0在同一网段。
  当我们每创建一个容器,宿主机上就会新建一个网卡与容器中的网卡相对应,如下图所示。
网桥
  容器桥接模式跨网络访问的结构示意图如下图所示
docker跨主机通信
  想实现不同宿主机上的容器跨主机肯定要经过宿主机来做网络转发,通过设置宿主机静态路由或者修改iptables规则来实现,可这时就面临一个问题:所有的容器服务默认的DHCP网段都是172.17.0.0/16网段,如果node1上的容器想直接访问node2宿主机上的容器,就会被直接当做docker0网桥的内部网段,数据报文根本都不会从node1主机的eth0网卡发出去,也根本到不了node2主机上。这种情况下,无论我们怎么修改iptables规则或者路由规则都无济于事的。所以我们想实现容器跨主机访问,首先要将不同宿主机上的容器分到不同的网段,然后才可以通过路由规则或者iptables进行跳转或转发。

修改docker网络的网段

  我们可以对每一个宿主机上的docker配置文件进行修改,实现每个宿主机的docker容器都在不同网段的目的,可以通过以下方式修改(未避免影响, 先在各服务器删除之前创建的所有容器,docker rm -f `docker ps -a -q`)。

  • 修改启动system脚本文件docker.service
    1
    vim /lib/systemd/system/docker.service
      在ExecStart=选项结尾加上--bip=10.1.0.1/24,就指定了10.1.0.0/24网段,然后执行命令重新加载配置文件和重启服务。
    1
    2
    systemctl daemon-reload
    systemctl restart docker
      注意:不能写10.1.0.0/24,会报错,虽然写网段结尾是0,如10.1.0.0/24更符合我们的习惯,不过确实会报错,报错信息如下:
    1
    2
    3
    4
    5
    6
    Dec 07 16:27:58 DockerUbuntu dockerd[14794]: failed to start daemon: Error initializing network controller: Error creating default "bridge" network: failed to allocate gateway (10.10.0.0): Address already in use
    Dec 07 16:27:58 DockerUbuntu systemd[1]: docker.service: Main process exited, code=exited, status=1/FAILURE
    Dec 07 16:27:58 DockerUbuntu systemd[1]: docker.service: Failed with result 'exit-code'.
    Dec 07 16:27:58 DockerUbuntu systemd[1]: Failed to start Docker Application Container Engine.
    -- Subject: Unit docker.service has failed
    -- Defined-By: systemd
  • 也可以修改daemon.json文件,在里面添加"bip": "10.2.0.1/24",如下所示(上面那个是我的阿里云加速器链接,注册阿里账号免费获取,之前文章有详细介绍,需改成自己的或者删掉):
    1
    2
    3
    4
    5
    6
    vim /etc/docker/daemon.json

    {
    "registry-mirrors": ["https://xxxxxxxx.mirror.aliyuncs.com","https://registry.docker-cn.com"],
    "bip": "10.1.0.1/24"
    }
      daemon.json文件是json数据格式,需要遵守json语法,换行记得要加,逗号。
      直接重启docker服务后生效
    1
    systemctl restart docker
      此时看网卡的ip就已经变为了我们设置的网段,之后创建的容器服务器就会自动获取我们设置好的网段中的IP了。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    root@DockerUbuntu:~# ifconfig
    docker0: flags=4099<UP,BROADCAST,MULTICAST> mtu 1500
    inet 10.10.0.1 netmask 255.255.255.0 broadcast 10.10.0.255
    inet6 fe80::42:25ff:fe2b:ecbc prefixlen 64 scopeid 0x20<link>
    ether 02:42:25:2b:ec:bc txqueuelen 0 (Ethernet)
    RX packets 0 bytes 0 (0.0 B)
    RX errors 0 dropped 0 overruns 0 frame 0
    TX packets 41 bytes 3526 (3.5 KB)
    TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
      同样的操作,将node2宿主机中的docker0网段设置为10.2.0.0/24

    修改静态路由

      我这里node1的eth0网卡ip为192.168.32.19,node2的eth0网卡ip为192.168.32.20,且两个宿主机之前网络是可以通过eth0网卡相互连接的。添加路由规则如下:
      在node1上添加静态路由
    1
    ip r add 10.2.0.0/24 via 192.168.32.20 dev eth0
      在node2上添加静态路由
    1
    ip r add 10.1.0.0/24 via 192.168.32.19 dev eth0

    修改iptables规则

      宿主机如果为centos7,则不需要修改iptables规则,而宿主机如果为ubuntu系统则需要添加forward规则来放行。我仔细看了下这两个系统的iptables规则,发现在centos系统docker创建的iptables规则中对Chain FORWARD是默认ACCEPT,而ubuntu系统中docker创建的iptables对Chain FORWARD是默认DROP
    dockecentosiptables
    dockerubuntuiptables
      之后两边各启动一个容器就可以实现相互通信或访问了。

    docker网络进阶

      之前我们演示了通过docker0网卡的桥接方式实现了容器跨主机访问。我们通过修改docker0网卡的网段设置来实现,每个主机上容器的网段不同。
      这种实现方式有个问题就是,但当每次我们修改了docker0网段之后,如果之后打算变更网段,之前的容器都将无法与docker0网桥桥接,导致网络不通,不能使用。
      对此我们有一个更灵活的方案来实现容器的跨主机通信。那就是我们还可以通过创建一个或多个自定义网络,将新创建的每个容器指定连接到我们创建的这个网络中,这样他们的网段就是我们设置的这个网络的网段,实现每个主机上容器网段都不相同。

    创建自定义网络

      可以将我们之前对docker0网卡的修改还原了的(当然,也可以不修改,出于控制变量方便观察考虑,建议修改回去)。
      我们可以通过docker network create命令来创建一个自定义网络
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    root@DockerUbuntu:~# 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
      例如我们在node1创建一个名为web1的桥接网络,网段为10.10.0.0/24,设置网关为10.10.0.1(可设置为此网段内任意ip)。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    root@DockerUbuntu:~# docker network create -d bridge --subnet 10.10.0.0/24 --gateway 10.10.0.1 web1
    a817cf36502eea3469e1cb4b9b7577044f8dce96f015ba57a47f6809c00d72c7
    root@DockerUbuntu:~# docker network ls
    NETWORK ID NAME DRIVER SCOPE
    afd91b2e1731 bridge bridge local
    241d3e94a6b3 host host local
    7cc9cf9eb69e none null local
    a817cf36502e web1 bridge local
    root@DockerUbuntu:~#
      可以用docker network ls(或docker network list)看到网络类型多了一种,也就是我们刚刚创建的web1类型。
    而用ifconfig或者ip a命令也可以看到我们的网卡设备里多了一个br-a817cf36502e,ip也恰好是我们指定的10.10.0.0/24网段。
    自定义网络
      此时我们就可以通过--net选项指定我们刚刚创建的web1网络,来创建并启动容器了。
    1
    2
    3
    root@DockerUbuntu:~# docker run -it -d -p 8080:8080 -p 8009:8009 --net=web1 tomcat-app1:v1
    afc1e3db8a6a670d30cdd0756af65da74895976ce5ebbf876329b04b452a3710
    root@DockerUbuntu:~#
      同样,在宿主机node2上也创建一个自定义网络web2,然后新创建的容器,也指定网络为web2,再设置静态路由和修改iptables规则放行,也可以实现容器间跨主机访问。

一个低调的男人