Docker桥接网络(overlay network)的原理和用法详解

本系列教程讲述涉及swarm集群服务的联网。有关独立容器联网的信息,请参见独立容器联网。如果您需要总体上了解有关Docker网络的更多信息,请参阅概述

本主题包括四个不同的教程。您可以在Linux,Windows或Mac上运行它们中的任何一个,但最后两个有所不同,需要在其他位置运行第二个Docker主机。

  • 使用默认覆盖网络 演示了如何使用在初始化或加入群集时Docker自动为您设置的默认覆盖网络。该网络不是生产系统的最佳选择。

  • 使用用户定义的覆盖网络 展示了如何创建和使用自定义覆盖网络来连接服务。建议将其用于生产中运行的服务。

  • 将覆盖网络用于独立容器 展示了如何使用覆盖网络在不同Docker后台程序上的独立容器之间建立关联。

  • 容器与swarm群集服务之间的通信 使用覆盖网络在独立容器与swarm群集服务之间建立通信。 Docker 17.06及更高版本支持此功能。

先决条件

要求您至少有一个单节点swarm群集,这意味着您已启动Docker并在主机上运行docker swarm init。 您也可以在多节点群集上运行示例。

最后一个示例需要Docker 17.06或更高版本。

使用默认的覆盖网络

在此示例中,您将启动alpine服务并从服务容器的角度检查网络的特征。

本教程不涉及有关特定操作系统覆盖网络如何实现的细节,而是从服务的角度着重介绍覆盖网络的功能。

先决条件

本教程需要三台物理或虚拟Docker主机,它们可以相互通信,并且都运行Docker 17.03或更高版本。本教程假定这三台主机在同一网络上运行,并且不涉及防火墙。

这些主机将称为managerworker-1worker-2manager主机将同时充当管理器和工作器,这意味着它既可以运行服务任务又可以管理swarm集群。 worker-1worker-2将仅充当工作器,

如果您没有三台主机,一个简单的解决方案是在云提供商(例如Amazon EC2)上配置三台Ubuntu主机,它们全部位于同一网络上,并允许与该网络上的所有主机进行所有通信(使用诸如EC2安全组),然后按照Ubuntu上Docker Engine-Community的安装说明进行操作。

演练

创建swarm集群

在此过程结束时,所有三个Docker主机都将加入swarm集群,并使用称为ingress的覆盖网络连接在一起。

  1. manager上。 初始化集群。 如果主机只有一个网络接口,则--advertise-addr标记是可选的。
$ docker swarm init --advertise-addr=<IP-ADDRESS-OF-MANAGER>

记下打印出来的文本,因为其中包含用于将worker-1worker-2加入到群中的token。 将token存储在密码管理器中是个好主意。

  1. worker-1上,加入集群。 如果主机只有一个网络接口,则--advertise-addr标记是可选的。
$ docker swarm join --token <TOKEN> --advertise-addr <IP-ADDRESS-OF-WORKER-1> <IP-ADDRESS-OF-MANAGER>:2377
  1. worker-2上,加入集群。 如果主机只有一个网络接口,则--advertise-addr标记是可选的。
$ docker swarm join --token <TOKEN> --advertise-addr <IP-ADDRESS-OF-WORKER-2> <IP-ADDRESS-OF-MANAGER>:2377
  1. manager,列出所有节点。 此命令只能由管理员执行。
$ docker node ls

ID                            HOSTNAME            STATUS              AVAILABILITY        MANAGER STATUS
d68ace5iraw6whp7llvgjpu48 *   ip-172-31-34-146    Ready               Active              Leader
nvp5rwavvb8lhdggo8fcf7plg     ip-172-31-35-151    Ready               Active
ouvx2l7qfcxisoyms8mtkgahw     ip-172-31-36-89     Ready               Active

您还可以使用--filter标记按角色进行过滤:

$ docker node ls --filter role=manager

ID                            HOSTNAME            STATUS              AVAILABILITY        MANAGER STATUS
d68ace5iraw6whp7llvgjpu48 *   ip-172-31-34-146    Ready               Active              Leader

$ docker node ls --filter role=worker

ID                            HOSTNAME            STATUS              AVAILABILITY        MANAGER STATUS
nvp5rwavvb8lhdggo8fcf7plg     ip-172-31-35-151    Ready               Active
ouvx2l7qfcxisoyms8mtkgahw     ip-172-31-36-89     Ready               Active
  1. managerworker-1worker-2上列出Docker网络,请注意,每个网络现在都有一个称为ingress的覆盖网络和一个名为docker_gwbridge的桥接网络。 这里仅显示manager列表:
$ docker network ls

NETWORK ID          NAME                DRIVER              SCOPE
495c570066be        bridge              bridge              local
961c6cae9945        docker_gwbridge     bridge              local
ff35ceda3643        host                host                local
trtnl4tqnc3n        ingress             overlay             swarm
c8357deec9cb        none                null                local

