用 Docker 部署服务时,网络是最容易出问题、也最容易被忽略的一环。默认情况下,容器用 bridge 网络,每次重建容器 IP 都可能变;容器之间要靠 IP 互访,改一次地址就得改一堆配置;有些场景(比如旧系统只认固定 IP、或者需要让容器像一台独立主机一样暴露在局域网里)默认网络根本满足不了。这篇文章把 Docker 的网络模式、自定义 bridge 网络的用法、固定 IP 的配置方法、容器名 DNS 解析讲清楚,再说明 macvlan 怎么让容器拿到独立 IP,最后列出几个常见坑。
一、Docker 的几种网络模式
Docker 内置了多种网络驱动,各有适用面:
| 驱动/模式 | 说明 | 适用场景 |
|---|---|---|
| bridge | 默认模式,容器接在虚拟网桥上,通过 NAT 出网 | 单机容器互访与对外服务 |
| host | 容器直接使用宿主网络栈,不隔离网络 | 追求极致网络性能、端口直接用宿主 |
| none | 不配置网络,只有 loopback | 完全隔离、不需要网络的批处理任务 |
| container | 与另一个容器共享网络命名空间 | 边车模式,共享同机网络 |
| macvlan | 给容器分配独立 MAC 和 IP,像物理机一样接入二层网络 | 需独立 IP、二层可达的旧系统 |
| overlay | 跨多台宿主机组网(配合 Swarm) | 多机集群通信 |
先看默认的 bridge。Docker 会在宿主机上创建一个名为 docker0 的虚拟网桥,默认网段常见是 172.17.0.0/16,每个容器分到一个 IP。容器之间可以通过这个 IP 互通,出网时通过宿主做源地址转换(NAT)。问题是:默认 bridge 上容器名不能互相解析(只有自定义网络才有内置 DNS),而且 IP 是动态分配的,重建就可能变。
# 查看现有网络与网段
docker network ls
docker network inspect bridge --format '{{json .IPAM.Config}}'
二、自定义 bridge 网络:最常用的选择
生产上推荐用"自定义 bridge 网络"替代默认 bridge,原因有两个:自定义网络自带 DNS 解析(容器名即主机名),还能自定义网段和网关,方便规划地址。而且它还有连接隔离的额外好处——只有同一个自定义网络里的容器才能互相访问,不同网络的容器默认互不打扰。
# 创建自定义 bridge 网络,指定子网与网关
docker network create \
--driver bridge \
--subnet 172.20.0.0/16 \
--gateway 172.20.0.1 \
--opt com.docker.network.bridge.name=br-app \
mynet
# 查看创建结果
docker network inspect mynet
参数说明:--subnet 定网段,规划时避开和宿主机已有网络、其他容器网络冲突的段(比如 172.17、172.18 若已被默认网络占用,就换 172.20);--gateway 指定网桥网关地址;--opt com.docker.network.bridge.name 给宿主上的网桥起个好认的名字,方便用 ip a 查看。
创建好网络后,把容器接进去:
docker run -d --name app --network mynet nginx:alpine
docker run -d --name db --network mynet mysql:8.0
# 在 app 容器里可直接用容器名 db 访问
docker exec -it app sh -c 'getent hosts db; ping -c1 db'
这正是容器名 DNS 解析的价值:连接串里写 db 而不是某个会变的 IP,重建容器也不影响配置。
三、配置固定 IP
自定义 bridge 网络支持给容器指定固定 IP。方法是在创建网络时定义好子网,然后在运行容器时用 --ip 指定。
# 前提:网络是用 --subnet 明确网段创建的
docker network create --driver bridge --subnet 172.20.0.0/16 --gateway 172.20.0.1 mynet
docker run -d --name app --network mynet --ip 172.20.0.10 nginx:alpine
docker run -d --name db --network mynet --ip 172.20.0.20 mysql:8.0
# 验证
docker inspect app --format '{{.NetworkSettings.Networks.mynet.IPAddress}}'
必须注意两点。第一,--ip 只有在网络是用 --subnet 明确定义过的自定义网络上才有效,在默认 bridge 上指定会报错。第二,指定的 IP 必须落在该子网范围内、且在网关之外,别和已分配的地址撞。第三,固定 IP 虽好,但能不用尽量不用——用容器名解析更灵活,固定 IP 主要是给那些不支持域名、只认 IP 的老系统准备的。
如果是 Compose,可以在 YAML 里为服务指定静态 IP:
networks:
mynet:
driver: bridge
ipam:
config:
- subnet: 172.20.0.0/16
gateway: 172.20.0.1
services:
app:
image: nginx:alpine
networks:
mynet:
ipv4_address: 172.20.0.10
四、容器名 DNS 解析的细节
自定义网络里的容器名解析,是 Docker 内置的 DNS 服务完成的(监听在 127.0.0.11)。有几点要知道:
第一,解析只作用于自定义网络,默认 bridge 网络不提供这个能力。这也是"为什么我用了容器名却解析不了"的最常见原因——容器还挂在默认网络上。
第二,解析的是"容器名"和"网络别名"。可以在接入网络时指定别名,一个容器在同一个网络里可以有多个名字。
# 接入网络时起别名,便于不同调用方使用不同名字
docker run -d --name mysql-prod --network mynet \
--network-alias db --network-alias mysql mysql:8.0
# 于是 mynet 里 db 和 mysql 都指向这个容器
第三,DNS 记录会随容器起停动态更新。容器重建(重新 run)通常会得到新 IP,但因为名字不变,依赖方无需改动。这正是比固定 IP 更推荐的原因。
第四,如果一个容器同时接入多个网络,它会在每个网络里各自注册名字,实现"多网络跨界"通信。
五、用 macvlan 让容器获得独立 IP
有些场景默认的 NAT 模型不好使:比如你需要让容器在局域网里像一台独立主机那样被访问(有自己的 IP、能被同网段其他设备直接 ping 到,而不是靠端口映射),或者要部署一个需要独立二层身份的服务。这时可以用 macvlan。
macvlan 的原理是给容器分配一个虚拟网卡,并赋予它独立的 MAC 地址和一个与宿主机同网段的 IP。容器直接"插在"物理网络上,像一个新设备。代价是:macvlan 通常要求宿主网卡开启混杂模式,某些云环境(虚拟网卡不支持或限制 MAC 数量)可能用不了;而且宿主机与 macvlan 容器之间默认不能直接通信(这是 macvlan 的设计特性,需额外在宿主上建一个 macvlan 接口来解决)。
# 创建 macvlan 网络:父接口为宿主物理网卡(如 eth0),子网与物理网同段
docker network create -d macvlan \
--subnet=192.168.1.0/24 \
--gateway=192.168.1.1 \
-o parent=eth0 \
macnet
# 让容器拿到一个独立的、与局域网同段的 IP
docker run -d --name legacy --network macnet --ip 192.168.1.50 nginx:alpine
# 从局域网另一台机器直接访问 192.168.1.50 即可
用 macvlan 时要确保分配的 IP 未被同网段其他设备占用,且 DHCP 地址池与手动分配的段错开,避免冲突。
六、常见坑与排查
坑一:用了容器名却解析不了。八成是容器在默认 bridge 上,或两个容器不在同一个网络里。用 docker network inspect <网络> 看容器的接入情况,或 docker inspect <容器> 看网络列表。
坑二:自定义网段和宿主机或 VPN 网段冲突。表现是容器出网异常、访问某些内网地址不通。规划时用 ip route 看一下宿主已有路由,避开冲突段。
坑三:--ip 报错 "user specified IP address is supported on user defined networks only"。说明你用在了默认 bridge 或者网络没定义 subnet。改用自定义网络。
坑四:容器重启后 IP 变了,配置里写死的 IP 失效。解决方案永远是改用容器名解析,而不是到处写 IP。
坑五:macvlan 容器和宿主机不通。这是 macvlan 的已知特性,需要在宿主机上再建一个 macvlan 子接口并配 IP 才能互相通信。
# 排查网络问题的常用命令
docker network ls
docker network inspect <网络名>
docker inspect <容器名> --format '{{json .NetworkSettings.Networks}}' | python3 -m json.tool 2>/dev/null \
|| docker inspect <容器名> | grep -A 15 '"Networks"'
docker exec <容器名> ip addr # 需镜像内有 ip 命令
docker exec <容器名> cat /etc/resolv.conf # 看 DNS 配置
docker port <容器名> # 看端口映射
七、怎么选:一份简短建议
大多数单机部署场景,用自定义 bridge 网络加容器名解析就够了:规划一个不冲突的网段,所有服务接进去,用名字互访。需要给老系统一个固定地址时,再在自定义网络上指定 --ip。要追求网络性能或让容器直接使用宿主端口,用 host 模式(但会失去端口隔离,注意端口冲突)。需要容器在局域网里拥有独立 IP、被同网段设备直接访问,才上 macvlan。跨多机通信属于 overlay 的领域,通常和 Swarm、Kubernetes 一起出现。
常见问题
Q:默认 bridge 和自定义 bridge 该用哪个?
优先自定义。自带 DNS、可自定义网段、更好的隔离性,几乎全面优于默认 bridge。默认 bridge 主要保留给 docker run 简单测试用。
Q:固定 IP 会不会因为重启而变?
用 --ip 指定的地址在容器重建(删掉再 run)时可能被别的容器占用;只要容器不删,重启 IP 保持。所以固定 IP 不如用容器名可靠,后者不依赖具体地址。
Q:容器之间怎么访问,能不能跨网络?
同一自定义网络内可直接用容器名互访。跨网络默认隔离,如果确实需要,可以建一个中间网络让两个网络的容器都接入,或者用网关转发。别为了图省事把所有容器都挂到一个大网络上,那会丢失隔离收益。
Q:host 模式有什么坑?
容器直接使用宿主网络,端口就是宿主端口,两个容器不能同时用同一个端口;容器里的服务监听 127.0.0.1 时,实际监听的是宿主回环。性能最好但灵活性最差。
Q:怎么防止容器网段和公司内网冲突?
规划阶段先用 ip route 和 ip a 查清宿主网络,再选一个空段(例如 172.30.0.0/16)。多个 Docker 环境共存时,给每台机分配不同的子网段,避免将来做跨机网络时地址打架。
总结
Docker 网络的核心是自定义 bridge:它提供内置 DNS(容器名即域名)、可自定义网段和网关,还具备连接隔离,是单机部署的首选,应优先于默认 bridge。
固定 IP 用 --ip 指定,但前提是网络必须由 --subnet 明确网段;能用容器名解析就不要写死 IP,因为名字不随重建改变,维护成本低得多。
macvlan 能让容器获得独立 MAC 与 IP、像物理机一样接入局域网,适合需要独立二层身份的场景,但要求父接口支持、并注意其与宿主默认不通、地址不能冲突等限制。
选择逻辑很简单:普通服务用自定义 bridge + 容器名;要固定地址给老系统用 --ip;要极致网络性能用 host;要独立 IP 上 macvlan;跨机集群才考虑 overlay。
排障先看容器挂在哪个网络、网段是否冲突、DNS 是否生效,用 docker network inspect 和 docker inspect 基本能定位绝大多数网络问题。
企业QQ咨询




