我们知道 如果在Kubernetes中支持GPU设备调度 需要做如下的工作
节点上安装nvidia驱动节点上安装nvidia-docker集群部署gpu?device?plugin 用于为调度到该节点的pod分配GPU设备。除此之外 如果你需要监控集群GPU资源使用情况 你可能还需要安装DCCM?exporter结合Prometheus输出GPU资源监控信息。
要安装和管理这么多的组件 对于运维人员来说压力不小。基于此 NVIDIA开源了一款叫NVIDIA?GPU?Operator的工具 该工具基于Operator?Framework实现 用于自动化管理上面我们提到的这些组件。
NVIDIA?GPU?Operator有以下的组件构成
安装nvidia?driver的组件安装nvidia?container?toolkit的组件安装nvidia?devcie?plugin的组件安装nvidia?dcgm?exporter组件安装gpu?feature?discovery组件本系列文章不打算一上来就开始讲NVIDIA?GPU?Operator 而是先把各个组件的安装详细的分析一下 然后手动安装这些组件 最后再来分析NVIDIA?GPU?Operator就比较简单了。
在本篇文章中 我们将详细介绍NVIDIA?GPU?Operator安装NVIDIA驱动的原理——基于容器安装NVIDIA驱动。
基于容器安装NVIDIA?GPU驱动大多数时候 运维人员都是直接将NVIDIA驱动直接安装在GPU节点上 但是这会有如下的一些缺点
驱动程序安装容易出错。驱动的安装方式与操作系统类型相关 不同的操作系统安装驱动不同。不可移植性。难以大规模部署或在容器平台上部署 例如 Kubernetes、Openshift等 。基于此 NVIDIA官方提供了一种基于容器安装NVIDIA驱动的方式 而且NVIDIA?GPU?Operator安装nvidia驱动也是采用的这种方式。基于容器安装NVIDIA驱动有如下的一些优点
速度快 一般只要几秒就能安装完成 。安全性 安全启动 受信任启动 内核锁定 。便携性 安装脚本被打包成镜像 便于分发和移植 。易于使用 安装/卸载驱动 启动/停止容器 。重现性 驱动程序受限制 防止sysadmin错误操作 。当NVIDIA驱动基于容器化安装后 整个架构将演变成图中描述的样子。
基于容器安装NVIDIA驱动的整个流程可以分为如下的两个部分
镜像构建阶段容器启动阶段镜像构建阶段要想了解在构建nvidia驱动镜像过程中都有哪些操作 可以阅读构建镜像的Dockerfile NVIDIA官方将这些Dockerfile存放在Gitlab上 我们以https://gitlab.com/nvidia/container-images/driver/-/blob/master/centos7/Dockerfile为例进行说明。
1.环境准备首先是准备环境 环境准备的主要是安装一些工具 比如curl、gcc等。
FROM?nvidia/cuda:11.2.1-base-centos7 RUN?yum?install?-y?\ ????????ca-certificates?\ ????????curl?\ ????????gcc?\ ????????glibc.i686?\ ????????make?\ ????????kmod? ?\ ????rm?-rf?/var/cache/yum/* RUN?curl?-fsSL?-o?/usr/local/bin/donkey?https://github.com/3XX0/donkey/releases/download/v1.1.0/donkey? ?\ ????curl?-fsSL?-o?/usr/local/bin/extract-vmlinux?https://raw.githubusercontent.com/torvalds/linux/master/scripts/extract-vmlinux? ?\ ????chmod? x?/usr/local/bin/donkey?/usr/local/bin/extract-vmlinux2.安装用户空间使用的工具
当环境准备好以后 接下来主要做如下几件事
下载NVIDIA驱动文件 以.run结尾 驱动版本可以在执行docker?build时通过--build-arg?DRIVER_VERSION 驱动版本 指定。执行nvidia-installer 由NVIDIA驱动文件提供 安装用户空间使用的工具 例如 nvidia-smi、nvidia-uninstall、nvidia-settings等工具 到镜像的/usr/bin下。将需要编译内核模块有关的文件存放在/usr/src/nvidia-$DRIVER_VERSION这个目录下 后面编译和安装内核模块时需要用到这些文件。#ARG?BASE_URL http://us.download.nvidia.com/XFree86/Linux-x86_64 ARG?BASE_URL https://us.download.nvidia.com/tesla ARG?DRIVER_VERSION ENV?DRIVER_VERSION $DRIVER_VERSION #?Install?the?userspace?components?and?copy?the?kernel?module?sources. RUN?cd?/tmp? ?\ ????curl?-fSsl?-O?$BASE_URL/$DRIVER_VERSION/NVIDIA-Linux-x86_64-$DRIVER_VERSION.run? ?\ ????sh?NVIDIA-Linux-x86_64-$DRIVER_VERSION.run?-x? ?\ ????cd?NVIDIA-Linux-x86_64-$DRIVER_VERSION*? ?\ ????./nvidia-installer?--silent?\ ???????????????????????--no-kernel-module?\ ???????????????????????--install-compat32-libs?\ ???????????????????????--no-nouveau-check?\ ???????????????????????--no-nvidia-modprobe?\ ???????????????????????--no-rpms?\ ???????????????????????--no-backup?\ ???????????????????????--no-check-for-alternate-installs?\ ???????????????????????--no-libglx-indirect?\ ???????????????????????--no-install-libglvnd?\ ???????????????????????--x-prefix /tmp/null?\ ???????????????????????--x-module-path /tmp/null?\ ???????????????????????--x-library-path /tmp/null?\ ???????????????????????--x-sysconfig-path /tmp/null? ?\ ????mkdir?-p?/usr/src/nvidia-$DRIVER_VERSION? ?\ ????mv?LICENSE?mkprecompiled?kernel?/usr/src/nvidia-$DRIVER_VERSION? ?\ ????sed? 9,${/^\(kernel\|LICENSE\)/!d} ?.manifest? ?/usr/src/nvidia-$DRIVER_VERSION/.manifest? ?\ ????rm?-rf?/tmp/*3.编译驱动内核模块
这一步主要操作是编译NVIDIA驱动的内核模块并且生成一个包 这个包将会在容器启动时 执行nvidia-installer安装驱动内核模块时用到。
在构建镜像过程中 KERNEL_VERSION可以通过docker?build?--build-arg??KERNEL_VERSION kernel版本 而且可以指定多个内核版本 通过逗号分隔 然后在Dockerfile中通过一个for循环为每个一个内核版本编译一个NVIDIA驱动内核模块包 供容器启动时nvidia-installer使用。
ARG?KERNEL_VERSION latest #?Compile?the?kernel?modules?and?generate?precompiled?packages?for?use?by?the?nvidia-installer. RUN?yum?makecache?-y? ?\ ????for?version?in?$(echo?$KERNEL_VERSION?|?tr? , ? ? ?do?\ ????????nvidia-driver?update?-k?$version?-t?builtin?${PRIVATE_KEY: -s?${PRIVATE_KEY} ?\ ????done? ?\ ????rm?-rf?/var/cache/yum/*
针对每一个内核版本 执行了“nvidia-driver?update”这条命令 nvidia-driver这个脚本的内容 可以从https://gitlab.com/nvidia/container-images/driver/-/blob/master/centos7/nvidia-driver查看。
“nvidia-driver?update”执行的是update()这个函数 函数内容如下
update()?{ ????exec?3 2 ????if?exec?2 ?/dev/null?4 ?${PID_FILE};?then ????????if?!?flock?-n?4? ?read?pid? 4? ?kill?-0? ${pid} ?then ????????????exec? ? (tee?-a? /proc/${pid}/fd/1 ) ????????????exec?2 ? (tee?-a? /proc/${pid}/fd/2 ? 3) ????????else ????????????exec?2 3 ????????fi ????????exec?4 - ????fi ????exec?3 - ????echo?-e? \n ?NVIDIA?Software?Updater? \n ????echo?-e? Starting?update?of?NVIDIA?driver?version?${DRIVER_VERSION}?for?Linux?kernel?version?${KERNEL_VERSION}\n ????trap? echo? Caught?signal ?exit?1 ?HUP?INT?QUIT?PIPE?TERM ????#?1.更新yum源的cache 便于使用yum安装kernel-headers、kernel-devels等包。 ????_update_package_cache ????#?2.用于重置KERNEL_VERSION这个环境变量 ????_resolve_kernel_version?||?exit?1 ????#?3.用于安装kernel-headers kernel-devel和kernel三个包 ????_install_prerequisites ????#?4.如果检测到/usr/src/nvidia-$DRIVER_VERSION/kernel/precompiled目录下存在对于某个内核版本 由KERNEL_VERSION指定 ????#?已经编译好的包 那么就不编译针对该内核版本所需的驱动内核包 如果没有找到 ????#?调用_create_driver_package构建针对该内核版本所需的驱动内核包 ????#?并存放在/usr/src/nvidia-$DRIVER_VERSION/kernel/precompiled目录下。 ????if?_kernel_requires_package;?then ????????_create_driver_package ????fi ????#?5.移除kernel-headers kernel-devel包 ????_remove_prerequisites ????#?6.清除yum源缓存 ????_cleanup_package_cache ????echo? Done ????exit?0
从_update_package_cache开始 该函数执行的逻辑如下
_update_package_cache用于更新yum源的cache 便于使用yum安装kernel-headers、kernel-devels等包。_resolve_kernel_version用于重置KERNEL_VERSION这个环境变量。_install_prerequisites主要用于安装kernel-headers kernel-devel和kernel三个包。如果检测到/usr/src/nvidia-$DRIVER_VERSION/kernel/precompiled目录下存在对于某个内核版本 由KERNEL_VERSION指定 已经编译好的包 那么就不编译针对该内核版本所需的驱动内核包 如果没有找到 调用_create_driver_package构建针对该内核版本所需的驱动内核包 并存放在/usr/src/nvidia-$DRIVER_VERSION/kernel/precompiled目录下。_remove_prerequisites用于移除kernel-headers kernel-devel包_cleanup_package_cache用于清除yum源缓存。下面将重点分析如下三个函数
_resolve_kernel_version_install_prerequisites_create_driver_package1.?_resolve_kernel_version分析_resolve_kernel_version作用是重新设置KERNEL_VERSION这个环境变量 KERNEL_VERSION这个环境变量非常重要 它的值的设置逻辑为
默认的值是执行uname?-r的结果。如果在执行nvidia-driver?update时指定了-k选项 那么-k选项的值会赋值给KERNEL_VERSION。_resolve_kernel_version函数检查KERNEL_VERSION是否有效并重新赋值。_resolve_kernel_version函数内容如下
#?Resolve?the?kernel?version?to?the?form?major.minor.patch-revision. #?Detect?the?kernel?version?from? yum?list ?command?and?set?the?env?KERNEL_VERSION _resolve_kernel_version()?{ ????local?version $(yum?-q?list?available?--show-duplicates?kernel-headers?| ??????awk?-v?arch $(uname?-m)? NR 1?{print?$2 . arch} ?|?tac?|?grep?-E?-m1? ^${KERNEL_VERSION/latest/.*} ) ????echo? Resolving?Linux?kernel?version... ????if?[?-z? ${version} ?then ????????echo? Could?not?resolve?Linux?kernel?version ? 2 ????????return?1 ????fi ????KERNEL_VERSION ${version} ????echo? Proceeding?with?Linux?kernel?version?${KERNEL_VERSION} ????return?0 }
上述代码中比较难以理解的是第一条命令 也就是“local?version ...”这条命令 这一条命令做了如下的事
使用yum?list获取可用kernel-headers包版本 像下面这样kernel-headers.x86_64??3.10.0-1160.el7????????base kernel-headers.x86_64??3.10.0-1160.el7????????updates kernel-headers.x86_64??3.10.0-1160.2.1.el7????updates kernel-headers.x86_64??3.10.0-1160.2.2.el7????updates kernel-headers.x86_64??3.10.0-1160.6.1.el7????updates kernel-headers.x86_64??3.10.0-1160.11.1.el7???updates获取中间的kernel一列 并降序排序
3.10.0-1160.11.1.el7.x86_64 3.10.0-1160.6.1.el7.x86_64 3.10.0-1160.2.2.el7.x86_64 3.10.0-1160.2.1.el7.x86_64 3.10.0-1160.el7.x86_64 3.10.0-1160.el7.x86_64使用grep按如下逻辑匹配一个内核版本 如果$KERNEL_VERSION不为空 那么返回匹配$KERNEL_VERSION的结果 有可能匹配成功 也有可能匹配不成功 。如果上面条件不满足 那么匹配带有latest关键字的内核版本 如果匹配成功 那么返回结果。如果上面条件不满足 那么匹配第一条内核版本 即3.10.0-1160.11.1.el7.x86_64。将匹配到的内核版本赋值给version这个变量。
然后检查$version这个值是否为空 如果为空 那么直接报错退出 如果不为空那么将$version的值赋值给KERNEL_VERSION。
2.?_install_prerequisites分析_install_prerequisites主要是安装如下两个包 KERNEL_VERSION为具体的内核版本
kernel-headers-${KERNEL_VERSION}kernel-devel-${KERNEL_VERSION}kernel-${KERNEL_VERSION}为什么会对这个函数加以说明 先看看这三个包的安装方式
????yum?-q?-y?install?kernel-headers-${KERNEL_VERSION}?kernel-devel-${KERNEL_VERSION}? ?/dev/null ????#?link?the?kernel?files?which?are?prebuilded ????ln?-s?/usr/src/kernels/${KERNEL_VERSION}?/lib/modules/${KERNEL_VERSION}/build ????echo? Installing?Linux?kernel?module?files... ????curl?-fsSL?$(repoquery?--location?kernel-${KERNEL_VERSION})?|?rpm2cpio?|?cpio?-idm?--quie
可以看到kernel-headers和kernel-devel是用yum安装的 kernel是直接下载rpm包 然后用rpm2cpio和cpio处理安装的。如果指定的内核版本的三个包没有在yum源中出现 安装就会出错 此时应该怎么处理呢 可以先将三个包下载 在制作docker镜像传入镜像中 然后使用 yum?localinstall 安装kernel-devel和kernel-headers 使用
“rpm2cpio? PATH_TO_KERNEL_RPM ?|?cpio?-idm?--quie”替换原有的命令。
3._create_driver_package分析_create_driver_package函数用于编译运行驱动所需的内核模块并对这些内核模块执行签名操作 最后利用这些内核模块生成一个名为nvidia-modules-${KERNEL_VERSION}-buildin的文件 在执行nvidia-installer时需要用到该文件。
#?Compile?the?kernel?modules,?optionally?sign?them,?and?generate?a?precompiled?package?for?use?by?the?nvidia-installer. _create_driver_package()?( ????local?pkg_name nvidia-modules-${KERNEL_VERSION%%-*}${PACKAGE_TAG: -${PACKAGE_TAG}} ????local?nvidia_sign_args ????local?nvidia_modeset_sign_args ????local?nvidia_uvm_sign_args ????trap? make?-s?-j?SYSSRC /lib/modules/${KERNEL_VERSION}/build?clean? ?/dev/null ?EXIT ????echo? Compiling?NVIDIA?driver?kernel?modules... ????cd?/usr/src/nvidia-${DRIVER_VERSION}/kernel ????export?IGNORE_CC_MISMATCH 1 ????make?-s?-j?SYSSRC /lib/modules/${KERNEL_VERSION}/build?nv-linux.o?nv-modeset-linux.o? ?/dev/null ????echo? Relinking?NVIDIA?driver?kernel?modules... ????rm?-f?nvidia.ko?nvidia-modeset.ko ????ld?-d?-r?-o?nvidia.ko?./nv-linux.o?./nvidia/nv-kernel.o_binary ????ld?-d?-r?-o?nvidia-modeset.ko?./nv-modeset-linux.o?./nvidia-modeset/nv-modeset-kernel.o_binary ????if?[?-n? ${PRIVATE_KEY} ?then ????????echo? Signing?NVIDIA?driver?kernel?modules... ????????donkey?get?${PRIVATE_KEY}?sh?-c? PATH ${PATH}:/usr/src/linux-headers-${KERNEL_VERSION}/scripts? ?\ ??????????sign-file?sha512?\$DONKEY_FILE?pubkey.x509?nvidia.ko?nvidia.ko.sign? ??????????????????????????\ ??????????sign-file?sha512?\$DONKEY_FILE?pubkey.x509?nvidia-modeset.ko?nvidia-modeset.ko.sign? ??????????\ ??????????sign-file?sha512?\$DONKEY_FILE?pubkey.x509?nvidia-uvm.ko ????????nvidia_sign_args --linked-module?nvidia.ko?--signed-module?nvidia.ko.sign ????????nvidia_modeset_sign_args --linked-module?nvidia-modeset.ko?--signed-module?nvidia-modeset.ko.sign ????????nvidia_uvm_sign_args --signed ????fi ????echo? Building?NVIDIA?driver?package?${pkg_name}... ????../mkprecompiled?--pack?${pkg_name}?--description?${KERNEL_VERSION}??????????????????????????????\ ????????????????????????????????????????--proc-mount-point?/lib/modules/${KERNEL_VERSION}/proc???????\ ????????????????????????????????????????--driver-version?${DRIVER_VERSION}???????????????????????????\ ????????????????????????????????????????--kernel-interface?nv-linux.o????????????????????????????????\ ????????????????????????????????????????--linked-module-name?nvidia.ko???????????????????????????????\ ????????????????????????????????????????--core-object-name?nvidia/nv-kernel.o_binary?????????????????\ ????????????????????????????????????????${nvidia_sign_args}??????????????????????????????????????????\ ????????????????????????????????????????--target-directory?.?????????????????????????????????????????\ ????????????????????????????????????????--kernel-interface?nv-modeset-linux.o????????????????????????\ ????????????????????????????????????????--linked-module-name?nvidia-modeset.ko???????????????????????\ ????????????????????????????????????????--core-object-name?nvidia-modeset/nv-modeset-kernel.o_binary?\ ????????????????????????????????????????${nvidia_modeset_sign_args}??????????????????????????????????\ ????????????????????????????????????????--target-directory?.?????????????????????????????????????????\ ????????????????????????????????????????--kernel-module?nvidia-uvm.ko????????????????????????????????\ ????????????????????????????????????????${nvidia_uvm_sign_args}??????????????????????????????????????\ ????????????????????????????????????????--target-directory?. ????mkdir?-p?precompiled ????mv?${pkg_name}?precompiled容器启动阶段
在容器启动阶段 主要运行“nvidia-driver?init”命令 该命令会调用nvidia-driver脚本中的init()函数 其内容如下
init()?{ ????echo?-e? \n ?NVIDIA?Software?Installer? \n ????echo?-e? Starting?installation?of?NVIDIA?driver?version?${DRIVER_VERSION}?for?Linux?kernel?version?${KERNEL_VERSION}\n ????exec?3 ?${PID_FILE} ????if?!?flock?-n?3;?then ????????echo? An?instance?of?the?NVIDIA?driver?is?already?running,?aborting ????????exit?1 ????fi ????echo?$$? 3 ????trap? echo? Caught?signal ?exit?1 ?HUP?INT?QUIT?PIPE?TERM ????trap? _shutdown ?EXIT ????#?1.检查驱动是否已经挂载 如果已经挂载 那么需要卸载它 ????_unload_driver?||?exit?1 ????#?2.检查驱动的rootfs是否已经mount 如果已经mount 那么需要卸载驱动的rootfs ????_unmount_rootfs ????#?3.如果当前内核版本所需的驱动内核包不存在 那么需要编译该驱动内核包 编译的步骤与update()函数 ????#?相同 ????if?_kernel_requires_package;?then ????????_update_package_cache ????????_resolve_kernel_version?||?exit?1 ????????_install_prerequisites ????????_create_driver_package ????????_remove_prerequisites ????????_cleanup_package_cache ????fi ????#?4.安装驱动 ????_install_driver ????#?5.加载驱动 ????_load_driver ????#?6.挂载驱动rootfs ????_mount_rootfs ????#?7.生成一个用于升级驱动的钩子脚本 ????_write_kernel_update_hook ????echo? Done,?now?waiting?for?signal ????sleep?infinity? ????trap? echo? Caught?signal ?_shutdown? ?{?kill?$!;?exit?0;?} ?HUP?INT?QUIT?PIPE?TERM ????trap?-?EXIT ????while?true;?do?wait?$!?||?continue;?done ????exit?0
从?_unload_driver函数开始 函数的执行逻辑如下
?_unload_driver 如果节点已经加载了驱动 可能由其他容器加载的 那么首先应该卸载驱动。_unmount_rootfs 卸载/var/run下驱动的rootfs。检查/usr/src/nvidia-${DRIVER_VERSION}/kernel/precompiled是否有针对当前KERNEL_VERSION 由uname?-r命令提供 已经编译好的驱动内核模块包 如果没有 执行编译操作 生成适合当前内核版本的驱动内核模块包。安装驱动所需的内核模块 这些内核模块由之前预先编译好的内核模块包提供。加载驱动。挂载驱动的rootfs到/var/run下。生成一个用于升级驱动的钩子脚本。设置一个陷阱 用户捕获当前进程退出时需要做哪些事情 卸载驱动、卸载/var/run下驱动的rootfs 。_unload_driver函数分析_unload_driver函数主要用于卸载驱动 每次加载驱动前先执行一个卸载操作。卸载操作的主要执行逻辑如下
如果/var/run/nvidia-persistenced/nvidia-persistenced.pid文件存在 那么读取文件中的进程号 然后使用kill命令杀死进程。执行kill操作后 在1秒内检查10次该进程是否存在 如果不存在 那么该进程已被杀死 可以执行后续操作 如果1秒后 该进程还存在 那么返回错误。检查是否有应用程序正在使用驱动 如果有应用程序正在使用驱动 那么返回错误 否则卸载驱动内核模块。#?Stop?persistenced?and?unload?the?kernel?modules?if?they?are?currently?loaded. _unload_driver()?{ ????local?rmmod_args () ????local?nvidia_deps 0 ????local?nvidia_refs 0 ????local?nvidia_uvm_refs 0 ????local?nvidia_modeset_refs 0 ????echo? Stopping?NVIDIA?persistence?daemon... ????#?如果/var/run/nvidia-persistenced/nvidia-persistenced.pid存在 ????if?[?-f?/var/run/nvidia-persistenced/nvidia-persistenced.pid?];?then ????????#?读取文件中进程号 ????????local?pid $( ?/var/run/nvidia-persistenced/nvidia-persistenced.pid) ????????#?杀死进程 ????????kill?-SIGTERM? ${pid} ????????#?检查10次 每次检查如果未发现被kill的进程 那么直接退出for循环 超时时间为1秒 ????????for?i?in?$(seq?1?10);?do ????????????kill?-0? ${pid} ?2 ?/dev/null?||?break ????????????sleep?0.1 ????????done ????????#?如果1秒后进程仍然存在 那么返回错误 ????????if?[?$i?-eq?10?];?then ????????????echo? Could?not?stop?NVIDIA?persistence?daemon ? 2 ????????????return?1 ????????fi ????fi ????#?卸载驱动内核模块 ????#?检查使用有应用程序正在使用驱动 ????echo? Unloading?NVIDIA?driver?kernel?modules... ????if?[?-f?/sys/module/nvidia_modeset/refcnt?];?then ????????nvidia_modeset_refs $( ?/sys/module/nvidia_modeset/refcnt) ????????rmmod_args ( nvidia-modeset ) ????????(( nvidia_deps)) ????fi ????if?[?-f?/sys/module/nvidia_uvm/refcnt?];?then ????????nvidia_uvm_refs $( ?/sys/module/nvidia_uvm/refcnt) ????????rmmod_args ( nvidia-uvm ) ????????(( nvidia_deps)) ????fi ????if?[?-f?/sys/module/nvidia/refcnt?];?then ????????nvidia_refs $( ?/sys/module/nvidia/refcnt) ????????rmmod_args ( nvidia ) ????fi ????#?如果以下任意一个条件满足 说明有应用程序正在使用驱动 ????#?1.变量nvidia_refs的值大于变量nvidia_deps ????#?2.变量nvidia_uvm_refs大于0 ????#?3.变量nvidia_modeset_refs大于0 ????if?[?${nvidia_refs}?-gt?${nvidia_deps}?]?||?[?${nvidia_uvm_refs}?-gt?0?]?||?[?${nvidia_modeset_refs}?-gt?0?];?then ????????echo? Could?not?unload?NVIDIA?driver?kernel?modules,?driver?is?in?use ? 2 ????????return?1 ????fi ????# ????if?[?${#rmmod_args[ ]}?-gt?0?];?then ????????rmmod?${rmmod_args[ ]} ????fi ????return?0 }_unmount_rootfs函数分析
_unmount_rootfs函数用于卸载驱动的rootfs 函数逻辑比较简单。
#?Unmount?the?driver?rootfs?from?the?run?directory. _unmount_rootfs()?{ ????echo? Unmounting?NVIDIA?driver?rootfs... ????#?驱动的rootfs路径为/run/nvidia/driver 如果发现驱动的rootfs已经挂载 就卸载/run/nvidia/driver目录 ????if?findmnt?-r?-o?TARGET?|?grep? ${RUN_DIR}/driver ? ?/dev/null;?then ????????umount?-l?-R?${RUN_DIR}/driver ????fi_install_driver函数分析
_install_driver主要是使用nvidia-installer安装nvidia驱动内核模块 此函数依赖_create_driver_package函数编译生成的内核驱动包。
_install_driver()?{ ????local?install_args () ????echo? Installing?NVIDIA?driver?kernel?modules... ????cd?/usr/src/nvidia-${DRIVER_VERSION} ????rm?-rf?/lib/modules/${KERNEL_VERSION}/video ????if?[? ${ACCEPT_LICENSE} ? ? yes ?then ????????install_args ( --accept-license ) ????fi ????nvidia-installer?--kernel-module-only?--no-drm?--ui none?--no-nouveau-check?${install_args[ ] ${install_args[ ]} } ????#?May?need?to?add?no-cc-check?for?Rhel,?otherwise?it?complains?about?cc?missing?in?path ????#?/proc/version?and?lib/modules/KERNEL_VERSION/proc?are?different,?by?default?installer?looks?at?/proc/?so,?added?the?proc-mount-point ????#?TODO:?remove?the?-a?flag.?its?not?needed.?in?the?new?driver?version,?license-acceptance?is?implicit ????#nvidia-installer?--kernel-module-only?--no-drm?--ui none?--no-nouveau-check?--no-cc-version-check?--proc-mount-point?/lib/modules/${KERNEL_VERSION}/proc?${install_args[ ] ${install_args[ ]} }_load_driver函数分析
_load_driver用于加载nvidia驱动 主要有两步
加载驱动所需的内核模块。以persistence?mode启动驱动。#?Load?the?kernel?modules?and?start?persistenced. _load_driver()?{ ????echo? Loading?IPMI?kernel?module... ????modprobe?ipmi_msghandler ????echo? Loading?NVIDIA?driver?kernel?modules... ????modprobe?-a?nvidia?nvidia-uvm?nvidia-modeset ????echo? Starting?NVIDIA?persistence?daemon... ????nvidia-persistenced?--persistence-mode_mount_rootfs函数分析
_mount_rootfs用于挂载驱动的rootfs。
#?Mount?the?driver?rootfs?into?the?run?directory?with?the?exception?of?sysfs. _mount_rootfs()?{ ????echo? Mounting?NVIDIA?driver?rootfs... ????#?递归地将整个子树标记为不可绑定 ????mount?--make-runbindable?/sys ????#?递归的将子树标记为私有 ????mount?--make-private?/sys ????#?如果目录/run/nvidia/drvier不存在 那么创建该目录 ????mkdir?-p?${RUN_DIR}/driver ????#?将容器的根目录挂载到/run/nvidia/driver目录下 以实现对/run/nvidia/driver目录的操作 ????#?等同于对根目录操作 ????mount?--rbind?/?${RUN_DIR}/driver
除此之外宿主机的/run/nvidia目录也将会挂载到容器的/run/nvidia目录 并且挂载时会开启选项“mountPropagation:?Bidirectional” 表示容器内操作/run/nvidia目录后 如果其他容器也挂载了该目录 它们对该目录的修改可见 之所以要这么配置 因为后面介绍的其他组件也会挂载宿主机的/run/nvidia目录到容器中 。
????????volumeMounts: ??????????-?name:?run-nvidia ????????????mountPath:?/run/nvidia ????????????mountPropagation:?Bidirectional ??????..... ??????volumes: ????????-?name:?run-nvidia ??????????hostPath: ????????????path:?/run/nvidia在Kubernetes集群中基于容器安装节点GPU驱动
下面将演示怎样在k8s集群中为GPU节点安装驱动。
前提条件在为集群节点安装GPU驱动之前 有些条件需要满足
节点的操作系统类型为Centos7。节点上不能安装NVIDIA驱动 如果已安装 需要卸载驱动并重启机器。k8s版本? ?1.13 本次演示使用的集群版本为1.16.9同时 本次演示所使用的nvidia驱动镜像没有采用NVIDIA官方提供的镜像 而是修改了nvidia-driver脚本的自定义镜像。
操作步骤1.下载gpu-operator项目源码。
$?git?clone?-b?1.6.2?https://github.com/NVIDIA/gpu-operator.git $?cd?gpu-operator $?export?GPU_OPERATOR $(pwd)
2.修改镜像名称。
$?cd?$GPU_OPERATOR/assets/state-driver $?export?CUSTOM_IMAGE registry.cn-beijing.aliyuncs.com/kube-ai/nvidia-driver:450.102.04-centos7 $?sed?-i? s FILLED?BY?THE?OPERATOR $CUSTOM_IMAGE g ?0500_daemonset.yaml
3.删除无关的yaml。
$??cd?$GPU_OPERATOR/assets/state-driver $?rm?-rf?0410_scc.openshift.yaml
4.编辑0300_rolebinding.yaml。
apiVersion:?rbac.authorization.k8s.io/v1 kind:?RoleBinding metadata: ??name:?nvidia-driver ??namespace:?gpu-operator-resources roleRef: ??apiGroup:?rbac.authorization.k8s.io ??kind:?Role ??name:?nvidia-driver ??#?注释这一行 ??#?namespace:?gpu-operator-resources subjects: -?kind:?ServiceAccount ??name:?nvidia-driver ??namespace:?gpu-operator-resources #?注释这两行?? #?userNames: #?-?system:serviceaccount:gpu-operator-resources:nvidia-driver
5.创建gpu-operator-resources这个namespace。
$?kubectl?create?ns?gpu-operator-resources
6.使用kubectl部署。
$?kubectl?apply?-f?$GPU_OPERATOR/assets/state-driver
7.给GPU节点打上标签“nvidia.com/gpu.present true” 节点只有打上该标签后 才会在该节点上安装驱动 。
$?kubectl?label?nodes? NODE_NAME ?nvidia.com/gpu.present true验证
1.查看pod是否处于Running状态。
$?kubectl?get?po?-n?gpu-operator-resources NAME????????????????????????????READY???STATUS????RESTARTS???AGE nvidia-driver-daemonset-mqklz???1/1?????Running???5??????????35m nvidia-driver-daemonset-w5sf4???1/1?????Running???5??????????35m nvidia-driver-daemonset-zghxj???1/1?????Running???5??????????35m
2.进入pod执行nvdia-smi 观察该命令能否正常执行。
$?kubectl?exec?-ti?nvidia-driver-daemonset-mqklz?-n?gpu-operator-resources?--?nvidia-smi Fri?Mar?19?12:59:18?2021 ----------------------------------------------------------------------------- |?NVIDIA-SMI?450.102.04???Driver?Version:?450.102.04???CUDA?Version:?11.0?????| |------------------------------- ---------------------- ---------------------- |?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??Tesla?V100-SXM2...??On???|?00000000:00:07.0?Off?|????????????????????0?| |?N/A???34C????P0????24W?/?300W?|??????0MiB?/?16160MiB?|??????0%??????Default?| |???????????????????????????????|??????????????????????|??????????????????N/A?| ------------------------------- ---------------------- ---------------------- ----------------------------------------------------------------------------- |?Processes:??????????????????????????????????????????????????????????????????| |??GPU???GI???CI????????PID???Type???Process?name??????????????????GPU?Memory?| |????????ID???ID???????????????????????????????????????????????????Usage??????| |??No?running?processes?found?????????????????????????????????????????????????| -----------------------------------------------------------------------------
3.登陆到节点上执行nvidia-smi 观察能否正常执行 如果是采用nvidia官方的镜像 无法在节点上执行nvidia-smi 。
$?[root iZ2zeb7ywy6f2gnxrmjid1Z?/]#?nvidia-smi Fri?Mar?19?21:01:01?2021 ----------------------------------------------------------------------------- |?NVIDIA-SMI?450.102.04???Driver?Version:?450.102.04???CUDA?Version:?11.0?????| |------------------------------- ---------------------- ---------------------- |?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??Tesla?V100-SXM2...??Off??|?00000000:00:07.0?Off?|????????????????????0?| |?N/A???33C????P0????23W?/?300W?|??????0MiB?/?16160MiB?|??????0%??????Default?| |???????????????????????????????|??????????????????????|??????????????????N/A?| ------------------------------- ---------------------- ---------------------- ----------------------------------------------------------------------------- |?Processes:??????????????????????????????????????????????????????????????????| |??GPU???GI???CI????????PID???Type???Process?name??????????????????GPU?Memory?| |????????ID???ID???????????????????????????????????????????????????Usage??????| |??No?running?processes?found?????????????????????????????????????????????????| -----------------------------------------------------------------------------
4.查看节点上/dev下有没有nvidia设备。
$??ls?/dev/nvidia* /dev/nvidia0??/dev/nvidiactl /dev/nvidia-caps: nvidia-cap1??nvidia-cap2
可以看到 有一张GPU卡 说明安装是成功的。
缺点目前 基于容器安装NVIDIA驱动还具有如下的一些不足
集群中的节点必须保证是同一类操作系统 比如全是CentOS?或全是Ubuntu? 因为安装NVIDIA驱动是以daemonset方式部署到集群中 而该daemonset的应用容器只有一个 所以只能选择一个镜像。基于容器安装NVIDIA驱动的稳定性还有待提高 在挂载驱动的rootfs函数 _mount_rootfs函数 中可以看到 是将容器根目录挂载到/run/nvidia/driver目录 当容器被kill时 容器检测到退出信号会自动执行_unmount_rootfs函数进行unmount?/run/nvidia/driver目录操作 如果此时有另一个安装驱动的容器启动并执行nvidia?init就会造成挂载混乱 简单例子就是先使用“kubectl?delete?daemonset”删除安装驱动的daemonset 然后立即使用kubectl?apply命令再部署该daemonset 这样就会导致同一个节点上 一个容器在挂载驱动 另一个容器再卸载驱动 挂载驱动的容器就会报错 驱动正在使用中。碰到这种问题 只能重启节点解决。总结本篇文章花了较多篇幅介绍基于容器安装NVIDIA?GPU驱动的Dockerfile和nvidia-driver脚本 主要是希望读者阅读该文章后能够基于自己的场景需求 定制化镜像。
公司介绍 长沙营智信息技术有限公司旗下易撰网,2017年10月份上线以来,基于数据...
阿里巴巴、腾讯、支付宝、网易、IBM、谷歌、京东、 百度、滴滴等一线互联网公司...
大数据市场如今正在呈爆炸式增长。根据调研机构Markets and Markets公司的调查,...
人脸识别 是目前商业应用最成熟、最广泛的人工智能技术之一,成为开发者、企业接...
案例背景 永安稻香小镇的体验式数字农业基地是余杭街道依托“阿里以西10分钟”的...
【51CTO.com快译】 数据分析是对数据进行判断、细化、更改和建模的过程,目的是...
【51CTO.com快译】不知道您是否听说过软件架构师最讨厌意大利面这个梗?它是指软...
操作场景 您可以删除不需要的私有镜像。 删除私有镜像后,将无法找回,请谨慎操...
大家在开发Python的过程中,一定会遇到很多反斜杠的问题,很多人被反斜杠的数量...
本月DataWorks产品月刊为您带来 产品活动 1.参与阿里云DataWorks问卷调研 (Aliyu...