第 5 篇:Docker 网络与数据卷
0. 本篇定位
前一篇我们已经把单个容器的生命周期、日志、退出码、inspect 和 exec 排障路径梳理了一遍。本篇继续向外扩展一层,进入后端容器化最容易出错的两个边界:网络边界和存储边界。网络决定容器之间怎么互相找到、外部请求怎么进入容器;存储决定容器重建后哪些数据还在、哪些文件会被宿主机目录覆盖。
这篇文章的重点不是记住更多命令,而是建立判断框架。看到 Connection refused、UnknownHostException、端口占用、MySQL 数据丢失、热加载不生效、镜像内文件突然消失时,你要能判断问题属于容器 DNS、端口映射、应用监听、volume 生命周期、bind mount 遮蔽,还是权限和路径设计。只有这个判断链清楚,后面学习 Docker Compose 和 Kubernetes 的 Service、Ingress、PV、PVC 时才不会把对象职责混在一起。
读完本篇后,你应该能做到三件事。第一,解释为什么容器之间优先用服务名和容器端口通信,而不是写宿主机端口或硬编码 IP。第二,区分 volume、bind mount 和容器可写层的生命周期差异。第三,能用一条证据链定位网络和存储问题,而不是靠重启、删容器、换端口来碰运气。
1. 问题背景
后端服务一旦容器化,就会同时遇到两个认知变化。第一个变化是网络命名空间变了,容器里的 localhost 不再是你的宿主机,也不再是另一个容器,而是容器自己。第二个变化是文件系统生命周期变了,镜像层是只读模板,容器可写层是临时状态,真正需要长期保留的数据必须明确放到可管理的存储位置。
很多团队的容器化事故都来自这两个变化没有被理解。应用容器里配置了 jdbc:mysql://localhost:3306/app,本地 IDE 可以连,放进容器就失败。MySQL 容器删除后数据没了,才发现数据一直写在容器可写层。把当前源码目录 bind mount 到 /app 后,镜像里构建好的依赖被宿主机空目录遮住,应用启动时报文件不存在。开发为了方便把端口全部映射出来,测试环境又因为宿主机端口冲突变得不可复现。
这些问题表面上像命令写错,实质上是边界没有设计清楚。容器之间通信、宿主机访问容器、容器持久化数据、开发态代码挂载、生产态数据目录,这些都应该有不同的对象和不同的治理方式。把所有问题都塞进一个 docker run 命令里,短期能跑通,长期一定会变成团队协作和排障成本。
本篇会用单机 Docker 解释这些概念,因为单机环境最适合看清基本原理。但请注意,Docker bridge 和本地 volume 不是生产集群方案。它们是学习网络和存储边界的地基,也是迁移到 Compose、Kubernetes Service、Ingress、PV、PVC、StatefulSet 之前必须掌握的基本模型。
2. 核心概念
Docker 网络和数据卷可以先按职责边界拆开。网络侧回答“谁能找到谁、通过什么名字、走哪个端口”;存储侧回答“数据写在哪里、谁管理生命周期、容器重建后是否还存在”。
| 对象 | 解决什么问题 | 不解决什么问题 | 常见误区 |
|---|---|---|---|
| bridge network | 单机容器之间建立隔离网络 | 不负责跨主机调度和集群服务发现 | 以为创建 bridge 后外部自然能访问容器 |
| container DNS | 同一用户自定义网络内按容器名解析 | 不解析未加入同一网络的容器 | 以为所有容器名都能互相访问 |
| port mapping | 把宿主机端口转发到容器端口 | 不是容器之间通信的必需条件 | 把宿主机端口写进容器间连接串 |
| volume | 由 Docker 管理持久化数据目录 | 不表达业务备份、容灾和数据迁移策略 | 以为删容器等于删 volume |
| bind mount | 把宿主机路径直接挂入容器 | 不具备跨机器可移植性 | 以为挂载只会追加文件,不会遮蔽目录 |
2.1 bridge network 与容器 DNS
默认情况下,每个容器都有自己的网络命名空间。容器内看到的网卡、路由表、监听端口和 localhost 都属于它自己。Docker 的 bridge 网络负责把多个容器连接到同一个虚拟二层网络中,让它们可以通过容器 IP 互通。更推荐使用用户自定义 bridge 网络,而不是依赖默认 bridge,因为用户自定义网络提供更清晰的容器名解析和隔离边界。
容器 DNS 解决的是“不要硬编码 IP”。在同一个用户自定义网络里,容器可以通过容器名或网络别名访问对方。例如后端容器访问 MySQL,可以写 jdbc:mysql://mysql:3306/app,这里的 mysql 是容器名或别名,3306 是 MySQL 容器内部监听端口。这个连接串不关心 MySQL 容器当次启动拿到哪个 IP,也不关心宿主机是否映射了 3306 端口。
这个机制有两个重要边界。第一,DNS 解析通常只在同一个用户自定义网络内成立,容器没有加入同一网络时,名称无法解析。第二,DNS 只负责把名字解析到目标容器地址,不保证目标进程已经启动、不保证端口正在监听、不保证应用协议正确。能解析到 mysql 只能说明“找到了对象”,不等于“数据库可用”。
2.2 port mapping 与 localhost
端口映射解决的是宿主机或外部工具访问容器的问题。-p 8080:8080 的含义是把宿主机的 8080 转发到容器的 8080。浏览器访问 http://localhost:8080 时,localhost 指的是宿主机,Docker 再把流量转发进容器。这个路径和容器之间互相访问不是一回事。
容器内部的 localhost 指向容器自己。应用容器里写 localhost:3306,意思是访问应用容器内部的 3306 端口,而不是访问宿主机上的 MySQL,也不是访问另一个 MySQL 容器。如果 MySQL 在另一个容器里,连接串应该写服务名或容器名,例如 mysql:3306。如果确实要从容器访问宿主机上的服务,则需要使用 Docker 提供的宿主机访问方式,例如 Docker Desktop 常见的 host.docker.internal,并且要明确这是本地开发便利,不应该随意固化到生产配置。
还要区分宿主机端口和容器端口。多个容器内部都可以监听 8080,因为它们位于不同网络命名空间;但同一台宿主机上的宿主机端口不能重复绑定。测试环境常见的端口冲突,不是容器端口冲突,而是多个容器都试图绑定宿主机 8080。如果只是容器之间互相访问,通常不需要把依赖容器端口映射到宿主机。
2.3 volume 与 bind mount
容器文件系统由镜像只读层和容器可写层组成。容器运行时产生的新文件默认写入容器可写层,容器删除后这部分数据也随之消失。数据库、消息队列、本地对象存储这类需要保留状态的组件,不能把核心数据放在可写层里,否则一次 docker rm 就可能让数据消失。
volume 是 Docker 管理的持久化目录。它不属于某个容器的可写层,容器删除后 volume 默认仍然存在,可以重新挂载给新的容器。对于 MySQL、PostgreSQL、Redis 持久化数据、MinIO 数据目录这类后端依赖,volume 通常比 bind mount 更适合作为本地或测试环境的默认选择,因为路径由 Docker 管理,跨平台差异更少,也不容易受到宿主机工作目录结构影响。
bind mount 是把宿主机上的某个路径直接挂入容器。它适合本地开发,例如把源码目录挂到容器里做热加载;也适合临时观察日志或导入导出文件。但 bind mount 有强烈的宿主机耦合,路径、权限、大小写、换行符、文件监听行为都可能受宿主机影响。更关键的是,挂载到容器某个已有目录时,宿主机目录会遮蔽镜像里原本的目录内容。它不是追加文件,而是替换容器视图。
3. 运行机制
Docker 网络和存储的运行机制可以用一条后端启动链路来理解:先创建网络和持久化目录,再启动依赖容器,最后启动业务容器,让业务容器通过服务名访问依赖,并通过宿主机端口暴露给浏览器或测试工具。
create network -> create volume -> start mysql on network with volume -> start redis on network -> start backend-api on network -> backend-api resolves mysql and redis names -> external client reaches backend-api through published port3.1 网络路径:容器到容器,宿主机到容器
容器到容器的路径应该优先走容器网络。后端容器访问 MySQL 时,连接目标是 mysql:3306,也就是网络内的服务名和容器端口。请求从后端容器的网络命名空间发出,进入 bridge 网络,由 Docker 网络层转发到 MySQL 容器。这个过程不需要宿主机端口映射,MySQL 也不需要暴露给宿主机。
宿主机到容器的路径走端口映射。浏览器或 Postman 在宿主机访问 localhost:8080,Docker 把宿主机 8080 的流量转发到后端容器 8080。这个路径常用于访问 API、调试页面、管理端口或本地测试入口。是否映射端口应该取决于外部是否真的需要访问,而不是“容器里有端口就都映射出来”。
这两个路径如果混在一起,会产生很多奇怪问题。把 mysql:3306 写成 localhost:3306,容器内会访问自己;把容器间连接写成宿主机映射端口,环境一变就容易失效;把所有依赖端口都映射到宿主机,测试环境会出现端口冲突和安全暴露。正确做法是:内部依赖走网络名,外部入口才做端口映射。
3.2 存储路径:可写层、volume、bind mount
容器启动时,镜像层作为只读基础,运行时再叠加一个容器可写层。应用临时文件、缓存、运行时生成的文件如果没有挂载出去,就会进入可写层。可写层和容器实例绑定,容器删除后消失。它适合临时状态,不适合业务数据。
volume 挂载会把容器内某个路径指向 Docker 管理的数据目录。例如 MySQL 的 /var/lib/mysql 挂到 mysql-data volume 后,数据库文件写入 volume。删除 MySQL 容器再用同一个 volume 启动新容器,数据仍然可以被看到。这里要注意,volume 保留数据不等于完成了备份策略。volume 只是生命周期独立,备份、恢复、权限、容量和迁移仍然要单独设计。
bind mount 则把宿主机路径挂进容器。例如把 ./src 挂到 /app/src,容器内看到的就是宿主机上的源码目录。它非常适合开发态修改代码立即生效,但也最容易引入宿主机差异。尤其是挂载到非空目录时,镜像原有内容会被遮住。很多“镜像里明明有文件,运行后找不到”的问题,本质就是 bind mount 覆盖了路径。
3.3 伪代码:从声明到运行态
可以把 Docker 处理网络和挂载的过程理解成下面这段伪代码。它不是 Docker 源码,但能帮助你按阶段排障。
function startBackendStack(spec): network = ensureNetwork(spec.network.name) volumes = ensureVolumes(spec.volumes)
for service in spec.services: containerConfig = mergeImageConfigAndRuntimeOptions(service)
validateMounts(containerConfig.mounts) validatePortBindings(containerConfig.ports)
container = createContainer(containerConfig) attachToNetwork(container, network, aliases = service.aliases) mountVolumes(container, volumes) publishPorts(container, service.publishedPorts)
startMainProcess(container) recordRuntimeState(container)
return observeStackHealth(spec.services)这段伪代码可以拆成四个排障阶段。第一,输入阶段检查网络名、volume 名、路径、端口和环境变量是否符合预期。第二,创建阶段检查容器是否能被创建,挂载路径是否存在,宿主机端口是否被占用。第三,启动阶段检查主进程是否启动、应用是否监听正确地址和端口。第四,运行阶段检查 DNS 是否解析、依赖是否就绪、数据是否写入预期位置。
伪代码里最容易被忽略的是 mergeImageConfigAndRuntimeOptions。镜像里写了 EXPOSE 8080,不代表宿主机会自动开放 8080;Dockerfile 里有默认环境变量,不代表运行时没有被覆盖;镜像里构建了 /app/node_modules 或 /app/target,不代表 bind mount 后还能看到。排障时必须以运行时事实为准,不能只看 Dockerfile 或 README。
4. 最小可运行示例
下面的示例用一个后端 API、MySQL 和 Redis 模拟本地依赖环境。它的目的不是搭出完整生产环境,而是让你观察三件事:容器间用服务名通信,外部访问只映射 API 端口,MySQL 数据写入 volume。
4.1 创建网络和数据卷
先创建一个用户自定义 bridge 网络和一个命名 volume。
docker network create backend-netdocker volume create mysql-databackend-net 是容器之间通信的网络边界。只有加入这个网络的容器,才能通过这个网络中的名字互相访问。mysql-data 是 Docker 管理的持久化目录,后面会挂载到 MySQL 的数据目录。
可以用下面的命令观察它们:
docker network inspect backend-netdocker volume inspect mysql-data如果网络不存在,后续 docker run --network backend-net 会失败。如果 volume 不存在,Docker 通常会按需创建,但显式创建更利于团队把依赖状态写清楚。
4.2 启动依赖容器和后端容器
启动 MySQL 时加入同一个网络,并把数据目录挂到 volume。
docker run -d --name mysql --network backend-net \ -e MYSQL_ROOT_PASSWORD=example \ -e MYSQL_DATABASE=app \ -v mysql-data:/var/lib/mysql \ mysql:8启动 Redis 时只加入网络,不把端口映射到宿主机。
docker run -d --name redis --network backend-net redis:7启动后端 API 时,通过服务名访问依赖,只映射 API 自己的外部入口。
docker run -d --name backend-api --network backend-net \ -p 8080:8080 \ -e DB_URL=jdbc:mysql://mysql:3306/app \ -e REDIS_URL=redis://redis:6379 \ registry.example.com/backend-api:1.0.0这组命令里,mysql:3306 和 redis:6379 是容器网络内部地址,localhost:8080 是宿主机访问后端 API 的地址。不要把它们混成一类。
4.3 观察、重建和清理
可以进入后端容器验证 DNS 和网络连通性。实际镜像可能没有 getent、nc 或 shell,生产镜像更可能是精简镜像,所以这些命令主要用于学习环境。
docker exec backend-api getent hosts mysqldocker exec backend-api getent hosts redisdocker logs backend-api --tail 100docker inspect backend-api验证 volume 生命周期时,可以删除并重建 MySQL 容器,但不要删除 volume。
docker rm -f mysqldocker run -d --name mysql --network backend-net \ -e MYSQL_ROOT_PASSWORD=example \ -e MYSQL_DATABASE=app \ -v mysql-data:/var/lib/mysql \ mysql:8如果使用同一个 mysql-data,数据目录会继续存在。清理练习环境时要分清删容器、删网络和删 volume 的影响。
docker rm -f backend-api redis mysqldocker network rm backend-netdocker volume rm mysql-datadocker volume rm mysql-data 才会删除这个 volume。生产或重要测试环境里,删除 volume 之前必须确认备份和恢复方案。
5. 配置逐行拆解
这一节把示例里的关键字段拆开。重点不是“字段是什么意思”,而是“字段一旦写错会影响哪条链路,以及应该从哪里拿证据”。
5.1 网络和 DNS 字段
docker network create backend-net 创建的是用户自定义 bridge 网络。用户自定义网络比默认 bridge 更适合后端多容器环境,因为它提供更明确的隔离边界和名称解析。网络名应该稳定且有业务含义,例如 backend-net、order-local-net,不要每个人随手创建一堆不可追踪网络。
--network backend-net 表示把容器加入这个网络。只有加入同一个网络的容器,才能通过这个网络中的名称访问。MySQL 容器名是 mysql,后端连接串写 mysql:3306,Docker 内置 DNS 才能解析。若后端容器没有加入 backend-net,症状通常不是端口拒绝,而是名称解析失败,例如 UnknownHostException、Name or service not known。
容器名和网络别名要谨慎使用。容器名适合本地单实例;Compose 或 Kubernetes 中更常使用服务名。学习阶段可以先理解 --name mysql 带来的 DNS 名称,但工程上最好逐步迁移到 Compose service name 或 Kubernetes Service name,因为它们更贴近多实例和声明式管理。
5.2 端口和环境变量字段
-p 8080:8080 左边是宿主机端口,右边是容器端口。宿主机端口用于浏览器、Postman、测试脚本从宿主机访问 API;容器端口是后端进程在容器内部监听的端口。若应用实际监听 127.0.0.1:8080 而不是 0.0.0.0:8080,端口映射存在也可能访问失败,因为服务只绑定在容器内部回环地址上。排障时要同时看 docker port、应用日志和容器内监听状态。
-e DB_URL=jdbc:mysql://mysql:3306/app 是运行时配置注入。这里的 mysql 不是宿主机名,也不是镜像名,而是同一网络内的容器名或服务名。3306 是 MySQL 容器端口,不是宿主机映射端口。只要后端和 MySQL 在同一 Docker 网络中,就不需要把 MySQL 的 3306 映射到宿主机。
环境变量的风险在于它很容易被不同环境覆盖。开发机、CI、测试环境、Compose 文件、Kubernetes Secret 都可能给同一个变量赋值。排障时不要只看源码里的默认值,要看运行态事实,例如 docker inspect backend-api 中的 Config.Env,以及应用启动日志中实际读取到的配置来源。
5.3 volume 和 bind mount 字段
-v mysql-data:/var/lib/mysql 是命名 volume 挂载。冒号左边没有以 / 或盘符开头时,通常表示 Docker volume 名;右边是容器内路径。MySQL 官方镜像把数据写到 /var/lib/mysql,所以这个路径必须挂载出去。挂错路径时,容器可能照常启动,但数据写到了可写层,删除容器后才暴露问题。
bind mount 常见写法是 -v "$PWD/src:/app/src" 或 --mount type=bind,source=...,target=...。它把宿主机路径直接投影到容器目标路径。若目标路径在镜像中原本有文件,运行时会被宿主机目录遮蔽。比如镜像构建阶段生成了 /app/dist,运行时把宿主机空目录挂到 /app,容器内 /app/dist 就看不到了。
生产或共享测试环境中,存储配置要避免“路径靠约定”。宿主机路径、权限和磁盘容量都应该被平台管理,而不是写在每个人自己的命令里。Docker 单机阶段可以用 volume 学会生命周期边界,到了 Kubernetes 阶段则要理解 PV、PVC、StorageClass 和 StatefulSet 的职责划分。
6. 后端工程场景
网络和数据卷不是 Docker 的边角知识,而是后端工程里依赖治理、环境复现和排障闭环的基础。不同环境里,同一个概念的使用强度不同。
6.1 本地开发和集成测试
本地开发最适合用 Docker 网络和 volume 管理依赖。后端应用可以本机运行,也可以容器运行;MySQL、Redis、Kafka、MinIO 这类依赖用容器启动,统一加入一个网络。这样团队成员不必在本机安装一堆中间件,也不必互相猜数据库版本和 Redis 配置。
如果后端应用也运行在容器中,依赖地址使用服务名,例如 mysql:3306。如果后端应用运行在宿主机 IDE 中,而 MySQL 运行在容器中,则宿主机需要通过端口映射访问 MySQL,例如 localhost:3306。这两种模式的连接串不同,不能混用。很多“IDE 能连、容器不能连”的问题,就是因为没有区分应用运行位置。
集成测试环境要控制数据状态。volume 可以保留数据,适合需要复用数据集的测试;也可以在每次测试前删除 volume,得到干净状态。关键是把策略写清楚。最糟糕的状态是测试环境既不是干净环境,也不是有版本的数据集,而是一堆没人知道来源的残留 volume。
6.2 CI、测试环境和预发环境
CI 中使用 Docker 依赖时,网络和 volume 应该短生命周期、可重复创建、可自动清理。测试任务开始时创建独立网络,启动依赖容器,执行测试,结束后删除容器和网络。是否删除 volume 取决于是否需要保留失败现场。如果要保留现场,应该把 volume 名、容器日志、inspect 结果和测试报告一起保存,而不是只留下一个随机命名的 volume。
共享测试环境比本地更容易端口冲突。每个分支或每套环境都想绑定 8080、3306、6379,宿主机端口很快就会冲突。更稳的做法是减少依赖端口映射,只暴露必要入口;内部依赖全部走容器网络或 Compose 服务名。外部入口端口由环境编排统一分配,而不是开发者手写。
预发环境还需要接近生产的约束。比如数据库数据不应该随手删除,volume 清理需要审批;依赖地址不能写死宿主机 IP;敏感配置不能直接写在命令行里;服务启动顺序不能只靠 sleep。Docker 单机命令可以帮助理解机制,但预发环境最好尽快迁移到 Compose 或 Kubernetes 声明,减少手工状态。
6.3 订单服务的贯穿案例
假设有一个订单 API,依赖 MySQL 保存订单,依赖 Redis 缓存库存。开发阶段可以创建 order-net,启动 mysql 和 redis,然后让 order-api 通过 jdbc:mysql://mysql:3306/order 和 redis://redis:6379 连接。浏览器和测试脚本只访问 localhost:8080,因此只需要映射订单 API 的端口。
如果订单 API 报数据库连接失败,排障路径应该先问应用在哪里运行。应用在容器里,就检查 order-api 是否加入 order-net,mysql 名称能否解析,MySQL 是否监听 3306,账号密码是否正确。应用在宿主机 IDE 里,就检查 MySQL 是否映射了宿主机端口,IDE 连接串是否使用 localhost:3306。同一个错误文本,在不同运行位置下对应的网络路径不一样。
如果订单数据突然丢失,先检查 MySQL 数据目录是否挂到了 volume。docker inspect mysql 可以看到挂载信息,docker volume inspect mysql-data 可以看到 volume 元数据。如果数据目录没有挂载,或者挂错路径,数据可能写在可写层里。修复不是“下次小心别删容器”,而是把数据目录挂载写进 Compose 或环境模板,并给清理脚本加上保护。
7. 常见错误与排障
网络和存储问题要按证据链排查,不要一上来就删容器。删容器可能会清掉最关键的现场,尤其是容器可写层、日志尾部、运行参数和错误状态。
7.1 localhost、DNS 和端口冲突
容器里访问 localhost 失败时,先确认“客户端进程在哪里”。如果客户端在后端容器内,localhost 指后端容器自己;如果客户端在宿主机,localhost 指宿主机;如果客户端在另一个容器,应该使用同网络内的服务名。这个判断比看端口更靠前。
DNS 失败时,先看容器是否加入同一用户自定义网络。
docker inspect backend-apidocker inspect mysqldocker network inspect backend-netdocker exec backend-api getent hosts mysql如果 getent hosts mysql 没有结果,多半是网络或名称问题;如果能解析但连接被拒绝,多半是目标端口没有监听或服务未就绪;如果能连接但认证失败,就是应用配置或账号问题。不要把这些现象都归为“网络不通”。
端口冲突通常发生在宿主机端口。Bind for 0.0.0.0:8080 failed: port is already allocated 说明宿主机 8080 被占用,不代表容器内部 8080 不能重复使用。解决方式可以是换宿主机端口,例如 -p 18080:8080,也可以是不映射依赖容器端口,只保留外部入口。
7.2 数据丢失、挂载遮蔽和权限问题
数据丢失先看数据写到了哪里。docker inspect mysql 中的 Mounts 能告诉你 /var/lib/mysql 是否挂到了 volume。若没有挂载,数据很可能在容器可写层。容器删除后,可写层消失,业务数据也随之消失。修复应回到启动声明,而不是把容器当作需要长期保存的服务器。
挂载遮蔽常见于 bind mount。比如镜像里 /app 下有构建产物,运行时把宿主机项目目录挂到 /app,如果宿主机目录没有这些产物,容器内就看不到。排障时比较两个事实:镜像构建时的文件路径,以及运行时 docker inspect 里的 Mounts。如果运行时挂载覆盖了目标路径,镜像里有文件也没用。
权限问题常见于数据库、日志目录和非 root 容器。volume 或 bind mount 的宿主机目录可能属于另一个用户,容器进程没有写权限。症状可能是应用启动失败、数据库初始化失败、日志写入失败。排障时看容器日志、目录权限、运行用户,以及镜像是否切换了 USER。不要简单地把容器改成 root 运行,除非只是临时验证。
7.3 证据链模板
网络问题可以按下面顺序收集证据。
docker ps -adocker network inspect backend-netdocker inspect backend-apidocker logs backend-api --tail 100docker exec backend-api getent hosts mysqldocker exec backend-api sh -c "nc -vz mysql 3306"如果镜像没有 sh 或 nc,可以临时启动一个调试容器加入同一个网络。
docker run --rm -it --network backend-net busybox sh存储问题可以按下面顺序收集证据。
docker inspect mysqldocker volume inspect mysql-datadocker logs mysql --tail 100docker exec mysql sh -c "df -h && ls -lah /var/lib/mysql"最后要把结论写回声明。排障报告至少包含:客户端和服务端分别在哪里运行,使用的连接串是什么,DNS 是否能解析,目标端口是否监听,数据目录是否挂载,挂载类型是什么,修复改到了哪个文件或命令。只有这样,下次环境重建时问题才不会回来。
8. 生产化边界
Docker bridge、volume 和 bind mount 能帮助你理解网络和存储,但它们不是生产化的全部。生产环境要补上集群调度、入口治理、数据备份、权限、安全、观测和变更流程。
8.1 单机 Docker 和生产集群的边界
bridge network 是单机网络。它适合本地开发和单机实验,不负责跨主机服务发现、负载均衡和故障转移。到了 Kubernetes 中,容器间通信会进入 Pod 网络、Service、EndpointSlice、Ingress 或 Gateway 等对象。理解 bridge 的价值在于建立“内部服务名”和“外部入口”的分层思维,而不是把 bridge 当成集群网络方案。
Docker volume 也是单机或运行时管理的本地持久化对象。它不自动提供跨节点调度、远程存储、容量扩展、快照、备份和恢复。生产数据库通常不应该只靠本地 Docker volume 承担数据安全,至少要明确备份策略、恢复演练和数据迁移路径。Kubernetes 中对应要学习 PV、PVC、StorageClass 和有状态工作负载。
bind mount 在生产中要非常谨慎。它把应用运行状态和某台宿主机路径绑定,天然降低可移植性。除非是明确的平台目录、日志采集目录、证书目录或节点级组件,否则业务容器不应该随意依赖宿主机路径。生产里最怕的不是挂载写法复杂,而是换一台机器就无法启动。
8.2 数据所有权、备份和恢复
volume 只回答“数据是否跟容器同生命周期”,不回答“数据归谁管”。生产环境必须明确数据所有权:数据库数据由 DBA、平台、业务团队还是云服务管理;备份频率是多少;恢复目标时间和恢复点目标是什么;谁有权限删除或迁移存储;清理脚本是否会误删。
后端团队在本地可以用 volume 快速启动 MySQL,但不能因此忽略数据治理。即使是测试环境,也应该区分可丢弃数据、可重建数据和需要保留的样本数据。对于需要复现问题的数据集,最好有导入脚本或快照来源,而不是依赖某个 volume 长期不被删除。
恢复比备份更重要。很多团队有备份文件,却从未演练恢复。容器化场景里,恢复还要考虑镜像版本、数据库版本、配置版本和迁移脚本版本是否匹配。只保留 volume 而没有记录应用版本和 schema 迁移状态,恢复时仍然可能不可用。
8.3 安全、观测和迁移路径
网络安全上,不要把所有依赖端口都映射到宿主机。端口映射会扩大暴露面,尤其是数据库、缓存和消息队列。默认只映射必须被外部访问的入口,内部依赖走容器网络。敏感配置不要直接写进命令行历史或公开脚本,至少要迁移到环境文件、Secret 管理或平台配置系统。
观测上,网络和存储问题不能只靠应用日志。需要同时保留容器状态、网络配置、端口映射、挂载信息、volume 元数据、启动日志和业务指标。对于一次连接失败,最好能回答:DNS 解析耗时、连接建立是否成功、认证是否成功、SQL 或缓存命令是否成功。只看到一条 connection refused,还不足以判断根因。
迁移路径上,先用 Docker 命令理解对象,再用 Docker Compose 固化本地多容器环境,最后再进入 Kubernetes 的 Service、Ingress、ConfigMap、Secret、PV、PVC 和 StatefulSet。不要跳过基础概念直接写大量 YAML。YAML 写得越多,越需要你知道每个对象到底解决哪一层问题。
9. 什么时候用,什么时候不用
Docker 网络和数据卷的使用要看目标。如果目标是学习、开发、集成测试和依赖复现,它们非常合适;如果目标是生产高可用、跨节点调度和数据容灾,则需要更完整的平台能力。
9.1 适合使用的场景
本地后端开发适合使用用户自定义 bridge 网络和 volume。它能让 MySQL、Redis、Kafka 等依赖稳定启动,让后端容器通过服务名访问依赖,让数据在容器重建后继续存在。相比每个人本机手动安装依赖,这种方式更容易统一版本和清理环境。
集成测试也适合使用临时网络和临时 volume。每个测试任务创建自己的网络,避免互相污染;依赖容器不映射宿主机端口,减少冲突;测试结束后自动清理。需要保留失败现场时,再把日志、inspect 和 volume 信息保存下来。
学习 Kubernetes 前,也适合先用 Docker 网络和 volume 建模。Docker bridge 对应“服务间内部通信”的基本直觉,port mapping 对应“外部入口”,volume 对应“存储生命周期独立于容器实例”。这些直觉会迁移到 Service、Ingress、PVC 等对象上。
9.2 不适合直接照搬的场景
生产集群不适合只靠单机 bridge 网络。跨主机服务发现、负载均衡、滚动发布、故障转移、网络策略都需要平台层能力。单机 Docker 可以跑服务,但它不等于具备生产治理能力。
关键业务数据不适合只靠本地 volume。volume 能让数据不随容器删除,但不能替代备份、快照、恢复演练、容量告警和权限审计。对于数据库这类核心状态,存储方案要被当成生产系统的一部分设计。
生产业务容器不适合随意使用 bind mount 绑定宿主机路径。bind mount 会让应用依赖某台机器的目录结构和权限,降低可迁移性。它适合开发态热加载和明确受控的平台路径,不适合成为业务部署的默认存储方案。
9.3 默认建议
默认建议是:容器之间通信用用户自定义网络和服务名,外部访问才做端口映射;需要保留的数据用命名 volume,开发热加载才用 bind mount;连接串里不要写容器内的 localhost,也不要把宿主机映射端口当成容器间通信端口。
排障默认顺序是:先确认客户端在哪里运行,再确认目标名字怎么解析,然后确认目标端口是否监听,最后确认应用协议和认证是否正确。存储问题则先确认数据目录挂载类型,再确认挂载路径是否正确,最后看权限、容量和生命周期。
工程演进默认路径是:单机 Docker 学机制,Docker Compose 固化本地环境,Kubernetes 接管生产声明。每一步都不要只看“能不能跑”,还要看“能不能复现、能不能审查、能不能回滚、能不能交给团队长期维护”。
10. 下一篇衔接
本篇已经把 Docker 网络和数据卷的核心边界讲清楚了。下一篇会进入 Docker Compose,重点是把本篇这些分散命令组织成一个可版本化的本地后端环境。Compose 会让网络、volume、服务名、环境变量、启动依赖和端口入口都写进同一份声明中,减少手工命令漂移。
读下一篇前,可以先检查自己是否能回答几个问题:容器里的 localhost 指向哪里,为什么后端容器访问 MySQL 应该写 mysql:3306,为什么 MySQL 通常不需要把 3306 映射到宿主机,volume 和 bind mount 的生命周期有什么区别,bind mount 为什么可能让镜像内文件消失。如果这些问题能解释清楚,Compose 的学习会顺很多。