上一篇 《Docker 核心概念与安装》 我们讲了 Docker 的四大概念并跑通了第一个 FastAPI 服务。这一篇我们直面两个「用 Docker 必然遇到」的问题:
- 数据持久化:容器删了,数据怎么办?(数据库、上传的模型文件、训练好的 checkpoint)
- 容器间通信:一个 Web 容器要访问另一个模型推理容器、一个 PostgreSQL 容器,怎么找得到彼此?
一、为什么需要数据持久化
在传统的软件开发中,数据通常存储在服务器的本地文件系统或数据库中。Docker 容器的出现带来了新的挑战:
容器本身是短暂、无状态的。默认情况下,容器文件系统中的所有数据都会随着容器的删除而永久消失。
但 AI 应用对「数据存活」有强诉求:
- 训练好的模型 checkpoint 不能因为重启容器就丢了
- 上传的训练语料、用户文件需要跨容器生命周期存在
- 数据库的 binlog、慢日志、WAL 文件必须保留
Docker 为此提供了多种方案,其中 Volume 是官方最推荐、功能最强的解决方案。
二、什么是 Docker Volume
Docker Volume(卷)是用于持久化容器数据的首选机制。它是一个由 Docker 管理、存在于宿主机文件系统特定部分(Linux 上默认是 /var/lib/docker/volumes/)的目录。
2.1 关键特性
- Docker 管理:卷的创建、删除、查看等所有生命周期操作都由 Docker CLI 或 API 统一管理,用户不应直接修改 Docker 管理区域内的文件。
- 解耦与独立:卷的生命周期独立于任何容器。即使创建、使用、销毁了多个容器,只要不明确删除卷,卷中的数据就会一直存在。
- 高性能:卷绕过了容器的可写层,直接与宿主机文件系统进行原生 I/O 操作,性能接近于直接读写宿主机磁盘。
- 安全与隔离:卷将应用数据与宿主机的具体目录结构隔离开来。
2.2 Volume vs 绑定挂载:不要混淆
容器持久化主要有两种机制,必须分清:
| 机制 | 谁管理 | 路径示例 | 推荐场景 |
|---|---|---|---|
| 绑定挂载(Bind Mounts) | 用户指定 | -v /host/path:/container/path | 开发环境的代码热加载、配置文件注入 |
| 命名卷(Named Volumes) | Docker 管理 | -v my_data:/container/path | 生产环境的数据库、模型文件、用户上传 |
简记:能用名字的用名字(命名卷);需要直接看文件的用路径(绑定挂载)。
三、Volume 命令实战
3.1 基础操作
docker volume create redis_data # 创建一个名为 redis_data 的卷
docker volume ls # 列出所有卷
docker volume inspect redis_data # 查看卷的元数据(含物理路径)
docker volume rm redis_data # 删除卷
docker volume prune # 清理所有未被任何容器使用的卷
注意 1:停止或删除容器,其数据卷依然存在,需要显式删除。 注意 2:如果一个 Volume 正在被任何一个容器(即使是已停止的容器)使用,Docker 会阻止你删除它,必须先删除所有引用该 Volume 的容器。
3.2 命名卷的「项目前缀」
Docker 默认会把卷名前面拼上「项目名_」来避免冲突,比如 Compose 工程 autogpt_platform 下的 clamav-data 卷,最终物理名是 autogpt_platform_clamav-data。这样不同项目可以有同名卷,互不冲突。
如果希望跨项目共享同一个卷,使用 external: true 声明(详见 Compose 篇)。
3.3 在 docker run 中使用卷
# 启动一个 Nginx 容器,把卷挂载到网站根目录
docker run -d --name my-web-server -p 8080:80 \
-v my-ai-model-storage:/usr/share/nginx/html \
nginx
把 HTML 文件放进 my-ai-model-storage 卷,访问 http://localhost:8080 就能看到。
四、容器间网络通信
4.1 问题场景
一个典型的 AI 系统至少有三个容器:
- Web 前端容器(Nginx)
- 后端 API 容器(FastAPI)
- 向量数据库容器(Milvus / Qdrant)
它们怎么通信?
4.2 默认网络:bridge
Docker 安装后会自动创建一个 bridge 网络,所有未指定网络的容器都会连上去。默认 bridge 上,容器之间只能通过 IP 互相访问,不能通过容器名——每次重启 IP 都变,很不友好。
4.3 自定义网络:服务名直接通信
Docker 在用户自定义网络中内置了 DNS 解析,容器之间可以直接通过服务名/容器名互相访问。这才是多容器应用应该用的方式。
# 1. 创建自定义网络
docker network create ai-network
# 2. 把容器连到同一个网络
docker run -d --name sentiment-service --network ai-network sentiment-api:1.0
docker run -d --name vector-db --network ai-network milvus:latest
docker run -d --name frontend-app --network ai-network nginx
# 3. 在 frontend-app 容器内通过「服务名」直接访问
docker exec -it frontend-app sh
apk add curl
curl http://sentiment-service:8000/ # 直接用名字访问
这就是「服务发现」最朴素的形式——后续 Compose 会自动化这一切。
4.4 端口映射(宿主机 ⇄ 容器)
外部访问容器内的服务,需要把容器的端口「映射」到宿主机:
# -p 宿主机端口:容器端口
docker run -d -p 8080:80 nginx # 宿主机 8080 -> 容器 80
docker run -d -p 127.0.0.1:8080:80 nginx # 只监听宿主机本地
docker run -d -p 53:53/udp dns-server # UDP 协议
只暴露需要对外的端口,内部服务只连内部网络,不对外映射,是容器安全的基础。
五、AI 应用中的实战模式
5.1 模型权重持久化
把预训练模型(比如一个 7B 的大模型)放在命名卷里:
docker volume create model-weights
docker run -d --name llm-server \
-v model-weights:/app/models \
-p 8000:8000 \
my-llm-image:1.0
升级推理代码时只换镜像,模型权重不动。启动速度从「每次重新下载 14GB」变成「秒级」。
5.2 用户上传文件
docker volume create user-uploads
docker run -d --name api-server \
-v user-uploads:/app/uploads \
-p 8000:8000 \
my-api:1.0
5.3 日志收集
docker run -d --name app \
-v app-logs:/var/log/app \
my-app:1.0
后续可以挂一个日志收集容器,共享同一个 volume 来读日志。
六、小结
这一篇你掌握了:
- 数据持久化的核心问题:容器短暂无状态 → 需要 Volume
- 命名卷 vs 绑定挂载的区别和使用场景
docker volume全部命令(create/ls/rm/prune)- 容器间网络通信:默认 bridge(按 IP)、自定义网络(按服务名)
- 端口映射与最小暴露原则
- AI 应用中 Volume 的三种典型用法(模型/上传/日志)
到这里你已经能跑起「一个完整的多容器 AI 系统」了。但每次都手敲一长串 docker run 命令非常痛苦——下一篇 《Docker Compose 多容器编排:从命令到声明式》,我们把它装进一份 docker-compose.yml,用声明式管理整个应用环境。