nvidia-docker(GPU)和存储驱动(storage_driver)

时间:2024-03-05 12:31:02

一、docker使用nvidia  GPU

1、nvidia-docker2

nvidia-docker2是对docker的封装,提供一些必要的组件可以很方便的在容器中用GPU资源执行代码,nvidia-docker共享了宿主机的CUDA Driver

2、nvidia-container-toolkit

最新版的nvidia-docker就是nvidia-container-toolkit,比nvidia-docker2更加优秀

官方的解释是"Usage of nvidia-docker2 packages are deprecated since NVIDIA GPUs are now natively supported as devices in the Docker runtime"。(机翻:不赞成使用nvidia-docker2包,因为nvidia GPU现在在Docker运行时本机作为可支持设备)

nvidia-container-toolkit需要主机已安装当前新版的docker 19.03

使用nvidia-container-toolkit的最大优点:

linux主机不需要安装cuda和cudnn,仅安装显卡驱动即可(tensorflow和nvidia-docker官方均强调这一点)

安装nvidia-container-toolkit

curl -fsSL https://nvidia.github.io/nvidia-docker/gpgkey | sudo apt-key add -
distribution=$(. /etc/os-release;echo $ID$VERSION_ID)
curl -s -L https://nvidia.github.io/nvidia-docker/$distribution/nvidia-docker.list | sudo tee /etc/apt/sources.list.d/nvidia-docker.list
sudo apt-get update

sudo apt-get install -y --no-install-recommends nvidia-container-toolkit
sudo systemctl restart docker

3、CUDA(运算平台)与cudnn(GPU加速库)

CUDA(ComputeUnified Device Architecture):显卡厂商NVIDIA推出的运算平台。 CUDA是一种由NVIDIA推出的通用并行计算架构,该架构使GPU能够解决复杂的计算问题。

cudnn:用于深度神经网络的GPU加速库。它强调性能、易用性和低内存开销。NVIDIA cuDNN可以集成到更高级别的机器学习框架中,如谷歌的Tensorflow、加州大学伯克利分校的流行caffe软件。简单的插入式设计可以让开发人员专注于设计和实现神经网络模型,而不是简单调整性能,同时还可以在GPU上实现高性能现代并行计算。

4、实操记录

设置下载源

yum install https://www.elrepo.org/elrepo-release-7.el7.elrepo.noarch.rpm

curl -s -L https://nvidia.github.io/nvidia-docker/centos7/x86_64/nvidia-docker.repo | sudo tee /etc/yum.repos.d/nvidia-docker.repo 

补充:有的网址是自动跳转的。curl使用 -L 参数,curl 就会跳转到新的网址。-s, --silent Silent mode. Don\'t output anything静默不输出信息

检测显卡驱动和型号

$ sudo yum install nvidia-detect

$ nvidia-detect -v
Probing for supported NVIDIA devices...
[10de:1e04] NVIDIA Corporation TU102 [GeForce RTX 2080 Ti]
This device requires the current 440.64 NVIDIA driver kmod-nvidia
[1af4:1050] Red Hat, Inc. Virtio GPU

Linux查看显卡信息:(ps:若找不到lspci命令,可以安装 yum install pciutils)
lspci | grep -i vga

使用nvidia GPU可以:
lspci | grep -i nvidia

查看显卡驱动
cat /proc/driver/nvidia/version

4、查找可安装的nvidia docker版本

yum search --showduplicates nvidia-docker

yum install -y nvidia-docker2 

5、下载对应版本的显卡驱动

NIVID官网:http://www.geforce.cn/drivers

下载的时候,在手动搜索驱动程序中,大致勾选自己机器的类型,然后查找到跟步骤3检测到的型号对应的版本进行下载

执行安装

# 如果无法正常安装nvidia
$ yum -y install gcc
$ yum install epel-release -y
$ yum install kernel-devel-$(uname -r) kernel-headers-$(uname -r) -y

$ chmod 777 NVIDIA-Linux-x86_64-450.66.run
$ bash NVIDIA-Linux-x86_64-450.66.run

