基於 Kubernetes 的 GPU 類型調度實現

2021-02-26 K8S中文社區

導讀:3 月 27 日,ACM 宣布深度學習的三位締造者——Yoshua Bengio、Yann LeCun 及 Geoffrey Hinton 獲得了 2018 年度的圖靈獎。與學術界相對應的,在工業界,人工智慧大潮也正洶湧奔來。除了衝擊人們的衣食住行醫,人工智慧也將成為企業轉型的顛覆性力量,是企業抓住下一輪創新發展的重要機遇。

作者: angao

來源: 才雲 Caicloud(ID:Caicloud2015)

現如今,隨著企業紛紛在機器學習和深度學習上加大投入,他們開始發現從頭構建一個 AI 系統並非易事。

以深度學習為例。對於深度學習來說,算力是一切的根本。為了用海量數據訓練性能更好的模型、加速整個流程,企業的 IT 系統需要具備快速、高效調用管理大規模 GPU 資源的能力。同時,由於算力資源十分昂貴,出於成本控制,企業也需要通過分布式訓練等方式最大化 GPU 資源利用率。

面對這類新要求,基於 Kubernetes 的雲原生技術為人工智慧提供了一種新的工作模式。憑藉其特性,Kubernetes 可以無縫將模型訓練、inference 和部署擴展到多雲 GPU 集群,允許數據科學家跨集群節點自動化多個 GPU 加速應用程式容器的部署、維護、調度和操作。

在 1.6 版本和 1.9 版本中,Kubernetes 先後提供了對 NVIDIA GPU、AMD GPU 容器集群管理調度的支持,進一步提高了對 GPU 等擴展資源進行統一管理和調度的能力。

但是,Kubernetes 作為新一代 AI 開發基礎也存在缺陷。為訓練任務分配算力資源時,它通常是隨機分配容器所在節點的 GPU,而不能指定使用某類 GPU 類型

雖然這對大部分深度學習模型訓練場景來說已經足夠了,但如果數據科學家希望能更靈活地使用更高性能的或某一類型的 GPU,Kubernetes 的能力就有些捉襟見肘了。

因此,在這篇文章中,我將介紹才雲科技在這一點上的經驗,談一談我們如何基於 Kubernetes 靈活實現 GPU 類型的調度。

問題:原生 Kubernetes 如何讓 Pod 使用指定類型的 GPU?

假設集群中有兩個節點有 GPU:節點 A 上有兩個 Tesla K80,節點 B 上有兩個 Tesla P100。Kubernetes 可以通過 Node Label 和 Node Selector,把 Pod 調度到合適的節點上,具體如下。

先給 Node 打上特定的 Label:

# Label your nodes with the accelerator type they have.
$ kubectl label nodes node-a accelerator=nvidia-tesla-k80
$ kubectl label nodes node-b accelerator=nvidia-tesla-p100

此時節點 A 如下:

$ kubectl describe node node-a
Name: node-a
Roles: <none>
Labels: ...
beta.kubernetes.io/arch=amd64
beta.kubernetes.io/os=linux
kubernetes.io/hostname=node-a
accelerator=nvidia-tesla-k80
Annotations: kubeadm.alpha.kubernetes.io/cri-socket: /var/run/dockershim.sock
.

當 Pod 想使用 NVIDIA Tesla K80 GPU 時,可以通過下面的方式:

apiVersion: v1
kind: Pod
metadata:
name: cuda-vector-add
spec:
containers:
- name: cuda-vector-add
image: "k8s.gcr.io/cuda-vector-add:v0.1"
resources:
limits:
nvidia.com/gpu: 1
nodeSelector:
accelerator: nvidia-tesla-k80

上述做法貌似解決了問題,但它其實治標不治本。

試想一下,如果用戶集群在同一個節點上掛載了多種 GPU,我們該如何實現篩選?如果用戶在同一個節點掛載了多個顯存不同的 NVIDIA Tesla K80,而且想使用大於 10GiB 顯存的 GPU,我們又該怎麼辦?

Kubernetes 的 Node Label 和 Node Selector 是沒法解決這些問題的。

在上遊社區,很多開發者也經常圍繞此類問題展開討論,但一直沒有實際可用的方案落地。儘管如此,社區還是提供了不少精彩見解,比如下面就是社區中討論最多的一個方案,我們的方案也借鑑了其中的部分設計。

