1. 概述

前面三篇已经完成了 Docker 的基本运行链路:知道镜像、容器、仓库之间的关系,能在 Linux 服务器上安装 Docker Engine,也能用 Dockerfile 把自己的应用构建成镜像。从这一篇开始,容器不再只是一个能启动的进程,而要真正接入主机、其他容器、局域网和外部服务。这个边界就是 Docker 网络。

容器网络是 Docker 单机运行中最容易被低估的一层。很多问题表面上看是应用故障,实际原因却是端口没有发布、容器接错网络、DNS 解析发生在错误的网络命名空间、防火墙规则被覆盖、宿主机端口被占用,或者把 EXPOSE 误解成了对外开放端口。对运维人员来说,掌握容器网络不是为了记住所有 driver 参数,而是为了能回答四个问题:

  1. 容器之间如何通信?
  2. 宿主机如何访问容器服务?
  3. 外部客户端如何访问容器服务?
  4. 容器如何访问外部网络和企业内网服务?

本文以单机 Docker Engine 为主线,重点讲清楚 bridge、host、none、macvlan、ipvlan、overlay 的适用边界,尤其是最常用的自定义 bridge 网络、端口发布、容器 DNS、网络排障和生产基线。Swarm overlay 只做概念说明,Kubernetes CNI 不展开;后续进入 Compose 和编排平台时,会继续复用本文建立的网络模型。

本文验证环境如下:

项目 说明
操作系统 Ubuntu 24.04 LTS 或 Ubuntu 22.04 LTS
Docker 组件 Docker Engine、Docker CLI
主要网络 默认 bridge、自定义 bridge、host、none
测试镜像 nginx:alpinebusybox:latestcurlimages/curl
命令权限 具备 Docker socket 访问权限,生产环境建议使用受控 sudo

所有命令都应先在测试主机执行。网络命令会创建容器、网络和端口映射,虽然不会修改业务数据,但可能与本机已有端口、iptables/nftables 策略或企业安全基线冲突。

2. 先建立容器网络的心智模型

容器运行时看到的是自己的网络命名空间。它不知道自己连接的是 Docker bridge、host、macvlan 还是 overlay;它只看到网卡、IP、网关、路由表和 DNS 配置。网络 driver 是 Docker daemon 和宿主机内核负责的事。

这张图解释了 bridge 模式下的基本路径:每个容器拥有独立的网络命名空间,Docker 通过 veth pair 把容器网卡接到宿主机上的 Linux bridge,再通过 NAT、路由和端口发布把流量转发出去。

这里有三个容易混淆的概念。

第一,容器内端口不等于宿主机端口。 应用在容器内监听 0.0.0.0:80,只是表示它在容器网络命名空间内监听 80 端口。外部客户端能不能访问,取决于是否通过 -p 或 Compose ports 发布到宿主机。

第二,EXPOSE 不会发布端口。 Dockerfile 中的 EXPOSE 80 只是镜像元数据,用于说明容器期望监听哪个端口。真正把端口暴露给宿主机或外部网络的是 docker run -pdocker run -P 或编排平台的服务发布配置。

第三,自定义 bridge 与默认 bridge 行为不同。 默认 bridge 网络可以让容器出网,也能用 IP 互通,但不提供现代项目期望的自动名称解析。自定义 bridge 网络提供容器名 DNS 解析、隔离边界和更清晰的生命周期管理,单机多容器应用应优先使用自定义 bridge。

3. Docker 内置网络 driver 怎么选

Docker Engine 在 Linux 上内置多个网络 driver。不要一上来就追求复杂模式,大多数单机应用只需要自定义 bridge + 明确端口发布。

Driver 适用场景 优点 注意事项
bridge 单机容器互通、Web/API/数据库组合 默认稳定,隔离清晰,可发布端口 跨主机通信需要其他方案
host 性能敏感、需要直接使用宿主机网络栈的服务 无端口 NAT 开销,容器直接使用宿主机网络 失去网络命名空间隔离,端口冲突风险高
none 完全禁止网络访问的离线任务、安全测试 网络面最小 只有 loopback,不能访问外部
overlay Docker Swarm 多主机服务互通 跨 Docker daemon 连接容器 需要 Swarm 或额外控制平面
macvlan 容器需要像局域网独立设备一样出现 容器可获得局域网 IP/MAC 云平台和交换机安全策略可能限制,宿主机到容器通信需额外设计
ipvlan 容器接入外部 VLAN,但希望减少 MAC 地址扩散 比 macvlan 更适合部分网络环境 需要理解 L2/L3 模式和上游网络策略

选择时可以按下面的流程判断。

Docker 内置网络 Driver 怎么选

这张图的核心结论是:不要把 hostmacvlan 当成默认方案。它们能解决特定问题,但也会绕开 Docker 的一部分隔离和可管理性。生产环境只有在需求明确、网络团队确认上游策略、排障路径清楚时再使用。

4. 查看 Docker 当前网络状态

先在测试主机上查看 Docker 默认网络。

bash
docker network ls

常见输出会包含:

