Docker 自定义网络:同一网络里用容器名互访
前几篇分别讲了用 nginx 托管静态页时的端口映射与文件挂载、练习后的清理、-p 左侧绑定地址、镜像标签与 digest、volume 与 bind mount,以及 tmpfs。-p 解决的是「主机怎么访问容器里的服务」;本篇只复查另一件事:两个容器要互相访问时,该怎么组网,怎样用名字找到对方,而不必把每个端口都映射到主机。下列命令均为本机说明示例,请按自己的环境判断是否执行;示例输出为示意,字段以你本机 docker 实际打印为准。
Docker 安装后自带一个名为 bridge 的默认网络(驱动也是 bridge)。直接 docker run、不指定 --network 时,容器会进这个网络,彼此可以用 IP 通信,但不能靠容器名做 DNS 解析(旧的 --link 能补名字,但已过时,新练习不要再用)。自己用 docker network create 建出来的 bridge 网络则不同:同一网络里的容器,会由 Docker 内嵌 DNS 按容器名(以及你设置的网络别名)解析到当前 IP。官方文档把这称为用户自定义网络上的自动服务发现。在这类网络里,容器的 /etc/resolv.conf 通常会指向 127.0.0.11,这是 Docker 在容器内的内嵌 DNS,不必再自己搭一台 DNS。IP 在容器删除重建后可能变化,名字相对稳定,所以容器互访应优先用名字,而不是把某次 inspect 看到的地址写死。
一个典型场景是应用容器要访问数据库或缓存:应用配置里写 http://demo-api:8080 或主机名 demo-db,而不是写某次分配到的 172.18.0.x。只要两端都在同一个用户自定义网络里,并且被访问的进程确实在容器内端口上监听,连接就可以建立。本篇用 nginx 当被访问方、Alpine 当客户端,只为把「名字能否解析、请求能否到达」验证清楚,不引入额外的数据库镜像。
1. 先建一个自定义网络,再把容器放进去
创建一个本地 bridge 网络(不写 -d 时,驱动默认就是 bridge):
docker network create demo-net
用 docker network ls 能看到它,DRIVER 为 bridge,SCOPE 一般为 local。注意列表里本来就有一个也叫 bridge 的默认网络,不要和刚创建的 demo-net 搞混。一般不必手工指定 --subnet;只有当你明确需要固定网段时再加,并且避开主机、VPN 或办公室网络已经占用的网段,以免路由冲突。
启动一个 nginx 作为被访问的服务,明确指定网络和容器名:
docker run -d --name demo-web --network demo-net nginx:stable-alpine
这里没有 -p。同一用户自定义网络内的互访,走的是网络内部路由,不依赖把端口发布到主机。nginx 镜像默认在容器内监听 80 端口,对端用 http://demo-web/ 或 http://demo-web:80/ 即可。EXPOSE 主要是镜像的说明信息,不会因为没写 EXPOSE 就拦掉网络里的连接,也不等于已经做了 -p。--name 在整台 Docker 主机上必须唯一,不只是在这个网络里唯一;重名会直接创建失败。容器删掉以后,同一个名字才可以再给新容器用。
再用一个一次性的 Alpine 容器当客户端,用名字去访问(Alpine 自带 BusyBox 的 wget):
docker run --rm --network demo-net alpine:3.20 \
wget -qO- http://demo-web/ | head -n 5
正常时应看到 nginx 欢迎页的 HTML 片段。--rm 让这个客户端用完就删,避免留下练习容器。验证互访时,优先请求对方正在监听的端口(这里是 HTTP 80)。有的精简镜像没有 ping,而且 ICMP 通不通也说明不了应用端口是否在听,所以本篇不用 ping 当验收标准。
若把客户端改挂到默认网络,只改这一处,名字解析就会失败:
# 预期失败:默认 bridge 上不能按容器名解析 demo-web
docker run --rm alpine:3.20 wget -qO- http://demo-web/
常见报错是 bad address 'demo-web' 或无法解析主机名。这正好说明:问题往往不是 nginx 没启动,而是两个容器不在能做名字解析的同一网络里。
2. 用 inspect 核对网络和地址
查看自定义网络里有谁、地址是多少:
docker network inspect demo-net
在 Containers 里应能看到 demo-web,以及类似 172.18.0.2/16 的 IPv4Address(网段以你本机实际分配为准,不一定是 172.18.0.0/16)。再看容器侧:
docker inspect -f '{{json .NetworkSettings.Networks}}' demo-web
这里应出现名为 demo-net 的键。需要分清两种起点:用 --network demo-net 启动的容器,默认只加入 demo-net,不会自动再挂到名为 bridge 的默认网络上;反过来,当初没写 --network、已经在默认 bridge 上跑着的容器,不必删掉重建,也可以后补:
docker network connect demo-net demo-web
connect 是给运行中的容器再挂一张网卡,原来的网络仍然保留。只有当你明确希望它离开默认 bridge 时,才另外执行 docker network disconnect bridge <容器名>;不要对一个从未加入默认 bridge 的容器去 disconnect,那条命令会失败。docker stop 后再 docker start 同一个容器,它仍留在原来加入过的网络里;docker rm 之后再 docker run 一个同名新容器,则是新实例,IP 可能不同,但只要还加入 demo-net 并沿用同一个 --name,对端仍可用这个名字访问。
一个容器也可以同时加入多个用户自定义网络,从而为不同应用隔离通信范围。常见做法是前端网络与后端网络分开:反向代理同时加入两张网,数据库只放在后端网。这样,只在前端网上的其他容器既解析不到数据库的名字,也走不到那张后端网。不要假设「这台机器上所有容器天然都能互相用名字访问」。
3. 和 -p、localhost 的关系
| 需求 | 做法 |
|---|---|
| 浏览器或主机上的进程访问容器服务 | 用 -p(必要时加上之前文章说的 127.0.0.1: 前缀) |
| 同一用户自定义网络里,容器访问另一容器的服务 | 用容器名(或 --network-alias),一般不必为这次互访单独 -p |
| 容器里访问「本机」 | localhost / 127.0.0.1 指的是这个容器自己,不是主机,也不是旁边那个容器 |
从容器 A 去访问你在主机上用 -p 8080:80 映射出来的端口,正确对象通常仍是容器 B 的名字和容器内端口(这里是 80),而不是 http://127.0.0.1:8080。把主机映射端口写进另一个容器的配置,是很常见的联调错误。-p 和自定义网络可以同时存在:主机用映射端口访问,容器之间仍用名字和容器内端口访问,两套路径不要抄混。
需要给同一个容器起另一个可解析的名字时,创建或连接网络时可以加别名,例如:
docker run -d --name demo-web2 --network demo-net \
--network-alias web nginx:stable-alpine
这样,同一网络里的其他容器既可以用 demo-web2,也可以用 web 解析到它。别名属于某个网络,不是全局主机名;在 demo-net 里设的别名,到另一张自定义网络上不会自动出现。
4. 常见误区
- 在默认
bridge上用容器名访问:IP 通、名字不通,这是默认网络的行为,不是你的 DNS 坏了。把双方都加入同一个用户自定义网络。 - 继续使用已过时的
--link:能在默认网络里写入主机名,但维护成本高,官方也不再推荐;用自定义网络替代。 - 从容器里访问
localhost或主机的映射端口,以为能打到另一个容器:localhost只代表当前容器。跨容器请用名字和容器内监听端口。 - 以为必须
-p容器才能互访:-p管的是主机侧发布。同一自定义网络内部,对端访问的是容器自己在听的端口。 - 把某次看到的 IP 写进配置并长期使用:容器删除重建后,地址可能变;名字(在你固定
--name的前提下)更合适。 - 两个容器各自建了一个名字不同的网络,或一方忘记
--network:名字解析和隔离都是按网络边界来的,不在同一网络就解析不到。 - 把
host或none网络和 bridge 自定义网络混用:--network host共用主机网络栈,不再按容器名做这套内嵌 DNS;--network none则几乎没有网络。本篇的做法只适用于用户自定义 bridge。 - 用 ping 失败就断定网络不通:先确认镜像里有没有
ping,再改用对方实际监听的端口做检查。
练习看完后,停掉并删除演示容器,再删掉自己创建的网络(不要删系统自带的 bridge、host、none):
docker container stop demo-web demo-web2
docker container rm demo-web demo-web2
docker network rm demo-net
若客户端用了 --rm,它应该已经不在了;用 docker network ls 确认 demo-net 已消失即可。网络里如果还挂着容器,network rm 会拒绝删除,先把容器停掉并移除(或先 disconnect)再删网络。
小结
默认 bridge 适合快速启动单个容器,但不提供按容器名的 DNS。需要容器互访时,先 docker network create 建一个用户自定义 bridge,再用 --network 把相关容器放进去,用 --name(或网络别名)访问对方的容器内端口。已经在跑的容器可以用 network connect 后补,不必为此先删掉。-p 留给主机访问;volume、bind、tmpfs 管数据放哪。组网时先问:通信的两端是不是都在同一个自定义网络里,以及访问的是名字还是误写成了 localhost。
- 点赞
- 收藏
- 关注作者
评论(0)