在容器化应用中,容器实例的IP地址是动态分配的,每次重建或重启都可能改变。如果多个容器之间需要通信,直接使用IP地址会带来极大的维护负担。Docker提供了一套基于DNS的服务发现机制,允许容器通过服务名(容器名或网络别名)互相访问,底层IP变化对应用透明。本文将深入剖析这一机制,并通过实际命令和编排文件演示如何配置。

一、容器网络与DNS服务发现基础
Docker默认的bridge网络(名称为bridge)中的容器只能通过IP地址通信,因为该网络没有内置DNS解析功能。如果容器A想访问容器B,必须知道B的IP,而IP在容器重启后会变化。这种静态IP方式显然不适合动态伸缩的场景。
为了解决这个问题,Docker允许创建自定义网络(custom network)。自定义网络默认启用内置DNS服务器,地址为127.0.0.11。当容器加入自定义网络时,Docker会自动修改容器的/etc/resolv.conf文件,将DNS服务器指向127.0.0.11。此后,容器内的进程发起DNS查询时,请求会发送到这个内置DNS服务器。该服务器维护了同网络中所有容器的名称与IP映射,名称包括容器名、网络别名(--network-alias)以及Compose中的服务名。
因此,只要两个容器处于同一个自定义网络中,就可以直接使用对方的名字(或别名)进行访问,而不需要关心实际IP。这个机制被称为Docker的嵌入式DNS服务发现。需要注意的是,默认bridge网络并不支持这种自动服务名解析,所以生产环境建议使用自定义网络。
二、Docker命令行实现服务名互访
通过docker命令可以快速体验服务名解析。首先创建一个自定义网络,例如命名为mynet。然后运行两个容器并加入该网络。容器名称分别为web和db。在web容器内部使用ping db命令,就能看到DNS解析成功并返回db容器的IP。
具体命令如下:
# 创建自定义网络 docker network create mynet # 启动一个名为db的容器,加入mynet网络 docker run -d --name db --network mynet nginx:alpine # 启动一个名为web的容器,加入mynet网络 docker run -it --name web --network mynet alpine sh # 在web容器内部执行ping db ping db
执行ping db后,可以看到类似输出,db被解析为172.x.x.x的IP。这说明内置DNS正常工作。如果两个容器加入的是默认bridge网络,则无法通过名称解析,只能通过IP通信,且需要在启动时使用--link参数(已过时,不推荐)。自定义网络完全替代了--link的功能。
此外,还可以通过--network-alias给容器设置多个别名。例如运行db容器时加上--network-alias database,那么在web容器中ping database也能解析到同一个容器。这为服务提供了更灵活的命名方式。
三、使用Docker Compose实现服务名互访
Docker Compose是管理多容器应用的主流工具。在Compose文件中,每个服务默认会加入一个由Compose自动创建的自定义网络(项目名_default),并且服务名本身就作为DNS名称。因此,在一个Compose项目中,服务之间可以直接通过服务名互相访问,无需额外配置。
下面是一个典型的docker-compose.yml示例,包含一个web服务和一个db服务。web服务通过环境变量指定数据库地址为db,这是服务名,而不是IP。
version: '3'
services:
web:
image: nginx:alpine
environment:
- DB_HOST=db
- DB_PORT=3306
depends_on:
- db
networks:
- appnet
db:
image: mysql:8.0
environment:
- MYSQL_ROOT_PASSWORD=secret
networks:
- appnet
networks:
appnet:
在这个例子中,web容器内的应用只需连接db:3306即可访问数据库。Compose会自动将db解析为db容器的IP。即使db容器重建后IP变化,DNS记录也会自动更新,web容器再次解析db时会得到新IP。这极大简化了微服务之间的连接配置。
需要注意的是,depends_on只保证容器启动顺序,不保证服务就绪。实际应用中常配合健康检查或等待脚本。如果服务需要跨多个Compose项目通信,可以使用外部网络(external network),将多个项目的服务加入同一个预先创建的自定义网络,这样也能通过服务名互访。
四、常见问题与诊断方法
尽管服务名解析很方便,但在实际使用中也可能遇到解析失败的情况。最常见的原因是容器不在同一个自定义网络中。例如一个容器在默认bridge网络,另一个在自定义网络,它们之间无法通过名称解析。此时应检查docker network inspect查看容器所属网络。
另一个常见问题是DNS缓存。虽然Docker内置DNS没有TTL缓存,但某些应用或系统层可能会缓存解析结果。如果容器IP变化后,旧进程仍使用旧IP,可以重启相关进程或容器。此外,如果通过docker exec进入容器使用ping命令,ping工具本身可能不会触发DNS解析刷新,但通常影响不大。
诊断服务名解析问题可以使用以下命令:
# 查看容器内DNS配置
docker exec web cat /etc/resolv.conf
# 使用nslookup解析服务名
docker exec web nslookup db
# 使用getent解析(无需额外安装)
docker exec web getent hosts db
# 宿主机查看容器IP
docker inspect --format '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' db
通过以上诊断步骤,大部分服务名解析问题都能定位。掌握服务名互访机制后,容器编排的灵活性和可维护性将显著提升。建议在项目初期就规划好自定义网络和服务命名,避免后期重构。