codedevin头像
关注

Docker 网络实战:bridge/host/none/自定义网络、容器互通与端口映射踩坑

Docker 网络实战:bridge/host/none/自定义网络、容器互通与端口映射踩坑

两个容器,一个是 web 一个是 mysql,web 里写 localhost:3306 连数据库,死活连不上。换成宿主机 IP 有时又通有时不通。这几乎是每个人上手 Docker 都会撞的墙。根因是没搞清 Docker 的网络模型——容器里的 localhost 是容器自己,不是宿主机,更不是隔壁容器。

这篇把 Docker 的几种网络模式讲透:bridgehostnone 各是什么、什么时候用哪个,为什么容器间该用自定义网络靠名字互访,以及 -p 端口映射那些容易踩的坑。

先看默认的 bridge 网络长什么样

装完 Docker 默认就有三个网络,docker network ls 看得到:

$ docker network ls
NETWORK ID     NAME      DRIVER    SCOPE
a1b2c3d4e5f6   bridge    bridge    local
f6e5d4c3b2a1   host      host      local
0a0b0c0d0e0f   none      null      local

不指定 --network 时,容器默认接到 bridge 这个网络上。Docker 会建一块虚拟网桥 docker0,每个容器分一个 172.17.x.x 的内网 IP,通过网桥转发流量。启动一个容器看它的 IP:

docker run -d --name c1 nginx
# 查看容器分到的 IP
docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' c1
# 输出类似 172.17.0.2

关键点:容器里的 localhost(127.0.0.1)指的是容器自己这个网络命名空间,不是宿主机。所以 web 容器里写 localhost:3306 连的是 web 容器自己的 3306,当然连不上 mysql 容器。这是头号误区。

默认 bridge 的坑:容器间不能用名字互访

你可能想:那我用 IP 连总行了吧?172.17.0.2:3306。能通,但 IP 是启动时动态分配的,容器一重建就变,写死在配置里就是定时炸弹。

更糟的是,默认的 bridge 网络不提供容器名的 DNS 解析。在默认 bridge 里,ping c1 是不通的,只能用 IP:

docker run -d --name db  -e MYSQL_ROOT_PASSWORD=root mysql:8
docker run -it --name app alpine ping db
# ping: bad address 'db'   ← 默认 bridge 里名字解析不了

这就是为什么要用自定义网络

正确姿势:自定义 bridge 网络,靠容器名互访

自己建一个 bridge 网络,把相关容器都接上去。Docker 会在自定义网络里提供内置 DNS,容器之间直接用容器名互相访问:

# 1. 建一个自定义网络
docker network create app-net

# 2. 两个容器都接到这个网络
docker run -d --name db  --network app-net -e MYSQL_ROOT_PASSWORD=root mysql:8
docker run -d --name web --network app-net nginx

# 3. web 里直接用容器名 db 连数据库,Docker 自动解析成 db 的当前 IP
docker exec -it web ping db     # 通!
docker exec -it web getent hosts db
# 172.18.0.2   db

现在 web 容器里连数据库就写 db:3306,不用管 IP 是多少、重建后变不变,Docker DNS 自动跟踪。这是多容器互通的标准做法,docker-compose 帮你做的其实也是这件事——它默认给整个 compose 项目建一个自定义网络,服务名就是主机名。

一句话:凡是容器要互相通信,就建自定义网络,用容器名当地址,永远别用 IP,更别指望默认 bridge 的 DNS。

host 模式:直接用宿主机网络,没有隔离

--network host 让容器直接共享宿主机的网络栈,不再有独立 IP,容器里监听的端口就是宿主机的端口,不需要 -p 映射:

# host 模式:容器里 nginx 监听 80,宿主机 80 直接就是它,无需 -p
docker run -d --network host nginx
curl http://localhost:80   # 宿主机上直接访问,不用端口映射

优点是没有网络地址转换(NAT)的开销,网络性能最好,适合对延迟敏感、或需要监听大量端口的场景(如某些高性能网关)。代价是牺牲隔离:容器和宿主机抢同一批端口,80 被占了另一个 host 容器就起不来;而且只在 Linux 上有意义,Docker Desktop(Mac/Windows)里 host 模式行为不一样,别指望和 Linux 一致。

none 模式:彻底断网

