Docker
这里对于docker的基本概念不过多补充, 主要是个人在使用 docker 过程中的常用场景和命令。需要提前了解下 镜像 (image) 、容器(container) 以及 仓库(Repository) 的概念。
安装docker
根据官方 Install Docker Engine和Install Docker Desktop 介绍选择自己的主机安装即可。一般流程是先卸载旧版的Docker Engine然后安装。并且安装过程中需要 root 权限。而在使用的时候一般是普通用户权限,因此很容易遇到错误提示:
1 | |
解决方案参考https://docs.docker.com/engine/install/linux-postinstall/ :通过创建一个 docker 用户组,并将普通用户加入到 docker 用户组即可。
下载docker镜像
一般是去docker官网 搜索,然后通过下载镜像命令拉取到本地:
1 | |
为了能够在容器中使用GPU设备,Nvidia官方提供了一些常用镜像:
- nvidia/cuda:xx.x.x-base-ubuntu xx.xx:包含了部署预构建
CUDA应用程序的最低限度(libcudart)配置。 - nvidia/cuda:xx.x.x-runtime-ubuntu xx.xx:基于
base-ubuntu添加了CUDA工具包中的所有共享库扩展包。 - nvidia/cuda:xx.x.x-devel-ubuntu xx.xx:基于
runtime-ubuntu添加编译器工具链、调试工具、头文件和静态库的扩展,最直观的感受就是能够使用nvcc命令了。 - nvcr.io/nvidia/pytorch:xx.xx-py3:是
NVIDIA提供的一个Pytorch的Docker镜像。其中包含了Pytorch以及GPU相关的库,xx.xx表示版本,py3表示内置使用python3。
除此之外,还有:
- lmsysorg/sglang:xxx:
sglang框架的官方镜像,最常用的版本是开发版(dev)、最新版(latest)。 - vllm/vllm-openai:xxx:
vllm框架镜像,直接根据版本号查。
docker常用命令&场景
常用images命令
1 | |
基于镜像运行docker容器
语法说明
语法可以通过 docker run --help 查看:
1 | |
常用OPTIONS:
-itd:-it交互式终端,-d在后台运行容器,输入exit时容器不关闭;--name <container-name>:设置容器名;--gpus all:指定在容器中使用所有GPU,也可以执行某一个GPU设备(如--gpus "device=3"),或者某些GPU设备(如--gpus "device=0,1,4")--shm-size <shared-memory-size>:设置共享CPU内存大小;-v <host-path>:<container-path>:目录挂载,将宿主主机上的容器挂载到容器中,可多次添加;常用挂载路径有:-v $HOME/.cache/modelscope/:/root/.cache/modelscope:宿主主机modelscope默认缓存路径;-v $HOME/.cache/huggingface/:/root/.cache/huggingface:宿主主机huggingface默认缓存路径;$HOME/.ssh:/root/.ssh:与宿主主机共享gitlab|github远程连接配置;
--network host:网络共享,使容器直接使用宿主机的网络;-p <host-ip>:<container-ip>:端口映射将宿主主机端口<host-ip>映射到容器主机端口<container-ip>;-e <cv-name>=<cv-value>环境变量:设置容器内环境变量<cv-name>的值为<cv-value>,可多次添加;--ipc=host:进程间通信的命名空间共享,允许容器内的进程与宿主机上的进程进行通信,共享IPC命名空间;--rm:容器停止后自动删除;--privileged=true:特权,给容器所有宿主主机的权限。--cap-add和--cap-drop: 更细粒度的管理容器的权限(后面有详细介绍)。
docker容器权限相关(Linux Capabilities)
注意,这个 Linux Capabilities 是 linux 系统的本身的机制,Docker 容器只是利用了这套机制给容器赋予不同的权限罢了。 具体的操作,我使用起来也不是很上手,最大的需求就是需要在容器中访问宿主主机的内核文件:
- 访问宿主主机内核驱动模块:
Nsight Systems是NVIDIA的性能分析工具,用于GPU性能监控和指标采集。在容器中运行时,它需要访问GPU设备及相关的内核驱动模块,进而采集GPU芯片上的一些硬件统计指标。 - 使用网络设备:
InfiniBand是一种高性能网络设备,容器需直接访问IB硬件(如网卡、设备文件)进行网络通讯。
加上对安全考虑没那么重要,直接使用 --cap-add CAP_SYS_ADMIN 权限足以,甚至 --privileged 权限即可。
Linux Capabilities 是 Linux 内核中一种细粒度的权限管理机制,由 Linux 内核在 2.2 版本引入,其目的是将传统的 root 用户的全部特权拆分为多个独立的能力(如 CAP_NET_ADMIN、CAP_SYS_ADMIN 等)。这一机制允许普通用户或进程在不完全获取 root 权限的情况下,执行特定的系统级操作(如绑定特权端口、修改网络配置等)。根据 Linux 系统内核版本的不同,其拆分为独立的 Capabilities 个数也不尽相同(在 Linux 5.x 版本的内核中将传统的 root 用户的全部特权拆分为38项独立的能力),而在 docker 中默认只会给其中一部分权限,可通过--cap-add 和 --cap-drop来增加和减少权限能力。当使用 --privileged 时,Docker 会继承宿主机所有 Capabilities 并开放设备访问权限 ,等同于赋予容器完整的 root 权限。
最常见的添加权限是 CAP_SYS_ADMIN ,这个属于高危操作(涉及系统管理操作),如:
- 挂载/卸载文件系统(如
mount、umount); - 创建或修改内核参数(如
sysctl); - 调整进程的
CPU亲和性(cpuset); - 管理
tmpfs或loop设备; - 更改内核模块或硬件配置,等。
使用方式如下:
1 | |
其实在 Linux 系统中,设备其实就是文件,在一定程度上通过精确挂载设备文件或目录可以避免使用 CAP_SYS_ADMIN 权限。但若需动态管理设备 或执行系统级操作 (如挂载文件系统、修改内核参数),则不可避免的需要 CAP_SYS_ADMIN 权限。
实际应用命令参考
调试sglang容器命令:
1 | |
常用容器命令
1 | |
虚悬镜像(dangling image)
docker image ls 列表中的镜像体积总和并非是所有镜像实际硬盘消耗。由于 Docker 镜像是多层存储结构,并且可以继承、复用,因此不同镜像可能会因为使用相同的基础镜像,从而拥有共同的层。基于Docker 使用 Union FS,相同的层只需要保存一份即可,因此实际镜像硬盘占用空间很可能要比这个列表镜像大小的总和要小的多。通过 docker system df 命令来便捷的查看镜像、容器、数据卷所占用的空间,如:
1 | |
其实在通过 docker images 查看的时候会发现存在镜像名(REPOSITORY)和标签(TAG)都是 <none> 的镜像,这种就是虚悬镜像(dangling image),可通过命令 docker image ls -f dangling=true 查看:
1 | |
这个其实会占用磁盘空间,删除全部虚悬镜像用特定的命令:
1 | |
基于这个理论,也存在 dangling container、dangling network、dangling volume等等。可以通过命令删除这些占用的磁盘空间(谨慎操作!!!)
1 | |
构建镜像(docker build & Dockerfile)
在日常开发中,一般需要自己构建 (build) 镜像(image),自己构建镜像可以安装特定版本的CUDA、软件、脚本等等。而构建镜像的过程就是基于Dockerfile的。
注意:这里的Dockerfile构建语法简单,如果想要优化Docker镜像大小和性能,可以考虑 多阶段构建 等方案。
Dockerfile语法
详细内容可以参考docker-practice,常用 Dockerfile 语法有:
FROM <base-image>:<tag>:指定基础镜像,告诉Docker要基于哪个现有的镜像开始构建;WORKDIR /path/to/workdir:设置工作目录,后续指令都会在这个目录下执行。如果目录不存在,WORKDIR会自动创建;RUN <command>:在镜像内部执行命令,每条RUN指令都会在当前镜像的基础上创建一个新的层。为了减少镜像层数和体积,加快构建速度,通常把多个apt-get install或者pip install命令用&&连在一条RUN指令里;COPY <host-path> <container-path>:将宿主机的文件或目录复制到镜像内的指定路径;CMD ["command", "param1", "param2"]:指定容器启动时默认执行的命令。还可写成CMD command param1 param2 (shell form)。注意,一个Dockerfile里只能有一条CMD指令,如果有多条,只有最后一条生效。如果在docker run时指定了命令,那么CMD的命令会被覆盖;ENV <key>=<value>:设置容器运行时的环境变量,镜像构建时不生效;ARG <key>=<value>:设置镜像构建时的临时变量,容器运行时不生效;ENTRYPOINT ["command", "param1", "param2"]:定义容器启动时默认执行的主命令,可以在启动命令docker run --entrypoint /bin/bash指定显式覆盖掉启默认主命令为/bin/bash;
常见 Dockerfile 编写参考
安装 python3.10+
ubuntu20.04远程仓库软件对应的python默认是3.8版本,而新的开发程序需要Python3.10+版本或者其他版本。就可以参考:
1 | |
或者源码编译编译安装:
1 | |
通过 https://bootstrap.pypa.io/get-pip.py获取的脚本的主要功能是创建一个临时目录,将存储旧版本的 pip 文件编码 DATA转换后保存到该临时目录中,以生成一个 pip 程序。通过 bootstrap 函数在该临时目录中执行 pip install 命令,从而升级并重新安装最新的 pip 及用户指定的其他包。主要执行逻辑介绍:
- 首先,从
sys模块获取当前Python版本,并与最低要求的版本进行比较。如果当前版本低于要求,则输出错误信息并终止程序。 - 定义两个辅助函数
include_setuptools和include_wheel,用于判断是否需要安装setuptools和wheel包。 - 通过
determine_pip_install_arguments函数构建一个argparse对象来处理用户的命令行选项,这里可以通过添加国内源args.extend(["--index-url","https://pypi.tuna.tsinghua.edu.cn/simple"])来加速这一个下载过程。
Docker Build 时使用SSH私钥鉴权
在实际工作的时候,通过 Docker Build 镜像时,需要在Docker 镜像中用到 SSH Private Key 的场景。最常见的就是 Clone gitlab 私有仓库代码。如果直接将鉴权SSH Private Key打包到Docker镜像中,会存在很大风险。基于此,常用方案:
- 多阶段构建:在多阶段构建过程中,只要私钥不出现在最后一个阶段,都是比较安全的。
SSH mount type:Docker在18.09版本后提出的新特性,具体参考:SSH agent socket or keys to expose to the build (–ssh)。
这里参考第二种使用SSH mount type方案:
- 首先,需要在 Dockerfile 首行开启特性:
1
# syntax=docker/dockerfile:1 - 然后添加下面的内容,下载对应网站的公钥
1
2# 注意这个域名,这里使用 gitlab
RUN mkdir -p -m 0700 ~/.ssh && ssh-keyscan gitlab.com >> ~/.ssh/known_hosts - 然后在
Dockerfile需要使用SSH Private Key鉴权的命令前加上--mount=type=ssh:1
RUN --mount=type=ssh git clone xxx.xxx.git@gitlab.com - 最后在
docker build时通过--ssh指定私钥:1
docker build -f Dockerfile -t <image-name>:<tag> . --ssh default=/root/.ssh/id_rsa
其他//todo
1 | |
构建镜像(docker build)
在命令行构建指令 docker build 使用可以通过docker build --help 查看,这里列举最常用的:
1 | |
说明:
--build-arg <arg-key>=<arg-value>:构建镜像时候传给Dockerfile中的参数,可设置多个:--build-arg <arg-key1>=<arg-value1> --build-arg <arg-key2>=<arg-value>2 ...-t <image-name>:<tag>:镜像的名字和标签;-f <docker-file>:Dockerfile的路径;<path>:指定Docker构建镜像时的构建上下文路径,即Docker可以访问的文件和目录的根路径。它决定了Dockerfile中文件引用(如COPY、ADD)的查找范围。通常设为.表示当前目录,但也可以是其他本地目录或Git仓库URL;--no-cache:构建过程中不使用缓存;--progress=plain:表示构建进度的输出模式(有的版本没这个参数),有auto,plain,tty;
构建调试
调试的原理主要是参考 Docker build cache: Docker 采用分层文件系统架构( UnionFS,联合文件系统),每个 Dockerfile 指令在执行后都会生成独立的只读镜像层( 可以通过 docker history <image_name> 来查看这些镜像层 )。这些层通过联合挂载形成最终镜像的文件系统。在构建过程中,所有中间层均会被保留作为缓存(除非显式使用 --squash 参数或手动清理缓存),且每个层都可作为临时镜像用于调试。通过指定构建阶段(使用 --target 参数)或直接引用层的 SHA256 标识符,可启动对应的中间容器检查文件系统状态。这种分层机制实现了高效的存储复用和构建缓存,同时为镜像调试提供了精确的层级追溯能力。
如有 Dockerfile 文件内容:
1 | |
很明显第二个执行命令拼写错误(echo --> ceho),此时镜像构建日志如下:
1 | |
可以看到 Step 2/3是执行成功的,并且镜像是 2e7f2122aea9,因此可以基于这个去调试执行后续指令:
1 | |
镜像共享
镜像上传远程仓库
镜像构建好以后,我们可以把本地镜像 docker push 到远程镜像仓库,如 Docker Hub 等。
- 首先需要给镜像打标签:对于
Docker Hub,镜像名通常是<dockerhub-username>/<repo-name>:<tag>。 如果你在docker build时已经用了这个格式,那这步可以跳过。如果没用,或者你想推送到不同的仓库/用户下,就需要重新打标签。1
docker tag <source-image>:<tag> <target-image>:<tag> - 登录到镜像仓库:在
docker hub注册账号后,使用docker login在本地登录1
docker login - 推送镜像:
1
docker push <image-name>:<tag>
使用网络传输(保存到磁盘,并从磁盘导入镜像)
1 | |