type NodeStatus struct {

ComputeResources []ComputeResource
}
type ComputeResource struct {
// unique and deterministically generated. 「resourceName-propertyHash」 naming convention,
// where propertyHash is generated by calculating a hash over all resource properties
Name string
// raw resource name. E.g.: nvidia.com/nvidia-gpu
ResourceName string
// resource metadata received from device plugin.
// e.g., gpuType: k80, zone: us-west1-b
Properties map[string]string
// list of deviceIds received from device plugin.
// e.g., ["nvidia0", "nvidia1"]
Devices []string
// similar to the above but only contains allocatable devices.
AllocatableDevices []string
}

擴展資源通過 Device Plugin API 向 Kubelet 組件註冊其信息,隨後 Kubelet 組件可以通過接收到的擴展資源信息更新節點狀態,即上一步中的 ComputeResources 欄位;

調度器根據 ResourceClass 的定義過濾選擇合適的節點。調度器監聽 NodeStatus.ComputeResources 的變化並緩存節點上 ComputeResource 的分配信息,以便 ResourceClass 匹配合適的節點。

相比 Node Label 和 Node Selector,社區的方案更成熟。但不難看出,這個方案雖然可以修改 Kubernetes 核心代碼和核心 API,但作為一個倍受關注的技術問題的解決方案,它的進度非常緩慢,一直沒有得出更進一步的結論。

為了儘快實現在 Pod 使用指定類型的 GPU,我們在上遊社區方案的基礎上提出了一種全新方案。

它充分利用了 Kubernetes 的擴展性和插件機制,並遵循最小侵入和方便移植的設計原則。但是,出於簡化用戶使用和降低開發維護難度等原因,它還是修改了 Kubelet 和 Scheduler 組件。

同時,由於我們採用了多調度器的實現方式,所以方案中對於 Scheduler 組件的修改不影響現有集群和之後的版本升級,而 Kubelet 組件採用了向後兼容式修改,不影響已經在集群中運行的應用。

該方案不僅支持 GPU 資源,還支持包括 Infiniband、FPGAs 等擴展資源,它依賴以下現有 Kubernetes 工作機制:

在 1.6 版本中,Kubernetes 可以通過 ThirdPartyResource(TPR) 創建自定義資源,但在 1.7 版本中,它推出了 TPR 的替代方法: CustomResourceDefinition(CRD)。

CRD 允許自定義一個資源類型,因此開發人員不再需要修改 Kubernetes 核心 API 或通過 API server aggregation 增加新資源,開發和維護難度大大降低。

在我們的方案中,我們通過 CRD 定義了兩種資源:ExtendedResource 和 ResourceClass。ExtendedResource 描述了一種擴展資源,比如 NVIDIA GPU;ResourceClass 則定義了容器選擇哪種擴展資源,它的使用方式和 Kubernetes 中的 Extended Resource(詳見參考文獻)類似,用戶可以直接在容器中指定,就像使用 CPU 和 Memory 一樣。

下面是才雲方案的基本架構圖:

核心模塊一:Scheduler Extender。Scheduler Extender 利用 Scheduler 組件的擴展性,負責調度容器中使用了 ResourceClass 資源對象的 Pod。它通過查詢 ResourceClass 對象的定義過濾選擇節點上的 ExtendedResource 資源,從而找到合適的節點並綁定,並將合適的 ExtendedResource 寫到 Pod Annotation 中,供 Kubelet 組件使用。由於 Scheduler Extender 的擴展機制是通過 HTTP 的方式實現的,為了不影響集群的默認調度器性能,通過多調度器的方式為僅需要使用擴展資源的 Pod 提供調度,並且這種方式具有可移植性。

核心模塊二:Nvidia Device Plugin。此組件僅針對 NVIDIA GPU 擴展資源,除了負責與 Kubelet 組件通信,它還負責創建和維護 ExtendedResource 資源對象。

那麼,當同一節點上有多種不同類型的 GPU 時,這個方案是如何解決類型指定的呢?

我們假設有節點 A 上有兩張 GPU,一張是 NVIDIA Tesla K80,另一張是 NVIDIA Tesla P100。那麼這個節點上的 NVIDIA Device Plugin 會創建兩個 ExtendedResource 資源對象,分別描述這兩張卡的基本屬性,如型號、顯存、頻率等。同時,它也會向 Kubelet 註冊,把 A 節點上有兩張 GPU 告知節點上的 Kubelet。

這時,如果用戶想創建一個使用 K80 這張 GPU 的應用,他只需要創建一個 ResourceClass 資源,在 ResourceClass 中聲明使用型號為 NVIDIA Tesla K80 的 GPU(比如通過 Selector 的方式聲明),然後在容器中使用這個 ResourceClass 資源。

