本系列教程讲述涉及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或更高版本。本教程假定这三台主机在同一网络上运行,并且不涉及防火墙。
这些主机将称为manager
,worker-1
和worker-2
。manager
主机将同时充当管理器和工作器,这意味着它既可以运行服务任务又可以管理swarm集群。 worker-1
和worker-2
将仅充当工作器,
如果您没有三台主机,一个简单的解决方案是在云提供商(例如Amazon EC2)上配置三台Ubuntu主机,它们全部位于同一网络上,并允许与该网络上的所有主机进行所有通信(使用诸如EC2安全组),然后按照Ubuntu上Docker Engine-Community的安装说明进行操作。
演练
创建swarm集群
在此过程结束时,所有三个Docker主机都将加入swarm集群,并使用称为ingress
的覆盖网络连接在一起。
- 在
manager
上。 初始化集群。 如果主机只有一个网络接口,则--advertise-addr
标记是可选的。
$ docker swarm init --advertise-addr=<IP-ADDRESS-OF-MANAGER>
记下打印出来的文本,因为其中包含用于将worker-1
和worker-2
加入到群中的token。 将token存储在密码管理器中是个好主意。
- 在
worker-1
上,加入集群。 如果主机只有一个网络接口,则--advertise-addr
标记是可选的。
$ docker swarm join --token <TOKEN> --advertise-addr <IP-ADDRESS-OF-WORKER-1> <IP-ADDRESS-OF-MANAGER>:2377
- 在
worker-2
上,加入集群。 如果主机只有一个网络接口,则--advertise-addr
标记是可选的。
$ docker swarm join --token <TOKEN> --advertise-addr <IP-ADDRESS-OF-WORKER-2> <IP-ADDRESS-OF-MANAGER>:2377
- 在
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
- 在
manager
、worker-1
和worker-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_gwbridge
将ingress
网络连接到Docker主机的网络接口,以便流量可以往返于集群管理器和工作器。 如果创建群集服务时未指定网络,则它们将连接到ingress
网络。 建议为每个应用程序或一组应用程序使用单独的覆盖网络。 在下一个步骤中,您将创建两个覆盖网络,并为每个覆盖网络之间连接服务。
创建服务
- 在
manager
上,创建一个名为nginx-net
的新覆盖网络:
$ docker network create -d overlay nginx-net
您无需在其他节点上创建覆盖网络,因为当其中一个节点开始运行需要该网络的服务任务时,该网络会自动创建。
- 在
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
。也就是如果浏览到manager
,worker-1
或worker-2
上的端口80,将连接到5个服务任务之一,即使在您浏览到的节点上当前没有任何任务在运行。如果要使用host
模式发布端口,则可以将mode=host
添加到--publish
输出中。但是,在这种情况下,还应该使用--mode global
而不是--replicas=5
,因为只有一个服务任务可以绑定给定节点上的给定端口。
运行
docker service ls
监视服务启动进度,这可能需要几秒钟。检查
manager
,worker-1
和worker-2
上的nginx-net
网络。请记住,您不需要在worker-1
和worker-2
上手动创建它,因为Docker为您创建了它。输出将很长,但请注意Containers
和Peers
部分。Containers
列出了从该主机连接到覆盖网络的所有服务任务(或独立容器)。在
manager
中,使用docker service inspect my-nginx
来检查服务,并注意有关服务使用的端口和端点的信息。创建一个新的网络
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
- 运行
docker service ls
以验证服务已更新,并且所有任务都已重新部署。 运行docker network inspect nginx-net
来验证没有容器连接到它。 对nginx-net-2
运行相同的命令,请注意所有服务任务容器都已连接到该命令。
**注意:**在群集工作节点上根据需要自动创建了覆盖网络,但它们不会被自动删除。
- 清理服务和网络。 在
manager
中,运行以下命令。 管理器将指示工作节点自动删除网络。
$ docker service rm my-nginx
$ docker network rm nginx-net nginx-net-2
使用用户定义的覆盖网络
先决条件
本教程假定已经建立了swarm集群,并且您正在manager中。
演练
- 创建用户定义的覆盖网络。
$ docker network create -d overlay my-overlay
- 使用覆盖网络启动服务,并将端口80发布到Docker主机上的端口8080。
$ docker service create --name my-nginx --network my-overlay --replicas 1 --publish published=8080,target=80 nginx:latest
运行
docker network
检查my-overlay
并验证my-nginx
服务任务是否已连接,通过查看Containers
部分。删除服务和网络。
$ 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端口。
此示例将我们集群中的两个节点称为host1
和host2
。此示例也是使用Linux主机,但是相同的命令在Windows上也可以使用。
演练
初始化集群。
在
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.
- 在
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
,验证您的网络和防火墙设置,然后重试。
- 在
host1
,创建一个可连接的覆盖网络,名为test-net
。
$ docker network create --driver=overlay --attachable test-net
uqsof8phj3ak0rq9k86zta6ht
注意 返回的NETWORK ID——从
host2
连接到它时,您将再次看到它。
- 在
host1
上,启动一个连接到test-net
的交互式(-it
)容器(alpine1
):
$ docker run -it --name alpine1 --network test-net alpine
/ #
- 在
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
- 在
host2
上,启动连接到test-net
的独立(-d
)和交互式(-it
)容器(alpine2
):
$ docker run -dit --name alpine2 --network test-net alpine
fb635f5ece59563e7b8b99556f816d24e6949a5f6a5b1fbd92ca244db17a4342
自动DNS容器发现仅适用于唯一的容器名称。
- 在
host2
上,验证是否已创建test-net
(并且具有与host1
上的test-net
相同的网络ID):
$ docker network ls
NETWORK ID NAME DRIVER SCOPE
...
uqsof8phj3ak test-net overlay swarm
- 在
host1
上,在alpine1
的交互式终端中pingalpine2
:
/ # 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
- 在
host1
上,关闭alpine1
会话(这也会停止容器):
/ # exit
- 清理您的容器和网络:
您必须在每个主机上独立停止和删除容器,因为Docker后台程序是独立运行的,并且它们是独立的容器。 您只需要删除host1
上的网络,因为当您停止host2
上的alpine2
时,test-net
就会消失。
- 在
host2
上,停止alpine2
,检查是否已删除测试网,然后删除alpine2
:
$ docker container stop alpine2
$ docker network ls
$ docker container rm alpine2
- 在
host1
上,删除alpine1
和test-net
:
$ docker container rm alpine1
$ docker network rm test-net
在容器和集群服务之间进行通信
先决条件
此示例中,您需要Docker 17.06或更高版本。
演练
在此示例中,您将在同一Docker主机上启动两个不同的alpine
容器,并进行一些测试以了解它们如何相互通信。 您需要安装并运行Docker。
- 打开一个终端窗口。 在执行其他任何操作之前,请先列出当前网络。 如果您从未在此Docker后台程序上添加网络或初始化群组,则应该看到以下内容。 您可能会看到不同的网络,但至少应该看到以下内容(网络ID会有所不同):
$ docker network ls
NETWORK ID NAME DRIVER SCOPE
17e324f45964 bridge bridge local
6ed54d316334 host host local
7092879f2cc8 none null local
列出了默认的bridge
网络,以及host
和none
。 后两个不是完全成熟的网络,但用于启动容器时,将容器直接连接到Docker后台程序主机的网络堆栈,或用于启动不包含网络设备的容器。 本教程将把两个容器连接到bridge
网络。
- 启动两个运行
ash
的alpine
容器,ash
是alpine
的默认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
- 检查
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地址的信息(alpine1
为172.17.0.2
,alpine2
为172.17.0.3
)。
- 容器在后台运行。 使用
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
显示的地址相同。
- 在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
- 现在尝试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'
通过使用分离命令
CTRL
+p
CTRL
+q
(按住CTRL
并键入p
后键入q
)从alpine1
分离而不停止它。 如果愿意,请连接到alpine2
并在那里重复步骤4、5和6,用alpine1
代替alpine2
。停止并删除两个容器。
$ docker container stop alpine1 alpine2
$ docker container rm alpine1 alpine2
请记住,不建议将默认bridge
网络用于生产。 要了解用户定义的桥接网络,请继续阅读下一个教程。
其他网络教程
既然您已经完成了覆盖网络的网络教程,那么您可能需要运行以下其他网络教程: