Kubernetes 設(shè)備插件

2022-05-27 10:13 更新

設(shè)備插件

FEATURE STATE: Kubernetes v1.10 [beta]

Kubernetes 提供了一個(gè) 設(shè)備插件框架,你可以用它來將系統(tǒng)硬件資源發(fā)布到 Kubelet。

供應(yīng)商可以實(shí)現(xiàn)設(shè)備插件,由你手動(dòng)部署或作為 DaemonSet 來部署,而不必定制 Kubernetes 本身的代碼。目標(biāo)設(shè)備包括 GPU、高性能 NIC、FPGA、 InfiniBand 適配器以及其他類似的、可能需要特定于供應(yīng)商的初始化和設(shè)置的計(jì)算資源。

注冊設(shè)備插件 

?kubelet ?提供了一個(gè) ?Registration ?的 gRPC 服務(wù):

service Registration {
	rpc Register(RegisterRequest) returns (Empty) {}
}

設(shè)備插件可以通過此 gRPC 服務(wù)在 kubelet 進(jìn)行注冊。在注冊期間,設(shè)備插件需要發(fā)送下面幾樣內(nèi)容:

  • 設(shè)備插件的 Unix 套接字。
  • 設(shè)備插件的 API 版本。
  • ?ResourceName ?是需要公布的。這里 ?ResourceName ?需要遵循 擴(kuò)展資源命名方案, 類似于 ?vendor-domain/resourcetype?。(比如 NVIDIA GPU 就被公布為 ?nvidia.com/gpu?。)

成功注冊后,設(shè)備插件就向 kubelet 發(fā)送它所管理的設(shè)備列表,然后 kubelet 負(fù)責(zé)將這些資源發(fā)布到 API 服務(wù)器,作為 kubelet 節(jié)點(diǎn)狀態(tài)更新的一部分。

比如,設(shè)備插件在 kubelet 中注冊了 ?hardware-vendor.example/foo? 并報(bào)告了 節(jié)點(diǎn)上的兩個(gè)運(yùn)行狀況良好的設(shè)備后,節(jié)點(diǎn)狀態(tài)將更新以通告該節(jié)點(diǎn)已安裝 2 個(gè) "Foo" 設(shè)備并且是可用的。

然后,用戶可以請(qǐng)求設(shè)備作為 Pod 規(guī)范的一部分。請(qǐng)求擴(kuò)展資源類似于管理請(qǐng)求和限制的方式, 其他資源,有以下區(qū)別:

  • 擴(kuò)展資源僅可作為整數(shù)資源使用,并且不能被過量使用
  • 設(shè)備不能在容器之間共享

示例

假設(shè) Kubernetes 集群正在運(yùn)行一個(gè)設(shè)備插件,該插件在一些節(jié)點(diǎn)上公布的資源為 ?hardware-vendor.example/foo?。 下面就是一個(gè) Pod 示例,請(qǐng)求此資源以運(yùn)行一個(gè)工作負(fù)載的示例:

---
apiVersion: v1
kind: Pod
metadata:
  name: demo-pod
spec:
  containers:
    - name: demo-container-1
      image: k8s.gcr.io/pause:2.0
      resources:
        limits:
          hardware-vendor.example/foo: 2
#
# 這個(gè) pod 需要兩個(gè) hardware-vendor.example/foo 設(shè)備
# 而且只能夠調(diào)度到滿足需求的節(jié)點(diǎn)上
#
# 如果該節(jié)點(diǎn)中有 2 個(gè)以上的設(shè)備可用,其余的可供其他 Pod 使用

設(shè)備插件的實(shí)現(xiàn) 

設(shè)備插件的常規(guī)工作流程包括以下幾個(gè)步驟:

  • 初始化。在這個(gè)階段,設(shè)備插件將執(zhí)行供應(yīng)商特定的初始化和設(shè)置, 以確保設(shè)備處于就緒狀態(tài)。
  • 插件使用主機(jī)路徑 ?/var/lib/kubelet/device-plugins/? 下的 Unix 套接字啟動(dòng) 一個(gè) gRPC 服務(wù),該服務(wù)實(shí)現(xiàn)以下接口:
  • service DevicePlugin {
          // GetDevicePluginOptions 返回與設(shè)備管理器溝通的選項(xiàng)。
          rpc GetDevicePluginOptions(Empty) returns (DevicePluginOptions) {}
    
          // ListAndWatch 返回 Device 列表構(gòu)成的數(shù)據(jù)流。
          // 當(dāng) Device 狀態(tài)發(fā)生變化或者 Device 消失時(shí),ListAndWatch
          // 會(huì)返回新的列表。
          rpc ListAndWatch(Empty) returns (stream ListAndWatchResponse) {}
    
          // Allocate 在容器創(chuàng)建期間調(diào)用,這樣設(shè)備插件可以運(yùn)行一些特定于設(shè)備的操作,
          // 并告訴 kubelet 如何令 Device 可在容器中訪問的所需執(zhí)行的具體步驟
          rpc Allocate(AllocateRequest) returns (AllocateResponse) {}
    
          // GetPreferredAllocation 從一組可用的設(shè)備中返回一些優(yōu)選的設(shè)備用來分配,
          // 所返回的優(yōu)選分配結(jié)果不一定會(huì)是設(shè)備管理器的最終分配方案。
          // 此接口的設(shè)計(jì)僅是為了讓設(shè)備管理器能夠在可能的情況下做出更有意義的決定。
          rpc GetPreferredAllocation(PreferredAllocationRequest) returns (PreferredAllocationResponse) {}
    
          // PreStartContainer 在設(shè)備插件注冊階段根據(jù)需要被調(diào)用,調(diào)用發(fā)生在容器啟動(dòng)之前。
          // 在將設(shè)備提供給容器使用之前,設(shè)備插件可以運(yùn)行一些諸如重置設(shè)備之類的特定于
          // 具體設(shè)備的操作,
          rpc PreStartContainer(PreStartContainerRequest) returns (PreStartContainerResponse) {}
    }
    Note:
    插件并非必須為 ?GetPreferredAllocation()? 或 ?PreStartContainer()? 提供有用 的實(shí)現(xiàn)邏輯,調(diào)用 ?GetDevicePluginOptions()? 時(shí)所返回的 ?DevicePluginOptions? 消息中應(yīng)該設(shè)置這些調(diào)用是否可用。?kubelet ?在真正調(diào)用這些函數(shù)之前,總會(huì)調(diào)用 ?GetDevicePluginOptions()? 來查看是否存在這些可選的函數(shù)。
  • 插件通過 Unix socket 在主機(jī)路徑 ?/var/lib/kubelet/device-plugins/kubelet.sock? 處向 kubelet 注冊自身。
  • 成功注冊自身后,設(shè)備插件將以服務(wù)模式運(yùn)行,在此期間,它將持續(xù)監(jiān)控設(shè)備運(yùn)行狀況, 并在設(shè)備狀態(tài)發(fā)生任何變化時(shí)向 kubelet 報(bào)告。它還負(fù)責(zé)響應(yīng) ?Allocate ?gRPC 請(qǐng)求。 在 ?Allocate ?期間,設(shè)備插件可能還會(huì)做一些設(shè)備特定的準(zhǔn)備;例如 GPU 清理或 QRNG 初始化。 如果操作成功,則設(shè)備插件將返回 ?AllocateResponse?,其中包含用于訪問被分配的設(shè)備容器運(yùn)行時(shí)的配置。 kubelet 將此信息傳遞到容器運(yùn)行時(shí)。

處理 kubelet 重啟

設(shè)備插件應(yīng)能監(jiān)測到 kubelet 重啟,并且向新的 kubelet 實(shí)例來重新注冊自己。 在當(dāng)前實(shí)現(xiàn)中,當(dāng) kubelet 重啟的時(shí)候,新的 kubelet 實(shí)例會(huì)刪除 ?/var/lib/kubelet/device-plugins? 下所有已經(jīng)存在的 Unix 套接字。 設(shè)備插件需要能夠監(jiān)控到它的 Unix 套接字被刪除,并且當(dāng)發(fā)生此類事件時(shí)重新注冊自己。

設(shè)備插件部署

你可以將你的設(shè)備插件作為節(jié)點(diǎn)操作系統(tǒng)的軟件包來部署、作為 DaemonSet 來部署或者手動(dòng)部署。

規(guī)范目錄 ?/var/lib/kubelet/device-plugins? 是需要特權(quán)訪問的,所以設(shè)備插件 必須要在被授權(quán)的安全的上下文中運(yùn)行。 如果你將設(shè)備插件部署為 DaemonSet,?/var/lib/kubelet/device-plugins? 目錄必須要在插件的 PodSpec 中聲明作為 卷(Volume) 被掛載到插件中。

如果你選擇 DaemonSet 方法,你可以通過 Kubernetes 進(jìn)行以下操作: 將設(shè)備插件的 Pod 放置在節(jié)點(diǎn)上,在出現(xiàn)故障后重新啟動(dòng)守護(hù)進(jìn)程 Pod,來進(jìn)行自動(dòng)升級(jí)。

API 兼容性

Kubernetes 設(shè)備插件支持還處于 beta 版本。所以在穩(wěn)定版本出來之前 API 會(huì)以不兼容的方式進(jìn)行更改。 作為一個(gè)項(xiàng)目,Kubernetes 建議設(shè)備插件開發(fā)者:

  • 注意未來版本的更改
  • 支持多個(gè)版本的設(shè)備插件 API,以實(shí)現(xiàn)向后/向前兼容性。

