Docker 基础体系 · 第 47/70 篇。示例以现代 Docker Engine、BuildKit 与 Compose 规范为基础,并明确 Linux 容器边界。

Docker 容器 DNS:嵌入式解析、Service Name、Search Domain 和故障

在 Linux 容器中,DNS 不只是“把域名转换成 IP”。一个容器能否通过 api 找到另一个容器,取决于容器是否连接到同一个 Docker 网络、查询名称是否是该网络中的别名、解析器如何处理 Search Domain,以及目标容器当前是否已经加入网络。

Docker DNS 需要区分三层问题:

  1. 名称是否能被解析:例如 api 是否能得到一个 IP 地址。
  2. 得到的 IP 是否可达:例如两个容器是否处于同一网络。
  3. 目标服务是否已经可用:例如容器虽然存在,但应用仍在启动。

DNS 只负责第一层。它不会保证端口开放、应用启动完成,也不会为已有 TCP 连接自动切换到新的 IP。


一、先区分几个容易混淆的名称

在 Docker 和 Compose 中,至少有以下几种名称:

名称 含义 主要作用
Service Name Compose 文件中的服务名 Compose 网络中的默认 DNS 名称
Container Name 容器的显式名称 Docker API、CLI 和部分 DNS 场景中的容器标识
Hostname 容器内部的主机名 hostname、部分应用自识别逻辑
Network Alias 某个网络范围内的别名 通过特定名称访问容器或服务
Search Domain DNS 短名称补全时追加的域名后缀 api 尝试为 api.example.test 等名称

这些名称可能相同,但语义并不相同。特别是:

services:
  api:
    container_name: my-api
    hostname: backend

在这个例子中:

  • api 是 Compose Service Name;
  • my-api 是 Container Name;
  • backend 是容器内部的 Hostname;
  • 它们是否都能作为 DNS 名称解析,还取决于网络和 Docker/Compose 创建的网络别名。

因此,应用之间的稳定通信通常应使用 Service Name 或显式 Network Alias,而不是依赖 container_name 或容器 IP。


二、Docker 嵌入式 DNS 的核心模型

2.1 用户自定义网络中的 127.0.0.11

连接到 Docker 用户自定义网络的容器,通常会看到类似的 /etc/resolv.conf

nameserver 127.0.0.11
options edns0 trust-ad

这里的 127.0.0.11 不是宿主机回环地址,也不是应用容器本身正在监听的普通 DNS 服务。Docker 在容器网络命名空间中提供了一个嵌入式 DNS 端点,容器内的 DNS 客户端把查询发送到它。

典型查询路径是:

flowchart LR
    A[应用程序] --> B[glibc 或 musl resolver]
    B --> C[/etc/resolv.conf]
    C --> D[127.0.0.11:53]
    D --> E{Docker 内部名称?}
    E -->|是| F[按网络别名查询 Docker 网络状态]
    E -->|否| G[转发到上游 DNS]
    F --> H[返回容器或服务 IP]
    G --> I[宿主机配置的外部 DNS]
    I --> D
    D --> B
    B --> A

查询内部名称时,Docker 根据容器连接的网络和网络别名返回地址;查询外部名称时,Docker 通常将请求转发给配置的上游 DNS。

这里的“内部名称”不是一个全局 Docker DNS 区域。它的可见性受网络连接关系限制:

容器 client ── app-net ── 容器 api
容器 client ── app-net ── 容器 db
容器 client ── other-net ── 容器 cache

在该拓扑中:

  • client 可以解析 apidb
  • client 通常不能通过 Docker 内部 DNS 解析 cache
  • apidb 是否能互相解析,还要看它们是否共同连接了某个网络;
  • 宿主机通常不能直接使用 api 这个名称解析容器,因为宿主机不在这个容器 DNS 网络命名空间中。

2.2 Docker DNS 不是普通的“静态 hosts 文件”

容器启动后,Docker 可能根据容器创建、销毁、重启和网络连接状态更新 DNS 结果。名称到 IP 的映射因此是动态状态,而不是构建镜像时固化的值。