--network none 给容器一个只有 lo(回环)的网络命名空间,完全没有外部网络:

docker run -it --network none alpine ip addr
# 只有 lo,没有 eth0,ping 任何外网都不通

用途很专一:跑一个纯计算、不需要联网的批处理任务,或出于安全考虑要彻底切断容器的网络能力(比如处理不可信输入的沙箱),把攻击面降到最低。

端口映射 -p:把容器端口暴露给外面

容器在 bridge 网络里,外部(宿主机之外)默认访问不到,得用 -p 宿主机端口:容器端口 做映射:

# 宿主机 8080 → 容器 80。外部访问宿主机 8080 就打到容器的 nginx
docker run -d -p 8080:80 nginx
curl http://<宿主机IP>:8080

几个高频坑:

  1. 顺序别搞反:-p 8080:80 是「宿主机:容器」,写反成 -p 80:8080 就把宿主机 80 转发到容器 8080,而容器里根本没人听 8080,访问就拒绝连接。

  2. 只对本机开放用 127.0.0.1 前缀:-p 8080:80 会绑到 0.0.0.0,任何能访问到宿主机的网络都能连——公网服务器上这等于把服务直接暴露出去。只想本机访问(比如数据库只给宿主机上的应用连)就写 -p 127.0.0.1:3306:3306:

# 只绑到本地回环,外网访问不到,安全得多
docker run -d -p 127.0.0.1:3306:3306 -e MYSQL_ROOT_PASSWORD=root mysql:8
  1. 容器内服务必须监听 0.0.0.0,不能只听 127.0.0.1:如果你的应用在容器里监听 127.0.0.1:8000,那 -p 映射进来的流量从 eth0 进来根本到不了回环地址,表现就是「端口映射了却连不上」。应用配置要监听 0.0.0.0。这是排查「映射了却访问不到」时优先要查的。

  2. 同一个容器可映射多个端口,-p 写多次即可:-p 80:80 -p 443:443

容器访问宿主机上的服务

反过来,容器里的程序想连宿主机上跑的服务(比如宿主机上的一个本地 API),不能用 localhost(那是容器自己)。Docker 提供了特殊主机名 host.docker.internal:

# 容器里访问宿主机的 5000 端口服务
docker run --rm alpine sh -c \
  "apk add --no-cache curl >/dev/null && curl http://host.docker.internal:5000"

Docker Desktop 原生支持这个名字;Linux 上的 Docker Engine 需要启动时显式加 --add-host=host.docker.internal:host-gateway:

docker run --add-host=host.docker.internal:host-gateway ...

断开与连接:一个容器接多个网络

容器可以同时接多个网络,做网络隔离分层很有用(比如 web 接「前端网」和「后端网」,db 只接「后端网」,这样前端流量碰不到 db):

docker network create frontend
docker network create backend

docker run -d --name web --network frontend nginx
docker network connect backend web   # 再把 web 也接到 backend
docker run -d --name db --network backend mysql:8
# 此时 web 能访问 db(共享 backend),但只接 frontend 的容器碰不到 db

docker network connect / disconnect 可以在容器运行中动态增删网络,不用重启容器。

小结

  • 容器里的 localhost容器自己,不是宿主机、不是别的容器——这是所有网络困惑的根源。
  • 默认 bridge 网络不支持容器名 DNS,容器互通请建自定义网络,用容器名当地址,永不写死 IP(docker-compose 本质就是这么做的)。
  • host 模式共享宿主机网络、性能最好但无隔离且仅 Linux;none 模式彻底断网,用于纯计算或安全沙箱。
  • -p 宿主机:容器 别写反;公网机器用 127.0.0.1: 前缀限制暴露面;容器内服务必须监听 0.0.0.0 否则映射了也连不上。
  • 容器访问宿主机服务用 host.docker.internal(Linux 需加 --add-host=...:host-gateway)。
  • 记忆点:互通建自定义网、用名字;暴露用 -p、别裸奔到 0.0.0.0;localhost 永远指自己

转载自 CSDN-专业IT技术社区

原文链接:https://blog.csdn.net/weixin_42662753/article/details/165434539

文章来源转载

评论

赞0

评论列表

微信小程序
QQ小程序

关于作者

点赞数:0
关注数:0
粉丝:0
文章:0
关注标签:0
加入于:--