如果你啟用 DevicePlugins 功能,并在需要升級(jí)到 Kubernetes 版本來獲得較新的設(shè)備插件 API 版本的節(jié)點(diǎn)上運(yùn)行設(shè)備插件,請(qǐng)?jiān)谏?jí)這些節(jié)點(diǎn)之前先升級(jí)設(shè)備插件以支持這兩個(gè)版本。 采用該方法將確保升級(jí)期間設(shè)備分配的連續(xù)運(yùn)行。

監(jiān)控設(shè)備插件資源

FEATURE STATE: Kubernetes v1.15 [beta]

為了監(jiān)控設(shè)備插件提供的資源,監(jiān)控代理程序需要能夠發(fā)現(xiàn)節(jié)點(diǎn)上正在使用的設(shè)備, 并獲取元數(shù)據(jù)來描述哪個(gè)指標(biāo)與容器相關(guān)聯(lián)。 設(shè)備監(jiān)控代理暴露給 Prometheus 的指標(biāo)應(yīng)該遵循 Kubernetes Instrumentation Guidelines, 使用 ?pod?、?namespace ?和 ?container ?標(biāo)簽來標(biāo)識(shí)容器。

kubelet 提供了 gRPC 服務(wù)來使得正在使用中的設(shè)備被發(fā)現(xiàn),并且還未這些設(shè)備提供了元數(shù)據(jù):

// PodResourcesLister 是一個(gè)由 kubelet 提供的服務(wù),用來提供供節(jié)點(diǎn)上 
// Pods 和容器使用的節(jié)點(diǎn)資源的信息
service PodResourcesLister {
    rpc List(ListPodResourcesRequest) returns (ListPodResourcesResponse) {}
    rpc GetAllocatableResources(AllocatableResourcesRequest) returns (AllocatableResourcesResponse) {}
}

List gRPC 端點(diǎn)

這一 ?List ?端點(diǎn)提供運(yùn)行中 Pods 的資源信息,包括類似獨(dú)占式分配的 CPU ID、設(shè)備插件所報(bào)告的設(shè)備 ID 以及這些設(shè)備分配所處的 NUMA 節(jié)點(diǎn) ID。 此外,對(duì)于基于 NUMA 的機(jī)器,它還會(huì)包含為容器保留的內(nèi)存和大頁的信息。

// ListPodResourcesResponse 是 List 函數(shù)的響應(yīng)
message ListPodResourcesResponse {
    repeated PodResources pod_resources = 1;
}

// PodResources 包含關(guān)于分配給 Pod 的節(jié)點(diǎn)資源的信息
message PodResources {
    string name = 1;
    string namespace = 2;
    repeated ContainerResources containers = 3;
}

// ContainerResources 包含分配給容器的資源的信息
message ContainerResources {
    string name = 1;
    repeated ContainerDevices devices = 2;
    repeated int64 cpu_ids = 3;
    repeated ContainerMemory memory = 4;
}

// ContainerMemory 包含分配給容器的內(nèi)存和大頁信息
message ContainerMemory {
    string memory_type = 1;
    uint64 size = 2;
    TopologyInfo topology = 3;
}

// Topology 描述資源的硬件拓?fù)浣Y(jié)構(gòu)
message TopologyInfo {
        repeated NUMANode nodes = 1;
}

// NUMA 代表的是 NUMA 節(jié)點(diǎn)
message NUMANode {
        int64 ID = 1;
}

// ContainerDevices 包含分配給容器的設(shè)備信息
message ContainerDevices {
    string resource_name = 1;
    repeated string device_ids = 2;
    TopologyInfo topology = 3;
}
Note:
?List ?端點(diǎn)中的 ?ContainerResources ?中的 cpu_ids 對(duì)應(yīng)于分配給某個(gè)容器的專屬 CPU。 如果要統(tǒng)計(jì)共享池中的 CPU,?List ?端點(diǎn)需要與 ?GetAllocatableResources ?端點(diǎn)一起使用,如下所述:
  1. 調(diào)用 ?GetAllocatableResources ?獲取所有可用的 CPUs。
  2. 在系統(tǒng)中所有的 ?ContainerResources ?上調(diào)用 ?GetCpuIds?。
  3. 用 ?GetAllocatableResources ?獲取的 CPU 數(shù)減去 ?GetCpuIds ?獲取的 CPU 數(shù)。

GetAllocatableResources gRPC 端點(diǎn)

FEATURE STATE: Kubernetes v1.23 [beta]

端點(diǎn) ?GetAllocatableResources ?提供工作節(jié)點(diǎn)上原始可用的資源信息。 此端點(diǎn)所提供的信息比導(dǎo)出給 API 服務(wù)器的信息更豐富。