例如:

  1. api 容器启动,获得 172.20.0.3
  2. 客户端解析 api,得到 172.20.0.3
  3. api 被删除并重新创建,获得 172.20.0.8
  4. 新的 DNS 查询可能返回 172.20.0.8
  5. 旧客户端若继续连接 172.20.0.3,连接可能失败。

DNS 重新解析只影响新的解析请求。已经建立的 TCP 连接不会因为 Docker DNS 的结果变化而自动迁移。


三、默认 bridge 与用户自定义网络的差异

Docker 的 bridge 网络容易造成误解。下面是两种常见用法:

docker run -d --name api nginx:alpine
docker run --rm --name client --link api alpine:3.20 sh

以及:

docker network create app-net

docker run -d --name api --network app-net nginx:alpine
docker run --rm --network app-net alpine:3.20 sh

第二种用户自定义网络是现代 Docker 容器间服务发现的常规模型。它提供了更明确的网络隔离和基于名称的解析能力。

默认 bridge 网络的历史行为与用户自定义网络不同。不要把旧式 --link 当成现代服务发现机制:

  • --link 是遗留功能;
  • 它会把特定容器关系注入到环境变量或 hosts 相关配置中;
  • 它不适合表达动态扩缩容和多服务拓扑;
  • 用户自定义网络应优先用于容器间名称解析。

生产配置中,应显式创建网络或让 Compose 创建项目网络,而不是依赖默认 bridge 的历史兼容行为。


四、Service Name 为什么能解析

4.1 Compose 的默认网络

考虑以下 Compose 文件:

services:
  api:
    image: nginx:alpine

  client:
    image: alpine:3.20
    command: ["sleep", "3600"]

执行:

docker compose up -d

Compose 通常会创建一个项目级默认网络,网络名类似:

项目名_default

两个服务都会连接到这个网络。api 作为 Service Name,会成为该网络中的一个可用名称,因此 client 可以查询:

docker compose exec client getent hosts api

可能得到:

172.19.0.2    api

这里的 IP 不是固定值。删除并重新创建 api 后,它可能变化;代码不应把 172.19.0.2 写入配置。

服务之间应使用容器端口访问:

http://api:80

而不是使用宿主机端口映射。例如:

services:
  api:
    image: nginx:alpine
    ports:
      - "8080:80"

在同一个 Docker 网络中的 client 访问 api 时,通常应使用:

http://api:80

不是:

http://api:8080

8080:80 的含义是:

宿主机 8080 ──> api 容器 80

容器到容器通信直接到达 api 的容器端口,不需要经过宿主机发布端口。

4.2 Service Name 的作用范围

Service Name 的解析作用域是网络,而不是整个 Docker 主机,更不是整个公司网络。

services:
  api:
    image: nginx:alpine
    networks:
      - frontend
      - backend

  client:
    image: alpine:3.20
    networks:
      - frontend

  db:
    image: postgres:16
    networks:
      - backend

networks:
  frontend:
  backend:

此时:

  • client 可以解析 api
  • api 可以解析 db
  • client 不能直接解析 db,因为它没有连接 backend
  • api 在两个网络上可能对应两个不同的网络地址。

一个服务连接多个网络时,不能只从“服务名”推断唯一 IP。解析结果和通信路径取决于发起查询的容器所处的网络。

4.3 Network Alias

Compose 可以为服务设置网络别名:

services:
  api:
    image: nginx:alpine
    networks:
      app:
        aliases:
          - users-api
          - internal-api

networks:
  app:

连接到 app 网络的其他容器可以尝试:

getent hosts api
getent hosts users-api
getent hosts internal-api

这些别名只在声明它们的网络中有效。为不同网络定义不同别名,可以表达不同的网络角色:

services:
  api:
    image: nginx:alpine
    networks:
      public:
        aliases:
          - public-api
      internal:
        aliases:
          - internal-api

不要把 Network Alias 当成全局 DNS 记录。容器若未连接对应网络,即使名称写法完全正确,也不会得到该内部记录。


五、Service Name 与扩缩容:一个名称可能对应多个地址

当一个服务有多个实例时,Service Name 可能对应多个 IP。例如使用 Compose 扩展服务:

docker compose up -d --scale api=3

在同一网络中的容器查询:

docker compose exec client getent hosts api

可能得到多行结果:

172.19.0.4    api
172.19.0.5    api
172.19.0.6    api

具体返回格式、顺序和是否在一次查询中看到全部地址,取决于 Docker DNS、解析库和缓存行为,应用不应依赖固定顺序。

这带来三个重要边界:

  1. DNS 多地址不是完整负载均衡保证
    客户端可能只使用第一个地址,也可能缓存结果。

  2. 失败重试由客户端负责
    若第一个 IP 连接失败,客户端是否尝试下一个地址取决于 HTTP 客户端、连接池或应用代码。

  3. 长连接不会自动重新选择实例
    一个连接建立后,它会继续指向原来的 IP,直到连接关闭或失败。

因此,使用 Service Name 扩缩容时,客户端需要具备合理的 DNS TTL、连接超时、失败重试和连接重建能力。Docker DNS 提供服务发现入口,不替代应用层负载均衡。


六、Search Domain:短名称如何被补全

6.1 Search Domain 的定义

如果 /etc/resolv.conf 中包含:

search corp.example internal.example
options ndots:5

应用查询短名称:

api

解析库可能按以下候选名称尝试:

api.corp.example
api.internal.example
api

这里的 Search Domain 是解析器的客户端行为,不是 Docker Service Name 本身。Docker 负责提供 DNS 端点和内部名称记录;具体如何尝试候选名称,通常由容器内的解析库实现。

6.2 ndots 如何影响查询顺序

ndots:n 表示:名称中至少有多少个点时,解析器才优先把它视为绝对或近似完整名称。

对名称:

api

点数为 0。若 ndots:5,它少于 5 个点,解析器通常会优先尝试 Search Domain 补全:

api.corp.example
api.internal.example
api

对名称:

api.prod

点数为 1,仍可能优先尝试:

api.prod.corp.example
api.prod.internal.example
api.prod

对名称:

api.prod.example.

末尾的点表示 DNS 根下的绝对名称。解析器通常不再追加 Search Domain。

因此,下面两种写法并不完全等价:

api
api.example.

前者依赖 Search Domain 和 ndots;后者明确指定了绝对名称。

6.3 Docker 中 Search Domain 的来源

可以通过 docker run 指定:

docker run --rm \
  --network app-net \
  --dns-search example.internal \
  alpine:3.20 \
  cat /etc/resolv.conf

Compose 中可以写:

services:
  client:
    image: alpine:3.20
    dns_search:
      - example.internal

容器中的最终 /etc/resolv.conf 还可能受到 Docker 默认配置、宿主机 DNS 设置和容器运行参数影响,因此应以容器内实际文件为准:

docker compose exec client cat /etc/resolv.conf

Search Domain 会产生额外查询。例如 api 可能先被查询为 api.example.internal,之后才查询为 api。这会导致:

  • 查询次数增加;
  • 某些外部 DNS 服务器记录了不应外泄的内部短名称;
  • 上游 DNS 对不存在的补全名称响应较慢时,短名称解析变慢;
  • 一个外部名称恰好与内部 Search Domain 拼接后名称相同,产生意外解析。

对 Docker Compose 的 Service Name,通常直接使用 api 即可;若环境中的 Search Domain 很复杂,应用也可以根据实际 DNS 规则使用明确的绝对名称。但不能假设所有应用都完全遵循 glibc 的行为,因为应用可能使用自己的 DNS 解析实现。


七、/etc/resolv.conf、Docker DNS 与宿主机 DNS 的关系

查看容器 DNS 配置:

docker run --rm --network app-net alpine:3.20 cat /etc/resolv.conf

常见结果是:

nameserver 127.0.0.11
options edns0 trust-ad

这表示容器首先访问 Docker 的嵌入式 DNS,而不是直接访问宿主机 /etc/resolv.conf 中列出的物理 DNS 服务器。

对于外部域名,例如:

docker run --rm --network app-net alpine:3.20 nslookup example.com

数据流通常是:

  1. Alpine 内的解析器读取 /etc/resolv.conf
  2. 请求发送到 127.0.0.11
  3. Docker DNS 判断 example.com 不是当前 Docker 网络中的内部名称;
  4. Docker 将请求转发到配置的上游 DNS;
  5. 上游返回结果;
  6. Docker DNS 将结果返回给容器。