安装过程中一些选项:
The distribution-provided pre-install script failed! Are you sure you want to continue?
选择 yes 继续。
Would you like to register the kernel module souces with DKMS? This will allow DKMS to automatically build a new module, if you install a different kernel later?
选择 No 继续。
问题大概是:Nvidia’s 32-bit compatibility libraries?
选择 No 继续。
Would you like to run the nvidia-xconfigutility to automatically update your x configuration so that the NVIDIA x driver will be used when you restart x? Any pre-existing x confile will be backed up.
选择 Yes 继续

卸载nvidia-driver

停掉占用

1. bash  xxx.run  --uninstall
2. 在现实的提示框中选择 *no* (默认选项),然后回车

nvidia-docker配置如下:

$ cat /etc/docker/daemon.json
{
  "registry-mirrors": ["https://xxxxxxx"],   #替换成自己的harbor仓库地址
  "live-restore": true,
  "default-shm-size": "128M",
  "max-concurrent-downloads": 10,
  "oom-score-adjust": -1000,
  "debug": false,
  "exec-opts": ["native.cgroupdriver=cgroupfs"],
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "20m",
    "max-file": "3"
  },
  "storage-driver": "overlay2",
  "default-runtime": "nvidia",
  "storage-opts": [
    "overlay2.override_kernel_check=true"
  ],
  "runtimes": {
    "nvidia": {
      "path": "/usr/bin/nvidia-container-runtime",
      "runtimeArgs": []
    }
  }
}

注意:

如果您有自定义/etc/docker/daemon.json,则nvidia-docker2程序包会覆盖它,可能导致docker 原有的 volume配置会全部丢失。

安装nvidia-docker2

下载安装包

apt download libnvidia-container1
apt download libnvidia-container-tools
apt download nvidia-container-runtime-hook
apt download nvidia-container-runtime
apt download nvidia-docker2

安装

dpkg -i libnvidia* nvidia*

添加普通用户至docke组

sudo groupadd docker #添加docker用户组
sudo gpasswd -a $USER docker #将登陆用户加入到docker用户组中
newgrp docker #更新用户组
docker ps #测试docker命令是否可以使用sudo正常使用

常见报错:

内核问题

 解决:

$ yum update  (升级后,需要重启进入当前更新后的内核版本)
$ yum -y install gcc
$ yum install epel-release -y
$ yum install kernel-devel-$(uname -r) kernel-headers-$(uname -r) -y
(注意:当uname -r看到的版本和手动安装的内核kernel-devel、kernel-headers不一致时,上述问题还会存在)

通信失败报错

NVIDIA-SMI has failed because it couldn\'t communicate with the NVIDIA driver. 
Make sure that the latest NVIDIA driver is installed and running.

卸载

卸载cuda-toolkit
cd /usr/local/cuda-8.0/bin/
./uninstall_***

卸载​显卡驱动
假如​安装的是NVIDIA-Linux-x86-340.96.run 
则运行如下命令:sh NVIDIA-Linux-x86-270.41.19.run --uninstall

解决:

sudo yum install dkms
ls /usr/src | grep nvidia
sudo dkms install -m nvidia -v 418.87.00

禁用Nouveau

ERROR: The Nouveau kernel driver is currently in use by your system.

解决:

关闭Nouveau:
把驱动加入黑名单中:
在/etc/modprobe.d/blacklist.conf 加入
blacklist nouveau

使用 dracut重新建立 initramfs image file :
* 备份 the initramfs file
$ sudo mv /boot/initramfs-$(uname -r).img /boot/initramfs-$(uname -r).img.bak

* 重新建立 the initramfs file
$ sudo dracut -v /boot/initramfs-$(uname -r).img $(uname -r)

$ lsmod | grep nouveau 检查nouveau没有被加载

报错Failed to run /sbin/dkms build -m nvidia -v

ERROR: Failed to run /sbin/dkms build -m nvidia -v 450.66 -k 3.10.0-1160.24.1.el7.x86_64: Error! echo
         Your kernel headers for kernel 3.10.0-1160.24.1.el7.x86_64 cannot be found at
         /lib/modules/3.10.0-1160.24.1.el7.x86_64/build or /lib/modules/3.10.0-1160.24.1.el7.x86_64/source.

解决:

yum install kernel-devel kernel-doc kernel-headers gcc\* glibc\* glibc-\*