Note:
?GetAllocatableResources ?應(yīng)該僅被用于評(píng)估一個(gè)節(jié)點(diǎn)上的可分配的 資源。如果目標(biāo)是評(píng)估空閑/未分配的資源,此調(diào)用應(yīng)該與 List() 端點(diǎn)一起使用。 除非暴露給 kubelet 的底層資源發(fā)生變化 否則 ?GetAllocatableResources ?得到的結(jié)果將保持不變。 這種情況很少發(fā)生,但當(dāng)發(fā)生時(shí)(例如:熱插拔,設(shè)備健康狀況改變),客戶端應(yīng)該調(diào)用 ?GetAlloctableResources ?端點(diǎn)。 然而,調(diào)用 ?GetAllocatableResources ?端點(diǎn)在 cpu、內(nèi)存被更新的情況下是不夠的, Kubelet 需要重新啟動(dòng)以獲取正確的資源容量和可分配的資源。
// AllocatableResourcesResponses 包含 kubelet 所了解到的所有設(shè)備的信息
message AllocatableResourcesResponse {
    repeated ContainerDevices devices = 1;
    repeated int64 cpu_ids = 2;
    repeated ContainerMemory memory = 3;
}

從 Kubernetes v1.23 開始,?GetAllocatableResources ?被默認(rèn)啟用。 你可以通過關(guān)閉 ?KubeletPodResourcesGetAllocatable ?特性門控 來禁用。

在 Kubernetes v1.23 之前,要啟用這一功能,?kubelet ?必須用以下標(biāo)志啟動(dòng):

?--feature-gates=KubeletPodResourcesGetAllocatable=true ?

?ContainerDevices ?會(huì)向外提供各個(gè)設(shè)備所隸屬的 NUMA 單元這類拓?fù)湫畔ⅰ?nbsp;NUMA 單元通過一個(gè)整數(shù) ID 來標(biāo)識(shí),其取值與設(shè)備插件所報(bào)告的一致。 設(shè)備插件注冊到 kubelet 時(shí) 會(huì)報(bào)告這類信息。

gRPC 服務(wù)通過 ?/var/lib/kubelet/pod-resources/kubelet.sock? 的 UNIX 套接字來提供服務(wù)。 設(shè)備插件資源的監(jiān)控代理程序可以部署為守護(hù)進(jìn)程或者 DaemonSet。 規(guī)范的路徑 ?/var/lib/kubelet/pod-resources? 需要特權(quán)來進(jìn)入, 所以監(jiān)控代理程序必須要在獲得授權(quán)的安全的上下文中運(yùn)行。 如果設(shè)備監(jiān)控代理以 DaemonSet 形式運(yùn)行,必須要在插件的 PodSpec 中聲明將 ?/var/lib/kubelet/pod-resources? 目錄以 卷的形式被掛載到設(shè)備監(jiān)控代理中。

對(duì)“PodResourcesLister 服務(wù)”的支持要求啟用 ?KubeletPodResources ?特性門控。 從 Kubernetes 1.15 開始默認(rèn)啟用,自從 Kubernetes 1.20 開始為 v1。

設(shè)備插件與拓?fù)涔芾砥鞯募?

FEATURE STATE: Kubernetes v1.18 [beta]

拓?fù)涔芾砥魇?nbsp;Kubelet 的一個(gè)組件,它允許以拓?fù)鋵?duì)齊方式來調(diào)度資源。 為了做到這一點(diǎn),設(shè)備插件 API 進(jìn)行了擴(kuò)展來包括一個(gè) ?TopologyInfo ?結(jié)構(gòu)體。

message TopologyInfo {
 repeated NUMANode nodes = 1;
}

message NUMANode {
    int64 ID = 1;
}

設(shè)備插件希望拓?fù)涔芾砥骺梢詫⑻畛涞?nbsp;TopologyInfo 結(jié)構(gòu)體作為設(shè)備注冊的一部分以及設(shè)備 ID 和設(shè)備的運(yùn)行狀況發(fā)送回去。然后設(shè)備管理器將使用此信息來咨詢拓?fù)涔芾砥鞑⒆龀鲑Y源分配決策。

?TopologyInfo ?支持定義 ?nodes ?字段,允許為 ?nil?(默認(rèn))或者是一個(gè) NUMA 節(jié)點(diǎn)的列表。 這樣就可以使設(shè)備插件可以跨越 NUMA 節(jié)點(diǎn)去發(fā)布。

下面是一個(gè)由設(shè)備插件為設(shè)備填充 ?TopologyInfo ?結(jié)構(gòu)體的示例:

pluginapi.Device{ID: "25102017", Health: pluginapi.Healthy, Topology:&pluginapi.TopologyInfo{Nodes: []*pluginapi.NUMANode{&pluginapi.NUMANode{ID: 0,},}}}

設(shè)備插件示例

下面是一些設(shè)備插件實(shí)現(xiàn)的示例:


以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)