docker_gwbridgeingress网络连接到Docker主机的网络接口,以便流量可以往返于集群管理器和工作器。 如果创建群集服务时未指定网络,则它们将连接到ingress网络。 建议为每个应用程序或一组应用程序使用单独的覆盖网络。 在下一个步骤中,您将创建两个覆盖网络,并为每个覆盖网络之间连接服务。

创建服务

  1. manager上,创建一个名为nginx-net的新覆盖网络:
$ docker network create -d overlay nginx-net

您无需在其他节点上创建覆盖网络,因为当其中一个节点开始运行需要该网络的服务任务时,该网络会自动创建。

  1. manager上,创建一个连接到nginx-net的5副本Nginx服务。 该服务将向外部发布80端口。 所有服务任务容器都可以彼此通信,而无需打开任何端口。

注意:服务只能在管理器上创建。

$ docker service create   --name my-nginx   --publish target=80,published=80   --replicas=5   --network nginx-net   nginx

当您不为--publish标记指定mode时使用,默认发布模式ingress。也就是如果浏览到managerworker-1worker-2上的端口80,将连接到5个服务任务之一,即使在您浏览到的节点上当前没有任何任务在运行。如果要使用host模式发布端口,则可以将mode=host添加到--publish输出中。但是,在这种情况下,还应该使用--mode global而不是--replicas=5,因为只有一个服务任务可以绑定给定节点上的给定端口。

  1. 运行docker service ls监视服务启动进度,这可能需要几秒钟。

  2. 检查managerworker-1worker-2上的nginx-net网络。请记住,您不需要在worker-1worker-2上手动创建它,因为Docker为您创建了它。输出将很长,但请注意ContainersPeers部分。Containers列出了从该主机连接到覆盖网络的所有服务任务(或独立容器)。

  3. manager中,使用docker service inspect my-nginx来检查服务,并注意有关服务使用的端口和端点的信息。

  4. 创建一个新的网络nginx-net-2,然后更新服务以使用该网络而不是nginx-net

$ docker network create -d overlay nginx-net-2
$ docker service update   --network-add nginx-net-2   --network-rm nginx-net   my-nginx
  1. 运行docker service ls以验证服务已更新,并且所有任务都已重新部署。 运行docker network inspect nginx-net来验证没有容器连接到它。 对nginx-net-2运行相同的命令,请注意所有服务任务容器都已连接到该命令。

**注意:**在群集工作节点上根据需要自动创建了覆盖网络,但它们不会被自动删除。

  1. 清理服务和网络。 在manager中,运行以下命令。 管理器将指示工作节点自动删除网络。
$ docker service rm my-nginx
$ docker network rm nginx-net nginx-net-2

使用用户定义的覆盖网络

先决条件

本教程假定已经建立了swarm集群,并且您正在manager中。

演练

  1. 创建用户定义的覆盖网络。
$ docker network create -d overlay my-overlay
  1. 使用覆盖网络启动服务,并将端口80发布到Docker主机上的端口8080。
$ docker service create   --name my-nginx   --network my-overlay   --replicas 1   --publish published=8080,target=80   nginx:latest
  1. 运行docker network检查my-overlay并验证my-nginx服务任务是否已连接,通过查看Containers部分。

  2. 删除服务和网络。

$ docker service rm my-nginx

$ docker network rm my-overlay

独立容器使用覆盖网络

此示例演示了DNS容器发现-具体地说,是如何使用覆盖网络在不同Docker后台程序上的独立容器之间进行通信。步骤如下:

  • host1上,将该节点初始化为群集(管理器)。
  • host2上,将节点加入群集(工作器)。
  • host1上,创建一个可附加的覆盖网络(test-net)。
  • host1上,在test-net上运行一个交互式alpine容器(alpine1)。
  • host2上,在测试网上运行一个交互式的,独立的alpine容器(alpine2)。
  • host1上,从一个alpine1会话内ping Alpine2

先决条件

为了进行此次测试,您需要两个可以相互通信的不同Docker主机。每个主机必须是Docker 17.06或更高版本,并且在两个Docker主机之间打开以下端口:

  • TCP端口2377
  • TCP和UDP端口7946
  • UDP端口4789

一种简单的设置方法是拥有两个VM(本地或在AWS等云提供商上),每个VM均已安装并运行Docker。如果您使用的是AWS或类似的云计算平台,最简单的配置是使用一个安全组,该安全组打开两个主机之间的所有传入端口以及来自客户端IP地址的SSH端口。

此示例将我们集群中的两个节点称为host1host2。此示例也是使用Linux主机,但是相同的命令在Windows上也可以使用。

演练

  1. 初始化集群。

  2. host1上,初始化集群(如果出现提示,请使用--advertise-addr指定与集群中其他主机进行通信的接口的IP地址,例如AWS上的私有IP地址):