text
NETWORK ID     NAME      DRIVER    SCOPE
...            bridge    bridge    local
...            host      host      local
...            none      null      local

不要在文档中假设 NETWORK ID,每台主机都不同。真正稳定的是网络名和 driver。

查看默认 bridge 详情:

bash
docker network inspect bridge

重点关注字段:

字段 意义
Driver 网络 driver,一般是 bridge
IPAM.Config.Subnet Docker 为该网络分配的子网
IPAM.Config.Gateway 容器默认网关
Containers 当前连接到该网络的容器
Options bridge 名称、iptables、masquerade 等配置

如果需要从宿主机层面观察,可以查看 bridge 设备和路由。

bash
ip link show type bridge
ip addr show docker0
ip route

注意:Docker 可能使用 iptables 或 nftables 兼容层管理转发规则,不同发行版和 Docker 版本的底层命令输出会有差异。生产排障时,先确认主机实际防火墙栈:

bash
sudo iptables -S | sed -n '1,80p'
sudo nft list ruleset | sed -n '1,120p'

这两条命令只用于观察,不要直接复制网络文章中的规则去覆盖生产防火墙。

5. 自定义 bridge:单机多容器应用的默认选择

默认 bridge 适合快速试验,但业务应用更推荐创建自定义 bridge。理由有三个:

  1. 容器可以通过名称自动解析。
  2. 每个应用拥有独立网络边界,减少无关容器互访。
  3. 网络生命周期清晰,便于清理和迁移到 Compose。

创建一个实验网络:

bash
docker network create \
  --driver bridge \
  --subnet 172.30.10.0/24 \
  --gateway 172.30.10.1 \
  app-net

验证网络:

bash
docker network inspect app-net

启动一个后端容器:

bash
docker run -d \
  --name app-api \
  --network app-net \
  nginx:alpine

启动一个测试容器访问它:

bash
docker run --rm \
  --network app-net \
  curlimages/curl:latest \
  curl -I http://app-api

在自定义 bridge 中,app-api 会通过 Docker 内置 DNS 解析到该容器在 app-net 里的 IP。这个能力比写死容器 IP 稳定得多,因为容器重建后 IP 可能变化,而容器名或 Compose 服务名可以保持不变。

下面的时序图展示了容器名访问在自定义 bridge 内部的基本过程。

自定义 bridge:单机多容器应用默认选择

这张图也解释了为什么容器名解析依赖“同一个网络”。如果两个容器不在同一个自定义 bridge 上,名称解析和直接访问都不会按预期工作,除非把容器同时接入多个网络或通过发布端口绕到宿主机。

查看容器连接的网络:

bash
docker inspect app-api --format '{{json .NetworkSettings.Networks}}'

将已有容器连接到另一个网络:

bash
docker network create ops-net
docker network connect ops-net app-api
docker inspect app-api --format '{{json .NetworkSettings.Networks}}'

断开连接:

bash
docker network disconnect ops-net app-api
docker network rm ops-net

多网络连接适合反向代理、网关、旁路监控等场景。例如 Nginx 反向代理同时连接 frontend-netbackend-net,外部只访问 Nginx,后端数据库只在 backend 网络中可见。

6. 端口发布:让外部访问容器

容器之间在同一 bridge 网络内可以直接访问容器端口,但外部客户端访问容器服务需要发布端口。最常见命令如下:

bash
docker run -d \
  --name web-published \
  --network app-net \
  -p 8080:80 \
  nginx:alpine

含义是:把宿主机所有地址上的 8080 端口映射到容器的 80 端口。验证:

bash
curl -I http://127.0.0.1:8080
docker port web-published

如果只希望宿主机本机访问,不希望局域网其他机器访问,应绑定到 loopback:

bash
docker run -d \
  --name web-local \
  --network app-net \
  -p 127.0.0.1:18080:80 \
  nginx:alpine

端口发布的安全边界非常重要。-p 8080:80 通常会监听宿主机所有网卡地址,实际是否能从外部访问还取决于主机防火墙、云安全组、上游 ACL 和本机路由。生产环境不要为了方便直接发布管理后台、数据库、Redis、Prometheus 等内部服务。

常见端口发布写法:

写法 含义 适用场景
-p 8080:80 宿主机任意地址 8080 转发到容器 80 Web/API 对外测试
-p 127.0.0.1:8080:80 只绑定本机 loopback 本机反向代理、开发调试
-p 192.168.10.20:8080:80 只绑定宿主机指定地址 多网卡主机控制暴露面
-P 随机发布镜像声明的端口 临时测试,不推荐生产

端口冲突时,容器会创建失败或启动失败。排查命令:

bash
sudo ss -lntp | grep ':8080'
docker ps --format 'table {{.Names}}\t{{.Ports}}'
docker logs web-published

不要用删除未知进程的方式解决端口冲突。先确认端口归属,再决定是改宿主机端口、停旧容器,还是调整反向代理。

7. 容器出网与 NAT

