如何使用Docker打包深度学习项目

对于没有深入了解或使用过Docker的人来说(比如我),Docker可能显得很复杂,经常容易混淆镜像、容器等概念,也弄不清其与虚拟机(Virtual Machine, VM)的区别。实际上,Docker 是一个非常好用的工具,特别是在做应用部署的时候。刚好,最近参加的几个算法比赛都要求使用Docker打包项目文件,借此机会我也好好学习了一下Docker技术的相关知识和使用方法,特此记录一下。

Docker 架构图

Docker和容器

结合Docker的官方教程以及各路大佬的讲解,可以知道Docker本身是一个容器引擎,用来管理和运行容器。那什么是容器呢?容器本质上就是加了隔离机制的进程而已,进程就不多说了,重点在于隔离上。举个例子,做过深度学习(Deep Learning, DL)研究的人都知道,安装Pytorch或Tensorflow的时候,最需要注意的就是匹配CUDA的版本。假设现在你需要同时运行两个DL项目,而这两个项目强依赖于不同的CUDA版本,这时候应该怎么办呢?引入容器就能优雅地解决这个问题,只需要利用Docker为不同的DL项目创建独立的容器,容器中包含项目运行所需的所有资源,如指定版本的CUDA,进而消除了跟其他项目之间依赖项的冲突,这里就体现了环境隔离的重要性。

镜像和容器

官方解释:镜像是一个只读模板,其中包含创建 Docker 容器的说明,而容器是镜像的可运行实例。说了好像没完全说,不过指出了镜像和容器之间的关系:容器是基于镜像构建的。其实,在理解的时候可以类比为C++里面的类、对象等概念。镜像对应类的定义,是静态的,包含了所需的程序、库、资源以及配置等,用来启动和创建容器。值得注意的是:镜像不包含任何动态数据,其内容在构建之后也不会被改变。另外,镜像支持多级构建,比如可以在别人提供的基础镜像上,加入更多依赖项,创建自己的镜像,就好比继承一个基类后定义一个新类。容器作为镜像的实例化对象,是动态的,每个容器运行一个独立的应用程序。因此,在利用Docker运行一个程序时:首先拉取或者创建一个镜像,将程序运行所需的资源全部“塞”进这个镜像,然后基于该镜像创建和启动容器,指定程序就可以在容器中运行。

Docker和虚拟机

最常听到的说法:Docker是一种轻量级、进程级虚拟机。对虚拟机的使用很多人可能并不陌生,比如利用VMware之类的软件在Windows上安装Ubuntu虚拟机。值得注意的是,虚拟机的核心是模拟,模拟CPU,内存和各种硬件,因此还需要在其上安装操作系统才能使用。而容器的核心是隔离,Docker并不会运行一个独立的操作系统,而只是给进程提供了一种假象,使进程运行得就好像在一个独立的操作系统上。这也是为什么Docker被称为进程级、轻量级的虚拟机。下表从资源消耗等四个维度对比了传统虚拟机和Docker容器虚拟化的差异。

传统虚拟机 Docker容器虚拟化
资源消耗 传统虚拟机要具备独立的虚拟CPU、内存、操作系统,如果有10个虚拟机,则需要安装10个操作系统,虚拟出来10个CPU、内存等,因此造成大量的资源浪费 容器可以运行相同的一个操作系统内核,让一个容器的消耗与一个进程一样
启动 虚拟机硬件需要初始化,需要启动时间 没有虚拟机硬件初始化过程,开箱即用
应用安装 需要大量的安装包及安装过程,如下载完整镜像也非常耗费时间 统一仓库拉取,结构简单,拉取速度快
故障 虚拟机内应用崩溃,理论上不会影响其它虚拟机以及上面运行的应用的,除非是硬件或者Hypervisor有Bug 因为共用内核,只靠cgroup隔离,应用之间的隔离是不如虚拟机彻底的,如果某个应用运行时导致内核崩溃,所有的容器都会崩溃

Docker打包DL项目

