首先明确目标:
并不是搞懂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>下
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiIn5GcuYDM0EWO5EmYyYGNkBTNzUjZ4cDZxMWYmFmY4UDM4YWMvwVbvNmLj5Wat4Wd5lGbh5iY1BXLn1WauU3bop3ZuFGat42YucWbp1iMhRXYvw1LcpDc0RHaiojIsJye.png)
所在的檔案
具體定義:
從注釋中可以看到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
<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機制的了解。