两个或多个docker容器之间通过名字相互访问

前言

需要部署的项目中有数据库和 Tomcat,Tomcat需要连接到数据库容器的 3306 端口上,由于容器的 IP 地址会变化,又不能写死 IP 地址,所以就有了下文。

docker 网卡介绍

docker 安装好之后默认会创建三个虚拟网卡,可以使用 docker network ls 命令来查看,三个虚拟网卡和 VMware 的类似。

1
2
3
4
5
b@ubuntu20:~$ docker network ls
NETWORK ID NAME DRIVER SCOPE
68633255abb2 bridge bridge local
adea9d9ae839 host host local
517ed92475cc none null local
  • bridge 是默认的网卡,网络驱动是 bridge 模式,类似于 Vmware 的 NAT 模式,如果容器启动时不指定网卡,则会默认连接到这块网卡上。如果需要访问容器内部的端口需要设置端口映射。
  • host 是直接使用主机的网络,网络驱动是 host 模式,类似于 Vmware 的桥接模式,可能会和主机的端口存在冲突,不需要设置端口映射即可连接到容器端口。
  • none 禁止所有联网,没有网络驱动,一般情况下用不到。

由于默认的网卡需要设置端口映射并且 IP 地址会随着容器的启动停止而变动,所以我们这里选择使用自定义网络来实现容器之间互相访问。

创建自定义网络

使用 docker network create my-net 命令来创建一个我们自定义的网络,网络驱动仍然使用 bridge

1
2
3
4
5
6
7
8
b@ubuntu20:~$ docker network create my-net
2ee565af7c72d6d4e719471138224496e976d5ea5ee4d00681d597f09c0e6560
b@ubuntu20:~$ docker network ls
NETWORK ID NAME DRIVER SCOPE
68633255abb2 bridge bridge local
adea9d9ae839 host host local
2ee565af7c72 my-net bridge local
517ed92475cc none null local

现在这个创建好的自定义网络就和默认的 bridge 网络隔离开了,互相之间不能访问,而且它们也不在同一个网段上。

使用 docker network inspect bridge 命令查看默认网卡的详细信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
b@ubuntu20:~$ docker network inspect bridge 
[
{
"Name": "bridge",
"Id": "68633255abb265ef682c163b401a810fb66fd08d0cf23f863974e325ad82bbcf",
"Created": "2020-09-24T02:37:43.329713459Z",
"Scope": "local",
"Driver": "bridge",
"EnableIPv6": false,
"IPAM": {
"Driver": "default",
"Options": null,
"Config": [
{
"Subnet": "172.17.0.0/16",
"Gateway": "172.17.0.1"
}
]
},
...后面省略
}
]

使用 docker network inspect my-net 命令查看自定义网卡的详细信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
b@ubuntu20:~$ docker network inspect my-net 
[
{
"Name": "my-net",
"Id": "2ee565af7c72d6d4e719471138224496e976d5ea5ee4d00681d597f09c0e6560",
"Created": "2020-09-24T07:43:14.684477356Z",
"Scope": "local",
"Driver": "bridge",
"EnableIPv6": false,
"IPAM": {
"Driver": "default",
"Options": {},
"Config": [
{
"Subnet": "172.18.0.0/16",
"Gateway": "172.18.0.1"
}
]
},
...后面省略
}
]

对比一下可以看出,默认网卡处于 172.17.0.0/16 这个网段,自定义网卡顺延了一位,处于 172.18.0.0/16 这个网段,它们两个肯定是不可以互相通信的。

创建容器

创建两个 python 容器,使用 docker run -it --name test1 --network my-net python bash 命令来创建第一个名字为 test1 的容器,--name 参数指定容器的名字,--network 参数指定使用自定义的网络, 然后开启一个 python 自带的 HTTP Server。

