天天看點

Kubernetes的Device Plugin機制源碼解析(1)

首先明确目标:

并不是搞懂Kubelet的所有實作,而是希望了解Device Manager如何在資源發現,Pod建立,裝置健康檢查過程中所做的工作以及其如何與Kubelet互動,是以我們會忽略掉與Device Manager無關的操作。

這裡是我閱讀代碼的原則和一些體會:

了解接口,搞清楚和外部子產品的互動

了解實作接口的結構體

從使用者場景的角度将方法調用和資料結構關聯起來,好比将劇情和人物串聯起來,了解了人物設定後,就可以更快速切入代碼的調用過程;而代碼調用的閱讀也可以加深對資料結構設計的了解

Kubernetes的代碼比較複雜,很難一下就搞清楚每一個資料結構定義的目的和用途,這時我們可以把問題和假設記下來,不要過分糾結,可以在後面求證。書讀百變其意自現,代碼也是一樣,當你逐漸熟悉了代碼的脈絡的時候,有些問題也會迎刃而解

由于Device Manager工作在Kubelet中,對于Kubelet的源碼通篇的了解是了解具體子產品運作機制的基礎

P.S. 本文解析的Kubernetes源碼版本是1.9.3

DeviceManager的核心代碼都在 <code>pkg/kubelet/cm/deviceplugin</code>下

Kubernetes的Device Plugin機制源碼解析(1)

所在的檔案

具體定義:

從注釋中可以看到DeviceManager負責管理節點上運作的所有裝置插件,這裡分别定義了可以和外界互動的6個方法:

<code>Start()</code>和<code>Stop()</code>分别是啟動裝置插件注冊和停止服務,這其實K8S中的常見套路

<code>Devices()</code>以map的形式列出device清單

以下3個方法是比較核心的工作:

<code>Allocate()</code>為Pod配置設定可用的裝置,并且調用裝置插件進行所需的裝置初始化

<code>GetDeviceRunContainerOptions()</code>獲得為容器配置裝置所需要的參數,比如Environment,Volume和Device,這個方法會用于建立容器的過程中

當然要更清楚的了解,還需要結合具體場景中的調用鍊路進行了解。這裡DeviceManager接口有兩個實作分别是:<code>MangerImpl</code> 和 <code>ManagerStub</code>, ManagerStub實際上是一個空實作,無需細看。下面簡單了解一下 <code>MangerImpl</code>的實作

在ManagerImpl的定義和注釋中,可以大緻猜測它在做三件事:

提供grpc的服務,支援多個Device Plugin的注冊

為Device Plugin提供回調函數<code>monitorCallback</code>,當裝置的狀态發生變化時,可以讓Device Manager被通知,進而做一些相應的處理。比如當某個裝置無法正常工作時,就需要将節點上可用資源總數減去一個

裝置的配置設定和管理,具體講就是記錄某種裝置一共有哪幾個,已經配置設定出去的是哪幾個。從這裡看,Device Plugin需要為每個裝置提供一個UUID, 這個UUID需要在本節點唯一并且不可改變,而Device Manager要做的事情就是維護這個UUID的集合,并且負責裝置更新和配置設定

這裡主要涉及五個場景:

Device Manager的初始化和啟動

接收Device Plugin的endpoint注冊,并且向Endpoint查詢Device ID清單

定時上報節點上的裝置資訊

建立Pod時,将裝置資訊與Pod結合,生成建立容器所需要的配置(Environment, Device, Volume)

當裝置狀态不健康的時候,通知Kubelet更新可用裝置的狀态

本文首先分析場景一:Device Manager的初始化和啟動過程

Kubernetes的代碼量巨大,但是細看每個子產品的啟動流程都有比較相似的套路,以Kubelet為例:

建立一個 <code>KubeletServer</code> 配置對象,這個對象儲存着 kubelet 運作需要的所有配置資訊

解析指令行,根據指令行的參數更新 <code>KubeletServer</code>

根據 <code>KubeletServer</code> 的配置建立真正的 <code>kubelet</code> 運作時對象

通過<code>Start()</code>方法啟動該 <code>kubelet</code> 運作時對象

