Docker 网络故障排查:高效解决连接问题

通过容器 DNS、用户自定义网络、端口发布、主机访问、DNS 和防火墙解决 Docker 网络问题。

Docker 网络故障排查:高效解决连接问题

当你能明确指出连接失败的方向时,Docker 网络问题会更容易解决。“容器无法连接”过于模糊。是主机尝试连接容器?是一个容器尝试连接另一个容器?是容器尝试连接互联网?还是流量从另一台机器进入?每种路径都使用不同的 Docker 行为。

首先用通俗语言描述路径:

主机上的浏览器 -> localhost:8080 -> 容器端口 80
api 容器 -> db 容器 -> 端口 5432
worker 容器 -> 公共互联网 -> api.example.com:443
远程笔记本 -> 服务器公网 IP -> 已发布的容器端口

一旦知道路径,就可以逐一测试每个跳点,而不是随机更改网络。

了解基本的 Docker 网络模式

大多数单主机 Docker 设置使用桥接网络。Docker 在主机上创建一个虚拟网络,为容器分配私有 IP 地址,并可以将选定的容器端口发布到主机上。

默认的 bridge 网络可以工作,但用户自定义的桥接网络更适合应用程序,因为它们提供基于容器名称的内置 DNS。这意味着如果 api 容器和 db 容器连接到同一个用户自定义网络,api 容器可以通过 db:5432 访问 db 容器。

创建自定义网络如下:

docker network create appnet
docker run -d --name db --network appnet postgres:16
docker run -d --name api --network appnet my-api

还有其他模式。host 网络共享主机的网络命名空间,并取消正常的端口发布行为;在某些 Linux 场景下有用,但会降低隔离性。none 使容器没有网络。overlay 用于多主机 Docker Swarm 网络。Compose 会自动为项目创建用户自定义网络,除非你另行配置。

“网络未找到”

此错误通常意味着网络名称错误,或者网络存在于与命令预期不同的上下文中。

检查可用网络:

docker network ls

检查你打算使用的网络:

docker network inspect appnet

如果网络不存在,创建它:

docker network create appnet

使用 Compose 时,实际网络名称可能带有项目名称前缀。compose.yml 中名为 backend 的网络可能显示为 myproject_backend。使用:

docker compose ps
docker network ls

如果你声明了外部 Compose 网络,Compose 不会为你创建它:

networks:
  appnet:
    external: true

在这种情况下,手动创建它,或者如果希望 Compose 管理它,则移除 external: true

容器间通信失败

要使两个容器通过名称通信,它们通常需要在同一个用户自定义网络上。首先确认这一点:

docker network inspect appnet

Containers 部分查找两个容器。

然后从包含基本工具的容器进行测试。你的应用程序镜像可能不包含 curldigping,这没关系。使用同一网络上的临时调试容器:

docker run --rm -it --network appnet nicolaka/netshoot

从内部:

dig db
curl -v http://api:8080/health
nc -vz db 5432

如果 DNS 失败,则容器可能不在同一个用户自定义网络上,或者你正在使用默认的桥接网络,期望它提供名称解析,但默认桥接网络并不以相同方式提供。如果 DNS 正常但连接失败,请检查目标服务是否在预期端口上监听。

在目标容器内部:

docker exec -it api sh
ss -ltnp || netstat -ltnp

一个常见错误是将应用程序绑定到容器内的 127.0.0.1。这只会监听该容器内部的回环地址。其他容器无法访问它。将应用程序配置为监听 0.0.0.0

另外,确保容器间通信使用容器端口,而不是主机发布的端口。如果数据库在容器中监听 5432 端口,其他容器应使用 db:5432,而不是 localhost:15432 或已发布的主机端口。

主机无法访问容器

要使主机访问桥接网络容器中的服务,通常需要发布端口:

docker run -d --name web -p 8080:80 nginx

这将主机端口 8080 映射到容器端口 80。从主机测试:

curl -v http://localhost:8080

检查 Docker 发布了什么:

docker port web
docker ps --format 'table {{.Names}}	{{.Ports}}'

如果没有端口映射,Dockerfile 中的 EXPOSE 不会发布端口。EXPOSE 是文档和元数据。你仍然需要 -p 或 Compose 的 ports:

如果端口已发布但连接失败,检查四件事:

  1. 应用程序在容器内监听容器端口。
  2. 应用程序监听 0.0.0.0,而不仅仅是 127.0.0.1
  3. 主机防火墙未阻止主机端口。
  4. 没有其他进程已占用主机端口。

查找主机端口冲突:

sudo lsof -i :8080
# 或
sudo ss -ltnp 'sport = :8080'

对于 Compose,记住语法:

ports:
  - "8080:80"

左侧是主机端口。右侧是容器端口。

容器无法访问互联网

分别测试 IP 连通性和 DNS:

docker exec -it app sh
ping -c 2 1.1.1.1
ping -c 2 example.com

某些镜像不包含 ping。如果可用,使用 curl:

curl -I https://example.com

如果 IP 正常但名称失败,则是 DNS 问题。检查:

cat /etc/resolv.conf

Docker 通常会注入解析器设置。企业 VPN、自定义 DNS 和 Docker Desktop 网络可能会使情况复杂化。你可以在 Docker 守护进程设置中配置守护进程级别的 DNS,或为特定容器传递 DNS:

docker run --dns 1.1.1.1 ...

在企业环境中不要盲目使用公共 DNS,因为内部名称必须解析。使用适合网络的 DNS 服务器。

如果 IP 和 DNS 都不行,检查容器是否在 --network none 上,主机防火墙/NAT 规则是否损坏,Docker 守护进程是否有自定义网络设置,以及主机本身是否有互联网访问。

容器需要访问主机上的服务

从容器内部,localhost 指的是容器本身,而不是主机。这是最常见的 Docker 网络意外之一。

在 Docker Desktop 上,host.docker.internal 通常解析为主机。在较新的 Linux Docker Engine 上,你可以添加主机网关条目:

docker run --add-host=host.docker.internal:host-gateway ...

然后容器可以调用:

curl http://host.docker.internal:3000

确保主机服务监听在 Docker 可访问的地址上,而不仅仅是 Docker 在你的环境中无法访问的回环绑定。如果本地开发服务器仅绑定到 127.0.0.1,你可能需要根据操作系统和安全要求将其绑定到 0.0.0.0 或主机接口。

远程机器无法访问容器

如果主机可以访问 localhost:8080,但另一台机器无法访问 server-ip:8080,Docker 可能没问题。检查主机防火墙、云安全组、路由器/NAT,以及 Docker 是否仅发布到回环地址。

这将发布到所有主机接口:

docker run -p 8080:80 nginx

这仅发布到 localhost:

docker run -p 127.0.0.1:8080:80 nginx

仅回环发布通常适用于本地开发或反向代理设置,但会故意阻止远程访问。

在云服务器上,还要检查提供商防火墙。如果云安全组仍然阻止端口,在 VM 上打开 ufw 也无济于事。

Compose 网络错误

Compose 为每个服务提供一个基于服务名称的 DNS 名称。如果你的服务名为 db,其他服务通常应连接到 db,而不是 localhost

示例:

services:
  api:
    build: .
    environment:
      DATABASE_URL: postgres://postgres:postgres@db:5432/app
    depends_on:
      - db
  db:
    image: postgres:16

depends_on 控制启动顺序,而不是就绪状态。数据库容器可能在 Postgres 准备好接受连接之前启动。你的应用程序应重试连接或使用健康检查感知的启动模式。

还要区分 portsexposeports 发布到主机。expose 记录或向链接的服务暴露端口,但不会以相同方式使其从主机可达。

数据包级调试

当常规检查无法解释问题时,使用网络调试镜像:

docker run --rm -it --network container:<目标容器> nicolaka/netshoot

这会加入目标容器的网络命名空间,让你从相同视角检查网络,而无需在应用程序镜像中安装工具。

有用的命令包括:

ip addr
ip route
cat /etc/resolv.conf
dig service-name
curl -v http://service-name:port
tcpdump -nn -i any port 8080

当你需要知道数据包是否到达时,使用 tcpdump。如果数据包从未到达,检查容器之前的部分:发布、防火墙、路由、负载均衡器。如果数据包到达但没有响应离开,检查容器内部或应用程序。

简短的故障排查流程

对于大多数 Docker 网络问题,按此顺序操作:

  1. 定义确切路径:主机到容器、容器到容器、容器到互联网,或远程到主机再到容器。
  2. 使用 docker network inspect 检查网络连接。
  3. 从源端检查名称解析。
  4. 检查目标进程是否在正确的接口和端口上监听。
  5. 仅对跨越主机到容器的流量检查端口发布。
  6. 检查 Docker 外部的防火墙、VPN、代理、DNS 和云安全规则。

大多数 Docker 网络问题并非深层的 Docker 错误。它们通常是名称错误、端口错误、回环绑定、缺少已发布端口、DNS 假设,或从错误边界测试流量所致。