外观
Docker 网络:四种模式与互访
容器连不上,先分清是容器之间连不上、宿主机访问容器连不上、还是容器访问外网不通。三类问题的排查路径完全不同。
场景
- 两个容器要互相调用(app → mysql)。
- 宿主机或外部要访问容器里的服务。
- 容器要访问宿主机上的服务(比如宿主机的 Redis)。
一、四种网络模式
bash
docker network lstext
NETWORK ID NAME DRIVER SCOPE
a1b2c3d4e5f6 bridge bridge local
f6e5d4c3b2a1 host host local
9c8b7a6f5e4d none null local| 模式 | 说明 | 使用场景 |
|---|---|---|
bridge(默认) | 每个容器有独立网络命名空间,通过 NAT 访问外网 | 绝大多数情况 |
host | 容器共享宿主机网络,无独立 IP | 高性能、需要监听大量端口 |
none | 只有 loopback | 完全隔离的计算任务 |
container:<name> | 共享另一个容器的网络 | sidecar、调试容器 |
默认 bridge 的容器不能靠名字互访
Docker 默认的 bridge 网络不支持自动 DNS。容器之间只能用 IP 互连,而 IP 在重启后会变。
必须自建 bridge 网络,Docker 会为它启用内嵌 DNS(127.0.0.11),容器名即可解析。
二、自建网络(推荐做法)
bash
docker network create \
--driver bridge \
--subnet 10.201.1.0/24 \
--gateway 10.201.1.1 \
appnetbash
docker run -d --name mysql --network appnet \
-e MYSQL_ROOT_PASSWORD=secret mysql:8.0
docker run -d --name order-api --network appnet \
-e DB_HOST=mysql \
myapp:1.0.0DB_HOST=mysql 直接写容器名。验证:
bash
docker run --rm --network appnet busybox nslookup mysqltext
Server: 127.0.0.11
Address: 127.0.0.11:53
Non-authoritative answer:
Name: mysql
Address: 10.201.1.2Server: 127.0.0.11 就是 Docker 的内嵌 DNS。它能解析:
- 容器名
--network-alias指定的别名- Compose 里的服务名
查网络里有哪些容器:
bash
docker network inspect appnet -f '{{range .Containers}}{{.Name}} {{.IPv4Address}}{{println}}{{end}}'text
mysql 10.201.1.2/24
order-api 10.201.1.3/24别名
同一个容器可以有多个名字(滚动发布时用):
bash
docker run -d --name order-api-v2 --network appnet --network-alias order-api myapp:2.0.0旧容器还持有 order-api 别名时,DNS 会返回多个 IP,Docker 做轮询。
动态接入
bash
docker network connect appnet some-container
docker network disconnect appnet some-container一个容器可以同时接入多个网络,适合"前端网络 + 后端网络"隔离:
bash
docker network create frontend
docker network create backend
docker run -d --name nginx --network frontend -p 80:80 nginx
docker network connect backend nginx
docker run -d --name mysql --network backend mysql:8.0nginx 能访问 mysql,但 frontend 里的其他容器不能。
三、端口映射
bash
docker run -d -p 8080:80 nginx
docker run -d -p 127.0.0.1:8081:80 nginx
docker run -d -p 53:53/udp coredns/coredns
docker run -d -P nginx # 随机映射所有 EXPOSE 的端口格式 [宿主IP:]宿主端口:容器端口[/协议]。
| 写法 | 含义 |
|---|---|
-p 8080:80 | 所有网卡的 8080 → 容器 80 |
-p 127.0.0.1:8080:80 | 只有 loopback,外部访问不到 |
-p 8080:80/udp | UDP |
-p 8080-8090:80-90 | 端口段(少用) |
bash
docker port order-api
ss -lntp | grep 8080text
8080/tcp -> 0.0.0.0:8080
LISTEN 0 4096 0.0.0.0:8080 0.0.0.0:* users:(("docker-proxy",pid=29411,fd=4))看到 docker-proxy 是正常的(用户态代理);iptables 模式下也可能直接由内核 DNAT。
-p 127.0.0.1 与云安全组
-p 127.0.0.1:8080:80 只监听 loopback,外部一定访问不到,即使安全组全开。反过来说,如果你只想让 Nginx 反代、不暴露后端,这是正确写法。
排查"端口不通"时先用 ss -lntp 看监听地址是 0.0.0.0 还是 127.0.0.1。
四、host 模式
bash
docker run -d --network host --name nginx nginx容器直接用宿主机网络,没有端口映射,nginx 监听 80 = 宿主机 80。
| 优点 | 缺点 |
|---|---|
| 没有 NAT,性能最好 | 端口冲突(宿主机和容器抢同一个端口) |
| 客户端 IP 是真实 IP,不是 172.x | 容器之间没有网络隔离 |
不需要 -p | 同一端口只能跑一个实例 |
只在"高性能网关"或"需要真实客户端 IP"时用。
五、容器访问宿主机
Linux 下宿主机在容器里的地址是 docker0 的网关(默认 172.17.0.1)。自建网络则是该网络的网关(上面例子是 10.201.1.1)。
bash
ip addr show docker0 | grep inet
docker network inspect appnet -f '{{range .IPAM.Config}}{{.Gateway}}{{end}}'text
inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0
10.201.1.1跨平台写法(Docker 20.10+):
bash
docker run --add-host host.docker.internal:host-gateway myapp:1.0.0容器内 host.docker.internal 就指向宿主机网关。这是唯一在 Linux/macOS/Windows 都能用的方式。
宿主机服务必须监听 0.0.0.0 或网关地址
如果宿主机的 Redis 配了 bind 127.0.0.1,容器通过 172.17.0.1 是连不上的。要么改 bind 0.0.0.0 并加防火墙白名单,要么用 --network host。
六、debug:两个容器互相 ping 不通
按这个顺序查:
1. 在同一个网络里吗
bash
docker inspect -f '{{range $k,$v := .NetworkSettings.Networks}}{{$k}} {{end}}' order-api
docker inspect -f '{{range $k,$v := .NetworkSettings.Networks}}{{$k}} {{end}}' mysqltext
appnet
bridge两个容器不在同一个网络里,DNS 解析不了,IP 也不通。
2. DNS 能解析吗
bash
docker exec order-api getent hosts mysql
docker exec order-api cat /etc/resolv.conftext
10.201.1.2 mysql
nameserver 127.0.0.11
options ndots:0nameserver 127.0.0.11 才对。如果是 8.8.8.8 之类,说明这个容器用的是默认 bridge(不提供 DNS)。
3. 端口在监听吗
bash
docker exec mysql ss -lntptext
LISTEN 0 151 0.0.0.0:3306 0.0.0.0:* users:(("mysqld",pid=1,fd=22))只监听 127.0.0.1:3306 的话,其他容器连不上。
4. 用临时容器验证
bash
docker run --rm -it --network appnet nicolaka/netshoot \
sh -c 'nc -vz mysql 3306; curl -sv http://order-api:8080/health'text
mysql (10.201.1.2:3306) open
* Connected to order-api (10.201.1.3) port 8080
< HTTP/1.1 200 OKnetshoot 带 ping、nc、curl、dig、tcpdump,是最快的验证工具。
七、网段冲突
默认 bridge 用 172.17.0.0/16,自建网络会从 172.18.0.0/16 往后分配。如果公司内网或 VPN 正好用 172.17,容器访问内网会全部失败。
现象:容器内 ping 172.17.5.10(内网真实机器)没响应,但宿主机能 ping 通。
bash
ip routetext
172.17.0.0/16 dev docker0 proto kernel scope link src 172.17.0.1
172.17.0.0/16 via 10.0.0.1 dev eth0 # VPN 路由两条路由冲突,容器走的是 docker0。
解决:在 daemon.json 里改地址池(见 安装与加速),或者用 --subnet 显式指定。
json
"default-address-pools": [
{ "base": "10.201.0.0/16", "size": 24 }
]改完要重建网络和容器(旧网络的网段不会自动变)。
八、overlay 与 macvlan
| 驱动 | 场景 |
|---|---|
overlay | 多主机(Swarm),跨机器容器互通 |
macvlan | 容器要拿和宿主机同网段的真实 IP |
ipvlan | 类似 macvlan,但共享 MAC,适合有 MAC 数量限制的环境 |
macvlan 示例(容器直接进物理网络):
bash
docker network create -d macvlan \
--subnet=10.0.0.0/24 --gateway=10.0.0.1 \
-o parent=eth0 pubnet
docker run -d --network pubnet --ip 10.0.0.50 nginx容器 IP 就是 10.0.0.50,同网段机器可以直接访问,不用端口映射。代价是宿主机自己访问不到这个容器(macvlan 的已知限制),需要额外建一个 macvlan 子接口。
九、踩过的坑
| 现象 | 根因 | 处置 |
|---|---|---|
| 容器名解析不了 | 用了默认 bridge | 自建 docker network create |
| 两个容器互 ping 不通 | 不在同一网络 | network connect 或重建时指定网络 |
| 宿主机访问不了容器端口 | -p 127.0.0.1:... 只监听 loopback | 改 0.0.0.0 或走 Nginx |
| 外部访问不了但宿主机 curl 正常 | 云安全组没放通 | 开安全组 |
| 容器访问内网失败 | 172.17 网段冲突 | 改 default-address-pools |
| 容器连不上宿主机服务 | 宿主机服务 bind 127.0.0.1 | 改监听地址,或 --network host |
| 重启后 IP 变了,写死 IP 的客户端挂了 | 容器 IP 不固定 | 用容器名/DNS,别用 IP |
| host 模式端口冲突 | 容器和宿主机都要 80 | 换端口或改回 bridge 映射 |
| 客户端 IP 全是 172.17.0.1 | 经过 NAT | 用 host 模式,或让 Nginx 读取 X-Forwarded-For |