任务 API 和 Redis 分别运行在两个容器里。API 不应该通过固定 IP 找 Redis,因为容器重建后 IP 可能变化。正确做法是把它们接入同一个自定义网络,并使用稳定的名字。
Docker 的自定义桥接网络提供内置 DNS。接入同一网络的容器,可以通过容器名解析对方。
我们会把 Redis 容器命名为 redis,因此 API 的连接地址写成:
redis:6379这里的 6379 是 Redis 在容器网络中监听的端口。Redis 不需要发布到外部,只要 API 能访问它即可。端口越少暴露,边界越清楚。
network namespace 让容器看到自己的网络接口、IP、路由表和回环地址。于是 localhost 的含义取决于命令在哪里执行:
localhost:6379,找的是 API 容器自己。localhost:6379,才可能找到 Redis。localhost:8080,走的是已经发布的外部端口。“把数据库地址写成 localhost”是多容器应用最常见的错误之一。容器之间需要通过共享网络中的服务名和容器端口连接。
在单个 Docker Engine 中,自定义 bridge 网络通常会带来三项能力:
容器 IP 是运行时分配结果,重建后可能改变;DNS 名称是我们声明的稳定契约。因此应用配置写 redis:6379,不应该抄一次 inspect 得到的 172.x.x.x 地址。
三个概念要分开:
API 与 Redis 已在同一网络,所以 Redis 不需要 -p。不发布并不妨碍容器间通信,反而减少了外部入口。
$ docker network create task-net
task-net
$ docker volume create task-data
task-data查看网络驱动:
$ docker network inspect task-net \
--format 'name={{.Name}} driver={{.Driver}}'
name=task-net driver=bridgebridge 适合单个 Docker 引擎上的容器通信。
进一步查看 IP 地址管理范围:
$ docker network inspect task-net \
--format '{{json .IPAM.Config}}'
[{"Subnet":"172.20.0.0/16","Gateway":"172.20.0.1"}]实际网段可能不同,由 Docker 从可用地址池中选择。我们只观察它,不把网段写入应用配置;否则网络被重建后,硬编码地址就可能失效。
先解释参数:--network task-net 接入专用网络;命名卷挂到 /data;redis-server --appendonly yes 开启追加文件持久化。
$ docker run -d \
--name redis \
--network task-net \
--mount type=volume,source=task-data,target=/data \
redis:8-alpine redis-server --appendonly yes检查 Redis 是否回应:
$ docker exec redis redis-cli ping
PONGPONG 说明 Redis 进程已经能处理命令。我们没有使用 -p 6379:6379,所以 Redis 不会暴露给外部网络。
这里的 docker exec 不会启动第二个容器,而是在现有 Redis 容器的隔离环境中再启动一个 redis-cli 进程。它共享该容器的文件系统和网络视野,所以 redis-cli 默认连接 localhost:6379 能成功。
redis-server --appendonly yes 则覆盖了镜像默认命令的参数,要求 Redis 使用 AOF 持久化。AOF 会记录改变数据的操作并写入 /data;数据卷保证这些文件不依赖 Redis 容器可写层。持久化策略仍需要结合性能、恢复目标和备份设计,本课程只建立容器存储边界。
API 镜像默认读取 REDIS_ADDR,这里显式写出地址,方便理解配置来源:
$ docker run -d \
--name task-api \
--network task-net \
-e REDIS_ADDR=redis:6379 \
-p 8080:8080 \
docker-task-api:1.0从 API 容器确认 DNS 解析:
$ docker exec task-api getent hosts redis
172.20.0.2 redis redisIP 可能不同,名字应是 redis。
请求健康接口:
$ curl -s http://localhost:8080/health
{"status":"ok"}status: ok 不是只检查 API 进程。处理函数会向 redis:6379 发送 PING,因此这条结果同时证明端口映射、容器 DNS 和 Redis 连接都有效。
把证据拆成层次,会更容易排错:
curl 返回 200
├─ 外部 8080 → API 容器 8080 的发布路径有效
├─ API HTTP 进程能处理 /health
├─ 名称 redis 能由容器 DNS 解析
└─ Redis 6379 能建立连接并返回 PONG如果只检查首页 /,最后两层并未得到验证。这也是健康端点应覆盖关键依赖、但又要设置短超时的原因。
服务名稳定不代表底层连接永远不变。Redis 容器重建后可能获得新 IP,DNS 会把 redis 指向新地址,但应用中已经建立的旧 TCP 连接可能失效。可靠客户端需要超时、重试和连接池恢复机制。
Docker 网络负责“如何找到对方”,不负责保证对方业务永远健康,也不会替应用完成请求级重试。第 10 章的健康检查和第 11 章的故障演练会继续区分这些责任。
一个容器可以在不同网络上拥有别名。容器名在手工实验中方便,但 Compose 更推荐用服务名作为应用契约,因为 Compose 会根据项目创建实际容器名,并为服务提供稳定 DNS 名称。后面配置中的 REDIS_ADDR=redis:6379 因此无需改变。
$ curl -s -X POST http://localhost:8080/tasks \
-H 'Content-Type: application/json' \
-d '{"title":"学习容器网络"}'
{"title":"学习容器网络","createdAt":"2026-07-15T02:45:00Z"}
$ curl -s http://localhost:8080/tasks
[{"title":"学习容器网络","createdAt":"2026-07-15T02:45:00Z"}]时间会不同。只要第二个请求能读回相同标题,API 与 Redis 的协作就成立。
可以直接从 Redis 侧核对数据,确认响应不是 API 的临时内存:
$ docker exec redis redis-cli LLEN tasks
1
$ docker exec redis redis-cli LRANGE tasks 0 -1
{"title":"学习容器网络","createdAt":"..."}这里完成了两端交叉验证:HTTP 接口读到任务,Redis 列表也保存了相同 JSON。将来若 API 返回异常而 Redis 数据仍在,问题范围就能缩小到 API 处理或网络,而不是先怀疑数据消失。
遇到“API 连不上 Redis”,不要先固定 IP。按以下顺序收集证据:
docker ps -a:两个主进程是否都在运行。docker network inspect task-net:两个容器是否真的接入同一网络。getent hosts redis:DNS 是否能解析服务名。docker exec redis redis-cli ping:Redis 自己是否就绪。三类网络错误通常指向不同边界:no such host 偏向 DNS 或名字;connection refused 表示路径到达目标但端口没有进程监听;timeout 可能涉及路由、防火墙、服务阻塞或目标不可达。错误文字是线索,不应统称为“网络不通”。
下一节会把这些参数写进 Compose,因此先清理容器和网络。保留卷可以继续观察数据生命周期;若不需要数据,再单独删除。
$ docker rm -f task-api redis
task-api
redis
$ docker network rm task-net
task-net
$ docker volume rm task-data
task-data手工命令有助于理解每个对象,但参数一多就容易漏改。Compose 的价值不是隐藏这些概念,而是把已经理解的服务、网络、卷和配置写成可审查文件。
清理后用三个查询核对对象边界:
$ docker ps -a --filter name=task-api --filter name=redis
$ docker network ls --filter name=task-net
$ docker volume ls --filter name=task-data预期都没有课程对象。这里没有使用大范围清理命令,因为我们能够精确说出自己创建了哪些容器、网络和卷。