因此,下面两类故障路径不同:

解析 api 失败

可能是:

  • 容器没有连接正确网络;
  • api 服务不存在;
  • 别名配置错误;
  • Docker 网络状态异常。

而:

解析 example.com 失败

可能是:

  • Docker DNS 端点异常;
  • 宿主机或 Docker daemon 的上游 DNS 配置异常;
  • 防火墙阻断 UDP/TCP 53;
  • VPN、公司 DNS 或 systemd-resolved 的转发路径有问题。

不要在容器内随意把 nameserver 改成宿主机的 127.0.0.53127.0.0.53 是某些宿主机上的本地 DNS stub 地址;容器自己的网络命名空间中,即使存在同样的数字地址,也不代表能访问宿主机上的服务。若需要自定义上游 DNS,应通过 Docker daemon、运行参数或 Compose 的 dns 配置管理,而不是把宿主机回环地址硬编码进镜像。


八、一个可执行的最小实验

下面的实验验证 Service Name、网络隔离和端口访问的区别。

8.1 创建网络并启动服务

docker network create app-net

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

docker run -d \
  --name client \
  --network app-net \
  alpine:3.20 \
  sleep 3600

验证网络成员:

docker network inspect app-net

在输出的 Containers 中应看到 apiclient

8.2 在客户端容器内解析 Service Name

docker exec client getent hosts api

可能输出:

172.20.0.2    api

如果 getent 不可用,可以使用:

docker exec client nslookup api

可能看到:

Name:      api
Address 1: 172.20.0.2 api

这里的关键条件是:

client ∈ app-net
api    ∈ app-net

若删除并重新创建 api,IP 可能改变,但 api 这个名称仍然是通信入口。

8.3 验证网络隔离

再创建一个不连接 app-net 的客户端:

docker run --rm \
  --name isolated-client \
  alpine:3.20 \
  nslookup api

通常会得到类似:

nslookup: can't resolve 'api'

原因不是 api 容器没有运行,而是 isolated-client 不在 app-net 中,没有进入能看到 api 别名的网络作用域。

8.4 验证应用端口

client 容器中访问 Nginx:

docker exec client wget -qO- http://api:80

应返回 Nginx 的 HTML 页面。

这个请求使用:

api:80

因为访问的是容器网络中的 Nginx 监听端口。如果执行:

docker exec client wget -qO- http://api:8080

即使宿主机曾经发布过 8080:80,也不应据此认为 api:8080 可用。容器间请求不自动经过宿主机的端口发布规则。

实验清理:

docker rm -f client api
docker network rm app-net

九、DNS 解析成功不等于服务可用

Compose 中常见如下配置:

services:
  api:
    image: nginx:alpine
    healthcheck:
      test: ["CMD", "wget", "-qO-", "http://127.0.0.1"]
      interval: 5s
      timeout: 2s
      retries: 5

  client:
    image: alpine:3.20
    depends_on:
      - api
    command: ["sleep", "3600"]

depends_on 的短写法通常只表达启动依赖顺序:Compose 会先尝试启动 api,再启动 client。它不等价于“api 已经通过健康检查”,更不等价于“应用已经接受所有业务请求”。

即使满足以下条件:

getent hosts api 成功

仍可能出现:

wget http://api:80 失败

因为:

  • 容器进程尚未启动;
  • 进程已启动但尚未监听端口;
  • 应用仍在迁移数据库;
  • 应用健康检查失败;
  • 网络可达但请求被应用拒绝。

如果需要让 Compose 根据健康状态建立启动条件,可以使用长写法:

services:
  db:
    image: postgres:16
    environment:
      POSTGRES_PASSWORD: example
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 5s
      timeout: 3s
      retries: 10

  api:
    image: my-api:latest
    depends_on:
      db:
        condition: service_healthy

这只能改善 Compose 创建阶段的顺序。服务运行期间,数据库重启、网络抖动或连接断开仍然可能发生。应用仍需要在连接失败时重试,并在恢复后重新建立连接。


