Connection refused? Docker 的网络

通常本地运行服务的时候,我们都会监听 127.0.0.1 ,比如下面这样(需要安装 python3 )

$ python3 -m http.server --bind 127.0.0.1
Serving HTTP on 127.0.0.1 port 8000 (http://127.0.0.1:8000/) ...
复制代码

然后在浏览器中就可以输入 http://127.0.0.1:8000 ,浏览器就会展示服务运行的当前的目录结构了。但是呢,当你在 container 中运行的时候,再通过此链接访问你就会遇到 connection refused 或者 connection reset 的问题。

$ docker run -p 8000:8000 -itd python:3.7-slim python3 -m http.server --bind 127.0.0.1
c2e94f44dc86dc48b9b4d03cc547c265134d070c55c9c144e5d0adcfd0da85a9
$ curl 127.0.0.1:8000
curl: (56) Recv failure: 连接被对方重设
复制代码

这个是为什么呢?为了知道如何解决这个为题,我们需要最小程度的了解 Docker 网络是如何运行的。这边文章将会覆盖如下三方面:

  • 网络的 namespaces ,以及 Docker 是如何使用它们的
  • docker run -p 5000:5000 到底做了什么,以及为什么上述的例子不能正常访问
  • 如何修复 image 可以使得服务可以正常的访问

不使用Docker时候的网络

我们从第一个场景出发:直接在操作系统内运行一个服务,然后直接访问这个服务。操作系统有多个网络的 interfaces 。例如我的电脑就有如下的网络接口:

$ ifconfig
docker0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
inet 172.17.0.1
ens33: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
inet 192.168.0.101
lo: flags=73<UP,LOOPBACK,RUNNING>  mtu 65536
inet 127.0.0.1
复制代码

我们来一起看看这三个网络接口:

  • 暂时忽略 docker0 这个网络接口
  • lo 是一个回环的网络接口,其 IPv4 的地址 127.0.0.1 。这个咱的电脑,不需要通过任何网络硬件仅仅在内存中就可进行寻址
  • ens33 是我的 WiFi 地址,其 IPv4 的地址:192.168.0.101。当电脑需要链接互联网的时候,发送的包都通过这个 interface

第一个场景起一个服务监听 127.0.0.1 ,然后本地直接访问这个服务,其可视化的图如下:

网络的 namespaces

你可以注意到上图中是一个 Default network namespace ,这个是什么?

Docker 是一个运行容器的系统: 一种让进程间互相隔离的方式。这个特性是基于一系列 Linux 内核特性构建起来的,其中一个就是 network namespaces —— 一种使得不同的进程拥有不同的网络设备, IPs ,防护墙规则等。

默认情况下,每个由 Docker 运行的容器都有自己的 network namespace ,和自己的 IP

$ docker run --rm -it busybox
/ # ifconfig
eth0      Link encap:Ethernet  HWaddr 02:42:AC:11:00:02
inet addr:172.17.0.2  Bcast:172.17.255.255  Mask:255.255.0.0
lo        Link encap:Local Loopback
inet addr:127.0.0.1  Mask:255.0.0.0
复制代码

所以这个容器有两个 interfaceeth0lo 每个都有自己的 IP 地址。但是由于这个是另外一个 network namespace ,所以上面的 Default network namespace 是不同的。

为了让上面的表述更加清晰,我们在容器中运行 http.server

$ docker run -itd python:3.7-slim python3 -m http.server --bind 127.0.0.1
复制代码

其网络结构如下图:

现在我们知道连接为什么会被拒绝了:服务监听的是容器 network namespace 中的 127.0.0.1 。而浏览器访问的是 Default network namespace 中的 127.0.0.1 。这是两个不同的 interface ,所以是不能建立链接的。

那我们应该怎样在两个 network namespace 建立连接呢?可以使用 Docker 的 port-forwarding

Docker run port-forwarding (is not enough)

当我们使用参数 -p 5000:5000 运行容器的时候,会转发Docker daemon运行所在 network namespace 中所有 interfaces 的端口5000的流量到容器 network namespace 的外部 interfaceIP 的5000端口。使用参数 -p 8080:80 则会转发Docker daemon运行所在的 network namespace 中所有8080端口的流量到容器 network namespace 的外部 interfaceIP 的80端口。

让我们来运行一个容器,通过图标来可视化这样做到底意味着什么

$ docker run -itd -p 8000:8000 python:3.7-slim python3 -m http.server --bind 127.0.0.1
复制代码

现在我们遇到了第二个问题:服务监听的是容器 network namespace 中的 127.0.0.1 ,而 port-forwarding 把流量全部转发到了容器的外部 interfaceIP:172.17.0.2

所以还会遇到 connection reset 或者 connection refused

解决方案:监听所有interfaces

port-forwarding 只能转发到一个地址,但是你可以修改服务监听的 interface ,可以通过服务监听 0.0.0.0 ,这样讲就可以监听所有的 interfaces 了,问题就得到了解决。

$ docker run -p 8000:8000 -itd python:3.7-slim python3 -m http.server --bind 0.0.0.0
1bd03d90310f02b5c8ca1d95f4dbadce543c6ca4a5741f5bbc6286e6a2b72850
$ curl 127.0.0.1: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="https://www.tuicool.com/articles/BfyMNrF/.dockerenv">.dockerenv</a></li>
<li><a href="https://www.tuicool.com/articles/BfyMNrF/bin/">bin/</a></li>
<li><a href="https://www.tuicool.com/articles/BfyMNrF/boot/">boot/</a></li>
<li><a href="https://www.tuicool.com/articles/BfyMNrF/dev/">dev/</a></li>
<li><a href="https://www.tuicool.com/articles/BfyMNrF/etc/">etc/</a></li>
<li><a href="https://www.tuicool.com/articles/BfyMNrF/home/">home/</a></li>
<li><a href="https://www.tuicool.com/articles/BfyMNrF/lib/">lib/</a></li>
<li><a href="https://www.tuicool.com/articles/BfyMNrF/lib64/">lib64/</a></li>
<li><a href="https://www.tuicool.com/articles/BfyMNrF/media/">media/</a></li>
<li><a href="https://www.tuicool.com/articles/BfyMNrF/mnt/">mnt/</a></li>
<li><a href="https://www.tuicool.com/articles/BfyMNrF/opt/">opt/</a></li>
<li><a href="https://www.tuicool.com/articles/BfyMNrF/proc/">proc/</a></li>
<li><a href="https://www.tuicool.com/articles/BfyMNrF/root/">root/</a></li>
<li><a href="https://www.tuicool.com/articles/BfyMNrF/run/">run/</a></li>
<li><a href="https://www.tuicool.com/articles/BfyMNrF/sbin/">sbin/</a></li>
<li><a href="https://www.tuicool.com/articles/BfyMNrF/srv/">srv/</a></li>
<li><a href="https://www.tuicool.com/articles/BfyMNrF/sys/">sys/</a></li>
<li><a href="https://www.tuicool.com/articles/BfyMNrF/tmp/">tmp/</a></li>
<li><a href="https://www.tuicool.com/articles/BfyMNrF/usr/">usr/</a></li>
<li><a href="https://www.tuicool.com/articles/BfyMNrF/var/">var/</a></li>
</ul>
<hr>
</body>
</html>
复制代码

注意: --bind 0.0.0.0 是一个 http.server 的参数;并不是 Docker 的参数。

此时网络图如下

结束语

本文翻译自 pythonspeed.com/articles/do… ,这篇文章让我收获还是挺大的,知道了 interface 的作用,知道了 127.0.0.10.0.0.0 的区别,还简单的了解到了 Docker 实现的原理。不过也挺尴尬的,都毕业一年半了,才知道这些东西!!!

稀土掘金
我还没有学会写个人说明!
上一篇

从MySQL开始聊聊“树”结构 (上)

下一篇

工厂的重组:是时候给软件工程师提供更多的工具

你也可能喜欢

评论已经被关闭。

插入图片