核心目标 让 4 个人(或 4 个任务)在一台有 4 块 GPU 的机器上,各自独立使用 Jupyter + conda + Pytorch + GPU,彼此互不干扰(文件、环境、GPU 都隔离)。
实现:
使用docker 构建一个标准化的镜像(conda、jupyter、pytorch、GPU支持…),然后用这个镜像启动4个独立的容器实例,每个容器实例分1块GPU、1个专属目录,指定内存和CPU的占用。
前期准备 网络 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 sudo cp /etc/resolv.conf /etc/resolv.conf.bak sudo vim /etc/resolv.conf search hpc.local. nameserver 192.168.0.165 nameserver 8.8.8.8 nameserver 114.114.114.114 ping -c 4 www.baidu.com ping -c 4 mirrors.aliyun.com
磁盘 1 2 3 4 5 6 7 8 9 10 11 mkfs.xfs -f /dev/sdb mkdir -p /data/dockerecho "/dev/sdb /data/docker xfs defaults 0 0" >> /etc/fstabmount -a df -h /data/docker
安装Nvidia驱动(视情况) https://www.nvidia.com/en-us/drivers/ 下载驱动
1 2 3 4 5 6 sudo nvidia-uninstall sudo reboot lspci | grep -i nvidia
应该选择的驱动类型是 ——「Linux 64-bit」版本(.run 文件)或者是RHEL 的版本的,不是 Ubuntu/RedHat/其他包格式)
因为:
CentOS 7 属于 RHEL 系 Linux
NVIDIA 不再发布 CentOS 7 专用 RPM (CentOS7 已 EOL,官方移除了)
所以官网只提供 Runfile(Linux 64-bit)
这个 run 文件是 跨发行版通用的 installer ,支持:
CentOS 7
RHEL 7
Fedora
Ubuntu
openSUSE
其他 Linux,只要是 x86_64
这是 NVIDIA 官方文档明确说明的。
但是如果没有你要找的版本的话,centos7 对应的还可以是RHEL 的版本,但是我用的是https://www.nvidia.com/en-us/drivers/details/214092/ Data Center Driver for Linux x64 525.147.05 | Linux 64-bit 选的是 12.0的cuda支持。
centos7最高只支持到 cuda11.8 ,所以我使用的是支持到cuda12.0的Nvidia驱动。
1 2 3 4 scp ./NVIDIA-Linux-x86_64-525.147.05.run root@172.26.1.20:/data/. sudo bash NVIDIA-Linux-x86_64-525.147.05.run
安装cuda 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 cat /etc/os-releaseuname -mwget https://developer.download.nvidia.com/compute/cuda/11.8.0/local_installers/cuda_11.8.0_520.61.05_linux.run sudo sh cuda_11.8.0_520.61.05_linux.run echo 'export PATH=/usr/local/cuda-11.8/bin:$PATH' | sudo tee -a /etc/profileecho 'export LD_LIBRARY_PATH=/usr/local/cuda-11.8/lib64:$LD_LIBRARY_PATH' | sudo tee -a /etc/profilesource /etc/profilenvcc --version [root@g01n01 local ] nvcc: NVIDIA (R) Cuda compiler driver Copyright (c) 2005-2022 NVIDIA Corporation Built on Wed_Sep_21_10:33:58_PDT_2022 Cuda compilation tools, release 11.8, V11.8.89 Build cuda_11.8.r11.8/compiler.31833905_0
centos装docker 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 sudo yum install -y yum-utils sudo yum-config-manager \ --add-repo \ http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo sudo yum install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin sudo systemctl start docker sudo systemctl enable docker sudo vi /etc/docker/daemon.json { "data-root" : "/data/docker" , "registry-mirrors" : ["https://docker.m.daocloud.io" ,"https://hub.daocloud.io" ] } sudo systemctl daemon-reload sudo systemctl restart docker docker -v docker info
配置 Docker 自定义根目录 Docker 默认把所有数据存在 /var/lib/docker。我们要改成 /data/docker。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 systemctl stop docker cat > /etc/docker/daemon.json <<EOF { "data-root": "/data/docker" } EOF systemctl restart docker docker info | grep "Docker Root Dir" docker pull busybox docker pull nvidia/cuda:12.0.1-base-ubuntu22.04
从此以后,所有镜像、容器、卷、构建缓存 都会存到 13.1TB 盘上!
(CentOS / RHEL 官方方法)
添加 NVIDIA repo:
1 2 3 distribution=$(. /etc/os-release;echo $ID$VERSION_ID ) sudo yum-config-manager --add-repo \ https://nvidia.github.io/nvidia-container-runtime/$distribution /nvidia-container-runtime.repo
安装:
1 sudo yum install -y nvidia-container-toolkit
配置 Docker 使其支持 GPU:
1 sudo nvidia-ctk runtime configure --runtime =docker
它会自动写入 /etc/docker/daemon.json:
例如:
1 2 3 4 5 6 7 8 { "runtimes" : { "nvidia" : { "path" : "nvidia-container-runtime" , "runtimeArgs" : [] } } }
重启 Docker:
1 sudo systemctl restart docker
测试:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 docker pull nvidia/cuda:12.0.1-devel-ubuntu22.04 docker run --rm --gpus all nvidia/cuda:12.0.1-devel-ubuntu22.04 nvidia-smi docker run --rm --gpus all nvidia/cuda:12.0.1-devel-ubuntu22.04 nvcc --version docker run --rm --gpus all nvidia/cuda:12.0.1-devel-ubuntu22.04 /bin/bash -c "\ echo '#include <stdio.h> #include <cuda_runtime.h> __global__ void add(int *a, int *b, int *c, int N) { int i = blockIdx.x * blockDim.x + threadIdx.x; if(i < N) c[i] = a[i] + b[i]; } int main() { int N = 10; int a[N], b[N], c[N]; for(int i=0;i<N;i++){ a[i]=i; b[i]=i*2; } int *d_a, *d_b, *d_c; cudaMalloc(&d_a, N*sizeof(int)); cudaMalloc(&d_b, N*sizeof(int)); cudaMalloc(&d_c, N*sizeof(int)); cudaMemcpy(d_a, a, N*sizeof(int), cudaMemcpyHostToDevice); cudaMemcpy(d_b, b, N*sizeof(int), cudaMemcpyHostToDevice); add<<<1, N>>>(d_a, d_b, d_c, N); cudaMemcpy(c, d_c, N*sizeof(int), cudaMemcpyDeviceToHost); printf(\"Vector addition result: \"); for(int i=0;i<N;i++) printf(\"%d \", c[i]); printf(\"\\n\"); cudaFree(d_a); cudaFree(d_b); cudaFree(d_c); int nDevices; cudaGetDeviceCount(&nDevices); printf(\"Number of CUDA devices: %d\\n\", nDevices); return 0; }' > test_vector.cu && \ nvcc test_vector.cu -o test_vector && ./test_vector"
构建镜像 以下操作都在 宿主机的 /data/docker/gpu-lab/ 文件夹下进行;
至于这个文件夹在哪里没有大关系,我仅仅是因为14T的硬盘被挂载在 /data/docker,空间比价大,所以gpu-lab这个文件夹丢在这里。
创建启动脚本 startJupyter.sh 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 #!/bin/bash if [ -z "$USER_PASSWORD " ]; then echo "Error: USER_PASSWORD environment variable is not set." exit 1 fi PASSWORD_HASH=$(python -c "from jupyter_server.auth import passwd; print(passwd('$USER_PASSWORD '))" ) echo "Starting Jupyter Lab with user-defined password..." exec jupyter lab \ --ip=0.0.0.0 \ --port=8888 \ --no-browser \ --allow-root \ --NotebookApp.token='' \ --NotebookApp.password="$PASSWORD_HASH "
以上的基础写法可以,但是会遇到一个问题,那就是每次 docker compose up -d 执行过后,配置好的容器中的 conda环境变成 初始状态。如果是给学生使用,会反复配置conda 环境。
这是因为 【容器分层存储机制】
┌─────────────────────────────────┐ │ Docker 镜像 (my-gpu-lab:v1) │ ← 只读层(初始 conda 环境) ├─────────────────────────────────┤ │ 容器可写层 (writable layer) │ ← 你手动 conda install 的位置 │ • 新安装的包存在这里 │ │ • 容器删除/重建 → 这层消失! │ ├─────────────────────────────────┤ │ 挂载卷 (volumes) │ ← 唯一持久化区域 │ • /data/gpu-lab/user1:/workspace │ ✅ 你已挂载 │ • /opt/conda/envs ❌ 未挂载 │ ← 问题根源! └─────────────────────────────────┘
为什么第二次 docker compose up -d 会丢失环境?
操作
是否重建容器
可写层是否保留
Conda 环境状态
首次 up -d
创建新容器
✅ 新建
镜像初始状态
手动 conda install
-
✅ 写入可写层
✅ 临时生效
再次 up -d(无变化)
❌ 复用容器
✅ 保留
✅ 还在
up -d --force-recreate
✅ 重建容器
❌ 删除重建
❌ 变回镜像
镜像更新/重新 build
✅ 重建容器
❌ 删除重建
❌ 变回新镜像
💡 关键结论 :只要容器重建,所有未挂载目录的修改都会丢失 。
所以我们改进一下,考虑到后面的Dockerfile中我们会在容器的/workspace/做持久化,我们把conda 的相关文件夹在/workspace/也做上持久化;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 #!/bin/bash export CONDARC=/workspace/.condarcmkdir -p /workspace/conda_envsmkdir -p /workspace/.conda_pkgsconda config --append envs_dirs /workspace/conda_envs conda config --append pkgs_dirs /workspace/.conda_pkgs conda config --set auto_activate_base false if [ -z "$USER_PASSWORD " ]; then echo "Error: USER_PASSWORD environment variable is not set." exit 1 fi PASSWORD_HASH=$(python -c "from jupyter_server.auth import passwd; print(passwd('$USER_PASSWORD '))" ) echo "Starting Jupyter Lab with user-defined password..." exec jupyter lab \ --ip=0.0.0.0 \ --port=8888 \ --no-browser \ --allow-root \ --NotebookApp.token='' \ --NotebookApp.password="$PASSWORD_HASH "
这样,用户配置好的 conda 环境就能在 docker compose up -d之后也保留原来的环境。
编辑 Dockerfile 查看自己的nvidia-smi 选好自己需要的pytorch 版本。
打开 https://hub.docker.com/
搜索 pytorch,查找 pytorch 的docker镜像,点击 Tags 选版本(cuda11.8)。火得一批。
因为:(PyTorch 2.6 基本只提供 CUDA 12.1+ 镜像)
所以:我们选老一点的 镜像版本。
1 2 docker pull pytorch/pytorch:2.3.0-cuda11.8-cudnn8-devel
Dockerfile
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 FROM pytorch/pytorch:2.3.0-cuda11.8-cudnn8-devel WORKDIR /workspace ENV DEBIAN_FRONTEND=noninteractive RUN apt-get update && apt-get install -y \ git \ curl \ vim \ && rm -rf /var/lib/apt/lists/* RUN pip install jupyterlab matplotlib pandas EXPOSE 8888 COPY startJupyter.sh /usr/local/bin/startJupyter.sh RUN chmod +x /usr/local/bin/startJupyter.sh CMD ["/usr/local/bin/startJupyter.sh" ]
重新构建镜像 就是把 Dockerfile 所在文件夹下的文件 构建成一个 镜像 my-gpu-lab:v1。
1 2 3 4 5 6 7 8 9 docker build -t my-gpu-lab:v1 . [root@g01n01 gpu-lab]$ docker images REPOSITORY TAG IMAGE ID CREATED SIZE my-gpu-lab v1 9dc6a356894f 3 hours ago 18.2GB busybox latest 08ef35a1c3f0 14 months ago 4.43MB nvidia/cuda 12.0.1-devel-ubuntu22.04 678edd19cf8f 2 years ago 7.34GB nvidia/cuda 12.0.1-base-ubuntu22.04 c41603a9ca98 2 years ago 237MB
规划资源 规划宿主机目录(文件隔离) 1 2 3 4 5 6 7 8 9 10 11 cd /data/gpu-labsudo mkdir -p /data/gpu-lab/user1 sudo mkdir -p /data/gpu-lab/user2 sudo mkdir -p /data/gpu-lab/user3 sudo mkdir -p /data/gpu-lab/user4 sudo chmod -R 777 /data/gpu-lab
限制每个容器的内存及CPU使用 Docker 原生支持资源限制,非常简单。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 [root@g01n01 gpu-lab] total used free shared buff/cache available Mem: 502G 17G 453G 44M 31G 483G Swap: 0B 0B 0B [root@g01n01 gpu-lab] Architecture: x86_64 CPU op-mode(s): 32-bit, 64-bit Byte Order: Little Endian CPU(s): 48 On-line CPU(s) list: 0-47 Thread(s) per core: 1 Core(s) per socket: 24 Socket(s): 2 NUMA node(s): 2 Vendor ID: GenuineIntel CPU family: 6 Model: 85 Model name: Intel(R) Xeon(R) Gold 5220R CPU @ 2.20GHz
修改你之前的 docker-compose.yml,为每个服务加上 mem_limit:502G 内存,4 个用户,每个分配 64G~96G 比较合理,留一些给系统。
编辑docker-compose.yml文件 1 2 docker compose version Docker Compose version v2.27.1
根据这个docker compose 版本让大模型生成yaml文件。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 version: '3.8' services: user1: image: my-gpu-lab:v1 container_name: lab-user1 restart: always ports: - "8881:8888" volumes: - /data/docker/gpu-lab/user1:/workspace environment: - USER_PASSWORD=User1_Is_The_Best shm_size: "8gb" deploy: resources: limits: cpus: '11' memory: 96G reservations: devices: - driver: nvidia device_ids: ['0' ] capabilities: ['gpu' ] user2: image: my-gpu-lab:v1 container_name: lab-user2 restart: always ports: - "8882:8888" volumes: - /data/docker/gpu-lab/user2:/workspace environment: - USER_PASSWORD=User2_Is_The_Best shm_size: "8gb" deploy: resources: limits: cpus: '11' memory: 96G reservations: devices: - driver: nvidia device_ids: ['1' ] capabilities: ['gpu' ] user3: image: my-gpu-lab:v1 container_name: lab-user3 restart: always ports: - "8883:8888" volumes: - /data/docker/gpu-lab/user3:/workspace environment: - USER_PASSWORD=User3_Is_The_Best shm_size: "8gb" deploy: resources: limits: cpus: '11' memory: 96G reservations: devices: - driver: nvidia device_ids: ['2' ] capabilities: ['gpu' ] user4: image: my-gpu-lab:v1 container_name: lab-user4 restart: always ports: - "8884:8888" volumes: - /data/docker/gpu-lab/user4:/workspace environment: - USER_PASSWORD=User4_Is_The_Best shm_size: "8gb" deploy: resources: limits: cpus: '11' memory: 96G reservations: devices: - driver: nvidia device_ids: ['3' ] capabilities: ['gpu' ]
启动容器
进来以后,会发现 这个 jupyter 的页面中的 terminal 和宿主机的会有不一样。
1 2 3 4 5 6 7 8 9 /workspace Untitled.ipynb base /opt/conda py3.10 /opt/conda/envs/py3.10
我们需要先执行初始化conda,才能用conda。
1 2 3 4 5 . /opt/conda/etc/profile.d/conda.sh conda activate py3.10
如果一样要让在jupyter的web页面可选 新环境,和以前操作一样。
1 2 3 4 5 6 7 8 9 10 11 conda activate XXX conda install ipykernel python -m ipykernel install --user --name py3.10 --display-name "Py3.10 (conda)" jupyter kernelspec uninstall python3.7 jupyter kernelspec list
重启其中一个容器服务:
1 2 3 4 5 6 7 8 9 docker compose stop user3 docker compose up -d --no-deps --force-recreate user3
传输文件 1、通过 jupyter web页面 拖拽 ;
2、# 在您的本地电脑上运行 :
scp -P 22 /path/to/local/my_data.zip username@172.26.1.20:/data/gpu-lab/user4/
传输完成后,文件会立即出现在您 Jupyter Terminal 中的 /workspace 目录下。
单独修改某一个容器的配置 案例一: 问题:
【 我之前的第一版构建镜像的脚本中写的比较草率,导致了docker compose up -d之后,用户在容器中辛辛苦苦建好的conda 环境会被初始化。其中只有user1 这个容器是可以修改的,user234 容器都是正在运行计算,不方便停和改动。】
解决方法:
(核心改动)
我在 startJupyter.sh 脚本中修改了内容,做好了conda相关文件的持久化;
重建镜像、修改yaml文件并重启容器;
1 2 3 4 5 6 7 8 9 10 11 12 docker build -t my-gpu-lab:v2 . services: user1: image: my-gpu-lab:v2 docker compose up -d --force-recreate user1
3、(可选)
如果 user1 里已有重要环境,需要把它们“搬”到新目录,否则重启后旧环境会消失。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 docker exec -it lab-user1 bash cp -r /opt/conda/envs/* /workspace/conda_envs/chmod -R 755 /workspace/conda_envsexit docker compose up -d --force-recreate user1
4、验证,user1容器进入它,搞一个新的环境,然后 重新在物理机执行 docker compose up -d ,在进入 user1 容器看看有没有保留这个新环境;
这样做的好处: ✅ 无需大修 docker-compose.yml(不用动挂载配置,避免权限坑)。 ✅ 数据绝对安全(/workspace 本来就会持久化)。 ✅ 用户无感知(conda activate 环境名 依然可用)