三个容器,一个 docker-compose.yml——docker compose ps 全绿,docker compose logs 没报错。但浏览器打开就是 502,应用日志里 Redis 连接超时。这是一次真实的 Docker 网络排查过程,从症状到根因,每一步都有命令和输出。
Docker 网络排查的核心难点不在命令本身,而在于理解 Compose 网络的隔离机制:默认 bridge、自定义 network、DNS 解析范围,这三者的边界在哪。这篇文章把一个同时踩了两个坑的案例拆开,演示 Docker 网络排查的完整思路。
场景:一个 WordPress 开发环境
本地搭了一套 WordPress + Redis + Nginx 的开发环境。也正是在这个环境下,Docker 网络排查的典型问题暴露了出来:
services:
nginx:
image: nginx:alpine
ports:
- "80:80"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf
wordpress:
image: wordpress:php8.3-fpm
environment:
WORDPRESS_DB_HOST: db
WORDPRESS_REDIS_HOST: redis
volumes:
- ./wp-content:/var/www/html/wp-content
db:
image: mariadb:11
redis:
image: redis:7-alpine
docker compose up -d 之后:
$ docker compose ps
NAME STATUS
app-db-1 Up 2 minutes
app-nginx-1 Up 2 minutes
app-redis-1 Up 2 minutes
app-wordpress-1 Up 2 minutes
四个容器全绿。然后 curl 一下:
$ curl -sI http://localhost
HTTP/1.1 502 Bad Gateway
502。Nginx 连不上 PHP-FPM。Docker 网络排查正式开始。
Docker 网络排查第一层:Nginx → WordPress
先看 Nginx 配置里 upstream 指向哪:
upstream php {
server wordpress:9000;
}
用的是服务名 wordpress:9000。进去 Nginx 容器验证 DNS 解析——这是 Docker 网络排查的核心步骤:
$ docker compose exec nginx nslookup wordpress
Server: 127.0.0.11
Address: 127.0.0.11#53
Name: wordpress
Address: 172.20.0.4
解析正常。再直接连 9000 端口:
$ docker compose exec nginx nc -zv wordpress 9000
wordpress (172.20.0.4:9000) open
端口也是通的。那问题不在 Nginx 到 WordPress 这条链路上。
回头看 WordPress 自己的日志,继续 Docker 网络排查:
$ docker compose logs wordpress | tail -5
WordPress 6.6.2 PHP 8.3.10
MySQL connected successfully
Redis: Connection timed out [tcp://redis:6379]
MySQL 正常,Redis 超时。WordPress 连不上 Redis。之前做 Redis Object Cache 配置时也遇到过类似现象,但那次是配置问题,这次是网络层面的。
Docker 网络排查第二层:WordPress → Redis
进 WordPress 容器测试 Redis 连通性:
$ docker compose exec wordpress sh
# nslookup redis
Server: 127.0.0.11
Address: 127.0.0.11#53
** server can't find redis: NXDOMAIN
# ping redis
ping: bad address 'redis'
redis 这个主机名根本解析不了。但 docker compose ps 里 redis 容器明明是 Up 的。这就是 Docker 网络排查的关键转折——DNS 不通,但容器在跑:
# ip addr show eth0
3: eth0@if45: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500
inet 172.20.0.3/16 brd 172.20.255.255
WordPress 在 172.20.0.0/16 网段。Redis 在同一个 docker inspect 输出里显示 app_default 网络——但 DNS 为什么解析不到?
重新检查 docker-compose.yml——前面贴的配置漏了一行,而这正是 Docker 网络排查的核心发现:
redis:
image: redis:7-alpine
networks:
- cache # <-- 独立网络
Redis 被手动指定到了 cache 网络。翻到文件底部的 networks 声明:
networks:
default:
driver: bridge
cache:
driver: bridge
Docker Compose 创建了两个隔离的 bridge 网络:app_default 和 app_cache。WordPress 在 app_default,Redis 在 app_cache——不同网络,DNS 互不可见。
这是 Docker 网络排查的第一个结论:Compose 里每个 named network 都是隔离的 bridge,容器只能解析同一网络内其他容器的服务名。这个行为在 Docker Compose 网络官方文档里有详细说明。
验证一下,分别检查两个网络中的容器列表:
$ docker network inspect app_default | python3 -c "
import sys,json
net=json.load(sys.stdin)
containers=[c['Name'] for c in net[0]['Containers'].values()]
print(containers)
"
['app-nginx-1', 'app-wordpress-1', 'app-db-1']
$ docker network inspect app_cache | python3 -c "
import sys,json
net=json.load(sys.stdin)
containers=[c['Name'] for c in net[0]['Containers'].values()]
print(containers)
"
['app-redis-1']
确认了:Redis 独自在 app_cache,其他三个在 app_default。Docker 网络排查定位到了根因。
修复:Docker 网络排查的两种解法
方案 A:把 Redis 也加到 default 网络
把 Redis 的 networks 改成同时加入两个网络:
redis:
image: redis:7-alpine
networks:
- default
- cache
重建后验证 DNS——这是 Docker 网络排查的验证习惯,每次改完都测一遍:
$ docker compose down && docker compose up -d
$ docker compose exec wordpress nslookup redis
Name: redis
Address: 172.20.0.5
$ docker compose exec wordpress nc -zv redis 6379
redis (172.20.0.5:6379) open
方案 B:全部放在同一个网络,删掉 cache
Redis 不需要独立网络的话,直接删 networks 声明:
redis:
image: redis:7-alpine
# 删掉 networks 声明
同时删掉 bottom 的 cache 网络声明。这样所有服务都在 app_default 里,DNS 全互通。选方案 B——更简单,后续做 Docker 网络排查也不用考虑多网络场景。
Docker 网络排查第三层:Nginx → WordPress(Round 2)
Redis 通了,但 curl 还是 502:
$ curl -sI http://localhost
HTTP/1.1 502 Bad Gateway
不过这次 Redis 日志正常了——Docker 网络排查的一个收获是排除了一个变量:
$ docker compose logs wordpress | grep Redis
Redis connected successfully
那 502 还是 Nginx → WordPress 的问题。前面测过 TCP 端口通,但 Nginx 找的不是端口——是文件。看 fastcgi 配置:
$ cat nginx.conf | grep -A5 fastcgi_pass
fastcgi_pass wordpress:9000;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
SCRIPT_FILENAME 指向 $document_root。而这个 root 在 Nginx 容器内部:
$ docker compose exec nginx grep root /etc/nginx/nginx.conf
root /var/www/html;
$ docker compose exec nginx ls /var/www/html
ls: /var/www/html: No such file or directory
Nginx 容器里根本没有 /var/www/html——网站文件在 WordPress 容器里。这个问题在之前的 Nginx 反向代理排坑中也遇到过类似的路径认知偏差。
修复:Nginx 里删掉 root,SCRIPT_FILENAME 直接用 FPM 容器内路径:
location ~ .php$ {
fastcgi_pass wordpress:9000;
fastcgi_param SCRIPT_FILENAME /var/www/html$fastcgi_script_name;
include fastcgi_params;
}
$ docker compose restart nginx
$ curl -sI http://localhost
HTTP/1.1 200 OK
X-Powered-By: PHP/8.3.10
200。这次 Docker 网络排查的两个问题全部解决。
Docker 网络排查总结
这个案例里有两个独立问题,表现相同(都是 502),但根因完全不同:
| 问题 | 现象 | 根因 | Docker 网络排查命令 |
|---|---|---|---|
| Redis 不可达 | Connection timed out | 独立 network 导致 DNS 隔离 | nslookup redis |
| Nginx 502 | curl 返回 502 | Nginx root 指向不存在路径 | ls /var/www/html |
总结 Docker 网络排查的四条核心规则:
- 不声明 networks,所有服务自动加入
{项目名}_defaultbridge 网络,DNS 互通 - 声明了自定义 network,容器只加入声明的网络——没加的网络里 DNS 不可见
docker compose exec 容器名 nslookup 服务名——Docker 网络排查第一步,区分 DNS 失败和端口不通nc -zv 服务名 端口——Docker 网络排查第二步,确认 TCP 层可达后再看应用层
另外一条教训:Nginx + PHP-FPM 分离部署时,Nginx 容器里不需要有网站文件。fastcgi_pass 把请求转发给 PHP-FPM 容器,SCRIPT_FILENAME 用 FPM 容器内的路径,不要写 Nginx 本地的路径。