Skip to content

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。
  • 容器里改的文件不见了:容器删除后,没有挂载到数据卷或宿主机目录的修改都会丢失。

作者:崔涵帅、姚智、王振胜、唐志清 · 边缘智能系统实验室