kind: ResourceClass
metadata:
name: nvidia.tesla.k80
spec:
selector:
matchLabels:
model: "NVIDIA Tesla K80"

kind: Pod
metadata:
name: example-pod
spec:
containers:
- name: example-container
resources:
limits:
nvidia.tesla.k80: 1

Kubernetes 默認調度器在經過一系列篩選過濾後,會調用 Scheduler Extender 的 Filter 方法,並將需要調度的 Pod 和過濾後的 NodeList 傳遞給 Filter,實現 ResourceClass 查找滿足需求的 ExtendedResource,從而找到合適的節點;

當調度器找到合適的節點後,調用 Scheduler Extender 的 Bind 方法,將 Pod 和 Node 綁定,並將合適的 ExtendedResource 資源對象寫到 Pod Annotation 中,供 Kubelet 組件使用。

當 Pod 和 Node 綁定後,節點上的 Kubelet 組件則開始創建容器,並通過 Pod Annotation 獲取容器需要使用哪塊 GPU 的信息,然後通過 Device Plugin API 調用 NVIDIA Device Plugin 的 Allocate 方法。

Allocate 方法參數是容器使用的 GPU DeviceID,它通過 DeviceID 查詢 GPU 的信息作為環境變量,返回給 Kubelet 用以真正創建 Pod。

從上述流程中可以看出,當我們想使用特定類型的 GPU 或者某一類 GPU 時,我們只需聲明該類型的 ResourceClass 資源對象,比如:

kind: ResourceClass
metadata:
name: nvidia.high.mem
spec:
selector:
- matchExpressions:
- key: "memory"
operator: "Gt"
values:
- "10GiB"

更進一步,我們可以通過實現一個 Controller 監聽集群中的 ExtendedResource 資源,自動為一種類型的 ExtendedResource 創建一個 ResourceClass 對象,為用戶提供一些默認規則的 ResourceClass 資源對象。

在實際生產集群環境中,我們不僅需要滿足不同應用對資源的使用,更是要做到不同應用對資源使用的限制,以及對不同的 namespace 分配不同的資源。而在 Kubernetes 中,我們一般會通過 ResourceQuota 資源對象來限制不同 namespace 的資源,例如:

kind: ResourceQuota
metadata:
name: example-quota
namespace: system
spec:
hard:
cpu: "10"
memory: 20Gi
nvidia.com/gpu: "5"

從上面的 ResourceQuota 定義裡,我們可以看到 default 命名空間可以使用 5 塊 NVIDIA GPU,但它並不限制具體該使用哪種類型的 GPU。

那麼,我們該如何實現對 GPU 類型的限制呢?

首先,GPU 這類擴展資源使用是標量,所以我們對標量資源的限制只能做到整數個數的限制。

其次,從上述方案中,我們知道一種 ResourceClass 代表了一種類型的擴展資源,因此對擴展資源的限制其實就是對 ResourceClass 的限制。

這樣理解之後,問題就很簡單明了了。下面直接給出相應的 ResourceQuota:

kind: ResourceQuota
metadata:
name: example-quota
namespace: system
spec:
hard:
cpu: "10"
memory: 20Gi
nvidia.tesla.k80: "5"

除了 GPU 類型調度,這個方案其實也可以解決 GPU 共享問題。這同樣是上遊社區的一個熱門討論話題。

ExtendedResource 資源中包含著 GPU 的頻率、顯存等信息,當多個容器想使用同一塊 GPU 時,我們可以定義一個 ResourceClass 資源對象,在 ResourceClass 中聲明使用多少顯存(這裡共享的是顯存)。這樣,應用部署時,我們只要在容器中聲明使用該 ResourceClass 資源即可,之後 Scheduler Extender 會過濾符合條件的 ExtendedResource 對象,綁定到合適的節點上。

如果要實現資源共享,我們可能需要在 ExtendedResource 中記錄顯存的用量情況,供調度參考。當然,這裡沒有考慮到資源的隔離和限制的問題,這需要單獨實現和更進一步的討論。

以上就是我們在探索如何讓 Pod 使用指定類型的 GPU 上得出的解決方案。如果你對這個主題感興趣,或有新想法,歡迎留言一起討論。

參考文獻

1.Extended Resource:https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/#extended-resources

2.CustomResourceDefinition: https://kubernetes.io/docs/concepts/extend-kubernetes/api-extension/custom-resources/#customresourcedefinitions

3.Multiple Schedulers: https://kubernetes.io/docs/tasks/administer-cluster/configure-multiple-schedulers/

4.Resource Quotas: https://kubernetes.io/docs/concepts/policy/resource-quotas/