如果再提示错误“NVIDIA-SMI has failed because it couldn\'t communicate with the NVIDIA driver”

需要运行
nvidia-uninstall

GPU占用

An NVIDIA kernel module \'nvidia-drm\' appears to already be loaded in your kernel. This may be because it is in use (for example, by an X server, a CUDA program, or the NVIDIA Persistence Daemon), but this may also happen if your kernel was configured without support for module unloading. Please be sure to exit any programs that may be using the GPU(s) before attempting to upgrade your driver. If no GPU-based programs are running, you know that your kernel supports module unloading, and you still receive this message, then an error may have occured that has corrupted an NVIDIA kernel module\'s usage count, for which the simplest remedy is to reboot your computer. 

解决:解决方式:关闭所有装置,并停止载入NVIDIA驱动程序(会 kill docker)

sudo systemctl isolate multi-user.target
sudo modprobe -r nvidia-drm

二、nvidia-smi命令

nvidia 的系统管理界面 (nvidia-smi),可以收集各种级别的信息,查看显存使用情况。此外, 可以启用和禁用 GPU 配置选项 (如 ECC 内存功能)。

1、nvidia-smi

当多块GPU坏掉一块 ,没法确认时,同时开启风扇吹热风,没风的就是坏的。

显存占用和GPU占用是两个不一样的东西,显卡是由GPU和显存等组成的,显存和GPU的关系有点类似于内存和CPU的关系。

2. nvidia-smi vgpu

查看当前vGPU的状态信息:

3. nvidia-smi vgpu -p    循环显示虚拟桌面中应用程序对GPU资源的占用情况

4. nvidia-smi -q 查看当前所有GPU的信息,也可以通过参数i指定具体的GPU。

比如nvidia-smi-q -i 0 代表我们查看服务器上第一块GPU的信息。

通过nvidia-smi -q 我们可以获取以下有用的信息:

GPU的SN号、VBIOS、PN号等信息:

GPU的总线、PCI-E总线倍速、风扇转速等信息:

GPU的显存、BAR1、所有资源利用率、ECC模式等信息:

nvidia-smi -q |grep "GPU Link" -A6

NVLink目前更主要的还是大大提升了GPU间通信的带宽。

5、nvidia-smi -L 命令:列出所有可用的 NVIDIA 设备

6、nvidia-smi topo --matrix 命令:查看系统拓扑

7、nvidia-smi topo -mp

8、查询GPU的版本等格式化输出

nvidia-smi --query-gpu=driver_version,name,memory.total,memory.used --format=csv
driver_version, name, memory.total [MiB], memory.used [MiB]
450.66, GeForce RTX 2080 Ti, 11019 MiB, 0 MiB
450.66, GeForce RTX 2080 Ti, 11019 MiB, 0 MiB

三、shell监控GPU脚本

monitor.sh

GPU跨平台通用监控脚本

功能: Useage: monitor.sh fast|mem|gpu|temp|all|[pathToLog sleepTimeNum]

注意: ./monitor.sh fast速度最快


#!/bin/bash

#. /etc/profile

#. ~/.bash_profile

#. ~/.bashrc

# 判断nvidia-smi命令是否存在

/usr/bin/nvidia-smi > /dev/null

if [ $? -eq 0 ]

then

  echo \'nvidia-smi check pass\' `date`

else

  echo \'nvidia-smi not exists\'

  exit 1

fi

# 获取GPU Count

function get_gpu_list()

{

  count=`nvidia-smi -L|wc -l`

  echo $count

}

#获取GPU id对应uuid

function get_uuid()

{

  uuid=`nvidia-smi -q -i $1|grep \'UUID\'|awk \'{print $4}\'`

  echo $uuid

}

#获取显存使用率 

function get_memory_usage()

{

  usage=`nvidia-smi -q -d MEMORY -i $1|grep -E \'Total|Used\'|head -2|awk \'{print $3}\'|xargs echo|awk \'{print $2/$1}\'`

  echo $usage

}

#获取内存详细信息

function get_memory_detail()

{

  detail=`nvidia-smi -q -d MEMORY -i $1|grep -E \'Total|Used|Free\'|head -3|awk \'{print $3}\'|xargs echo`

  echo $detail

}

