Docker 基础体系 · 第 47/70 篇。示例以现代 Docker Engine、BuildKit 与 Compose 规范为基础,并明确 Linux 容器边界。
Docker 容器 DNS:嵌入式解析、Service Name、Search Domain 和故障
在 Linux 容器中,DNS 不只是“把域名转换成 IP”。一个容器能否通过 api 找到另一个容器,取决于容器是否连接到同一个 Docker 网络、查询名称是否是该网络中的别名、解析器如何处理 Search Domain,以及目标容器当前是否已经加入网络。
Docker DNS 需要区分三层问题:
- 名称是否能被解析:例如
api是否能得到一个 IP 地址。 - 得到的 IP 是否可达:例如两个容器是否处于同一网络。
- 目标服务是否已经可用:例如容器虽然存在,但应用仍在启动。
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可以解析api和db;client通常不能通过 Docker 内部 DNS 解析cache;api与db是否能互相解析,还要看它们是否共同连接了某个网络;- 宿主机通常不能直接使用
api这个名称解析容器,因为宿主机不在这个容器 DNS 网络命名空间中。
2.2 Docker DNS 不是普通的“静态 hosts 文件”
容器启动后,Docker 可能根据容器创建、销毁、重启和网络连接状态更新 DNS 结果。名称到 IP 的映射因此是动态状态,而不是构建镜像时固化的值。
例如:
api容器启动,获得172.20.0.3;- 客户端解析
api,得到172.20.0.3; api被删除并重新创建,获得172.20.0.8;- 新的 DNS 查询可能返回
172.20.0.8; - 旧客户端若继续连接
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、解析库和缓存行为,应用不应依赖固定顺序。
这带来三个重要边界:
-
DNS 多地址不是完整负载均衡保证
客户端可能只使用第一个地址,也可能缓存结果。 -
失败重试由客户端负责
若第一个 IP 连接失败,客户端是否尝试下一个地址取决于 HTTP 客户端、连接池或应用代码。 -
长连接不会自动重新选择实例
一个连接建立后,它会继续指向原来的 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
数据流通常是:
- Alpine 内的解析器读取
/etc/resolv.conf; - 请求发送到
127.0.0.11; - Docker DNS 判断
example.com不是当前 Docker 网络中的内部名称; - Docker 将请求转发到配置的上游 DNS;
- 上游返回结果;
- Docker DNS 将结果返回给容器。
因此,下面两类故障路径不同:
解析 api 失败
可能是:
- 容器没有连接正确网络;
api服务不存在;- 别名配置错误;
- Docker 网络状态异常。
而:
解析 example.com 失败
可能是:
- Docker DNS 端点异常;
- 宿主机或 Docker daemon 的上游 DNS 配置异常;
- 防火墙阻断 UDP/TCP 53;
- VPN、公司 DNS 或 systemd-resolved 的转发路径有问题。
不要在容器内随意把 nameserver 改成宿主机的 127.0.0.53。127.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 中应看到 api 和 client。
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 完整学习路线:从镜像与容器到安全、可观测和生产交付
- 上一篇:Docker Bridge 与 iptables/nftables:NAT、端口发布、转发和冲突
- 下一篇:Docker IPv6、Macvlan 与 Host 网络:场景、隔离和路由边界
- 延伸:Docker 网络完整指南:Bridge、端口映射、DNS、Overlay 和排障
- 延伸:Compose 服务发现与依赖:DNS、Healthcheck、启动顺序和重连
官方资料
本文依据 Docker、OCI 与 CNCF 官方文档重新梳理;正文与生产检查清单由 WR BLOG 编写。

评论
0 条讨论