5.New Resource API: https://github.com/vikaschoudhary16/community/blob/55ecd0aa37acd748c19493b82956dfed5191c0d9/keps/sig-node/00014-resource-api.md

Kubernetes線下實戰培訓,採用3+1+1新的培訓模式(3天線下實戰培訓,1年內可免費再次參加,每期前10名報名,可免費參加價值3600元的線上直播班),資深一線講師,實操環境實踐,現場答疑互動,培訓內容覆蓋:Docker方面:Docker架構、鏡像、數據存儲、網絡、以及最佳實踐。Kubernetes實戰內容,Kubernetes設計、Pod、常用對象操作,Kuberentes調度系統、QoS、Helm、網絡、存儲、CI/CD、日誌監控等。

上海:5月17-19日

報名:https://www.bagevent.com/event/2409655

深圳:5月24-26日

報名:https://www.bagevent.com/event/2409699

相關焦點

  • 踢掉Docker 後,Kubernetes 還能歡快地跑 GPU?
    我這裡簡單描述下,Kubernetes 是通過 CRI 來對接容器運行時的,而 Docker 本身是沒有實現 CRI 的,所以 Kubernetes 內置了一個 「為 Docker 提供 CRI 支持」 的 dockershim 組件。現在 Kubernetes 宣布不再維護這個組件了。
  • SkyForm ECP基於Kubernetes構建AIaaS平臺
    社區中也出現了很多tensorflow on kubernetes的方案,比如最早使用jinja模板的方案(https://github.com/tensorflow/ecosystem/tree/master/kubernetes),以及後來的kubeflow(https://github.com/google/kubeflow)和Tensorflow/k8s(https://github.com
  • 機器學習平臺在Kubernetes上的實踐
    任務開發所用的容器化環境,在底層Kubernetes上是通過StatefulSet類型實現,對應資源編排文件如下(已精簡細節):kind: StatefulSet apiVersion: apps/v1 metadata: name: ${name} namespace: "${namespace}" spec: replicas: 1 selector: matchLabels
  • 深入解析Kubernetes service 概念
    一旦有service資源發生變動(增刪改查)kube-proxy可以及時轉化為能夠調度到後端Pod節點上的規則,這個規則可以是iptables也可以是ipvs,取決於service實現方式Kubernetes 三大IPNode Network 節點網絡 節點網絡地址是配置在節點網絡之上Pod Network
  • kubernetes面試題匯總
    kubernetes是什麼?Kubernetes是什麼?kubernetes,簡稱K8s,是用8代替8個字符「ubernete」而成的縮寫。kubernetes面試題匯總1.kubernetes是什麼?Kubernetes(k8s)是自動化容器操作的開源平臺,這些操作包括部署,調度和節點集群間擴展。如果你曾經用過Docker容器技術部署容器,那麼可以將Docker看成Kubernetes內部使用的低級別組件。
  • Kubernetes scheduler學習筆記
    > 遍歷nodeInfo中Node的所有狀況:如果Node類型為ready,並且狀態不是True,則認為結點為notReady如果Node類型為OutOfDisk,並且狀態不是False,則認為結點OutOfDisk如果Node類型為NetworkUnavailable
  • Kubernetes 調度詳解!
    本文將對 Kubernetes Scheduler 進行深入研究,首先概述一般的調度以及具有親和力(affinity)和 taint 的驅逐調度,然後討論調度程序的瓶頸以及生產中可能遇到的問題,最後研究如何微調調度程序的參數以適合集群。
  • 一篇讀懂Kubernetes Scheduler擴展功能
    但是在實際生產環境中我們常常會需要一些特殊的調度策略,比如批量調度(aka coscheduling or gang scheduling),這是kubernetes默認調度策略所無法滿足的,這個時候就需要我們對scheduler進行擴展來實現這個功能了。
  • Kubernetes-應用部署問題定位和處理
    另外為了能夠在Kubernetes集群外訪問MySQL資料庫,對外暴露了MySQL master的NodePort類型服務,服務名稱為mysql-0-svc。2、調試Pods在調試Pod之前,通過kubectl get pods命令查看一下Pod的運行狀態。
  • 使用 Kubernetes 最易犯的 10 個錯誤
    在調度 Pod 時,你需要根據很多調度約束條件(如 Pod 和節點的親和性,汙點和容忍,資源請求,QoS 等等)來進行決策。如果一個外部自動伸縮器不能理解這些約束,可能會帶來很大的麻煩。假設一個新的 pod 要被調度,但是所有可用的 CPU 都被佔用了,而且這個 pod 現在處於掛起狀態。
  • Kubernetes 1.8.0 版本發布
    本地持久化存儲由於需要與調度討論設計,進展較慢。快照現在處於 prototype 階段,Kubernetes 對使用中的 Volume 快照會出現不一致的情況。另外一個值得注意的情況是 SIG 的融合。目前,Kubernetes Node、Storage、Scheduling 等小組在合作形成 Resource Group,旨在使 Kubernetes 能夠支持更多類型的應用。
  • kubernetes-issue-1:ephemeral-storage引發的pod驅逐問題
    ephemeral-storage(短暫存儲)的概念和作用ephemeral-storage是為了管理和調度Kubernetes中運行的應用的短暫存儲。在每個Kubernetes的節點上,kubelet的根目錄(默認是/var/lib/kubelet)和日誌目錄(/var/log)保存在節點的主分區上,這個分區同時也會被Pod的EmptyDir類型的volume、容器日誌、鏡像的層、容器的可寫層所佔用。
  • Kubernetes決定棄用Docker,到底會影響到誰?
    Docker原本有個原生的調度引擎——Swarm,幾年前在調度編排領域,還是Kubernetes、Mesos、Swarm三者並存,Kubernetes最終勝出,但Docker仍有「繼續向上做一層的意願」。
  • 源碼視角,全方位學習Kubernetes scheduler
    > 遍歷nodeInfo中Node的所有狀況:如果Node類型為ready,並且狀態不是True,則認為結點為notReady如果Node類型為OutOfDisk,並且狀態不是False,則認為結點OutOfDisk如果Node類型為NetworkUnavailable,並且狀態不是False,則認為結點狀態為:NetworkUnavailable
  • Docker+Kubernetes是容器創建與編排調度的利器
    關於Docker,它源自PaaS提供商dotCloud開源的一個基於LXC的高級容器引擎, 基於go語言並遵從Apache2.0協議。而且由於Docker其基於LXC的輕量級虛擬化,docker相比KVM之類最明顯的特點就是啟動快,資源佔用小。關於K8S,它是Google在2014年啟動的開源項目,用於容器編排、調度和管理,支持資源調度、自動部署、服務發現、擴容縮容和應用容器化管理。
  • Kubernetes RBAC角色權限控制
    Kubernetes RBAC權限控制Kubernetes在Kubernetes中所有的API對象都保存在ETCD裡,可是,對這些API對象的操作,卻一定是通過訪問kube-apiserver實現的。我們需要APIServer來幫助我們授權工作,而在Kubernetes項目中,負責完成授權(Authorization)的工作機制就是RBQC:基於角色的訪問控制 (Role-Based Access Control)RBAC是基於角色的訪問控制 (Role-Based Access Control) 在RBAC
  • Kubernetes ELK 日誌收集
    Kubernetes EFK日誌收集Kubernetes日誌收集架構Kubernetes集群本身不提供收集日誌的解決方案,目前基於ELK日誌收集的方案主要有三種在節點運行一個agent收集日誌通過應用程式收集日誌除了上面通過宿主機運行agent採集和在容器中創建sidecar外,還有一種方案就是在代碼層面實現,通過代碼層面直接將日誌輸出到對應的後端存儲更多內容可以參閱官方文檔https://v1-15.docs.kubernetes.io/zh/docs/concepts/cluster-administration
  • Kubernetes集群的監控報警策略最佳實踐
    監控有助於減少對突發事件的響應時間,實現對系統問題的檢測、故障排除和調試。在基礎設施高度動態的雲時代,監控也是容量規劃的基礎。有效的警報是監控策略的基石。當你轉向容器和Kubernetes編排環境,你的警報策略也需要演進。
  • [譯]將 Kubernetes 擴展至7500個節點
    我們轉而使用基於別名的 IP 地址的另一個原因是,在我們最大的集群上,任何時候都可能有大約20萬個 IP 地址正在使用。當我們測試基於路由的 Pod 網絡時,我們發現可以有效使用的路由數量存在明顯的限制。避免封裝會增加對底層 SDN 或路由引擎的需求,但它使我們的網絡配置變得簡單。
  • Kubernetes的Local Persistent Volumes使用小記
    最重要的區別,就是Local PV和具體節點是有關聯的,這意味著使用了Local PV的pod,重啟多次都會被Kubernetes scheduler調度到同一節點,而如果用的是HostPath Volume,每次重啟都可能被Kubernetes scheduler調度到新的節點,然後使用同樣的本地路徑;當我們要用HostPath Volume的時候