LabHub
学习 学习路径 课程

卷、网络与 Compose

容器之间怎么找到对方

在 LabHub 中继续学习

一句话总结

网络命名空间不仅会复制接口和路由表,还会把回环地址和端口号空间完整复制一份。因此,容器的 127.0.0.1 与主机的 127.0.0.1 是完全不同的地址。

概念图: 回环地址和端口号空间完整复制一份 · 该容器自身 · 在默认桥接网络中,容器无法通过容器名称找到彼此。 · 默认桥接网络只是为了向后兼容而保留的;实际工作中始终应创建自定义网络。

为什么需要它

初学者经常遇到的三种症状,都可以用上面这句话解释。

  1. 浏览器提示连接被拒绝
  2. 相邻容器执行 curl http://api:3000 时无法解析名称
  3. 容器执行 curl http://127.0.0.1:5432 时无法连接主机数据库

第三种尤其容易令人困惑。在容器内连接 127.0.0.1,指向的是该容器自身,而不是主机。

工作原理

在默认桥接网络中,容器无法通过容器名称找到彼此。

docker run -d --name db postgres:16
docker run --rm alpine:3.20 ping -c1 db
# ping: bad address 'db'

创建自定义网络并将容器接入即可解决。差异会直接反映在容器内的 /etc/resolv.conf 中:名称服务器被设为一个仅存在于容器命名空间中的地址,请求再转交给运行时的解析器。解析器知道同一网络内容器的名称和别名,因此能够响应。默认桥接网络只是为了向后兼容而保留的;实际工作中始终应创建自定义网络。

第二个重点是应用程序的绑定地址

docker exec api ss -tlnp
# LISTEN 0 511 127.0.0.1:3000 ...

从主机进入的数据包绝不可能到达这个套接字,因为数据包会抵达 eth0 地址。正确答案是 0.0.0.0:3000。有人会质疑“0.0.0.0 难道不危险吗”,但容器内的 0.0.0.0 只表示该容器的整个网络命名空间;是否对外暴露由发布端口时的绑定地址决定。混淆这两个层次,会在两方面都得出错误结论。

实际现场中的情况

端口发布默认绑定主机的所有接口。

docker run -d -p 5432:5432 postgres:16            # 위험
docker run -d -p 127.0.0.1:5432:5432 postgres:16  # 안전

如果服务器拥有公网 IP,第一行就等于把数据库暴露给互联网。也不能因为主机防火墙进行了阻止就掉以轻心:发往容器的数据包的最终目的地不是主机,因此不会经过常规 INPUT 规则,目的地址在更早阶段就已改变。最安全也最简单的方法,是从一开始就限制绑定地址。

仅供容器之间通信的服务根本不应发布端口。还要记住,容器可以同时接入多个网络。把前端网络和后端网络分开,只让应用同时加入两边,就能使数据库从外部彻底不可见。

网络类型及适用场景

bridge  (기본)   가상 스위치. 컨테이너끼리 사설 IP 로 통신
host             호스트 네트워크를 그대로 씀. 격리 없음
none             네트워크 없음. 계산만 하는 작업
container:<이름>  다른 컨테이너의 네임스페이스 공유

默认 bridge 与自定义 bridge 并不相同。 在默认网络中,容器不能按名称找到彼此,必须知道 IP。用户创建的网络带有内置 DNS,容器名称就是主机名

docker network create app-net
docker run -d --name db --network app-net postgres:16
docker run --rm --network app-net alpine ping -c1 db     # 이름으로 닿는다

因此,实际工作中总会创建一个网络。Compose 会自动完成这件事。

端口映射做什么、不做什么

-p 8080:80 是一条把主机 8080 端口连接到容器 80 端口的 NAT 规则。容器之间的通信与该映射无关,它们直接使用容器端口

호스트에서 → localhost:8080
다른 컨테이너에서 → web:80        ← 8080 이 아니다

常见错误是在容器内调用 localhost:8080。容器的 localhost它自己。要调用其他容器,应使用其名称。

-p 127.0.0.1:8080:80 这样加上地址,就不会对外暴露。开放开发数据库时养成这个习惯可以避免事故——没有防火墙时,-p 5432:5432 就是在互联网上开放数据库。

从主机查看容器内部

即使容器中没有 sstcpdump,也可以从主机进入其命名空间。

PID=$(docker inspect -f '{{.State.Pid}}' myapp)
nsenter -t $PID -n ss -ltnp          # 컨테이너의 리스닝 포트
nsenter -t $PID -n tcpdump -i any -c 20 port 80

-n 表示只进入网络命名空间,因此可以直接使用主机工具查看容器网络。这是诊断 distroless 镜像最可靠的方法。

下一项实验要做什么

创建自定义网络,使容器能够按名称找到彼此;确认其他网络中的容器确实不可见;最后尝试只在回环地址上发布端口。