大多数容器需要访问外部网络,例如拉取配置、访问对象存储、连接外部 API 或访问企业内部 DNS。bridge 网络下,容器默认通过宿主机进行 masquerade/SNAT 出网,外部服务看到的源地址通常是宿主机地址,而不是容器内部 IP。

可以用下面命令验证容器出网:

bash
docker run --rm \
  --network app-net \
  curlimages/curl:latest \
  curl -I https://docs.docker.com

如果公司网络需要代理,不能只在宿主机 shell 里设置环境变量,还要把代理传给容器运行时或 Docker daemon。单个容器可以这样运行:

bash
docker run --rm \
  --network app-net \
  -e HTTP_PROXY=http://proxy.example.com:8080 \
  -e HTTPS_PROXY=http://proxy.example.com:8080 \
  -e NO_PROXY=localhost,127.0.0.1,.example.local \
  curlimages/curl:latest \
  curl -I https://docs.docker.com

生产基线中,NO_PROXY 尤其重要。企业内部域名、服务网段、Kubernetes service CIDR、Docker bridge 子网和本机地址不应该误走外部代理,否则会出现“能解析但访问慢”“服务偶发 407”“内部 API 被代理拒绝”等问题。

容器出网失败时,按下面顺序排查:

  1. 容器是否接入正确网络:docker inspect <container>
  2. 容器内是否有默认路由:ip route
  3. DNS 是否能解析:nslookup docs.docker.comgetent hosts docs.docker.com
  4. 宿主机是否能访问目标地址:curl -I <url>
  5. Docker 防火墙/NAT 规则是否被安全软件或运维脚本覆盖。
  6. 企业代理、云安全组、出口防火墙是否允许访问。

用于网络排障的临时容器可以这样启动:

bash
docker run --rm -it \
  --network app-net \
  nicolaka/netshoot

如果企业环境不允许从公网拉取 nicolaka/netshoot,可以准备一个内部排障镜像,包含 iproute2curlbind-toolstcpdumpsstraceroute 等工具。生产主机不建议在业务镜像里常驻大量排障工具,排障镜像和业务镜像应分开管理。

8. DNS 与容器名解析

Docker 在自定义网络中提供内置 DNS。容器的 /etc/resolv.conf 通常指向 Docker 的内部解析地址,容器名、网络别名和 Compose 服务名都依赖这个机制。

创建两个容器验证名称解析:

bash
docker run -d --name redis-demo --network app-net redis:7-alpine

docker run --rm --network app-net busybox:latest \
  sh -c 'nslookup redis-demo && nc -vz redis-demo 6379'

如果 busybox 镜像里的工具集不同,可以改用:

bash
docker run --rm --network app-net curlimages/curl:latest \
  sh -c 'getent hosts redis-demo || true'

网络别名适合让同一个服务在某个网络中以稳定业务名暴露:

bash
docker run -d \
  --name redis-blue \
  --network app-net \
  --network-alias redis-primary \
  redis:7-alpine

之后同一网络内的容器可以访问 redis-primary:6379。需要注意,别名只在对应 Docker 网络内有效,不会注册到企业 DNS。

容器 DNS 配置也可以通过运行参数控制:

bash
docker run --rm \
  --dns 10.20.1.2 \
  --dns-search example.local \
  busybox:latest \
  cat /etc/resolv.conf

这类参数适合在企业内网中访问内部域名,但不建议每个容器各自随意配置。更稳定的做法是制定主机级 Docker daemon 配置或 Compose 模板,避免不同项目 DNS 行为不一致。

9. host 与 none:两个边界模式

host 网络模式让容器直接使用宿主机网络栈。Linux 上运行:

bash
docker run --rm --network host nginx:alpine

此时 Nginx 会直接监听宿主机的 80 端口。如果宿主机 80 已被占用,容器内进程会失败。-p 在 host 模式下没有意义,因为已经没有容器网络命名空间到宿主机端口的映射边界。

host 模式适合:

  • 高性能网络代理或采集器,明确需要减少 NAT 和端口转发层。
  • 需要监听大量动态端口的特殊服务。
  • 某些必须感知宿主机网络栈的监控或安全工具。

host 模式不适合:

  • 普通 Web/API 服务。
  • 多个副本在同一宿主机并行运行。
  • 需要通过 Docker 网络隔离不同应用的场景。
  • 安全边界要求高的生产环境。

none 网络模式则完全相反。它禁用容器网络连接,只保留 loopback:

bash
docker run --rm --network none busybox:latest ip addr

适合离线计算、文件处理、安全测试和构建验证。例如一个只读取挂载目录并生成报告的批处理容器,不需要任何网络,就可以用 none 降低风险。

10. macvlan 与 ipvlan:让容器进入局域网

有些场景希望容器直接出现在局域网中,拥有独立 IP,像一台普通主机一样被交换机、监控系统或其他设备访问。例如家庭实验室中的 DNS 服务、某些老旧系统对源 IP 有强依赖、或需要在二层网络中模拟多台设备。这时可以评估 macvlan 或 ipvlan。

macvlan 示例:

bash
docker network create -d macvlan \
  --subnet=192.168.10.0/24 \
  --gateway=192.168.10.1 \
  -o parent=ens160 \
  lan-macvlan