前面描述了Docker几个比较重要的概念,言归正传,实践中如何用Docker打包自己的DL项目呢?其实非常简单,下面对流程进行了大概梳理,(OS:Ubuntu 20.04)。

  • Step 1: 安装DockerNvidia-Docker

    参考官网教程即可,如果没有sudo权限,可以向管理员申请加入docker group,并取消sudo执行docker,后续命令执行就不需要sudo权限,管理员需要做如下配置:

    1
    2
    3
    4
    5
    $ sudo cat /etc/group | grep docker
    $ sudo groupadd docker
    $ sudo gpasswd -a ${USER} docker
    $ sudo chmod a+rw /var/run/docker.sock
    $ sudo systemctl restart docker
  • Step 2: 拉取基础镜像

    根据项目需要,从DockerHub挑选并拉取合适的基础镜像,以本人的实际项目为例,原始开发环境为:Pytorch 1.11.0 / CUDA 11.3 / CUDNN 8。 因此,在挑选基础镜像时尽可能得去匹配这些项,选好后拉取下来。

    1
    $ docker pull pytorch/pytorch:1.11.0-cuda11.3-cudnn8-runtime

    拉取下来后,查看已有镜像:

    1
    2
    3
    $ docker images
    REPOSITORY TAG IMAGE ID CREATED SIZE
    pytorch/pytorch 1.11.0-cuda11.3-cudnn8-runtime ca04e7f7c8e5 6 months ago 5.82GB

    测试镜像,可以看到正常打印指定显卡信息。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    $ docker run --rm --gpus '"device=3"' pytorch/pytorch:1.11.0-cuda11.3-cudnn8-runtime nvidia-smi
    Fri Sep 16 11:06:29 2022
    +-----------------------------------------------------------------------------+
    | NVIDIA-SMI 465.19.01 Driver Version: 465.19.01 CUDA Version: 11.3 |
    |-------------------------------+----------------------+----------------------+
    | GPU Name Persistence-M| Bus-Id Disp.A | Volatile Uncorr. ECC |
    | Fan Temp Perf Pwr:Usage/Cap| Memory-Usage | GPU-Util Compute M. |
    | | | MIG M. |
    |===============================+======================+======================|
    | 0 NVIDIA Graphics... On | 00000000:E1:00.0 Off | 0 |
    | N/A 30C P0 40W / 300W | 0MiB / 81251MiB | 0% Default |
    | | | Disabled |
    +-------------------------------+----------------------+----------------------+

    +-----------------------------------------------------------------------------+
    | Processes: |
    | GPU GI CI PID Type Process name GPU Memory |
    | ID ID Usage |
    |=============================================================================|
    | No running processes found |
    +-----------------------------------------------------------------------------+

  • Step 3: 创建自定义镜像

    通过编辑Dockerfile实现镜像定义。

    1
    2
    3
    4
    5
    6
    FROM pytorch/pytorch:1.11.0-cuda11.3-cudnn8-runtime
    ADD . /
    WORKDIR /code
    RUN pip install -r requirements.txt
    RUN python main.py
    # CMD ["python", "main.py"]

    From指定基础镜像,ADD . / 代表将当前目录所有文件添加到镜像的根目录下, WORKDIR指定默认的工作目录,即进入容器后的初始目录,RUN用来运行指定命令,如安装依赖包或者程序执行命令。注意:由于我选择的基础镜像已经有了conda,所以可以直接使用pip install安装依赖包,否则需要使用RUN自行安装conda或pip工具,RUN在执行多条命令时用&&连接。

    定义完成后,创建容器:

    1
    $ docker build -t IMAGE_NAME .

    这里的IMAGE_NAME为自定义镜像名(我用的package),重新检查镜像列表,会看到新的镜像已被创建。

    1
    2
    3
    4
    $ docker images
    REPOSITORY TAG IMAGE ID CREATED SIZE
    package latest a3a4e8f3bbbc 2 weeks ago 17.7GB
    pytorch/pytorch 1.11.0-cuda11.3-cudnn8-runtime ca04e7f7c8e5 6 months ago 5.82GB
  • Step 4: 启动容器

    按需配置显卡、内存等资源,启动容器,两种启动方式。

    交互式:CONTAINER_NAME为自定义容器名

    1
    2
    $ docker run --rm --name CONTAINER_NAME --shm-size 8G -it --gpus '"device=3"' package /bin/bash
    root@362c182ee05c:/code#

    非交互式:

    1
    $ docker run --rm --name CONTAINER_NAME --shm-size 8G --gpus '"device=3"' package

    --rm的作用是在容器程序执行完后自动删除该容器,不加该参数容器会一直存在。

    有时需要获取容器程序产生的数据,可以通过挂载数据卷的方式实现,将宿主机的目录映射到容器的指定位置,这样就可以将数据从容器中传输到宿主机。

    1
    $ docker run --rm --name CONTAINER_NAME --shm-size 8G --gpus '"device=3"' -v /YOUR_PATH_TO_OUTPUT:/code/output package
  • Step 5: 导出镜像

    容器测试完成后,将镜像导出用于发布或提交

    1
    $ docker save -o DOCKER_IMAGE.tar package:latest

    至此,镜像制作、打包就完成了。

一些重要的Docker命令

删除容器,需要先停止容器

1
2
$ docker ps -a
$ docker rm CONTAINER_ID

容器启动停止

1
2
$ docker stop CONTAINER_NAME
$ docker start CONTAINER_NAME

进入容器

1
$ docker exec -it CONTAINER_NAME /bin/bash

删除镜像,需要先停止该镜像实例化的容器

1
2
$ docker images
$ docker rmi IMAGE_ID

导入镜像

1
$ docker load -i DOCKER_IMAGE.tar

文件复制

1
$ docker cp /YOUR_PATH_TO_FILE CONTAINER_NAME:/

参考

如何通俗解释Docker是什么?

传统虚拟机与Docker虚拟化技术的对比

Docker打包深度学习项目

打赏
  • 版权声明: 本博客所有文章除特别声明外,著作权归作者所有。转载请注明出处!
  • Copyrights © 2022 Shi Jun
  • 访问人数: | 浏览次数:

请我喝杯咖啡吧~

支付宝
微信