而DeviceManger的初始化就是發生在步驟3和步驟4

Kubernetes的Device Plugin機制源碼解析(1)

<code>app.kubelet</code>對應的是<code>cmd/kubelet/kubelet.go</code>

<code>server</code>對應的是<code>cmd/kubelet/app/server.go</code>

<code>kubelet</code>對應的是<code>pkg/kubelet/kubelet.go</code>

<code>container_manager_linux</code>對應的是<code>pkg/kubelet/cm/container_manager_linux.go</code>

<code>device.manager</code>對應的是<code>pkg/kubelet/cm/deviceplugin/manager.go</code>

以上時序圖就是Kubelet如何初始化和啟動DeviceManager的流程(為了友善了解,這裡會忽略和DeviceManager無關的方法)

可以看到<code>server</code>中<code>run()</code>方法做兩件事情:<code>NewMainKubelet</code>和<code>startKubelet</code>,而Device Manager的初始化與啟動也是在這兩個步驟中完成,同時啟動grpc注冊服務,這時Device Plugin就可以注冊進來。

<code>DeviceManger</code>的初始化是在建立<code>ContainerManager</code>對象時完成的,而<code>ContainerManager</code>對象作為<code>NewMainKubelet</code>建立<code>Kubelet</code>運作時對象的參數,

實際定義在:<code>pkg/kubelet/cm/container_manager_linux.go</code>

由于這個功能目前還比較新,需要通過feature gate打開, 即配置 --feature-gates=DevicePlugins=true,預設該功能是關閉的。當該功能打開時會調用<code>deviceplugin.NewManagerImpl()</code>,否則會有stub實作,不作任何事情。

<code>deviceplugin.NewManagerImpl()</code>定義在<code>pkg/kubelet/cm/deviceplugin/manager.go</code>内,

實際上真正做初始的工作都是在下列方法完成的

這裡隻是做ManagerImpl的初始化,有意義的工作隻有兩個

設定DeviceManager内置grpc服務的監聽檔案 <code>socketPath</code>, 由于DeviceManager和Device Plugin部署在同一個節點,是以隻需要利用Unix Socket的模式通信

設定裝置狀态的回調函數 <code>genericDeviceUpdateCallback</code>

就像注釋中提到 <code>The following structs are populated with real implementations in manager.Start()</code>的一樣,實際上在初始化階段,并沒有

<code>DeviceManger</code>的<code>Start()</code>是在啟動Kubelet<code>運作時</code>initializeModules<code>調用的,具體還是</code>ContainerManager`啟動的一部分。

這裡會把活躍的pod清單以及pod中繼資料的來源(FILE, URL, api-server)作為輸入用來啟動DeviceManager, 這兩個參數在啟動的時候并沒有用到

<code>Start</code>主要核心做兩件事情:

<code>m.readCheckpoint()</code> 負責從本地checkpoint(/var/lib/kubelet/device-plugins/kubelet_internal_checkpoint)中擷取已經注冊和配置設定了的裝置資訊,為什麼要這樣做呢?這主要是因為Kubelet負責裝置的配置設定和管理工作, 這些資訊隻存在于Kubelet的記憶體中。一旦Kubelet重新開機了之後,哪些裝置已經配置設定了出去,以及這些配置設定出去的裝置具體和哪個Pod關聯

DeviceManager在每次配置設定裝置給Pod後會将Pod和裝置的映射關系以json格式記錄到本地的一個檔案

<code>go m.server.Serve(s)</code> 以背景grouting的方式啟動grpc服務,這樣就可以完成Device Plugin的注冊,我們會在後面介紹grpc開放的服務如何與Device Plugin進行互動。

小結:

閱讀開源源代碼可以幫助我們提升技術水準, 不但能深入技術底層原理,快速了解技術架構;同樣也可以幫助我們學習優秀的代碼風格和設計模式。本文這裡隻是抛磚引玉,對Device Manager初始化場景進行了分析,後續我們也會對其他場景繼續研究,加深對Kubernetes的Device Plugin機制的了解。

繼續閱讀