十、容器重启、服务重建与 DNS 缓存

DNS 记录的生命周期和容器生命周期相关,但客户端通常会缓存解析结果。

典型故障过程如下:

sequenceDiagram
    participant C as client
    participant D as Docker DNS
    participant A1 as api 旧实例
    participant A2 as api 新实例

    C->>D: 查询 api
    D-->>C: 172.20.0.3
    C->>A1: 建立 TCP 连接
    A1--xC: 容器被删除或重启
    Note over D: api 重新注册到 172.20.0.8
    C->>A1: 继续使用旧连接或旧地址
    A1-->>C: 连接失败
    C->>D: 重新查询 api
    D-->>C: 172.20.0.8
    C->>A2: 建立新连接

这里有两个独立状态:

  • Docker DNS 中的当前记录;
  • 客户端进程中的缓存、连接池和已建立连接。

所以“重启服务后 DNS 已经恢复”并不能保证业务立刻恢复。客户端可能仍然:

  • 复用已失效的连接;
  • 缓存旧地址;
  • 对失败请求不重试;
  • 只尝试多地址结果中的一个地址。

生产应用应把以下错误视为可恢复网络错误,而不是永久配置错误:

connection refused
connection reset
timeout
no route to host
temporary failure in name resolution

但重试必须有限制,使用超时和退避,避免在 DNS 或目标服务故障时形成重试风暴。


十一、常见失败表现与诊断路径

11.1 ping api 失败,但 getent hosts api 成功

这通常说明名称解析正常,问题可能在:

  • 容器中没有安装或没有启用 ping
  • 目标容器没有响应 ICMP;
  • 网络策略或防火墙阻断 ICMP;
  • 目标服务只监听 TCP 端口。

应区分:

getent hosts api

和:

wget -S -O- http://api:80

前者验证名称解析,后者验证 TCP 和 HTTP。不要用 ping 作为唯一的 Docker DNS 诊断工具。

11.2 api 解析失败

按以下顺序检查。

首先确认客户端和目标服务是否在同一个网络:

docker inspect client \
  --format '{{json .NetworkSettings.Networks}}'

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

然后检查网络成员:

docker network inspect app-net

再检查客户端实际 DNS 配置:

docker exec client cat /etc/resolv.conf

最后在客户端内部直接查询:

docker exec client getent hosts api
docker exec client nslookup api

可能的结论:

现象 更可能的原因
resolv.conf 没有预期 DNS,且内部名称失败 容器运行参数或网络类型不符合预期
外部域名也失败 Docker DNS 上游转发、宿主机 DNS 或防火墙问题
外部域名成功,api 失败 网络未连接、Service Name 错误或别名错误
api 有解析结果,但端口连接失败 应用未监听、端口写错或服务未就绪
偶发解析到多个地址后连接失败 扩缩容、旧 IP 缓存或客户端重试能力不足

11.3 Compose 中使用了错误的名称

例如服务定义为:

services:
  user-api:
    image: nginx:alpine

正确的默认 Service Name 是:

user-api

而不是:

user_api

也不是:

项目名_user-api

项目名通常影响网络名称,例如:

myproject_default

但它不是服务间访问名称的前缀。

应用应使用:

http://user-api:80

如果需要另一个名字,显式配置 alias:

services:
  user-api:
    image: nginx:alpine
    networks:
      default:
        aliases:
          - users

networks:
  default: {}

11.4 宿主机能访问发布端口,但容器不能访问 Service Name

这两个方向使用的路径不同:

宿主机浏览器 ── localhost:8080 ──> 容器 api:80
容器 client   ── api:80 ──────────> 容器 api:80

宿主机验证发布端口:

curl http://127.0.0.1:8080

容器验证服务发现和容器端口:

docker exec client wget -qO- http://api:80

第一个成功不能证明第二个成功,反之亦然。

11.5 容器能解析外部域名,但 DNS 请求很慢

重点检查 Search Domain 和 ndots

docker exec client cat /etc/resolv.conf

如果看到较长的 search 列表和较大的 ndots,查询 api 时可能先产生多次补全查询。可以用 nslookup 或抓包工具观察实际查询顺序;不要只根据最终成功或失败判断中间发生了几次 DNS 请求。

