Nvidia GPU如何在Kubernetes 里工作
Nvidia GPU如何在Kubernetes 里工作
本文介紹Nvidia GPU設(shè)備如何在Kubernetes中管理調(diào)度。 整個工作流程分為以下兩個方面:
- 如何在容器中使用GPU
- Kubernetes 如何調(diào)度GPU
如何在容器中使用GPU
想要在容器中的應(yīng)用可以操作GPU, 需要實兩個目標
詳細介紹可見:?https://devblogs.nvidia.com/gpu-containers-runtime/
Nvidia-docker
GitHub:?https://github.com/NVIDIA/nvidia-docker
Nvidia提供Nvidia-docker項目,它是通過修改Docker的Runtime為nvidia runtime工作,當我們執(zhí)行?nvidia-docker create?或者?nvidia-docker run?? ?時,它會默認加上?--runtime=nvidia?參數(shù)。將runtime指定為nvidia。
當然,為了方便使用,可以直接修改Docker daemon 的啟動參數(shù),修改默認的 Runtime為?nvidia-container-runtime?
gpu-containers-runtime
GitHub:??https://github.com/NVIDIA/nvidia-container-runtime
gpu-containers-runtime? 是一個NVIDIA維護的容器 Runtime,它在runc的基礎(chǔ)上,維護了一份?Patch, 我們可以看到這個patch的內(nèi)容非常簡單, 唯一做的一件事情就是在容器啟動前,注入一個?prestart? 的hook?到容器的Spec中(hook的定義可以查看?OCI規(guī)范?)。這個hook 的執(zhí)行時機是在容器啟動后(Namespace已創(chuàng)建完成),容器自定義命令(Entrypoint)啟動前。nvidia-containers-runtime?定義的 prestart 的命令很簡單,只有一句??nvidia-container-runtime-hook prestart??
gpu-containers-runtime-hook
GitHub:?https://github.com/NVIDIA/nvidia-container-runtime/tree/master/hook/nvidia-container-runtime-hook?
gpu-containers-runtime-hook? 是一個簡單的二進制包,定義在Nvidia container runtime的hook中執(zhí)行。 目的是將當前容器中的信息收集并處理,轉(zhuǎn)換為參數(shù)調(diào)用?nvidia-container-cli? 。
主要處理以下參數(shù):
- 根據(jù)環(huán)境變量?NVIDIA_VISIBLE_DEVICES?判斷是否會分配GPU設(shè)備,以及掛載的設(shè)備ID。如果是未指定或者是?void?,則認為是非GPU容器,不做任何處理。? ?否則調(diào)用?nvidia-container-cli?, GPU設(shè)備作為?--devices? 參數(shù)傳入
- ?環(huán)境環(huán)境變量?NVIDIA_DRIVER_CAPABILITIES?判斷容器需要被映射的 Nvidia 驅(qū)動庫。
- 環(huán)境變量?NVIDIA_REQUIRE_*? 判斷GPU的約束條件。 例如?cuda>=9.0?等。 作為?--require=?參數(shù)傳入
- 傳入容器進程的Pid
gpu-containers-runtime-hook? 做的事情,就是將必要的信息整理為參數(shù),傳給?nvidia-container-cli configure?并執(zhí)行。
nvidia-container-cli
nvidia-container-cli 是一個命令行工具,用于配置Linux容器對GPU 硬件的使用。支持
- list:? 打印 nvidia 驅(qū)動庫及路徑
- info:? 打印所有Nvidia GPU設(shè)備
- configure: 進入給定進程的命名空間,執(zhí)行必要操作保證容器內(nèi)可以使用被指定的GPU以及對應(yīng)能力(指定 Nvidia 驅(qū)動庫)。 configure是我們使用到的主要命令,它將Nvidia 驅(qū)動庫的so文件 和 GPU設(shè)備信息, 通過文件掛載的方式映射到容器中。
代碼如下:?https://github.com/NVIDIA/libnvidia-container/blob/master/src/cli/configure.c#L272
/* Mount the driver and visible devices. */if (perm_set_capabilities(&err, CAP_EFFECTIVE, ecaps[NVC_MOUNT], ecaps_size(NVC_MOUNT)) < 0) {warnx("permission error: %s", err.msg);goto fail;}if (nvc_driver_mount(nvc, cnt, drv) < 0) {warnx("mount error: %s", nvc_error(nvc));goto fail;}for (size_t i = 0; i < dev->ngpus; ++i) {if (gpus[i] != NULL && nvc_device_mount(nvc, cnt, gpus[i]) < 0) {warnx("mount error: %s", nvc_error(nvc));goto fail;}}如果對其他模塊感興趣,可以在?https://github.com/NVIDIA/libnvidia-container? 閱讀代碼。
以上就是一個nvidia-docker的容器啟動的所有步驟。
當我們安裝了nvidia-docker, 我們可以通過以下方式啟動容器
docker run --rm -it -e NVIDIA_VISIBLE_DEVICES=all ubuntu:18.04在容器中執(zhí)行?mount? 命令,可以看到名為?libnvidia-xxx.so?和?/proc/driver/nvidia/gpus/xxx? 映射到容器中。 以及?nvidia-smi?和?nvidia-debugdump?等nvidia工具。
# mount ## .... /dev/vda1 on /usr/bin/nvidia-smi type ext4 (ro,nosuid,nodev,relatime,data=ordered) /dev/vda1 on /usr/bin/nvidia-debugdump type ext4 (ro,nosuid,nodev,relatime,data=ordered) /dev/vda1 on /usr/bin/nvidia-persistenced type ext4 (ro,nosuid,nodev,relatime,data=ordered) /dev/vda1 on /usr/bin/nvidia-cuda-mps-control type ext4 (ro,nosuid,nodev,relatime,data=ordered) /dev/vda1 on /usr/bin/nvidia-cuda-mps-server type ext4 (ro,nosuid,nodev,relatime,data=ordered) /dev/vda1 on /usr/lib/x86_64-linux-gnu/libnvidia-ml.so.396.37 type ext4 (ro,nosuid,nodev,relatime,data=ordered) /dev/vda1 on /usr/lib/x86_64-linux-gnu/libnvidia-cfg.so.396.37 type ext4 (ro,nosuid,nodev,relatime,data=ordered) /dev/vda1 on /usr/lib/x86_64-linux-gnu/libcuda.so.396.37 type ext4 (ro,nosuid,nodev,relatime,data=ordered) /dev/vda1 on /usr/lib/x86_64-linux-gnu/libnvidia-opencl.so.396.37 type ext4 (ro,nosuid,nodev,relatime,data=ordered) /dev/vda1 on /usr/lib/x86_64-linux-gnu/libnvidia-ptxjitcompiler.so.396.37 type ext4 (ro,nosuid,nodev,relatime,data=ordered) /dev/vda1 on /usr/lib/x86_64-linux-gnu/libnvidia-fatbinaryloader.so.396.37 type ext4 (ro,nosuid,nodev,relatime,data=ordered) /dev/vda1 on /usr/lib/x86_64-linux-gnu/libnvidia-compiler.so.396.37 type ext4 (ro,nosuid,nodev,relatime,data=ordered) devtmpfs on /dev/nvidiactl type devtmpfs (ro,nosuid,noexec,relatime,size=247574324k,nr_inodes=61893581,mode=755) devtmpfs on /dev/nvidia-uvm type devtmpfs (ro,nosuid,noexec,relatime,size=247574324k,nr_inodes=61893581,mode=755) devtmpfs on /dev/nvidia-uvm-tools type devtmpfs (ro,nosuid,noexec,relatime,size=247574324k,nr_inodes=61893581,mode=755) devtmpfs on /dev/nvidia4 type devtmpfs (ro,nosuid,noexec,relatime,size=247574324k,nr_inodes=61893581,mode=755) proc on /proc/driver/nvidia/gpus/0000:00:0e.0 type proc (ro,nosuid,nodev,noexec,relatime)我們可以執(zhí)行nvidia-smi查看容器中被映射的GPU卡
Kubernetes 如何調(diào)度GPU
之前我們介紹了如何在容器中使用Nvidia GPU卡。 那么當一個集群中有成百上千個節(jié)點以及GPU卡,我們的問題變成了如何管理和調(diào)度這些GPU。
Device plugin
Kubernetes 提供了Device Plugin 的機制,用于異構(gòu)設(shè)備的管理場景。原理是會為每個特殊節(jié)點上啟動一個針對某個設(shè)備的DevicePlugin pod, 這個pod需要啟動grpc服務(wù), 給kubelet提供一系列接口。
type DevicePluginClient interface {// GetDevicePluginOptions returns options to be communicated with Device// ManagerGetDevicePluginOptions(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*DevicePluginOptions, error)// ListAndWatch returns a stream of List of Devices// Whenever a Device state change or a Device disapears, ListAndWatch// returns the new listListAndWatch(ctx context.Context, in *Empty, opts ...grpc.CallOption) (DevicePlugin_ListAndWatchClient, error)// Allocate is called during container creation so that the Device// Plugin can run device specific operations and instruct Kubelet// of the steps to make the Device available in the containerAllocate(ctx context.Context, in *AllocateRequest, opts ...grpc.CallOption) (*AllocateResponse, error)// PreStartContainer is called, if indicated by Device Plugin during registeration phase,// before each container start. Device plugin can run device specific operations// such as reseting the device before making devices available to the containerPreStartContainer(ctx context.Context, in *PreStartContainerRequest, opts ...grpc.CallOption) (*PreStartContainerResponse, error) }DevicePlugin 注冊一個 socket 文件到?/var/lib/kubelet/device-plugins/?目錄下,kubelet 通過這個目錄下的socket文件向?qū)?yīng)的 Device plugin 發(fā)送grpc請求。
本文不過多介紹Device Plugin 的設(shè)計, 感興趣可以閱讀這篇文章:?https://yq.aliyun.com/articles/498185
Nvidia plugin
Github:?https://github.com/NVIDIA/k8s-device-plugin
為了能夠在Kubernetes中管理和調(diào)度GPU, Nvidia提供了Nvidia GPU的Device Plugin。 主要功能如下
- 支持ListAndWatch 接口,上報節(jié)點上的GPU數(shù)量
- 支持Allocate接口, 支持分配GPU的行為。?
Allocate 接口只做了一件事情,就是給容器加上?NVIDIA_VISIBLE_DEVICES? 環(huán)境變量。?https://github.com/NVIDIA/k8s-device-plugin/blob/v1.11/server.go#L153
// Allocate which return list of devices. func (m *NvidiaDevicePlugin) Allocate(ctx context.Context, reqs *pluginapi.AllocateRequest) (*pluginapi.AllocateResponse, error) {devs := m.devsresponses := pluginapi.AllocateResponse{}for _, req := range reqs.ContainerRequests {response := pluginapi.ContainerAllocateResponse{Envs: map[string]string{"NVIDIA_VISIBLE_DEVICES": strings.Join(req.DevicesIDs, ","),},}for _, id := range req.DevicesIDs {if !deviceExists(devs, id) {return nil, fmt.Errorf("invalid allocation request: unknown device: %s", id)}}responses.ContainerResponses = append(responses.ContainerResponses, &response)}return &responses, nil }前面我們提到, Nvidia的?gpu-container-runtime? 根據(jù)容器的?NVIDIA_VISIBLE_DEVICES?環(huán)境變量,會決定這個容器是否為GPU容器,并且可以使用哪些GPU設(shè)備。 而Nvidia GPU device plugin做的事情,就是根據(jù)kubelet 請求中的GPU DeviceId, 轉(zhuǎn)換為?NVIDIA_VISIBLE_DEVICES?環(huán)境變量返回給kubelet, kubelet收到返回內(nèi)容后,會自動將返回的環(huán)境變量注入到容器中。當容器中包含環(huán)境變量,啟動時?gpu-container-runtime? 會根據(jù)?NVIDIA_VISIBLE_DEVICES?里聲明的設(shè)備信息,將設(shè)備映射到容器中,并將對應(yīng)的Nvidia Driver Lib 也映射到容器中。
總體流程
整個Kubernetes調(diào)度GPU的過程如下:
- GPU Device plugin 部署到GPU節(jié)點上,通過?ListAndWatch? 接口,上報注冊節(jié)點的GPU信息和對應(yīng)的DeviceID。?
- 當有聲明?nvidia.com/gpu? 的GPU Pod創(chuàng)建出現(xiàn),調(diào)度器會綜合考慮GPU設(shè)備的空閑情況,將Pod調(diào)度到有充足GPU設(shè)備的節(jié)點上。
- 節(jié)點上的kubelet 啟動Pod時,根據(jù)request中的聲明調(diào)用各個Device plugin 的 allocate接口, 由于容器聲明了GPU。 kubelet 根據(jù)之前?ListAndWatch?接口收到的Device信息,選取合適的設(shè)備,DeviceID 作為參數(shù),調(diào)用GPU DevicePlugin的?Allocate?接口
- GPU DevicePlugin ,接收到調(diào)用,將DeviceID 轉(zhuǎn)換為?NVIDIA_VISIBLE_DEVICES?環(huán)境變量,返回kubelet
- kubelet將環(huán)境變量注入到Pod, 啟動容器
- 容器啟動時,?gpu-container-runtime?調(diào)用?gpu-containers-runtime-hook?
- gpu-containers-runtime-hook? 根據(jù)容器的?NVIDIA_VISIBLE_DEVICES?環(huán)境變量,轉(zhuǎn)換為?--devices?參數(shù),調(diào)用?nvidia-container-cli prestart??
- nvidia-container-cli?根據(jù)?--devices?,將GPU設(shè)備映射到容器中。 并且將宿主機的Nvidia Driver Lib 的so文件也映射到容器中。 此時容器可以通過這些so文件,調(diào)用宿主機的Nvidia Driver。
原文鏈接
本文為云棲社區(qū)原創(chuàng)內(nèi)容,未經(jīng)允許不得轉(zhuǎn)載。
總結(jié)
以上是生活随笔為你收集整理的Nvidia GPU如何在Kubernetes 里工作的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 阿里云马劲:保证云产品持续拥有稳定性的实
- 下一篇: 黑科技揭秘:百种异常随机注入,专有云为何