Skip to content

Docker 网络:四种模式与互访 ​

容器连不上,先分清是容器之间连不上、宿主机访问容器连不上、还是容器访问外网不通。三类问题的排查路径完全不同。

场景 ​

  • 两个容器要互相调用(app → mysql)。
  • 宿主机或外部要访问容器里的服务。
  • 容器要访问宿主机上的服务(比如宿主机的 Redis)。

一、四种网络模式 ​

bash
docker network ls
text
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 \
  appnet
bash
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.0

DB_HOST=mysql 直接写容器名。验证:

bash
docker run --rm --network appnet busybox nslookup mysql
text
Server:		127.0.0.11
Address:	127.0.0.11:53

Non-authoritative answer:
Name:	mysql
Address: 10.201.1.2

Server: 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.0

nginx 能访问 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/udpUDP
-p 8080-8090:80-90端口段(少用)
bash
docker port order-api
ss -lntp | grep 8080
text
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}}' mysql
text
appnet
bridge

两个容器不在同一个网络里,DNS 解析不了,IP 也不通。

2. DNS 能解析吗 ​

bash
docker exec order-api getent hosts mysql
docker exec order-api cat /etc/resolv.conf
text
10.201.1.2      mysql

nameserver 127.0.0.11
options ndots:0

nameserver 127.0.0.11 才对。如果是 8.8.8.8 之类,说明这个容器用的是默认 bridge(不提供 DNS)。

3. 端口在监听吗 ​

bash
docker exec mysql ss -lntp
text
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 OK

netshoot 带 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 route
text
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