对于外部完整域名:

registry.example.com.

末尾点可以避免 Search Domain 补全。但是否在应用配置中使用末尾点,要考虑该应用自身是否接受这种 URI 或主机名格式;DNS 解析语义正确,不代表所有业务配置解析器都正确处理它。


十二、DNS 配置项的边界

12.1 dns

Compose 可以为容器指定 DNS 服务器:

services:
  client:
    image: alpine:3.20
    dns:
      - 10.0.0.53

该配置用于外部或自定义 DNS 查询路径。它不能把 Docker 网络中的 Service Name 变成任意外部 DNS 服务器都能解析的记录。

例如,外部 DNS 服务器通常不知道 Compose 中的:

api
db

Docker 内部服务发现和企业外部 DNS 是两个命名系统。把外部 DNS 配置进容器,不会自动替代 Docker 的网络别名数据库。

12.2 dns_search

services:
  client:
    image: alpine:3.20
    dns_search:
      - corp.example

这改变的是短名称补全规则。例如应用查询 registry 时,解析器可能尝试:

registry.corp.example
registry

它不会创建 registry 这个 Docker Service,也不会改变 Docker 网络中已有别名。

12.3 extra_hosts

services:
  client:
    image: alpine:3.20
    extra_hosts:
      - "legacy-api:192.0.2.10"

这通常会向容器的 /etc/hosts 写入静态条目。它适合少量固定映射,不适合替代 Docker Service Discovery:

  • IP 改变后不会自动更新;
  • 多副本服务不能自然表达多个地址;
  • 它可能优先于 DNS 解析;
  • Compose 重建配置前,旧容器中的 hosts 内容不会自动跟随外部服务变化。

如果名称表示 Docker 网络中的服务,应优先使用 Service Name 或 Network Alias。


十三、容器内的 localhost 不是宿主机,也不是其他服务

DNS 故障排查中常见一个错误:

client 访问 http://localhost:8080

client 容器内,localhost 指向 client 自己的网络命名空间。它不会指向:

  • api 容器;
  • Docker 宿主机;
  • Compose 项目中的其他服务。

正确的容器间访问方式通常是:

http://api:80

如果确实要访问宿主机,需要使用适合当前 Docker 平台和网络模式的宿主机地址或特殊映射机制;这不是 Docker Service Name 的功能,也不应与嵌入式 DNS 混为一谈。


十四、IPv4、IPv6 与解析结果

DNS 不只返回 IPv4 的 A 记录,也可能返回 IPv6 的 AAAA 记录。应用和 Docker 网络是否启用 IPv6,会影响最终结果和连接行为。

例如:

getent ahosts api

可能返回多个地址族:

172.20.0.2    STREAM api
172.20.0.2    DGRAM  api
172.20.0.2    RAW    api

若启用 IPv6,也可能同时出现 IPv6 地址。应用的地址选择策略可能优先尝试 IPv6;如果网络实际没有可用的 IPv6 路由,就可能表现为连接延迟或失败。

因此,遇到“DNS 返回了地址但连接超时”时,应记录:

getent ahosts api
ip addr
ip route

不能只看到解析成功就断定网络路径正确。IPv6 是否启用、地址是否可达、应用是否监听 IPv6,都是独立条件。


十五、不要把容器 IP 当成服务地址

容器 IP 适合诊断当前状态:

docker inspect -f \
  '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' api

但不适合作为应用配置:

DATABASE_HOST=172.20.0.5

因为容器的 IP 可能在以下操作后变化:

docker compose up -d --force-recreate
docker compose down
docker compose up -d
docker compose up --scale api=3

稳定的配置应写成:

DATABASE_HOST=db
API_BASE_URL=http://api:80

如果服务跨越多个 Docker 主机,普通 bridge 网络中的 Service Name 也不会自动跨主机解析。跨主机服务发现需要使用适合该部署模型的网络机制,例如 Swarm Overlay 或外部服务发现系统;不能假设 Compose 项目在不同主机上使用相同服务名就能互相解析。


十六、把 DNS 故障和网络故障分层验证