1
2
3
b@ubuntu20:~$ docker run -it --name test1 --network my-net python bash
root@0136e7769fc4:/# python -m http.server
Serving HTTP on 0.0.0.0 port 8000 (http://0.0.0.0:8000/) ...

新开启一个终端,使用 docker run -it --name test2 --network my-net python bash 命令创建第二个名字为 test2 的容器,尝试使用 curl 通过别名 test1 来访问第一个容器。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
b@ubuntu20:~$ docker run -it --name test2 --network my-net python bash
root@fdbc076a6753:/# curl test1:8000
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>Directory listing for /</title>
</head>
<body>
<h1>Directory listing for /</h1>
<hr>
<ul>
<li><a href=".dockerenv">.dockerenv</a></li>
<li><a href="bin/">bin/</a></li>
<li><a href="boot/">boot/</a></li>
<li><a href="dev/">dev/</a></li>
<li><a href="etc/">etc/</a></li>
<li><a href="home/">home/</a></li>
<li><a href="lib/">lib/</a></li>
<li><a href="lib64/">lib64/</a></li>
<li><a href="media/">media/</a></li>
<li><a href="mnt/">mnt/</a></li>
<li><a href="opt/">opt/</a></li>
<li><a href="proc/">proc/</a></li>
<li><a href="root/">root/</a></li>
<li><a href="run/">run/</a></li>
<li><a href="sbin/">sbin/</a></li>
<li><a href="srv/">srv/</a></li>
<li><a href="sys/">sys/</a></li>
<li><a href="tmp/">tmp/</a></li>
<li><a href="usr/">usr/</a></li>
<li><a href="var/">var/</a></li>
</ul>
<hr>
</body>
</html>

成功返回了 test1 容器的根目录内容,使用 ping 命令测试也是可以正常 ping 的通的。

1
2
3
4
5
6
7
8
root@fdbc076a6753:/# ping test1
PING test1 (172.18.0.2) 56(84) bytes of data.
64 bytes from test1.my-net (172.18.0.2): icmp_seq=1 ttl=64 time=0.054 ms
64 bytes from test1.my-net (172.18.0.2): icmp_seq=2 ttl=64 time=0.084 ms
^C
--- test1 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 27ms
rtt min/avg/max/mdev = 0.054/0.069/0.084/0.015 ms

由于在创建容器时并没有使用 -p 参数设置端口映射,所以外部不能访问到容器内部的内容,但是容器之间全端口都是可以正常访问的。

再次使用 docker network inspect my-net 命令来验证两个容器连接到了同一个自定义网络下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
b@ubuntu20:~$ docker network inspect my-net 
[
{
"Name": "my-net",
"Id": "2ee565af7c72d6d4e719471138224496e976d5ea5ee4d00681d597f09c0e6560",
"Created": "2020-09-24T07:43:14.684477356Z",
"Scope": "local",
"Driver": "bridge",
"EnableIPv6": false,
"IPAM": {
"Driver": "default",
"Options": {},
"Config": [
{
"Subnet": "172.18.0.0/16",
"Gateway": "172.18.0.1"
}
]
},
"Internal": false,
"Attachable": false,
"Ingress": false,
"ConfigFrom": {
"Network": ""
},
"ConfigOnly": false,
"Containers": {
"0136e7769fc47b6ef5f0a5d4fe178c043e9228db159bd5bdcc6fbf3097757691": {
"Name": "test1",
"EndpointID": "8bd05a32b42f3e675693c7473c4a4883b9e188970bf52aad865a79640557c527",
"MacAddress": "02:42:ac:12:00:02",
"IPv4Address": "172.18.0.2/16",
"IPv6Address": ""
},
"fdbc076a675367b2b74c8233fccefcc859a783cbbaedcda382383a9d66d7f6b0": {
"Name": "test2",
"EndpointID": "6c19d56dd06cc832af027f425bc65d965b40f508af4fa452725b490dc339e3d8",
"MacAddress": "02:42:ac:12:00:03",
"IPv4Address": "172.18.0.3/16",
"IPv6Address": ""
}
},
"Options": {},
"Labels": {}
}
]

在 Containers 字段中可以看到两个容器连接到了同一个网段下,并且自动分配了同网段的 IP 地址。

默认网络和自定义网络区别

说到这里可能有人会问了,那默认的网卡的网卡驱动也是 bridge 模式的,用户自定义的网络也是 bridge 模式,不就是换了一个名字吗,为什么默认的网卡不可以使用别名进行 IP 地址解析呢?

这个问题问得好,官方特意解释了这两个网卡的区别

User-defined bridges provide automatic DNS resolution between containers.

Containers on the default bridge network can only access each other by IP addresses, unless you use the –link option, which is considered legacy. On a user-defined bridge network, containers can resolve each other by name or alias.

翻译过来大意:就是用户自定义的网卡可以在容器之间提供自动的 DNS 解析,缺省的桥接网络上的容器只能通过 IP 地址互相访问,除非使用 –link 参数。在用户自定义的网卡上,容器直接可以通过名称或者别名相互解析。

文档中提到了 –link 参数,官方文档中已经不推荐使用 –link 参数,并且最终可能会被删除,所以最好不要使用 –link 参数来连接两个容器,并且它有多个缺点。

如果使用 –link 参数,需要在容器之间手动创建链接,这些链接需要双向创建,如果容器多于两个的话,将会很困难。或者也可以通过编辑 hosts 文件的方式来指定解析结果,但是这样将会非常难以调试。

默认网卡测试

接下来验证默认网卡无法使用自动的 DNS 解析。

使用 b@ubuntu20:~$ docker network disconnect my-net test2b@ubuntu20:~$ docker network disconnect my-net test1 命令断开容器和自定义网络的连接。

使用 b@ubuntu20:~$ docker network connect bridge test1 命令和 b@ubuntu20:~$ docker network connect bridge test2 命令将两个容器连接到默认的网卡上,使用 docker network inspect bridge 命令来验证。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
b@ubuntu20:~$ docker network inspect bridge 
[
{
"Name": "bridge",
"Id": "68633255abb265ef682c163b401a810fb66fd08d0cf23f863974e325ad82bbcf",
"Created": "2020-09-24T02:37:43.329713459Z",
"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": {
"0136e7769fc47b6ef5f0a5d4fe178c043e9228db159bd5bdcc6fbf3097757691": {
"Name": "test1",
"EndpointID": "3f7f4cbf2123ad8f9bad47a94de5f1b4c51083cddf05e648a2b25b7986a4d14c",
"MacAddress": "02:42:ac:11:00:02",
"IPv4Address": "172.17.0.2/16",
"IPv6Address": ""
},
"fdbc076a675367b2b74c8233fccefcc859a783cbbaedcda382383a9d66d7f6b0": {
"Name": "test2",
"EndpointID": "d8944f694706729c82571041ec4e01b036b87bf0a224192507242e91a763cb65",
"MacAddress": "02:42:ac:11:00:03",
"IPv4Address": "172.17.0.3/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": {}
}
]

重启两个容器,使用 docker exec -it test1 bash 命令进入到 test1 容器中,重新开启 HTTP Server。使用同样的命令进入到 test2 容器中,使用 ping 命令 ping test 是无法解析的状态,curl 也无法访问,我手动结束了请求,证明了以上的说法。

1
2
3
b@ubuntu20:~$ docker exec -it test1 bash
root@0136e7769fc4:/# python -m http.server 8000
Serving HTTP on 0.0.0.0 port 8000 (http://0.0.0.0:8000/) ...
1
2
3
4
5
b@ubuntu20:~$ docker exec -it test2 bash
root@fdbc076a6753:/# curl test1:8000
^C
root@fdbc076a6753:/# ping test1
ping: test1: Name or service not known

总结

以上就是通过自定义网卡来使两个容器互相连接的方法,这种方法便于部署和调试,而且还提供了网络隔离功能,两个容器之间不会互相干扰,可以随时断开或者连接,并且可以使用 --subnet 参数指定自定义网络的 IP 段,这里就不详细展开了。

本文章首发于个人博客 LLLibra146’s blog
本文作者:LLLibra146
版权声明:本博客所有文章除特别声明外,均采用 © BY-NC-ND 许可协议。非商用转载请注明出处!严禁商业转载!
本文链接https://blog.d77.xyz/archives/2443eb85.html