README: - 新增「离线部署(导入预构建镜像)」章节:docker load + 启动命令 - 功能特性更新:设置页、任务删除、进度细分、离线运行、/docs 公开 docs/DOCKER.md: - 新增「导入预构建镜像(离线部署)」完整章节: 前置要求、导出步骤、需拷贝文件清单、导入启动命令、离线说明、后续更新代码 .gitignore: - 排除 *.tar(导出的镜像文件不入库)
274 lines
10 KiB
Markdown
274 lines
10 KiB
Markdown
← [返回主页](../README.md)
|
||
|
||
# Docker 说明
|
||
|
||
一份 Dockerfile 出 CPU / GPU 两个镜像,依赖层缓存复用,改代码秒级重建。本文档覆盖
|
||
构建、重建、缓存管理与 Volume 挂载。部署流程见 [部署指南](./DEPLOYMENT.md)。
|
||
|
||
---
|
||
|
||
## 一份 Dockerfile,两个镜像
|
||
|
||
`ARG VARIANT=cpu|gpu` 控制基础镜像与 torch 轮子:
|
||
|
||
| VARIANT | 基础镜像 | torch |
|
||
|---|---|---|
|
||
| `cpu`(默认) | `python:3.12-slim` | CPU 版(`--index-url .../whl/cpu`) |
|
||
| `gpu` | `nvidia/cuda:12.1.0-runtime-ubuntu22.04` | CUDA 版 |
|
||
|
||
两个镜像的 Python 依赖列表(`requirements.txt`)完全一致,仅 torch 不同。镜像内 apt 装
|
||
`ffmpeg` + `patchelf`。
|
||
|
||
安全约束:PyTorch CPU wheel 与 GPU(CUDA) wheel 是两个不兼容二进制包,CPU 版
|
||
`torch.cuda.is_available()=False`,GPU 版 `=True`。torch 必须按 VARIANT 分叉装不同 wheel,
|
||
绝不能跨 variant 共享依赖层。deps 阶段用 `FROM base-${VARIANT}`,CPU/GPU 是两条独立
|
||
构建链,各自装对应 torch。
|
||
|
||
---
|
||
|
||
## 新建 / 重建容器
|
||
|
||
项目提供 `setup.sh` / `start.sh` / `stop.sh` 包装脚本,也可直接用 `docker` / `docker compose`。
|
||
|
||
### 首次新建(新机器 / 全新拉取代码后)
|
||
|
||
```bash
|
||
# 1. 构建镜像 + 生成 config.yaml(CPU 默认)
|
||
./setup.sh
|
||
# GPU:AUDIO2TEXT_VARIANT=gpu ./setup.sh
|
||
|
||
# 2. 启动容器
|
||
./start.sh
|
||
# GPU:start.sh 检测到 audio2text:gpu 镜像 + nvidia-smi 自动加 --gpus all
|
||
```
|
||
|
||
`setup.sh` 做三件事:检查 docker → 构建 `audio2text:{variant}` 镜像 → 把
|
||
`config.{variant}.yaml` 复制为 `config.yaml`(运行时实际读取的文件)。
|
||
|
||
### 重建镜像(改了 app 代码或 requirements 后)
|
||
|
||
依赖层(apt + pip + torch)由 BuildKit 缓存挂载复用,只有 `COPY app` 层重建,通常
|
||
30 秒内完成。**重建不会动运行时数据**(`./data` / `./models` 是挂载的 volume)。
|
||
|
||
```bash
|
||
# CPU:直接重跑 setup.sh(幂等,会复用缓存层)
|
||
./setup.sh
|
||
# 或显式构建:
|
||
docker build --build-arg VARIANT=cpu -t audio2text:cpu .
|
||
|
||
# GPU:
|
||
AUDIO2TEXT_VARIANT=gpu ./setup.sh
|
||
# 或:
|
||
docker build --build-arg VARIANT=gpu -t audio2text:gpu .
|
||
|
||
# 重建后重启容器(替换运行中的旧镜像):
|
||
./stop.sh && ./start.sh
|
||
```
|
||
|
||
### 改配置(不重建镜像)
|
||
|
||
`config.yaml` 是只读挂载,改完重启容器即生效,**无需重建镜像**:
|
||
|
||
```bash
|
||
cp config.gpu.yaml config.yaml # 切换配置(或直接编辑 config.yaml)
|
||
./stop.sh && ./start.sh
|
||
```
|
||
|
||
### 改依赖(requirements.txt / torch 版本)
|
||
|
||
会触发 deps 层重建,耗时较长(重装 torch + 全部依赖,CPU ~3 分钟,GPU ~5 分钟)。
|
||
BuildKit 的 pip 缓存挂载(`/root/.cache/pip`)跨构建复用已下载的 wheel,二次构建会快
|
||
很多。
|
||
|
||
```bash
|
||
# 编辑 requirements.txt 后
|
||
./setup.sh # 或 docker build --build-arg VARIANT=gpu -t audio2text:gpu .
|
||
./stop.sh && ./start.sh
|
||
```
|
||
|
||
### docker compose(替代脚本)
|
||
|
||
```bash
|
||
docker compose --profile dev up -d --build # 开发:源码挂载 + uvicorn reload,改代码零重建
|
||
docker compose --profile cpu up -d --build # CPU 生产
|
||
docker compose --profile gpu up -d --build # GPU 生产(需 nvidia runtime)
|
||
```
|
||
|
||
### 导入预构建镜像(离线部署)
|
||
|
||
当目标机器无法访问 Docker Hub(或构建太慢)时,可在已构建好镜像的机器上导出 tar,
|
||
拷到新机器导入,跳过整个构建过程。
|
||
|
||
#### 前置要求(新机器)
|
||
|
||
- **NVIDIA GPU 驱动**(宿主机)
|
||
- **nvidia container runtime**(`nvidia-container-toolkit`)
|
||
- Docker
|
||
- `config.gpu.yaml` 配置文件(从项目仓库取,或自行编写)
|
||
- 模型缓存 `./models` 目录(约 5.5GB,从源机器拷贝或联网预拉)
|
||
|
||
验证 GPU 可用:
|
||
|
||
```bash
|
||
nvidia-smi # 宿主能看到 GPU
|
||
docker run --rm --gpus all nvidia/cuda:12.1.0-runtime-ubuntu22.04 nvidia-smi
|
||
```
|
||
|
||
#### 步骤 1:源机器导出镜像
|
||
|
||
```bash
|
||
# 在已构建好 audio2text:gpu 镜像的机器上
|
||
docker save -o audio2text-gpu.tar audio2text:gpu
|
||
ls -lh audio2text-gpu.tar # ~4.3GB
|
||
```
|
||
|
||
#### 步骤 2:拷贝到新机器
|
||
|
||
需要拷贝的文件:
|
||
|
||
| 文件/目录 | 大小 | 说明 |
|
||
|---|---|---|
|
||
| `audio2text-gpu.tar` | ~4.3GB | Docker 镜像(含 ffmpeg + torch + faster-whisper + transformers + app 代码) |
|
||
| `config.gpu.yaml` | <1KB | GPU 配置文件 |
|
||
| `models/` | ~5.5GB | 模型缓存(Whisper large-v3-turbo + NLLB distilled-1.3B 权重) |
|
||
|
||
> `models/` 可不拷贝,新机器联网时用 `prefetch_models.sh` 预拉。但离线部署必须拷贝。
|
||
|
||
```bash
|
||
# 用 scp / rsync / U盘 等方式拷贝
|
||
scp audio2text-gpu.tar config.gpu.yaml user@newhost:~/audio2text/
|
||
rsync -avP models/ user@newhost:~/audio2text/models/
|
||
```
|
||
|
||
#### 步骤 3:新机器导入并启动
|
||
|
||
```bash
|
||
cd ~/audio2text
|
||
|
||
# 1. 导入镜像
|
||
docker load -i audio2text-gpu.tar
|
||
# 输出:Loaded image: audio2text:gpu
|
||
|
||
# 2. 准备数据目录
|
||
mkdir -p data-gpu/uploads data-gpu/.work data-gpu/outputs
|
||
|
||
# 3. 启动容器
|
||
docker run -d --name audio2text-gpu \
|
||
--gpus all \
|
||
-p 8001:8000 \
|
||
-v "$(pwd)/data-gpu:/data" \
|
||
-v "$(pwd)/models:/models" \
|
||
-v "$(pwd)/config.gpu.yaml:/app/config.yaml:ro" \
|
||
--restart unless-stopped \
|
||
audio2text:gpu
|
||
|
||
# 4. 验证
|
||
curl -s http://127.0.0.1:8001/health | python -m json.tool
|
||
# 应见 cuda_available=true, gpu="NVIDIA GeForce RTX 3090"
|
||
```
|
||
|
||
打开 `http://127.0.0.1:8001/` 即可使用。
|
||
|
||
#### 离线运行说明
|
||
|
||
镜像内置 `HF_HUB_OFFLINE=1` + `TRANSFORMERS_OFFLINE=1` 环境变量,模型缓存就位后
|
||
**完全离线运行**,不会尝试访问 HuggingFace。这避免了离线环境下 transformers
|
||
pipeline 因网络请求超时导致的翻译失败。
|
||
|
||
#### 后续更新代码
|
||
|
||
导入的镜像包含导出时的 app 代码。如需更新代码,有两个选择:
|
||
|
||
1. **重新构建**:把项目代码拷到新机器,`docker build --build-arg VARIANT=gpu -t audio2text:gpu .`
|
||
2. **挂载源码**(临时调试):启动时加 `-v "$(pwd)/app:/app/app"` 覆盖镜像内代码
|
||
|
||
---
|
||
|
||
## 缓存分层与删除边界
|
||
|
||
这套构建涉及三类缓存,**删除策略截然不同**,乱删会导致全量重建:
|
||
|
||
| 缓存类型 | 位置 | 存什么 | 能删吗 | 删了会怎样 |
|
||
|---|---|---|---|---|
|
||
| **BuildKit 构建缓存** | Docker 内部(`docker builder` 管理) | Dockerfile 各层(base / deps / final)的构建产物 | ⚠️ 谨慎,见下方 | 命中失效 → 该层及下游全量重建 |
|
||
| **pip wheel 缓存** | BuildKit cache mount `/root/.cache/pip` | 下载过的 `.whl` 文件 | ✅ 可删 | 下次构建重新下载 wheel(不重编译) |
|
||
| **模型缓存** | `./models` volume(容器内 `/models`) | Whisper / NLLB 权重(HF + ctranslate2) | ✅ 可删 | 下次启动重新下载模型(~5.5GB GPU) |
|
||
| **运行时数据** | `./data` volume(容器内 `/data`) | 上传视频 / 中间音频 / 输出字幕 / SQLite | ⚠️ 视情况 | 删了任务历史和产物全没 |
|
||
|
||
### ⚠️ 不要用 `docker builder prune --filter until`
|
||
|
||
**这是踩过的坑**。BuildKit 的 `--filter "until=30m"`(或任意时长)会清除"最近 N 分钟未
|
||
访问"的缓存层。问题在于:**稳定的基础层**(如 `base-gpu` 的 apt 装 python3.12)只在
|
||
首次构建时执行一次,之后每次构建都直接 CACHED 跳过——它的"最后访问时间"一直停在首次
|
||
构建那一刻,永远不会更新。于是 `--filter "until=..."` 会把这些**仍然在用的稳定层**当成
|
||
"很久没访问"清掉,导致下一次构建从 base 层开始全量重来(GPU 镜像 ~10 分钟 + 重新下载
|
||
torch ~2.5GB)。
|
||
|
||
正确做法:
|
||
|
||
```bash
|
||
# ✅ 想清理磁盘、释放 BuildKit 缓存:用不带 filter 的 prune(清全部未引用缓存)
|
||
docker builder prune -f
|
||
# 或只清 dangling(悬挂的、无引用的中间层)
|
||
docker builder prune -f --filter "type=regular"
|
||
|
||
# ✅ 清旧镜像(不影响构建缓存)
|
||
docker image prune -a # 删所有未被容器使用的镜像
|
||
docker image prune # 只删 dangling 镜像
|
||
|
||
# ✅ 清 pip wheel 缓存(BuildKit cache mount,安全)
|
||
docker builder prune -f --filter "type=exec.cachemount"
|
||
|
||
# ❌ 永远不要这样用——会清掉仍在用的稳定 base 层
|
||
docker builder prune -f --filter "until=30m"
|
||
docker builder prune -f --filter "until=24h"
|
||
```
|
||
|
||
> 根因:BuildKit 的 `until` filter 按"最后访问时间"判定,而非"是否仍在被引用"。CACHED
|
||
> 跳过的层不会刷新访问时间,于是被误判为可回收。这是 BuildKit 的已知行为,不是 bug,
|
||
> 但对"稳定 base + 频繁改代码"的构建模式特别致命。详见
|
||
> [moby/buildkit#2414](https://github.com/moby/buildkit/issues/2414)。
|
||
|
||
### 什么时候需要主动清缓存
|
||
|
||
- **磁盘紧张**:`docker builder prune -f` + `docker image prune` 释放空间
|
||
- **依赖换了 torch / CUDA 大版本**:BuildKit 可能复用了不兼容的旧 wheel,清 pip 缓存
|
||
mount 强制重下:`docker builder prune -f --filter "type=exec.cachemount"`
|
||
- **换 VARIANT(cpu↔gpu)**:不需要清——两条构建链独立,缓存互不干扰
|
||
- **想从零验证构建**:`docker builder prune -af` 清全部,模拟新机器首次构建
|
||
|
||
### 模型缓存(`./models`)
|
||
|
||
模型权重在 `./models` volume(容器内 `HF_HOME=/models/huggingface`、
|
||
`CT2_CACHE=/models/ctranslate2`),跨容器复用。首次启动下载,之后秒起。
|
||
|
||
```bash
|
||
# 查看模型缓存大小
|
||
du -sh ./models
|
||
|
||
# 删了强制重下(GPU 大模型 ~5.5GB,建议用 prefetch 脚本提前下好)
|
||
rm -rf ./models
|
||
./scripts/prefetch_models.sh config.gpu.yaml
|
||
```
|
||
|
||
---
|
||
|
||
## Volume 挂载
|
||
|
||
| 容器路径 | 宿主路径 | 用途 | 删除影响 |
|
||
|---|---|---|---|
|
||
| `/data` | `./data`(CPU)/ `./data-gpu`(GPU) | 上传视频、中间音频、输出字幕、SQLite | 任务历史和产物全没 |
|
||
| `/models` | `./models` | 模型缓存(HF + ctranslate2),跨容器复用 | 下次启动重下模型 |
|
||
| `/app/config.yaml` | `./config.yaml`(只读) | 配置文件 | 改配置需重启容器 |
|
||
|
||
镜像本身无状态、无敏感数据。
|
||
|
||
---
|
||
|
||
## ctranslate2 可执行栈修复
|
||
|
||
ctranslate2 的 `.so`(在 `ctranslate2.libs/` 隐藏目录)带 PT_GNU_STACK 可执行栈标志,
|
||
在某些内核 + Docker 组合下会报 `cannot enable executable stack as shared object requires`。
|
||
Dockerfile 在构建时用 `patchelf --clear-execstack` 清掉该标志,无需放宽容器安全策略。
|
||
构建末尾有 `python -c "import ctranslate2"` 验证。
|