启动容器:

bash
docker run -d \
  --name lan-web \
  --network lan-macvlan \
  --ip 192.168.10.50 \
  nginx:alpine

这类命令必须根据真实网卡名、网段、网关和地址规划修改,不能直接照抄。还要确认:

  • 上游交换机端口是否允许多个 MAC。
  • 云厂商是否禁止 macvlan。
  • 企业 NAC、DHCP、ARP 安全策略是否允许。
  • 容器 IP 是否纳入 IPAM/CMDB 管理。
  • 宿主机是否需要访问 macvlan 容器;如果需要,通常要额外创建宿主机侧 macvlan 接口。

ipvlan 与 macvlan 类似,但共享父接口 MAC,更适合某些限制 MAC 数量的网络环境。它同样不是默认方案,应该由网络团队参与设计。

如果只是想让外部访问 Web 服务,优先使用 bridge + -p + 反向代理,而不是直接把容器放进局域网。

11. Compose 中的网络写法预热

虽然 Compose 会在下一篇展开,但这里先看一个网络相关的最小例子,因为它能帮助理解“服务名就是 DNS 名”的实际用法。

yaml
services:
  web:
    image: nginx:alpine
    ports:
      - "127.0.0.1:18080:80"
    networks:
      - frontend

  api:
    image: hashicorp/http-echo:1.0
    command: ["-text=hello from api"]
    networks:
      - frontend
      - backend

  worker:
    image: busybox:latest
    command: ["sh", "-c", "sleep 3600"]
    networks:
      - backend

networks:
  frontend:
    driver: bridge
  backend:
    driver: bridge

在这个模型中:

  • webapi 都在 frontend,可以互相按服务名访问。
  • apiworker 都在 backend,可以互相访问。
  • web 不在 backend,不能直接访问 worker
  • 只有 web 的 80 端口被发布到宿主机 127.0.0.1:18080
Compose 网络:入口、应用与数据库分层

这张图体现了 Compose 网络设计的基本原则:按访问关系划分网络,而不是让所有容器都接入同一张大网。小型项目可以先用一张网络,生产环境则应区分入口层、应用层、数据层和运维层。

12. 排障 runbook:从现象到证据

容器网络排障不要先改配置。先把现象分层,找到失败发生在哪一段链路。

容器网络排障 Runbook

这个排障视图把容器网络故障拆成监听、容器间解析、宿主机端口、出网路径、iptables/NAT 和抓包验证几层。排障时先确认失败发生在哪一层,再选择对应证据命令。 常用证据命令如下。

检查容器是否监听:

bash
docker exec <container> ss -lntp
docker logs --tail=100 <container>

检查容器网络:

bash
docker inspect <container> --format '{{json .NetworkSettings.Networks}}'
docker network inspect <network>

从同网络临时容器访问:

bash
docker run --rm --network <network> curlimages/curl:latest \
  curl -v http://<container-name>:<port>/

检查宿主机端口:

bash
docker port <container>
sudo ss -lntp | grep '<host-port>'
curl -v http://127.0.0.1:<host-port>/

检查外部访问路径:

bash
ip addr
ip route
sudo iptables -S | sed -n '1,120p'
sudo nft list ruleset | sed -n '1,160p'

常见故障与处理建议:

现象 常见原因 处理
宿主机能访问,外部不能访问 -p 127.0.0.1:8080:80 只绑定本机;防火墙或安全组阻断 确认暴露范围,按最小权限开放
容器之间用名称访问失败 不在同一自定义网络;使用默认 bridge;容器名写错 创建自定义网络,确认 docker network inspect
EXPOSE 后外部仍不能访问 混淆 EXPOSE 与 publish 使用 -p 或 Compose ports
容器能访问 IP,不能访问域名 DNS 配置错误;企业内部域名未配置搜索域 检查 /etc/resolv.conf,配置 --dns 或 daemon DNS
容器出网失败 NAT/forward 被禁用;代理缺失;主机防火墙覆盖 Docker 规则 验证宿主机出网和 Docker 转发规则
端口发布失败 宿主机端口已占用 改端口或停止冲突服务
macvlan 容器从局域网可达,宿主机不可达 macvlan 常见宿主机通信限制 额外创建宿主机侧 macvlan 或改用 bridge

13. 生产环境网络基线

生产环境的 Docker 网络配置应尽量保守、可审计、可回滚。

13.1 只发布必须公开的端口

数据库、缓存、消息队列、管理后台和内部指标端口默认不应对外发布。它们应放在内部网络中,只允许应用容器或反向代理访问。

推荐模式:

  • Web/API 入口通过 Nginx、Traefik、HAProxy 或云负载均衡统一暴露。
  • 后端服务只在内部 bridge 网络通信。
  • 管理端口只绑定 127.0.0.1 或运维专用地址。
  • 对外端口由主机防火墙和云安全组双重约束。

13.2 自定义网络要有命名规范

网络名称应能表达业务和用途,例如:

text
crm-frontend
crm-backend
observability-net
batch-isolated

