如果你有跨平台开发的需求,或者对每次在新机器上部署项目感到头疼,那么 Docker 是你的理想选择!Docker 通过容器化技术将应用程序与其运行环境隔离,实现快速部署和跨平台支持,极大地简化了开发和部署流程。本文详细介绍了 Docker 的核心组件、工作原理和基本操作流程,并结合算法模型部署的实际应用,以 CUDA 环境部署为例,深入讲解了如何利用 Docker 进行模型部署和调试,帮助你轻松应对复杂的开发与部署挑战。
1. Docker 简介
Docker 是一个用于开发、运输和运行应用程序的开源平台。它将应用程序与环境进行隔离,实现项目的快速部署上线,减少项目在环境部署上所花费的成本。
2. Docker 架构与工作流程
2.1 Docker 架构
Docker 的架构基于客户端-服务器模式,主要由 Docker 客户端和 Docker 守护进程两大核心组件构成。Docker 客户端是用户与系统交互的命令行界面(CLI),用户通过 CLI 发送命令(如构建镜像、启动容器等),这些命令会被传递到 Docker 守护进程(通常为 dockerd
)。守护进程作为 Docker 的核心组件,负责管理容器的生命周期、构建镜像、分发镜像等任务,并以后台进程的形式持续运行,监听并处理来自客户端的请求。这种架构使得 Docker 能够高效地构建、管理和运行容器化应用,为用户提供了灵活且强大的开发与部署体验。
2.2 Docker 架构的工作流程
- 构建镜像:使用
Dockerfile
创建镜像。 - 推送镜像到注册表:将镜像上传到 Docker Hub 或私有注册表中。
- 拉取镜像:通过
docker pull
从注册表中拉取镜像。 - 运行容器:使用镜像创建并启动容器。
- 管理容器:使用 Docker 客户端命令管理正在运行的容器(例如查看日志、停止容器、查看资源使用情况等)。
- 网络与存储:容器之间通过 Docker 网络连接,数据通过 Docker 卷或绑定挂载进行持久化。
3. Docker 的主要特点
-
容器化技术:
-
镜像管理:
- 镜像构建:通过 Dockerfile 定义应用程序的运行环境和依赖,然后构建为镜像。
- 镜像分发:镜像可以存储在本地或远程仓库(如 Docker Hub),方便分发和共享。
-
容器管理:
-
跨平台支持:
- Docker 支持多种操作系统,包括 Linux、Windows 和 macOS,能够确保应用程序在不同环境中的一致性。
4. Docker 的工作原理
- Docker 守护进程(Docker Daemon) :守护进程是 Docker 的核心组件,负责管理镜像、容器、网络和存储卷等资源。
- Docker 客户端(Docker CLI) :用户通过命令行工具与守护进程交互,执行各种操作,如拉取镜像、运行容器等。
- Docker 镜像:镜像是只读模板,包含运行容器所需的文件系统和依赖。镜像由多层组成,每一层代表一次构建操作。
- Docker 容器:容器是镜像的运行实例,具有独立的文件系统、网络接口和资源限制。容器通过联合文件系统(Union File System)在镜像的基础上添加可读写层。
- Docker 仓库:用于存储和分发镜像。Docker Hub 是官方的公共仓库,用户也可以搭建私有仓库。
5. Docker 容器与镜像之间的关系
5.1 定义
- 镜像(Image) :Docker 镜像是一个只读模板,它包含了运行一个容器所需的文件系统、操作系统、应用程序及其依赖项。镜像是静态的,不可修改,是容器运行的基础。
- 容器(Container) :容器是从镜像创建的运行实例。它是镜像的动态运行状态,可以启动、停止、删除等。容器在镜像的基础上添加了可读写层,用于存储运行时产生的临时数据。
5.2 关系
- 镜像是容器的模板:容器是基于镜像创建的。镜像定义了容器的初始状态,包括文件系统、操作系统环境、预安装的软件和配置等。
- 容器是镜像的运行实例:容器是镜像的动态运行版本。当启动一个容器时,Docker 会在镜像的基础上添加一个可读写层,用于存储容器运行时产生的临时数据。
- 镜像的不可变性与容器的可变性:镜像是不可变的,一旦创建,其内容不会改变。而容器是可变的,用户可以在容器中安装软件、修改配置等。
- 容器的创建依赖于镜像:没有镜像,容器无法被创建。用户可以通过拉取公共镜像仓库中的镜像,或者使用 Dockerfile 构建自定义镜像来创建容器。
- 容器的销毁不影响镜像:删除容器后,镜像仍然存在。用户可以基于同一个镜像创建多个容器,每个容器都是独立的运行实例。
5.3 总结
镜像是容器的静态模板,定义了容器的初始状态;容器是镜像的动态运行实例,具有独立的生命周期。镜像为容器提供了运行环境和基础结构,而容器则在镜像的基础上实现了应用程序的运行和管理。
6. Docker 基本操作流程
6.1 创建容器
通过上面,我们知道,容器时基于镜像创建的,因此,根据本地有没有镜像,创建容器的方式可以有这样三种,若本地有可用镜像,则可通过镜像直接创建容器;若本地没有可用镜像,则需先拉取镜像,在通过相应操作创建容器;若需要基于某个镜像自定义镜像并创建容器,则需先拉取基础镜像,然后定义Dockerfile文件,基于基础镜像创建自定义镜像,并通过相应操作创建容器。具体操作过程如下:
6.1.1 直接创建容器
docker run [OPTIONS] IMAGE [COMMAND] [ARG...]
- OPTIONS:可选参数,用于配置容器的各种属性,比如指定容器名称、端口映射、卷挂载等。
- IMAGE:指定用于创建容器的镜像名称或镜像 ID。
- COMMAND 和 ARG:可选,用于在容器启动时运行的命令及其参数。
示例:
-
从官方镜像创建一个简单的容器:
docker run -d nginx
-
指定容器名称:
docker run --name my-container -d nginx
-
端口映射:
docker run -p 8080:80 -d nginx
-
挂载卷:
docker run -v /host/path:/container/path -d nginx
-
设置环境变量:
docker run -e VAR_NAME=value -d my-image
6.1.2 通过镜像创建容器
6.1.2.1 拉取镜像
docker pull [镜像名称]:[标签]
示例:
docker pull nginx:latest
6.1.2.2 使用镜像创建容器
docker run [选项] [镜像名称]:[标签]
以下是一些常用的命令选项:
-
--name
:为容器指定一个名称,方便后续管理。 -
-p
:端口映射,将容器内部的端口映射到宿主机的端口。- 格式:
宿主机端口:容器内部端口
。
- 格式:
-
-v
:挂载卷,将宿主机的目录或文件挂载到容器内部。- 格式:
宿主机路径:容器内部路径
。
- 格式:
-
-e
:设置环境变量。 -
--network
:指定容器使用的网络。
示例:
docker run -d --name my-nginx -p 8080:80 nginx:latest
-
-d
:后台运行。 -
--name my-nginx
:容器名称为my-nginx
。 -
-p 8080:80
:将容器内部的 80 端口映射到宿主机的 8080 端口。 -
nginx:latest
:使用nginx
镜像的latest
版本。
6.1.3 通过 Dockerfile 构建自定义镜像并创建容器
-
拉取基础镜像:
docker pull python:3.9-slim
-
编写 Dockerfile:
dockerfile">FROM python:3.9-slim WORKDIR /app COPY . /app RUN pip install -r requirements.txt CMD ["python", "app.py"]
-
构建自定义镜像:
docker build -t my-custom-python-app:v1 .
-
基于自定义镜像创建容器:
docker run -d --name my-python-container my-custom-python-app:v1
6.2 容器基本操作
6.2.1 启动容器
docker start [容器名称或ID]
示例:
docker start my-container
6.2.2 进入容器
docker exec -it [容器名称或ID] /bin/bash
常见命令选型说明:
-
-it
:这两个参数组合起来表示以交互模式进入容器。-
-i
:表示交互式,保持 STDIN 打开。 -
-t
:分配一个伪终端。
-
-
/bin/bash
:指定在容器内部启动的命令,通常是/bin/bash
或/bin/sh
,用于进入 shell。
示例:
docker exec -it my-container /bin/bash
6.2.3 退出容器
docker stop [容器名称或ID]
示例:
docker stop my-container
7. 模型部署与 Docker
7.1 基于 Dockerfile 构建环境
基于Dockerfile创建虚拟开发环境的基本流程如下:
# 拉取依赖镜像
docker pull nvidia/cuda:12.3.2-cudnn9-devel-ubuntu22.04
# 基于 Dockerfile 文件构建新镜像
docker build -t cuda-cudnn:12.3.2 .
# 基于镜像生成对应容器
docker run --name cuda-12.3.2 -it --gpus all cuda-cudnn:12.3.2
# 检查容器是否生成
docker ps -a
# 启动容器
docker start cuda-12.3.2
# 进入容器
docker exec -it cuda-12.3.2 /bin/bash
# 停止容器
docker stop cuda-12.3.2
7.2 基于 Dockerfile 构建环境,并通过挂载方式实现本地开发容器环境项目
我们创建容器的目的是,在容器中进行项目的开发和测试,因此,在上述容器创建过程的基础上,还需通过挂载的方式,将本地项目映射到容器中去,这样,就可以实现在宿主机(主机中开发),在容器中编译和测试了。具体流程如下:
先在本地创建项目,项目中除了项目代码外,还需包括Dockerfile文件
cuda_test/
├── Dockerfile
├── print_index.cu
└── CMakeLists.txt
进入到项目目录下,通过docker创建项目环境。
-
拉取镜像:
docker pull nvidia/cuda:12.3.2-cudnn9-devel-ubuntu22.04
-
构建新镜像:
docker build -t cuda-cudnn:12.3.2 .
-
生成容器并挂载本地路径:
docker run --name cuda_test -it --gpus all -v /mnt/e/Code/Cuda_test:/workspace cuda-cudnn:12.3.2 /bin/bash
通过上述操作完成了项目环境的创建,然后进入宿主机进行项目开发,完成编码后,启动镜像进行编译。
#启动容器(若容器停止执行)
docker start cuda_test
#进入容器
docker exec -it cuda_test /bin/bash
#进行编译
ls
mkdir build && cd build
cmake ..
make
./you_project
7.3 挂载到容器的项目在容器中进行 Debug
我们在开发的过程中还需要deubg代码,下面是通过vscode配置deocker容器debug的过程。
-
创建可进行 Debug 的容器:
docker run --name cuda_test -it --gpus all --cap-add=SYS_PTRACE --security-opt seccomp=unconfined -v /mnt/e/Code/Cuda_test:/workspace cuda-cudnn:12.3.2 /bin/bash
-
在本地项目中创建 VSCode 的
launch.json
文件:{ "version": "0.2.0", "configurations": [ { "name": "Docker_debug", "type": "cppdbg", "request": "launch", "program": "/workspace/you_path", //可执行文件路径 "args": [], "stopAtEntry": false, "cwd": "/workspace/you_path", //命令行路径 "environment": [], "externalConsole": false, "sourceFileMap": { "/workspace/you_path": "${workspaceFolder}/you_path" //源码在容器中和工作空间的路径对应关系 }, "pipeTransport": { "pipeProgram": "docker", "pipeArgs": [ "exec", "-i", "container_name", //容器名称 "/usr/bin/gdb", "--interpreter=mi" ], "debuggerPath": "/usr/bin/gdb", "pipeCwd": "${workspaceFolder}" }, "MIMode": "gdb", "setupCommands": [ { "description": "Enable pretty-printing for gdb", "text": "-enable-pretty-printing", "ignoreFailures": true } ] } ] }
配置好launch.json文件后,即可在vscode中进行debug。