$ docker swarm init
Swarm initialized: current node (vz1mm9am11qcmo979tlrlox42) is now a manager.

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

    docker swarm join --token SWMTKN-1-5g90q48weqrtqryq4kj6ow0e8xm9wmv9o6vgqc5j320ymybd5c-8ex8j0bc40s6hgvy5ui5gl4gy 172.31.47.252:2377

To add a manager to this swarm, run 'docker swarm join-token manager' and follow the instructions.
  1. host2上,按照上述说明加入集群:
$ docker swarm join --token <your_token> <your_ip_address>:2377
This node joined a swarm as a worker.

如果节点加入群集失败,则docker swarm join命令会超时。 要解决此问题,请在host2上运行docker swarm leave --force,验证您的网络和防火墙设置,然后重试。

  1. host1,创建一个可连接的覆盖网络,名为test-net
$ docker network create --driver=overlay --attachable test-net
uqsof8phj3ak0rq9k86zta6ht

注意 返回的NETWORK ID——从host2连接到它时,您将再次看到它。

  1. host1上,启动一个连接到test-net的交互式(-it)容器(alpine1):
$ docker run -it --name alpine1 --network test-net alpine
/ #
  1. host2上,列出可用的网络注意test-net尚不存在:
$ docker network ls
NETWORK ID          NAME                DRIVER              SCOPE
ec299350b504        bridge              bridge              local
66e77d0d0e9a        docker_gwbridge     bridge              local
9f6ae26ccb82        host                host                local
omvdxqrda80z        ingress             overlay             swarm
b65c952a4b2b        none                null                local
  1. host2上,启动连接到test-net的独立(-d)和交互式(-it)容器(alpine2):
$ docker run -dit --name alpine2 --network test-net alpine
fb635f5ece59563e7b8b99556f816d24e6949a5f6a5b1fbd92ca244db17a4342

自动DNS容器发现仅适用于唯一的容器名称。

  1. host2上,验证是否已创建test-net(并且具有与host1上的test-net相同的网络ID):
 $ docker network ls
 NETWORK ID          NAME                DRIVER              SCOPE
 ...
 uqsof8phj3ak        test-net            overlay             swarm
  1. host1上,在alpine1的交互式终端中ping alpine2
/ # ping -c 2 alpine2
PING alpine2 (10.0.0.5): 56 data bytes
64 bytes from 10.0.0.5: seq=0 ttl=64 time=0.600 ms
64 bytes from 10.0.0.5: seq=1 ttl=64 time=0.555 ms

--- alpine2 ping statistics ---
2 packets transmitted, 2 packets received, 0% packet loss
round-trip min/avg/max = 0.555/0.577/0.600 ms

两个容器和连接两个主机的覆盖网络通信。 如果您在未分离的host2上运行另一个alpine容器,则可以从host2 ping alpine1(在这里我们添加了remove选项以自动清除容器):

$ docker run -it --rm --name alpine3 --network test-net alpine
/ # ping -c 2 alpine1
/ # exit
  1. host1上,关闭alpine1会话(这也会停止容器):
/ # exit
  1. 清理您的容器和网络:

您必须在每个主机上独立停止和删除容器,因为Docker后台程序是独立运行的,并且它们是独立的容器。 您只需要删除host1上的网络,因为当您停止host2上的alpine2时,test-net就会消失。

  1. host2上,停止alpine2,检查是否已删除测试网,然后删除alpine2
$ docker container stop alpine2
$ docker network ls
$ docker container rm alpine2
  1. host1上,删除alpine1test-net
$ docker container rm alpine1
$ docker network rm test-net

在容器和集群服务之间进行通信

先决条件

此示例中,您需要Docker 17.06或更高版本。

演练

在此示例中,您将在同一Docker主机上启动两个不同的alpine容器,并进行一些测试以了解它们如何相互通信。 您需要安装并运行Docker。

  1. 打开一个终端窗口。 在执行其他任何操作之前,请先列出当前网络。 如果您从未在此Docker后台程序上添加网络或初始化群组,则应该看到以下内容。 您可能会看到不同的网络,但至少应该看到以下内容(网络ID会有所不同):
$ docker network ls

NETWORK ID          NAME                DRIVER              SCOPE
17e324f45964        bridge              bridge              local
6ed54d316334        host                host                local
7092879f2cc8        none                null                local

列出了默认的bridge网络,以及hostnone。 后两个不是完全成熟的网络,但用于启动容器时,将容器直接连接到Docker后台程序主机的网络堆栈,或用于启动不包含网络设备的容器。 本教程将把两个容器连接到bridge网络

  1. 启动两个运行ashalpine容器,ashalpine的默认shell,而不是bash-dit标记的意思是启动单独的容器(在后台),交互(具有键入的能力)和TTY(以便您可以看到输入和输出)。 由于您是单独启动的,因此您不会连接到该容器。 而是打印出容器的ID。 因为您未指定--network标记,所以容器将连接到默认的bridge网络。
