Docker 网络,百思不得其解
Docker networking, baffled and puzzled
我有一个简单的 python 应用程序,它在 Elasticsearch 实例中存储和搜索数据。 python 应用程序在它自己的容器中运行,就像 Elasticsearch 一样。
Elasticsearch 公开其默认端口 9200 和 9300,python 应用程序公开端口 5000。用于 Docker 的网络类型是用户定义的桥接网络。
当我启动两个容器时,应用程序启动良好,两个容器通过容器名称相互查看并且通信正常。
但是从 docker 主机 (linux) 连接到暴露的端口 5000 是不可能的。所以一个简单的 curl http://localhost:5000/
呈现超时。本文档中的 Docker 提示:https://docs.docker.com/network/bridge/ 没有解决这个问题。
经过一番努力后,我尝试了一些完全不同的方法,我尝试从 docker 主机外部连接到 python 应用程序。我很困惑,从世界上任何地方我都可以做到 curl http://<fqdn>:5000/
并且得到了应用程序。
这意味着,真正的问题解决了,因为我能够向外界提供应用程序。 (所以是的,容器内的应用程序在 0.0.0.0 上侦听,这是其他人报告的 90% 网络问题的解决方案。)
但这仍然让我感到困惑,是什么导致了这种奇怪的行为?在我的开发机器(Windows 10、WSL、Docker 桌面、Linux 容器)上,我能够连接到 localhost:5000、127.0.0.1:5000 等上的服务。在我的 Linux(生产)机器上,除了从 docker 主机连接到容器外,一切正常。
我希望有人能阐明这一点,我试图理解为什么会这样。
一些相关信息
Docker主持人:
# ifconfig -a
br-77127ce4b631: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
inet 172.18.0.1 netmask 255.255.0.0 broadcast 172.18.255.255
[snip]
docker0: flags=4099<UP,BROADCAST,MULTICAST> mtu 1500
inet 172.17.0.1 netmask 255.255.0.0 broadcast 172.17.255.255
[snip]
ens3: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
inet 1xx.1xx.199.134 netmask 255.255.255.0 broadcast 1xx.1xx.199.255
# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
1e7f2f7a271b pplbase_api "flask run --host=0.…" 20 hours ago Up 19 hours 0.0.0.0:5000->5000/tcp pplbase_api_1
fdfa10b1ce99 elasticsearch:7.5.1 "/usr/local/bin/dock…" 21 hours ago Up 19 hours 0.0.0.0:9200->9200/tcp, 0.0.0.0:9300->9300/tcp pplbase_elastic_1
# docker network ls
NETWORK ID NAME DRIVER SCOPE
[snip]
77127ce4b631 pplbase_pplbase bridge local
# iptables -L -n
[snip]
ACCEPT tcp -- 0.0.0.0/0 0.0.0.0/0 tcp dpt:5000
Chain FORWARD (policy ACCEPT)
target prot opt source destination
DOCKER-USER all -- 0.0.0.0/0 0.0.0.0/0
DOCKER-ISOLATION-STAGE-1 all -- 0.0.0.0/0 0.0.0.0/0
ACCEPT all -- 0.0.0.0/0 0.0.0.0/0 ctstate RELATED,ESTABLISHED
DOCKER all -- 0.0.0.0/0 0.0.0.0/0
ACCEPT all -- 0.0.0.0/0 0.0.0.0/0 ctstate RELATED,ESTABLISHED
DOCKER all -- 0.0.0.0/0 0.0.0.0/0
Chain DOCKER (2 references)
target prot opt source destination
ACCEPT tcp -- 0.0.0.0/0 172.18.0.2 tcp dpt:9300
ACCEPT tcp -- 0.0.0.0/0 172.18.0.2 tcp dpt:9200
ACCEPT tcp -- 0.0.0.0/0 172.18.0.3 tcp dpt:5000
Chain DOCKER-ISOLATION-STAGE-1 (1 references)
target prot opt source destination
DOCKER-ISOLATION-STAGE-2 all -- 0.0.0.0/0 0.0.0.0/0
DOCKER-ISOLATION-STAGE-2 all -- 0.0.0.0/0 0.0.0.0/0
RETURN all -- 0.0.0.0/0 0.0.0.0/0
Chain DOCKER-ISOLATION-STAGE-2 (2 references)
target prot opt source destination
DROP all -- 0.0.0.0/0 0.0.0.0/0
DROP all -- 0.0.0.0/0 0.0.0.0/0
RETURN all -- 0.0.0.0/0 0.0.0.0/0
Chain DOCKER-USER (1 references)
target prot opt source destination
RETURN all -- 0.0.0.0/0 0.0.0.0/0
Docker 撰写文件:
version: '3'
services:
api:
build: .
links:
- elastic
ports:
- "5000:5000"
networks:
- pplbase
environment:
- ELASTIC_HOSTS=elastic localhost
- FLASK_APP=app.py
- FLASK_ENV=development
- FLASK_DEBUG=0
tty: true
elastic:
image: "elasticsearch:7.5.1"
ports:
- "9200:9200"
- "9300:9300"
networks:
- pplbase
environment:
- discovery.type=single-node
volumes:
- ${PPLBASE_STORE}:/usr/share/elasticsearch/data
networks:
pplbase:
driver: bridge
越挖越深,谜底越来越大。使用 netcat 时我可以建立连接
Connection to 127.0.0.1 5000 port [tcp/*] succeeded!
在没有客户端连接时使用 netstat 检查见:
tcp6 0 0 :::5000 :::* LISTEN 27824/docker-proxy
尝试从 docker 主机连接时,已建立连接:
tcp 0 1 172.20.0.1:56866 172.20.0.3:5000 SYN_SENT 27824/docker-proxy
tcp6 0 0 :::5000 :::* LISTEN 27824/docker-proxy
tcp6 0 0 ::1:58900 ::1:5000 ESTABLISHED 31642/links
tcp6 592 0 ::1:5000 ::1:58900 ESTABLISHED 27824/docker-proxy
所以我现在怀疑 docker 主机上有一些网络巫术。
Flask 实例 运行 在 0.0.0.0:5000
。
你试过了吗:curl http://0.0.0.0:5000/
?
可能是您的主机配置将 localhost 映射为 127.0.0.1
而不是 0.0.0.0
所以当我在解决这个问题的时候,慢慢地找到解决方案,我发现我最后的建议毕竟是正确的。在防火墙 (iptables) 中,我记录了所有丢弃的数据包,是的,docker-桥(不是 docker0,而是 br- 和容器 (veth) 之间的数据包被 iptables 丢弃了。添加允许来自接口的流量流动的规则解决了这个问题。
就我而言:sudo iptables -I INPUT 3 -s 172.20.0.3 -d 172.20.0.1 -j ACCEPT
其中 172.20.0.0/32 是由 Docker 生成的桥接网络。
我有一个简单的 python 应用程序,它在 Elasticsearch 实例中存储和搜索数据。 python 应用程序在它自己的容器中运行,就像 Elasticsearch 一样。 Elasticsearch 公开其默认端口 9200 和 9300,python 应用程序公开端口 5000。用于 Docker 的网络类型是用户定义的桥接网络。 当我启动两个容器时,应用程序启动良好,两个容器通过容器名称相互查看并且通信正常。
但是从 docker 主机 (linux) 连接到暴露的端口 5000 是不可能的。所以一个简单的 curl http://localhost:5000/
呈现超时。本文档中的 Docker 提示:https://docs.docker.com/network/bridge/ 没有解决这个问题。
经过一番努力后,我尝试了一些完全不同的方法,我尝试从 docker 主机外部连接到 python 应用程序。我很困惑,从世界上任何地方我都可以做到 curl http://<fqdn>:5000/
并且得到了应用程序。
这意味着,真正的问题解决了,因为我能够向外界提供应用程序。 (所以是的,容器内的应用程序在 0.0.0.0 上侦听,这是其他人报告的 90% 网络问题的解决方案。)
但这仍然让我感到困惑,是什么导致了这种奇怪的行为?在我的开发机器(Windows 10、WSL、Docker 桌面、Linux 容器)上,我能够连接到 localhost:5000、127.0.0.1:5000 等上的服务。在我的 Linux(生产)机器上,除了从 docker 主机连接到容器外,一切正常。
我希望有人能阐明这一点,我试图理解为什么会这样。
一些相关信息
Docker主持人:
# ifconfig -a
br-77127ce4b631: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
inet 172.18.0.1 netmask 255.255.0.0 broadcast 172.18.255.255
[snip]
docker0: flags=4099<UP,BROADCAST,MULTICAST> mtu 1500
inet 172.17.0.1 netmask 255.255.0.0 broadcast 172.17.255.255
[snip]
ens3: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
inet 1xx.1xx.199.134 netmask 255.255.255.0 broadcast 1xx.1xx.199.255
# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
1e7f2f7a271b pplbase_api "flask run --host=0.…" 20 hours ago Up 19 hours 0.0.0.0:5000->5000/tcp pplbase_api_1
fdfa10b1ce99 elasticsearch:7.5.1 "/usr/local/bin/dock…" 21 hours ago Up 19 hours 0.0.0.0:9200->9200/tcp, 0.0.0.0:9300->9300/tcp pplbase_elastic_1
# docker network ls
NETWORK ID NAME DRIVER SCOPE
[snip]
77127ce4b631 pplbase_pplbase bridge local
# iptables -L -n
[snip]
ACCEPT tcp -- 0.0.0.0/0 0.0.0.0/0 tcp dpt:5000
Chain FORWARD (policy ACCEPT)
target prot opt source destination
DOCKER-USER all -- 0.0.0.0/0 0.0.0.0/0
DOCKER-ISOLATION-STAGE-1 all -- 0.0.0.0/0 0.0.0.0/0
ACCEPT all -- 0.0.0.0/0 0.0.0.0/0 ctstate RELATED,ESTABLISHED
DOCKER all -- 0.0.0.0/0 0.0.0.0/0
ACCEPT all -- 0.0.0.0/0 0.0.0.0/0 ctstate RELATED,ESTABLISHED
DOCKER all -- 0.0.0.0/0 0.0.0.0/0
Chain DOCKER (2 references)
target prot opt source destination
ACCEPT tcp -- 0.0.0.0/0 172.18.0.2 tcp dpt:9300
ACCEPT tcp -- 0.0.0.0/0 172.18.0.2 tcp dpt:9200
ACCEPT tcp -- 0.0.0.0/0 172.18.0.3 tcp dpt:5000
Chain DOCKER-ISOLATION-STAGE-1 (1 references)
target prot opt source destination
DOCKER-ISOLATION-STAGE-2 all -- 0.0.0.0/0 0.0.0.0/0
DOCKER-ISOLATION-STAGE-2 all -- 0.0.0.0/0 0.0.0.0/0
RETURN all -- 0.0.0.0/0 0.0.0.0/0
Chain DOCKER-ISOLATION-STAGE-2 (2 references)
target prot opt source destination
DROP all -- 0.0.0.0/0 0.0.0.0/0
DROP all -- 0.0.0.0/0 0.0.0.0/0
RETURN all -- 0.0.0.0/0 0.0.0.0/0
Chain DOCKER-USER (1 references)
target prot opt source destination
RETURN all -- 0.0.0.0/0 0.0.0.0/0
Docker 撰写文件:
version: '3'
services:
api:
build: .
links:
- elastic
ports:
- "5000:5000"
networks:
- pplbase
environment:
- ELASTIC_HOSTS=elastic localhost
- FLASK_APP=app.py
- FLASK_ENV=development
- FLASK_DEBUG=0
tty: true
elastic:
image: "elasticsearch:7.5.1"
ports:
- "9200:9200"
- "9300:9300"
networks:
- pplbase
environment:
- discovery.type=single-node
volumes:
- ${PPLBASE_STORE}:/usr/share/elasticsearch/data
networks:
pplbase:
driver: bridge
越挖越深,谜底越来越大。使用 netcat 时我可以建立连接
Connection to 127.0.0.1 5000 port [tcp/*] succeeded!
在没有客户端连接时使用 netstat 检查见:
tcp6 0 0 :::5000 :::* LISTEN 27824/docker-proxy
尝试从 docker 主机连接时,已建立连接:
tcp 0 1 172.20.0.1:56866 172.20.0.3:5000 SYN_SENT 27824/docker-proxy
tcp6 0 0 :::5000 :::* LISTEN 27824/docker-proxy
tcp6 0 0 ::1:58900 ::1:5000 ESTABLISHED 31642/links
tcp6 592 0 ::1:5000 ::1:58900 ESTABLISHED 27824/docker-proxy
所以我现在怀疑 docker 主机上有一些网络巫术。
Flask 实例 运行 在 0.0.0.0:5000
。
你试过了吗:curl http://0.0.0.0:5000/
?
可能是您的主机配置将 localhost 映射为 127.0.0.1
而不是 0.0.0.0
所以当我在解决这个问题的时候,慢慢地找到解决方案,我发现我最后的建议毕竟是正确的。在防火墙 (iptables) 中,我记录了所有丢弃的数据包,是的,docker-桥(不是 docker0,而是 br- 和容器 (veth) 之间的数据包被 iptables 丢弃了。添加允许来自接口的流量流动的规则解决了这个问题。
就我而言:sudo iptables -I INPUT 3 -s 172.20.0.3 -d 172.20.0.1 -j ACCEPT
其中 172.20.0.0/32 是由 Docker 生成的桥接网络。