一个可靠的排障顺序是从名称到地址,再到端口和应用:

第一步:确认容器是否运行

docker ps
docker compose ps

容器停止时,DNS 记录自然可能不存在或不可用。

第二步:确认网络成员关系

docker network ls
docker network inspect app-net

目标是确认两个容器是否确实共享同一个用户自定义网络。

第三步:确认容器内解析配置

docker exec client cat /etc/resolv.conf

重点观察:

  • 是否存在预期的 nameserver
  • 是否存在 Search Domain;
  • 是否存在影响查询顺序的 ndots
  • 是否误配置了宿主机回环 DNS 地址。

第四步:分别测试内部和外部名称

docker exec client getent hosts api
docker exec client getent hosts example.com

这两个测试能把问题初步分为:

内部名称失败,外部名称成功

或:

内部、外部名称都失败

前者更偏向 Docker 网络成员、Service Name 和 alias;后者更偏向容器 DNS 端点、上游 DNS 或宿主机网络。

第五步:测试解析结果对应的端口

docker exec client wget -S -O- http://api:80

若解析成功但这里失败,再检查:

docker exec api ss -lnt
docker logs api

需要确认应用是否监听正确端口,以及监听地址是否为:

0.0.0.0:80

如果应用只监听:

127.0.0.1:80

那么它只能接受自身容器内的回环连接,其他容器即使能解析到它的 IP,也无法访问该监听端口。

第六步:观察重启和重建后的地址变化

docker inspect -f \
  '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' api

docker compose restart api
docker inspect -f \
  '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' api

重启是否改变 IP 取决于具体生命周期和网络状态,不能依赖“通常不变”或“通常会变”的经验。应用应始终把 IP 视为动态结果,并在连接失败后重新解析或重新建立连接。


十七、规范保证、实现行为与工程假设

需要把几类结论分开:

Docker 和 Compose 应提供的机制

  • 用户自定义网络支持容器间基于名称的服务发现;
  • Compose Service Name 可作为该项目网络中的服务访问名称;
  • 容器通过 Docker 提供的 DNS 端点查询内部名称和外部名称;
  • 服务重建后,名称仍可作为入口,但具体 IP 可能变化;
  • Network Alias 的作用范围受网络限制。

不能假设的行为

  • 不能假设容器 IP 永久不变;
  • 不能假设 DNS 返回多个 IP 时客户端一定轮流连接;
  • 不能假设 DNS 成功意味着应用已经 Ready;
  • 不能假设宿主机能解析 Compose Service Name;
  • 不能假设所有容器都能解析同一个 Service Name;
  • 不能假设 Compose 网络能跨 Docker 主机提供服务发现;
  • 不能假设任何应用都按相同方式处理 Search Domain、ndots 和 DNS 缓存。

应用必须自行处理的状态

  • DNS 临时失败;
  • 目标服务尚未监听;
  • 容器重启造成的旧连接失效;
  • 多地址结果中的部分地址不可用;
  • 服务发现成功但健康检查未通过;
  • 长连接断开后的重新解析和重连。

十八、核心判断

Docker 容器 DNS 可以用一个条件模型概括:

设:

  • C 是发起查询的容器;
  • S 是目标服务;
  • N(C)C 所连接的网络集合;
  • Alias(S, N) 是服务 S 在网络 N 上的名称集合;
  • q 是应用查询的名称。

只有当存在某个网络 N 满足:

N ∈ N(C)
且
q ∈ Alias(S, N)

Docker 内部 DNS 才有条件返回 S 在该网络上的地址。

即使这个条件成立,业务请求还需要满足:

DNS 解析成功
∧
网络路由可达
∧
目标端口正在监听
∧
应用已经准备好处理请求

Search Domain 只改变查询名称的候选序列;端口映射只改变宿主机到容器的入口;Healthcheck 只反映配置的健康判定;连接重试则决定故障是否能够恢复。把这些机制分别验证,才能准确判断“DNS 故障”究竟发生在名称、网络、端口还是应用生命周期阶段。


系列导航与关联阅读

官方资料

本文依据 Docker、OCI 与 CNCF 官方文档重新梳理;正文与生产检查清单由 WR BLOG 编写。