$ docker run -dit --name alpine1 alpine ash

$ docker run -dit --name alpine2 alpine ash

检查两个容器是否已确实启动:

$ docker container ls

CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES
602dbf1edc81        alpine              "ash"               4 seconds ago       Up 3 seconds                            alpine2
da33b7aa74b0        alpine              "ash"               17 seconds ago      Up 16 seconds                           alpine1
  1. 检查bridge网络以查看连接了哪些容器。
$ docker network inspect bridge

[
    {
        "Name": "bridge",
        "Id": "17e324f459648a9baaea32b248d3884da102dde19396c25b30ec800068ce6b10",
        "Created": "2017-06-22T20:27:43.826654485Z",
        "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,
        "Containers": {
            "602dbf1edc81813304b6cf0a647e65333dc6fe6ee6ed572dc0f686a3307c6a2c": {
                "Name": "alpine2",
                "EndpointID": "03b6aafb7ca4d7e531e292901b43719c0e34cc7eef565b38a6bf84acf50f38cd",
                "MacAddress": "02:42:ac:11:00:03",
                "IPv4Address": "172.17.0.3/16",
                "IPv6Address": ""
            },
            "da33b7aa74b0bf3bda3ebd502d404320ca112a268aafe05b4851d1e3312ed168": {
                "Name": "alpine1",
                "EndpointID": "46c044a645d6afc42ddd7857d19e9dcfb89ad790afb5c239a35ac0af5e8a5bc5",
                "MacAddress": "02:42:ac:11:00:02",
                "IPv4Address": "172.17.0.2/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": {}
    }
]

在上面的输出中,列出了有关bridge网络的信息,包括Docker主机和bridge网络之间的网关的IP地址(172.17.0.1)。 在Containers键下,列出了每个连接的容器及其IP地址的信息(alpine1172.17.0.2alpine2172.17.0.3)。

  1. 容器在后台运行。 使用docker attach命令连接到alpine1
$ docker attach alpine1

/ #

提示符将更改为#以指示您是容器中的root用户。 使用ip addr show命令从容器中查看alpine1的网络接口:

# ip addr show

1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN qlen 1
    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
27: eth0@if28: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue state UP
    link/ether 02:42:ac:11:00:02 brd ff:ff:ff:ff:ff:ff
    inet 172.17.0.2/16 scope global eth0
       valid_lft forever preferred_lft forever
    inet6 fe80::42:acff:fe11:2/64 scope link
       valid_lft forever preferred_lft forever

第一个接口是回送设备。 现在忽略它。 请注意,第二个接口的IP地址为172.17.0.2,与上一步中为alpine1显示的地址相同。

  1. 在alpine1内部,通过ping google.com确保您可以连接到互联网。 -c 2标记将命令限制为两次ping操作。
# ping -c 2 google.com

PING google.com (172.217.3.174): 56 data bytes
64 bytes from 172.217.3.174: seq=0 ttl=41 time=9.841 ms
64 bytes from 172.217.3.174: seq=1 ttl=41 time=9.897 ms

--- google.com ping statistics ---
2 packets transmitted, 2 packets received, 0% packet loss
round-trip min/avg/max = 9.841/9.869/9.897 ms
  1. 现在尝试ping第二个容器。 首先,通过其IP地址172.17.0.3对其执行ping操作:
# ping -c 2 172.17.0.3

PING 172.17.0.3 (172.17.0.3): 56 data bytes
64 bytes from 172.17.0.3: seq=0 ttl=64 time=0.086 ms
64 bytes from 172.17.0.3: seq=1 ttl=64 time=0.094 ms

--- 172.17.0.3 ping statistics ---
2 packets transmitted, 2 packets received, 0% packet loss
round-trip min/avg/max = 0.086/0.090/0.094 ms

这样成功了。 接下来,尝试按容器名称ping alpine2容器。 将失败。

# ping -c 2 alpine2

ping: bad address 'alpine2'
  1. 通过使用分离命令CTRL + p CTRL + q(按住CTRL并键入p后键入q)从alpine1分离而不停止它。 如果愿意,请连接到alpine2并在那里重复步骤4、5和6,用alpine1代替alpine2

  2. 停止并删除两个容器。

$ docker container stop alpine1 alpine2
$ docker container rm alpine1 alpine2

请记住,不建议将默认bridge网络用于生产。 要了解用户定义的桥接网络,请继续阅读下一个教程

其他网络教程

既然您已经完成了覆盖网络的网络教程,那么您可能需要运行以下其他网络教程:

主机网络教程 独立网络教程 Macvlan网络教程