#获取GPU使用率

function get_volatile_gpu()

{

  vol=`nvidia-smi -q -d UTILIZATION -i $1 |grep -A 5 "GPU Utilization"|tail -1|awk \'{print $3}\'` 

  echo $vol

}

#获取GPU Current 温度

function get_temperature()

{

  temp=`nvidia-smi -q -d Temperature -i $1|grep \'GPU Current\'|awk \'{print $5}\'`

  echo $temp

}

#获取Pod_id

function get_pod_id()

{

  echo `hostname`

}

#数据output

#output $1 $2 $3 $4 $5

#$1 字段名 $2 pod_id $3 gpu编号 $4 uuid $5 监控值

function output()

{

  echo $1"{podid=\""$2"\",gpu=\""$3"\",uuid=\""$4"\"}" $5

}

#输出mem prometheus格式数据

#dcgm_mem_usage{pod_id="localhost"}

function mem_prm()

{

  for((i=0;i<`get_gpu_list`;i++))

  do

      name="dcgm_mem_usage"

      pod_id=`get_pod_id`

      uuid=`get_uuid $i`

      value=`get_memory_usage $i`

      output $name $pod_id $i $uuid $value

  done

}

#输出mem detail prometheus格式数据

#dcgm_mem_detail{pod_id="localhost"}

function mem_detail_prm()

{

  for((i=0;i<`get_gpu_list`;i++))

  do

      pod_id=`get_pod_id`

      uuid=`get_uuid $i`

      value=`get_memory_detail $i`

      output "dcgm_fb_total" $pod_id $i $uuid `echo $value|awk \'{print $1}\'`

      output "dcgm_fb_used" $pod_id $i $uuid `echo $value|awk \'{print $2}\'`

      output "dcgm_fb_free" $pod_id $i $uuid `echo $value|awk \'{print $3}\'`

  done

}

#输出gpu prometheus格式数据

#dcgm_gpu_utilization{...}

function gpu_prm()

{

  for((i=0;i<`get_gpu_list`;i++))

  do

      name="dcgm_gpu_utilization"

      pod_id=`get_pod_id`

      uuid=`get_uuid $i`

      value=`get_volatile_gpu $i`

      output $name $pod_id $i $uuid $value

  done

}

#输出温度 prometheus格式数据

#dcgm_temp{...}

function temp_prm()

{

  for((i=0;i<`get_gpu_list`;i++))

  do

      name="dcgm_temp"

      pod_id=`get_pod_id`

      uuid=`get_uuid $i`

      value=`get_temperature $i`

      output $name $pod_id $i $uuid $value

  done

}

function allinone()

{

  mem_prm

  mem_detail_prm

  gpu_prm

  temp_prm

}

#快速获取

function fast()

{

  nvidia-smi -q > /tmp/1

  num=0

  count=0

  uuid=\'\'

  first=0

  for i in `cat /tmp/1|grep -E \'Minor Number|UUID|GPU Current Temp|Gpu|Total|Used|Free\'|cut -d \':\' -f2|awk \'{print $1}\'`

  do

    if [ $num -eq 0 ];then

        uuid=$i

    elif [ $num -eq 1 ];then

        count=$i

    elif [ $num -eq 2 ];then

        if [ $first -lt 13 ];then

          echo \'# HELP dcgm_fb_total Framebuffer memory total (in MiB).\'

          echo \'# TYPE dcgm_fb_total gauge\'

        fi

        output \'dcgm_fb_total\' ${HOSTNAME} $count $uuid $i

    elif [ $num -eq 3 ];then

        if [ $first -lt 13 ];then

          echo \'# HELP dcgm_fb_used Framebuffer memory used (in MiB).\'

          echo \'# TYPE dcgm_fb_used gauge\'

        fi

        output \'dcgm_fb_used\' ${HOSTNAME} $count $uuid $i

    elif [ $num -eq 4 ];then

        if [ $first -lt 13 ];then

          echo \'# HELP dcgm_fb_free Framebuffer memory free (in MiB).\'

          echo \'# TYPE dcgm_fb_free gauge\'

        fi

        output \'dcgm_fb_free\' ${HOSTNAME} $count $uuid $i

    elif [ $num -eq 8 ];then

        if [ $first -lt 13 ];then

          echo \'# HELP dcgm_gpu_utilization GPU utilization (in %).\'

          echo \'# TYPE dcgm_gpu_utilization gauge\'

        fi

        output \'dcgm_gpu_utilization\' ${HOSTNAME} $count $uuid $i

    elif [ $num -eq 13 ];then

        if [ $first -le 13 ];then

          echo \'# HELP dcgm_gpu_temp GPU temperature (in C).\'

          echo \'# TYPE dcgm_gpu_temp gauge\'

        fi

        output \'dcgm_gpu_temp\' ${HOSTNAME} $count $uuid $i

    fi

    if [ $num -eq 13 ];then

        num=0

    else

        ((num++))

    fi

    ((first++))

  done

}

