我手起刀落用 docker run 跑了一个 PHP 应用的容器,又用 docker run 跑了一个 MySQL 的容器。在配置 PHP 连数据库时,我填了 MySQL 容器的名称 db_mysql,结果死活报错:Unknown MySQL server host 'db_mysql'。
一开始我以为是 MySQL 容器没启动成功,进去看了下服务是正常的。又以为是防火墙问题,检查了一下端口也是通的。最后才意识到问题出在 Docker 的网络上。
Docker 默认 bridge 网络的限制
以前用 docker-compose 的时候都是直接写服务名就能互相访问,为啥单跑 docker run 就不行?
去查了 Docker 官方文档才发现:所有直接 docker run 起来的容器,默认会被丢进一个叫 bridge 的默认桥接网络里。而这个默认桥接网络有一个很重要的限制:不支持内建的 DNS 解析服务。
也就是说,在默认 bridge 网络里,容器之间不能通过容器名互相访问,只能写死内网 IP,比如 172.17.0.2。但容器 IP 每次重启可能都会变,写死 IP 显然不现实。
验证默认网络的行为
为了确认这一点,我做了个小实验。
启动两个容器:
1docker run -d --name container_a alpine sleep 3600
2docker run -d --name container_b alpine sleep 3600
进入 container_a 尝试 ping container_b:
1docker exec -it container_a ping container_b
结果确实是 bad address 'container_b'。
但 ping IP 是可以的:
1docker exec -it container_a ping 172.17.0.3
这就验证了默认 bridge 网络确实不支持 DNS。
解决方案一:创建自定义网络
正确的做法是创建一个自定义的 bridge 网络。自定义网络默认就支持 DNS 解析。
1docker network create my_net
创建容器的时候直接指定网络:
1docker run -d --name php_app --network my_net nginx:alpine
2docker run -d --name db_mysql --network my_net mysql:8
这样 php_app 里直接 ping db_mysql 就能解析到内部 IP。
如果你已经启动了容器,也可以事后把容器加入网络:
1docker network connect my_net php_app
2docker network connect my_net db_mysql
解决方案二:用 Docker Compose
其实最简单的方法还是直接用 Docker Compose。Compose 会自动给项目里的所有服务创建一个默认网络,并且服务名就是 DNS 名。
一个简单的 docker-compose.yml:
1version: '3.8'
2
3services:
4 web:
5 image: nginx:alpine
6 networks:
7 - my_network
8
9 db:
10 image: mysql:8
11 environment:
12 MYSQL_ROOT_PASSWORD: example
13 networks:
14 - my_network
15
16networks:
17 my_network:
18 driver: bridge
在这个例子里,web 容器里可以直接通过 db 访问 MySQL。
这也是我之前用 Compose 没遇到过这个问题的原因,它帮我隐藏了网络配置的复杂性。
解决方案三:使用 –link(不推荐)
Docker 早期有一个 --link 参数可以实现容器名解析,比如:
1docker run -d --name php_app --link db_mysql:db mysql:8
但这个参数已经被官方标记为废弃了,不建议在新项目中使用。未来可能会被移除。
排查 DNS 问题的通用思路
遇到容器间网络不通的问题,我一般会按这个顺序排查:
- 确认容器都在同一个网络里:
1docker network inspect my_net
- 确认 DNS 是否能解析:
1docker exec -it 容器名 nslookup 目标容器名
- 确认端口是否监听:
1docker exec -it 容器名 netstat -tlnp
- 检查防火墙规则:宿主机或者容器内的 iptables 规则可能拦截了流量。
一个相关的坑:自定义 DNS
有时候容器里的 DNS 解析会慢或者解析不到某些域名。这可能是因为 Docker 默认使用宿主机的 DNS 配置。
可以在 docker run 时指定 DNS:
1docker run -d --dns 223.5.5.5 --dns 8.8.8.8 nginx:alpine
或者在 daemon 配置里全局设置:
1{
2 "dns": ["223.5.5.5", "8.8.8.8"]
3}
国内服务器我一般用阿里云的公共 DNS 223.5.5.5,解析国内域名会快一些。
总结
这次的问题虽然不大,但让我对 Docker 网络的理解更深了一层。核心结论:
- 默认 bridge 网络不支持容器名 DNS 解析。
- 自定义 bridge 网络支持 DNS,推荐用这种方式。
- Docker Compose 最省心,适合多容器项目。
--link已废弃,不要再用了。
搞懂了 Docker 的底层网络隔离机制,以后部署就不会再抓瞎了。