深色模式
Docker
为什么要用 Docker
使用别人写好的软件,最大的障碍往往是配环境。配环境的折磨会极大地消磨你对编程的兴趣。虚拟机能解决一部分问题,但它庞大笨重,为了一个应用模拟一整个操作系统并不值得。
Docker 用轻量级的容器代替整个操作系统:应用连同它的运行环境被打包成一个镜像,镜像可以在任何装了 Docker 的机器上运行成容器,在你电脑上能跑,在服务器上也能跑。课题组的模型服务、数据库、实验环境大多用 Docker 部署,K8s 调度的也是容器。
推荐先看:30 分钟 Docker 入门教程,然后跟着官方文档动手。
核心概念
| 概念 | 类比 | 说明 |
|---|---|---|
| 镜像(image) | 安装包 | 只读的模板,由多个层(layer)叠加而成,相同的层可以共享 |
| 容器(container) | 运行中的程序 | 镜像运行起来的实例,可以启动、停止、删除 |
| 仓库(registry) | 应用商店 | 存放镜像的地方,如 Docker Hub |
| 数据卷(volume) | 外接硬盘 | 容器删除后数据仍保留,用来存数据库数据、模型权重 |
| 端口映射 | 门牌号 | 把容器内的端口映射到宿主机端口,外部才能访问 |
镜像分层与课题组的研究直接相关,见下面的镜像层一节。
安装
Ubuntu 上按官方文档安装 Docker Engine,然后把自己加入 docker 组,免得每次都要 sudo:
bash
sudo usermod -aG docker $USER # 重新登录后生效
docker run hello-world # 测试docker 组相当于 root 权限
docker 组的成员可以通过容器获得整台机器的 root 权限。只在自己的电脑或虚拟机上这样做;课题组的共享服务器请找管理员,或者使用 rootless 模式。
WSL2 和 Mac 用户可以安装 Docker Desktop(Mac 也可用 OrbStack)。
常用命令
bash
# 镜像
docker pull ubuntu:24.04 # 拉取镜像
docker images # 列出本地镜像
docker rmi <镜像> # 删除镜像
# 运行容器
docker run -it --rm ubuntu:24.04 bash # 交互式运行,退出后自动删除
docker run -d --name web -p 8080:80 nginx # 后台运行,宿主机 8080 端口映射到容器 80 端口
# 管理容器
docker ps # 运行中的容器(加 -a 显示全部)
docker logs -f web # 查看并持续跟踪日志
docker exec -it web bash # 进入运行中的容器
docker stop web && docker rm web # 停止并删除
# 清理
docker system df # 查看磁盘占用
docker system prune # 删除已停止的容器、未使用的网络和悬空镜像docker run 常用参数
| 参数 | 作用 | 示例 |
|---|---|---|
-d | 后台运行 | |
--name | 给容器起名,之后用名字操作 | --name pg |
-p 宿主机端口:容器端口 | 端口映射 | -p 5432:5432 |
-v 宿主机路径或卷名:容器路径 | 挂载目录或数据卷 | -v ~/models:/models |
-e 变量=值 | 设置环境变量 | -e POSTGRES_PASSWORD=xxx |
--gpus | 使用 GPU | --gpus all 或 --gpus '"device=0,1"' |
--ipc=host | 共享宿主机的进程间通信,PyTorch 多进程加载数据需要 | |
--rm | 容器退出后自动删除 |
在容器里使用 GPU
宿主机装好 NVIDIA 驱动后,再安装 NVIDIA Container Toolkit,然后测试:
bash
docker run --rm --gpus all ubuntu nvidia-smi能看到显卡信息就说明配置成功。
写一个 Dockerfile
把自己的实验环境打包成镜像,换机器、交给别人复现都很方便:
dockerfile
FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
CMD ["python", "main.py"]bash
docker build -t my-exp:v1 .
docker run --rm my-exp:v1需要 GPU 的 PyTorch 环境,可以从 pytorch/pytorch 或 nvidia/cuda 的官方镜像开始构建。
镜像层
Dockerfile 中的 FROM 引入基础镜像的全部层,之后每条 RUN、COPY 一般会新增一层。不同镜像只要基础相同,就共享这些层,节点上已有的层不需要再下载。动手看一看:
bash
docker pull python:3.12-slim
docker pull python:3.13-slim # 输出中显示 Already exists 的层,就是和上一个镜像共享、不用再下载的层
docker history python:3.12-slim # 每一层由哪条命令产生、有多大
docker image inspect --format '{{json .RootFS.Layers}}' python:3.12-slim # 层的列表两个镜像要基于同一个版本的 Debian 才会共享基础层。同一个标签对应的 Debian 版本会随时间变化,例如现在 python:3.12-slim 基于 Debian 13(trixie),而 node:22-slim 仍基于 Debian 12(bookworm),两者没有共享层。换别的镜像做演示前,先用 docker image inspect 对比一下层的列表。
docker image inspect 列出的是解压后内容的摘要,和镜像仓库清单(manifest)中压缩层的摘要不是同一个值,但一一对应。L9 中的脚本直接从镜像仓库读取压缩层的摘要和大小,也就是实际要下载的量。
和研究的关系:边缘节点带宽小,下载一个几百 MB 的镜像可能比运行任务本身还慢。调度时考虑节点上已经有哪些层,可以大幅减少下载时间,这是课题组方向四的一条主线,见组内论文导读。
写 Dockerfile 时利用分层:把不常变的步骤(安装系统软件、安装依赖)放在前面,常变的步骤(复制代码)放在后面。改了代码重新构建时,前面的层直接用缓存,构建快,推送和拉取的数据量也小。
Docker Compose:一次启动多个服务
实验经常需要多个服务配合,例如模型服务加向量数据库。用 compose.yaml 描述它们:
yaml
services:
db:
image: pgvector/pgvector:pg17
environment:
POSTGRES_PASSWORD: change-me
ports:
- "5432:5432"
volumes:
- pgdata:/var/lib/postgresql/data
llm:
image: ollama/ollama
ports:
- "11434:11434"
volumes:
- ollama:/root/.ollama
volumes:
pgdata:
ollama:bash
docker compose up -d # 启动全部服务
docker compose logs -f # 查看日志
docker compose down # 停止并删除容器(数据卷保留)常见问题
- 拉取镜像超时:国内需要配置镜像加速,见网络加速。
permission denied ... docker.sock:没有加入 docker 组,或者加入后没有重新登录。- 端口被占用:换一个宿主机端口,例如
-p 15432:5432。 - 容器里改的文件不见了:容器删除后,没有挂载到数据卷或宿主机目录的修改都会丢失。