case $1 in

  "help")

    echo \'Useage: monitor.sh fast|mem|gpu|temp|all|[pathToLog sleepTimeNum]\'

  ;;

  "mem")

    mem_prm     

    mem_detail_prm

  ;;

  "gpu")

    gpu_prm

  ;;

  "temp")

    temp_prm

  ;;

  "fast")

    fast

  ;;

  "all")

    allinone

  ;;

  "onebyone")

    if [ ! -n "$1" ];then

        if [ ! -d "/run/prometheus" ];then

          mkdir -p /run/prometheus

        fi

        while true;do allinone > /run/prometheus/`hostname`_dcgm.prom;sleep 15;done

    else

        if [ ! -n "$2" ];then

          while true;do allinone > $1;sleep 15;done

        else

          while true;do allinone > $1;sleep $2;done

        fi

    fi

  ;;

  *)

    if [ ! -n "$1" ];then

        if [ ! -d "/run/prometheus" ];then

          mkdir -p /run/prometheus

        fi

        while true;do fast > /run/prometheus/`hostname`_dcgm.prom;sleep 15;done

    else

        if [ ! -n "$2" ];then

          while true;do fast > $1;sleep 15;done

        else

          while true;do fast > $1;sleep $2;done

        fi

    fi

  ;;

esac

四、docker的存储驱动类型及其作用

https://docs.docker.com/storage/storagedriver/select-storage-driver/   官网说明

1、理解的前提:

(1)镜像是分层的文件系统(分层镜像机制),不可写,容器才是可写入数据的。

(2)容器 = 镜像 + 读写层

(3)多个容器可以共用一个images,利用写时复制(CoW)机制。

CoW就是copy-on-write,表示只在需要写时才去复制,这个是针对已有文件的修改场景。比如基于一个image启动多个Container,如果为每个Container都去分配一个image一样的文件系统,那么将会占用大量的磁盘空间。而CoW技术可以让所有的容器共享image的文件系统,所有数据都从image中读取,只有当要对文件进行写操作时,才从image里把要写的文件复制到自己的文件系统进行修改。所以无论有多少个容器共享同一个image,所做的写操作都是对从image中复制到自己的文件系统中的复本上进行,并不会修改image的源文件,且多个容器操作同一个文件,会在每个容器的文件系统里生成一个复本,每个容器修改的都是自己的复本,相互隔离,相互不影响。使用CoW可以有效的提高磁盘的利用率。

(4)存储驱动是干嘛的:每个容器都有一个本地存储空间,用于保存层叠的镜像层以及挂载的容器文件系统。默认情况下,容器的所有读写操作都发生在其镜像层上或挂载的文件系统中,所以存储是每个容器的性能和稳定性不可或缺的一个环节。本地存储是通过存储驱动(Storage Driver)进行管理的,有时候也被称为 Graph Driver。docker 存储驱动的职责就是将镜像层和可写容器层管理起来.不同的驱动实现管理的方式也不一致。实现容器与镜像管理的两个关键技术就是分层镜像机制和copy-on-write (写时复制)。

2、docker支持的几种存储驱动

Docker支持以下存储驱动程序:

(1)overlay2 :当前所有受支持的Linux发行版的首选存储驱动程序,不需要任何额外的配置,已经并入内核主线。

  overlayFS 是Linux内核3.18后支持的,也是一种Union FS,和AUFS的多层不同的是Overlay只有两层:一个upper文件系统和一个lower文件系统,分别代表