不要长期使用 test1net2my-net 这类无意义名称。清理时无法判断归属,审计时也无法解释用途。

13.3 子网规划要避开企业网段

Docker 自定义 bridge 会分配私有地址。如果它与企业 VPN、办公网、生产网、Kubernetes service CIDR 冲突,会出现非常隐蔽的路由问题。建议企业统一规划 Docker 单机实验网段,例如:

text
172.30.0.0/16 用于单机 Docker bridge
172.31.0.0/16 用于实验环境

具体使用前仍要与网络团队确认,不能假设所有私有地址都可用。

13.4 不把 Docker socket 暴露给普通容器

有些反向代理或自动化工具会要求挂载:

bash
-v /var/run/docker.sock:/var/run/docker.sock

这等于让容器具备控制宿主机 Docker daemon 的高权限。生产环境必须评估风险,优先使用只读代理、受限 API、专用 socket proxy 或编排平台原生发现机制。

13.5 记录网络变更

每次新增网络、发布端口、调整绑定地址或引入 macvlan/ipvlan,都应记录:

  • 业务系统与负责人。
  • Docker 网络名、driver、子网、网关。
  • 发布端口与绑定地址。
  • 防火墙、云安全组、负载均衡变更。
  • 回滚方式和验证命令。

容器网络不是“开发自己起一下”的临时配置。只要进入测试或生产环境,它就是基础设施变更的一部分。

14. 典型落地场景:反向代理、应用与数据库分层

为了把前面的网络概念串起来,下面用一个更接近企业内网测试环境的场景说明如何设计 Docker 单机网络。

假设一台测试服务器上运行三个组件:

  • edge-nginx:统一入口,只对测试人员开放 HTTPS 或 HTTP 端口。
  • app-api:业务 API,只允许入口代理访问。
  • postgres:数据库,只允许 API 访问,不允许入口代理和外部客户端直接访问。

这个场景不应该把三个容器都放在默认 bridge 里,也不应该把数据库端口直接 -p 5432:5432 发布到宿主机。更合理的网络分层是:

text
入口网络 edge-net:edge-nginx 与 app-api 共享
数据网络 data-net:app-api 与 postgres 共享
宿主机发布端口:只发布 edge-nginx 的 80/443

可以用命令创建网络:

bash
docker network create edge-net
docker network create data-net

启动数据库:

bash
docker run -d \
  --name postgres \
  --network data-net \
  -e POSTGRES_PASSWORD='change-me-in-lab-only' \
  -v pgdata:/var/lib/postgresql/data \
  postgres:16-alpine

启动 API 时同时连接两张网络:

bash
docker run -d \
  --name app-api \
  --network data-net \
  -e DATABASE_URL='postgresql://postgres:change-me-in-lab-only@postgres:5432/postgres' \
  my-registry.example.com/demo/app-api:2026-05-26

docker network connect edge-net app-api

启动入口代理:

bash
docker run -d \
  --name edge-nginx \
  --network edge-net \
  -p 127.0.0.1:18080:80 \
  -v "$PWD/nginx.conf:/etc/nginx/conf.d/default.conf:ro" \
  nginx:alpine

这里故意把入口绑定到 127.0.0.1:18080,表示测试服务器本机或本机上的二级反向代理可访问。如果要开放给局域网,需要把绑定地址改为测试网卡地址,并同步申请防火墙或安全组规则。

Nginx 配置可以写成:

nginx
server {
    listen 80;

    location / {
        proxy_pass http://app-api:8000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

这个配置能成立的前提是 edge-nginxapp-api 同在 edge-net,所以 app-api 能被 Docker DNS 解析。如果 Nginx 与 API 不在同一网络,proxy_pass http://app-api:8000 会解析失败。

验证顺序应该从内到外:

bash
# API 到数据库
docker exec app-api sh -c 'getent hosts postgres && nc -vz postgres 5432'

# 入口代理到 API
docker exec edge-nginx sh -c 'getent hosts app-api && wget -S -O- http://app-api:8000/health'

# 宿主机访问入口
curl -v http://127.0.0.1:18080/health

这个案例的价值不在于命令本身,而在于分层思路:

  1. 对外发布的只有入口层。
  2. API 不需要发布宿主机端口。
  3. 数据库只在数据网络内可见。
  4. 服务发现依赖容器名,而不是写死 IP。
  5. 每一层都能独立验证。

当这个模型迁移到 Compose 时,edge-netdata-net 会变成 networks 配置;迁移到 Kubernetes 时,会变成 Service、NetworkPolicy、Ingress 和命名空间策略。无论工具怎么变,入口层、应用层、数据层的隔离思路不变。

15. IPv6 与双栈网络注意事项

很多企业内网开始引入 IPv6 或双栈环境,但 Docker 单机 IPv6 不能按“默认就能用”来理解。Docker 网络默认通常启用 IPv4,IPv6 需要显式配置 Docker daemon 和网络。是否启用 IPv6,还要看宿主机内核、路由、防火墙、上游网络和安全策略是否支持。

创建仅 IPv6 网络的命令示例:

bash
docker network create \
  --ipv6 \
  --ipv4=false \
  --subnet fd00:30:10::/64 \
  v6net

创建双栈网络示例:

bash
docker network create \
  --subnet 172.30.20.0/24 \
  --ipv6 \
  --subnet fd00:30:20::/64 \
  dual-net

这类命令只能作为实验模板。生产启用 IPv6 前要确认:

  • Docker daemon 是否允许 IPv6。
  • 宿主机是否具备 IPv6 默认路由。
  • 防火墙是否同时覆盖 IPv4 和 IPv6。
  • 监控、日志、访问控制和资产台账是否能记录 IPv6 地址。
  • 应用是否监听 IPv6 地址,例如 :: 或明确的 IPv6 socket。
  • 反向代理是否正确处理 X-Forwarded-For 中的 IPv6 地址。

双栈环境里最常见的问题是“IPv4 正常、IPv6 半通”。例如 DNS 返回 AAAA 记录,容器优先尝试 IPv6,但上游防火墙未放行,表现为访问超时而不是立即失败。排查时不要只看 curl http://domain,应分别测试:

bash
curl -4 -I https://example.com
curl -6 -I https://example.com
getent ahosts example.com
ip -6 route

如果企业暂时没有完整 IPv6 运维能力,不建议在 Docker 单机生产环境里随意开启双栈。可以先在实验网段验证地址分配、日志记录、访问控制和监控告警,再进入测试环境。

16. 防火墙与 Docker 规则的协同

Docker 会在宿主机上管理转发和 NAT 规则,这也是很多生产环境争议最大的部分。运维人员习惯通过系统防火墙统一管理端口,Docker 则会为了端口发布自动写入规则。两者如果缺少约定,很容易出现“安全组看起来没开,容器却能访问”或“明明发布了端口,外部还是不通”的问题。

生产环境建议明确三条规则。

第一,Docker 端口发布必须经过变更。 应用团队不能随意 -p 0.0.0.0:端口:端口。端口、绑定地址、来源网段和业务负责人都要记录。

第二,主机防火墙和云安全组都要检查。 端口发布只是让 Docker 在宿主机层面接收和转发流量,不代表云平台安全组、物理防火墙、堡垒机策略或上游 ACL 已经允许。

第三,禁止绕过入口代理直接暴露内部服务。 如果数据库为了排障临时发布端口,排障结束后必须回收,并在日志中记录原因和持续时间。

检查 Docker 端口暴露面可以用:

bash
docker ps --format 'table {{.Names}}\t{{.Image}}\t{{.Ports}}'

检查所有自定义网络:

bash
docker network ls --filter type=custom

检查某个网络里的容器:

bash
docker network inspect edge-net \
  --format '{{range $id, $c := .Containers}}{{println $c.Name $c.IPv4Address}}{{end}}'

如果使用 ufwfirewalld 或企业安全代理,应该在标准操作手册里写清楚 Docker 与防火墙的协同方式,而不是让每个项目自行试错。尤其是 ufw 与 Docker 的组合,经常因为规则链顺序造成“UFW 拒绝了但 Docker 发布端口仍可访问”的误解。处理这类问题时,应参考 Docker 官方关于 packet filtering/firewalls 的说明和企业安全基线,而不是直接在生产主机手工改 iptables。

17. 容器网络命名与资产台账

小团队常把 Docker 网络当成临时资源,久而久之测试服务器上会积累几十个无主网络。真正排障时,没有人知道 app-net-oldbackend2test-net 属于哪个系统,能不能删,是否承载临时验证任务。

建议把 Docker 网络纳入轻量资产台账,至少记录以下字段:

字段 示例 用途
网络名 crm-backend 快速识别业务归属
Driver bridge 判断网络能力和风险
子网 172.30.42.0/24 避免地址冲突
网关 172.30.42.1 排障路由
负责人 CRM 应用组 变更确认
暴露端口 无或 127.0.0.1:18080 安全审计
创建原因 CRM 测试环境后端网络 判断是否可清理
过期时间 2026-06-30 清理计划

如果企业已经使用 NetBox 或 CMDB,可以把 Docker 单机网络作为虚拟网络或服务附件记录;如果没有正式系统,也可以先用仓库里的 Markdown 表格或 YAML 文件维护。关键是让网络资源有归属、有生命周期、有回收入口。

18. 从 Docker 网络过渡到 Compose 和 Kubernetes

掌握 Docker 单机网络不是终点,而是理解更高层编排的基础。

在 Compose 中:

  • 自定义 bridge 会由 networks 自动创建。
  • 服务名会成为同网络内 DNS 名。
  • ports 对应 Docker 端口发布。
  • expose 只表达服务内部端口,不等于发布到宿主机。
  • 多网络服务常用于入口代理、API 网关和旁路采集器。

在 Kubernetes 中:

  • Pod 拥有自己的网络命名空间。
  • Service 提供稳定虚拟 IP 和 DNS 名。
  • Ingress 或 Gateway API 负责入口流量。
  • NetworkPolicy 控制 Pod 之间的访问边界。
  • CNI 插件负责具体网络实现。

所以,从 Docker 单机迁移到编排平台时,不应该机械寻找“一一对应参数”,而要迁移设计意图:

Docker 单机意图 Compose 表达 Kubernetes 表达
容器按服务名互访 同一 network 下的服务名 Service DNS
入口服务对外发布 ports Ingress/Gateway/Service
数据库不对外暴露 backend network 内部访问 ClusterIP + NetworkPolicy
控制访问边界 多网络分层 Namespace + NetworkPolicy
临时排障容器 docker run --rm --network 临时 debug pod

这也是为什么本文反复强调不要写死容器 IP。只要服务发现依赖名称,网络边界依赖策略,迁移到 Compose 或 Kubernetes 时才有清晰路径。

19. 网络配置验收清单

容器网络配置完成后,不应只以“页面能打开”作为验收标准。页面能打开只能证明入口链路在某个时刻可用,不能证明网络边界、故障恢复、安全暴露面和运维证据都达标。建议把下面这组清单作为测试环境进入生产前的网络验收基线。

19.1 连通性验收

连通性验收要覆盖四条路径:

  1. 外部客户端到入口端口。
  2. 宿主机本机到发布端口。
  3. 入口容器到应用容器。
  4. 应用容器到依赖服务和外部 API。

每条路径都要留下命令和结果。例如:

bash
curl -v http://<host-ip>:<published-port>/health
curl -v http://127.0.0.1:<published-port>/health
docker exec edge-nginx wget -S -O- http://app-api:8000/health
docker exec app-api sh -c 'getent hosts postgres && nc -vz postgres 5432'
docker exec app-api curl -I https://docs.docker.com

如果企业禁止容器直接出公网,则最后一条应该换成访问内部 API、内部镜像仓库或代理出口验证。验收标准不是必须出公网,而是必须证明容器到它应访问的目标是可达的,并且不应访问的目标不可达。

19.2 隔离性验收

隔离性验收经常被忽略。很多项目只验证“能访问”,没有验证“不该访问的不能访问”。例如数据库只在 data-net 中,入口代理不应该直接访问数据库。可以用临时容器或现有容器验证:

bash
docker exec edge-nginx sh -c 'nc -vz postgres 5432'

如果这条命令成功,说明入口代理也能直接访问数据库,网络分层没有达到预期。此时应检查 edge-nginx 是否误接入了 data-net,或者数据库是否被发布到了宿主机端口。

隔离性验收还包括:

  • 未发布端口不能从局域网访问。
  • 管理端口不能绑定到 0.0.0.0
  • 内部服务不能通过宿主机公网地址绕回访问。
  • 临时排障容器不能长期留在生产网络。
  • 多个业务系统之间不能共享同一张无边界的大网络。

19.3 DNS 与名称稳定性验收

容器 IP 是运行时属性,不应写入应用配置、Nginx 配置或运维手册。验收时要检查所有服务间访问是否使用服务名、容器名、网络别名或上层服务发现名称。

可以检查配置文件:

bash
grep -R "172\\.|192\\.168\\.|10\\." ./config ./nginx.conf 2>/dev/null || true

这条命令只能发现明显的私有地址,不能替代人工审查。真正的验收重点是确认配置表达的是“访问哪个服务”,而不是“访问某个临时容器 IP”。如果必须使用固定 IP,例如 macvlan 场景,则应在 IPAM/CMDB 中登记,并说明为什么不能使用名称或负载均衡入口。

19.4 重启与重建验收

容器网络配置还要经受重启和重建。至少验证:

bash
docker restart app-api
docker restart edge-nginx
curl -v http://127.0.0.1:18080/health

如果容器重建后 IP 变化,服务仍应通过名称访问成功:

bash
docker rm -f app-api
docker run -d --name app-api --network data-net my-registry.example.com/demo/app-api:2026-05-26
docker network connect edge-net app-api
docker exec edge-nginx wget -S -O- http://app-api:8000/health

如果这一步失败,通常说明配置里写死了旧 IP,或者容器重建后没有重新连接到正确网络。生产平台应尽量用 Compose、systemd unit、Nomad job 或其他声明式方式固化网络连接,减少人工重建时漏接网络的风险。

20. 常见反模式与改进方式

20.1 所有服务共用一张 default bridge

反模式表现:所有容器都不指定网络,默认进入 bridge。短期看能跑,长期看很难治理。容器名解析不稳定,访问边界不清晰,排查时无法判断哪些容器属于同一个应用。

改进方式:每个应用创建自己的自定义 bridge;需要入口代理的服务接入入口网络,需要数据访问的服务接入数据网络。跨系统访问通过明确入口、API 或消息队列完成,而不是让所有容器直接互通。

20.2 数据库端口直接发布到 0.0.0.0

反模式表现:

bash
docker run -p 5432:5432 postgres
docker run -p 6379:6379 redis

这种写法在开发机上很方便,在生产或共享测试服务器上风险很高。只要主机防火墙或云安全组开放,数据库就可能被局域网甚至公网访问。

改进方式:数据库默认不发布端口,只放在内部网络。如果确实需要临时运维访问,优先通过堡垒机、SSH 隧道、受控跳板或绑定 127.0.0.1 的短期端口完成,并记录回收时间。

20.3 应用监听 127.0.0.1 导致容器内可用、容器外不可用

反模式表现:应用在容器内启动成功,docker logs 看起来正常,但从其他容器或宿主机访问失败。常见原因是应用只监听容器内的 127.0.0.1,而不是 0.0.0.0

排查命令:

bash
docker exec app-api ss -lntp

如果看到 127.0.0.1:8000,其他容器无法通过容器 IP 访问。应把应用监听地址改为 0.0.0.0,端口暴露面由 Docker 网络和反向代理控制,而不是靠应用只监听 loopback 来做容器间隔离。

20.4 用容器 IP 写配置

反模式表现:Nginx 里写 proxy_pass http://172.30.10.5:8000;,应用环境变量写 REDIS_HOST=172.30.10.8。容器重建后 IP 变化,服务立即中断。

改进方式:自定义 bridge 中使用容器名或网络别名;Compose 中使用服务名;Kubernetes 中使用 Service DNS。只有网络设备模拟、macvlan 固定地址或特殊协议要求时,才考虑固定 IP。

20.5 把网络排障工具塞进业务镜像

反模式表现:为了排障方便,业务镜像安装 tcpdumpcurlvimnet-toolsbind-tools 等大量工具。这样会增加镜像体积和攻击面,也会让生产镜像偏离最小运行时原则。

改进方式:业务镜像保持精简;企业维护统一的网络排障镜像。需要排障时,用临时容器接入同一网络:

bash
docker run --rm -it --network app-net registry.example.com/base/net-debug:latest

排障结束即销毁,避免排障容器长期持有生产网络访问权限。

21. 变更与回滚方案

容器网络变更有时比应用发布更敏感,因为它可能影响同一主机上的多个服务。正式变更前,应至少准备下面几类信息。

变更前快照:

bash
docker network ls
docker ps --format 'table {{.Names}}\t{{.Networks}}\t{{.Ports}}'
docker network inspect <network> > network-before.json
sudo ss -lntp > ports-before.txt

变更动作记录:

  • 新增或删除哪些 Docker network。
  • 哪些容器连接或断开了哪些网络。
  • 哪些端口从未发布改为发布。
  • 哪些绑定地址从 127.0.0.1 改为局域网地址或 0.0.0.0
  • 是否调整了主机防火墙、云安全组或上游 ACL。

回滚方式:

回滚不应只写“恢复原配置”。应写成可执行步骤。例如:

bash
docker rm -f edge-nginx
docker run -d \
  --name edge-nginx \
  --network old-edge-net \
  -p 127.0.0.1:18080:80 \
  -v "$PWD/old-nginx.conf:/etc/nginx/conf.d/default.conf:ro" \
  nginx:alpine

如果变更涉及防火墙或安全组,也要写明回滚责任人和入口。容器侧回滚完成但网络侧规则未回滚,仍可能造成访问失败或过度暴露。

回滚验证:

bash
curl -v http://127.0.0.1:18080/health
docker exec edge-nginx wget -S -O- http://app-api:8000/health
docker ps --format 'table {{.Names}}\t{{.Networks}}\t{{.Ports}}'

这套变更记录看起来比单条 docker run 命令繁琐,但它能让容器网络从“个人经验配置”变成“团队可接手的基础设施配置”。对于长期运行的测试平台和生产环境,这个差异非常关键。

22. 清理实验环境

完成实验后清理容器和网络:

bash
docker rm -f app-api web-published web-local redis-demo redis-blue lan-web 2>/dev/null || true
docker network rm app-net lan-macvlan 2>/dev/null || true

如果你还创建了其他测试网络,可以查看后再清理:

bash
docker network ls
docker network prune

docker network prune 会删除未被容器使用的网络。生产主机执行前必须确认不会删除仍被调试任务或临时服务依赖的网络。

23. 总结

本文完成了 Docker 容器化实战系列的第四步:容器网络配置。核心结论如下:

  1. 容器网络的基本单位是网络命名空间,Docker driver 负责把它接入宿主机网络。
  2. 单机多容器应用优先使用自定义 bridge,不要长期依赖默认 bridge。
  3. 容器内监听端口、Dockerfile EXPOSE、宿主机端口发布是三个不同概念。
  4. 外部访问容器服务必须通过 -p、反向代理或编排平台服务发布。
  5. 容器出网通常经过宿主机 NAT,代理、DNS、防火墙和路由都可能影响访问。
  6. host、macvlan、ipvlan 解决的是特定网络需求,不应作为普通服务默认配置。
  7. 生产环境应按最小暴露面设计网络,记录端口、子网、防火墙和回滚路径。

下一篇将进入数据卷与持久化。到那一步,重点会从“容器如何通信”转向“容器重建后数据如何保留”,包括 named volume、bind mount、备份恢复、权限问题和数据目录治理。

参考资料