给应用加上 network_mode: service:vpn 并重启栈之后,应用就挂了,因为 db 再也解析不到。compose 文件里没有任何重命名,所以显而易见的嫌疑是 DNS 和数据库。真正的变化更小也更奇怪:应用不再是那个名字所在的 Compose 网络的成员。在你决定数据库该从哪个命名空间应答之前,任何主机名修复都只是猜测。

db 主机名为什么不再解析

使用 network_mode 的服务在 Compose 网络上没有自己的地址;它借用被命名服务的命名空间。那个命名空间自带接口、路由、端口空间和解析器,并取代了应用与 backend 网络的连接。db 这个名字从来不是抽象的:它存在于那个网络上,并通过网络内置的 DNS 解析。一旦断开连接,这个名字就无处解析。

要在容器内部确认,而不是从宿主机确认。应用里失败、数据库容器里却成功的查询说明名字没问题,成员身份有问题。这个结果也把修复位置放在 compose 拓扑上,而不是应用的解析器设置里。

改文件之前的三问

按顺序回答,因为每一问都会缩小拓扑范围。数据库应该靠什么应答:其他容器使用的主机名,还是只有 loopback 上的应用?应用是否需要向宿主机发布端口,又该由哪个服务持有?数据库必须能从宿主机或另一台机器访问,还是只能从隧道内部访问?

如果数据库需要自己的名字而应用不发布任何端口,就让它们各自保持独立连接。如果应用必须发布端口、一切都走隧道,就把数据库放进 VPN 命名空间并使用 loopback。两者混用会得到一个干净启动、却在连接时失败的技术栈。

让数据库留在原处

较小的改动是把命名空间的持有者接到已经包含数据库的网络上。应用因为共享 vpn 栈而继承这条连接,db 的解析与之前完全一致。

services:
  vpn:
    networks:
      - backend
  app:
    network_mode: service:vpn
  db:
    networks:
      - backend
networks:
  backend:

不要在应用上再加 networks。Compose 会拒绝在同一服务中与 network_mode 并存的那个属性,而报错指向文件本身而不是这对组合。如果 VPN 客户端在命名空间内过滤 backend 目标,还要调整它支持的本地网络策略;共享连接提供了路径,但不会覆盖那个过滤器。

把数据库移进命名空间

较大的改动是把数据库放进同一个命名空间,并用真实端口把应用指向 127.0.0.1。两个进程不会仅仅因为在不同容器里启动就能同时绑定同一个地址和端口,所以同时在 wildcard 地址上监听的数据库会与其他这样做的东西冲突。移动之前先检查监听者。

已发布的端口属于持有命名空间的服务。给应用加 ports 会与 network_mode 一起被拒绝,而凭空发明一个独立的应用地址只是在描述一个不存在的接口。

就绪、阻断与解析器

depends_on 只安排容器的启动顺序;它不是隧道已就绪的证据,也不会在隧道断开时阻断流量。请把可达性、隧道就绪和故障阻断当作三项独立的要求。只满足第一项的栈,在好日子里能通过手工测试,在坏日子里就会泄漏。把这三个答案写在 compose 文件旁边,写在下一个会看的地方。

共享命名空间还意味着 应用会继承 VPN 容器的解析器,这在隧道推送 DNS 服务器时有用,在隧道不推送时会让人困惑。不要假设宿主机的解析器适用,而应检查命名空间里究竟有什么。Compose 服务参考 在一处记录了 network_mode、ports 以及依赖关系的行为,包括为什么 network_mode 服务没有自己的地址。

相关文章