Docker的镜像层和容器层。当需要修改一个文件时,使用CoW将文件从只读的lower复制到可写的upper进行修改,结果也保存在upper层。在Docker中,底下的只

读层就是image,可写层就是Container。目前最新的OverlayFS为Overlay2。

(2)aufs是更早版本的首选存储驱动程序。

  AUFS(AnotherUnionFS)是一种Union FS,是文件级的存储驱动。AUFS是一个能透明覆盖一个或多个现有文件系统的层状文件系统,把多层合并成文件系统的单层表示。简单来说就是支持将不同目录挂载到同一个虚拟文件系统下的文件系统。这种文件系统可以一层一层地叠加修改文件。无论底下有多少层都是只读的,只有最上层的文件系统是可写的。当需要修改一个文件时,AUFS创建该文件的一个副本,使用CoW将文件从只读层复制到可写层进行修改,结果也保存在可写层。在Docker中,底下的只读层就是image,可写层就是Container。

(3)常用存储驱动对比

存储驱动特点优点缺点适用场景
AUFS 联合文件系统、未并入内核主线、文件级存储 作为docker的第一个存储驱动,已经有很长的历史,比较稳定,且在大量的生产中实践过,有较强的社区支持 有多层,在做写时复制操作时,如果文件比较大且存在比较低的层,可能会慢一些 大并发但少IO的场景
overlayFS 联合文件系统、并入内核主线、文件级存储 只有两层 不管修改的内容大小都会复制整个文件,对大文件进行修改显示要比小文件消耗更多的时间 大并发但少IO的场景
Devicemapper 并入内核主线、块级存储 块级无论是大文件还是小文件都只复制需要修改的块,并不是整个文件 不支持共享存储,当有多个容器读同一个文件时,需要生成多个复本,在很多容器启停的情况下可能会导致磁盘溢出 适合io密集的场景
Btrfs 并入linux内核、文件级存储 可以像devicemapper一样直接操作底层设备,支持动态添加设备 不支持共享存储,当有多个容器读同一个文件时,需要生成多个复本 不适合在高密度容器的paas平台上使用
ZFS 把所有设备集中到一个存储池中来进行管理 支持多个容器共享一个缓存块,适合内存大的环境 COW使用碎片化问题更加严重,文件在硬盘上的物理地址会变的不再连续,顺序读会变的性能比较差 适合paas和高密度的场景

AUFS VS OverlayFS

AUFS和Overlay都是联合文件系统,但AUFS有多层,而Overlay只有两层,所以在做写时复制操作时,如果文件比较大且存在比较低的层,则AUSF可能会慢一些。而且Overlay并入了linux kernel mainline,AUFS没有。目前AUFS已基本被淘汰

OverlayFS VS Device mapper

OverlayFS是文件级存储,Device mapper是块级存储,当文件特别大而修改的内容很小,Overlay不管修改的内容大小都会复制整个文件,对大文件进行修改显示要比小文件要消耗更多的时间,而块级无论是大文件还是小文件都只复制需要修改的块,并不是整个文件,在这种场景下,显然device mapper要快一些。因为块级的是直接访问逻辑盘,适合IO密集的场景。而对于程序内部复杂,大并发但少IO的场景,Overlay的性能相对要强一些。

 

 

 

 

注:修改驱动之前做好数据备份!!!!!

 云原生实验室 (cnblogs.com)   Kubernetes 教程:在 Containerd 容器中使用 GPU

https://nvidia.github.io/nvidia-docker/     nvidia-docker的部署

https://fanfuhan.github.io/2019/11/22/docker_based_use/  配合上一个地址使用

https://gitlab.com/nvidia  

https://github.com/NVIDIA/gpu-operator

https://github.com/zhebrak/nvidia_smi_exporter

https://github.com/mindprince/nvidia_gpu_prometheus_exporter  gpu监控

Installation Guide — NVIDIA Cloud Native Technologies documentation

Index of /compute/cuda/repos/debian10/x86_64 (nvidia.com)

https://mp.weixin.qq.com/s/smLeFV5NOHspoW4wWqWvNQ