Kubernetesのデバイスプラグインメカニズムのソースコード分析

はじめに:  Kubernetes 1.8で導入されたデバイスプラグインメカニズムは、拡張により、GPU、FPGA、高性能NIC、InfiniBandなどのさまざまなデバイスの統合をサポートできます。デバイスマネージャーは、Kubeletでのデバイスプラグインの相互作用とデバイスライフサイクル管理を担当するモジュールです。この基本的な設計を理解した後、この記事ではデバイスマネージャーのソースコードを分析し、その動作モードを理解します。

Kubernetes 1.8で導入されたデバイスプラグインメカニズムは、GPU、FPGA、高性能NIC、InfiniBandなどのさまざまなデバイスの統合を拡張してサポートします。デバイスマネージャーは、Kubeletでのデバイスプラグインの相互作用とデバイスライフサイクル管理を担当するモジュールです。その基本設計を理解した後、デバイスマネージャーのソースコードを分析して、その動作モードを理解する必要があります。

基本原理

最初に目標を明確にします。

Kubeletのすべての実装を理解するのではなく、デバイスマネージャーがリソース検出、ポッド作成、デバイスヘルスチェック、およびそれがKubeletとどのように相互作用するかを理解するため、デバイスマネージャーとは関係のない操作は無視します。

コードを読んだときの私の原則といくつかの経験を以下に示します。

  • インターフェースを理解し、外部モジュールとの相互作用を理解する
  • インターフェースを実装する構造を理解する
  • ユーザーシナリオの観点からメソッド呼び出しとデータ構造を関連付けることは、プロットと文字を接続するようなものです。タスク設定を理解した後、コード呼び出しプロセスにすばやく切り込むことができます。また、コード呼び出しの読み取りによってデータ構造を深めることもできます。デザインの理解
  • Kubernetesのコードはより複雑であり、目的と各データ構造定義の目的を一目で把握することは困難です。現時点では、質問と仮定を書き留めることができます。複雑すぎず、後で確認できます。本を読むと意味が変わる可能性があり、コードも同じです。コードのコンテキストに慣れると、いくつかの問題が解決されます。
  • デバイスマネージャーはKubeletで動作するため、Kubeletのソースコードを理解することが、特定のモジュールの動作メカニズムを理解するための基礎となります。

PSこの記事で分析したKubernetesソースコードのバージョンは1.9.3です。

デバイス・マネージャのコアコードは、  pkg/kubelet/cm/deviceplugin

devicePlugin.png

DeviceManagerインターフェース定義

ファイル

pkg / kubelet / cm / deviceplugin / types.go

特定の定義:

// Manager manages all the Device Plugins running on a node.
type Manager interface {
    // Start starts device plugin registration service.
    Start(activePods ActivePodsFunc, sourcesReady config.SourcesReady) error

    // Devices is the map of devices that have registered themselves
    // against the manager.
    // The map key is the ResourceName of the device plugins.
    Devices() map[string][]pluginapi.Device

    // Allocate configures and assigns devices to pods. The pods are provided
    // through the pod admission attributes in the attrs argument. From the
    // requested device resources, Allocate will communicate with the owning
    // device plugin to allow setup procedures to take place, and for the
    // device plugin to provide runtime settings to use the device (environment
    // variables, mount points and device files). The node object is provided
    // for the device manager to update the node capacity to reflect the
    // currently available devices.
    Allocate(node *schedulercache.NodeInfo, attrs *lifecycle.PodAdmitAttributes) error

    // Stop stops the manager.
    Stop() error

    // GetDeviceRunContainerOptions checks whether we have cached containerDevices
    // for the passed-in <pod, container> and returns its DeviceRunContainerOptions
    // for the found one. An empty struct is returned in case no cached state is found.
    GetDeviceRunContainerOptions(pod *v1.Pod, container *v1.Container) *DeviceRunContainerOptions

    // GetCapacity returns the amount of available device plugin resource capacity
    // and inactive device plugin resources previously registered on the node.
    GetCapacity() (v1.ResourceList, []string)
}

コメントから、DeviceManagerがノードで実行されているすべてのデバイスプラグインの管理を担当していることがわかります。

  • Start()およびstop()は、それぞれデバイスプラグインの登録を開始し、サービスを停止します。これは、実際にはK8Sの一般的なルーチンです
  • Devices()は、マップの形式でデバイスリストをリストします。

次の3つの方法がコアワークです。

  • Allocate()はポッドに使用可能なデバイスを割り当て、デバイスプラグインを呼び出して必要なデバイスの初期化を実行します
  • GetDeviceRunContainerOptions()は、環境、ボリューム、デバイスなど、コンテナのデバイスを構成するために必要なパラメータを取得します。このメソッドは、コンテナの作成プロセスで使用されます
  • GetCapacity()は、ノードが拡張リソースの数をAPIサーバーに報告するために使用されます

もちろん、より明確に理解するには、特定のシナリオでの呼び出しリンクも理解する必要があります。ここには、DeviceManagerインターフェースの2つの実装があります。MangerImpl また  ManagerStub、ManagerStubは実際には空の実装であるため、詳細に調べる必要はありません。MangerImpl実装を簡単に理解しましょう 

DeviceManagerインターフェースの実装

ファイル

pkg/kubelet/cm/deviceplugin/manager.go

特定の定義:

// ManagerImpl is the structure in charge of managing Device Plugins.
type ManagerImpl struct {
    socketname string
    socketdir  string

    endpoints map[string]endpoint // Key is ResourceName
    mutex     sync.Mutex

    server *grpc.Server

    // activePods is a method for listing active pods on the node
    // so the amount of pluginResources requested by existing pods
    // could be counted when updating allocated devices
    activePods ActivePodsFunc

    // sourcesReady provides the readiness of kubelet configuration sources such as apiserver update readiness.
    // We use it to determine when we can purge inactive pods from checkpointed state.
    sourcesReady config.SourcesReady

    // callback is used for updating devices' states in one time call.
    // e.g. a new device is advertised, two old devices are deleted and a running device fails.
    callback monitorCallback

    // allDevices contains all of registered resourceNames and their exported device IDs.
    allDevices map[string]sets.String

    // allocatedDevices contains allocated deviceIds, keyed by resourceName.
    allocatedDevices map[string]sets.String

    // podDevices contains pod to allocated device mapping.
    podDevices podDevices
}

ManagerImplの定義とコメントでは、次の3つのことを行っているとおおまかに推測できます。

  • grpcサービスを提供し、複数のデバイスプラグインの登録をサポートする
  • デバイスプラグインのコールバック関数を提供しますmonitorCallbackデバイスのステータスが変化すると、デバイスマネージャーに通知して対応する処理を実行できます。たとえば、デバイスが正常に動作しない場合、ノードで利用可能なリソースの総数から1を減算する必要があります
  • 機器の割り当てと管理は、具体的には、特定の機器の総数と割り当てられた数を記録することです。この観点から、デバイスプラグインは各デバイスにUUIDを提供する必要があります。このUUIDはこのノード上で一意で変更不可である必要があります。デバイスマネージャが行うべきことは、UUIDのセットを維持し、デバイスの更新と配布を担当することです。

シーン分類

ここでは主に5つのシナリオが関係しています。

  • デバイスマネージャーの初期化と起動
  • デバイスプラグインのエンドポイント登録を受信し、エンドポイントにデバイスIDリストを照会する
  • ノードのデバイス情報を定期的に報告する
  • ポッドを作成するときに、デバイス情報をポッドと組み合わせて、コンテナーの作成に必要な構成(環境、デバイス、ボリューム)を生成します
  • デバイスのステータスが異常な場合は、利用可能なデバイスのステータスを更新するようにKubeletに通知します

この記事では、まずシナリオ1を分析します。デバイスマネージャーの初期化と起動プロセス

デバイスマネージャの初期化と起動プロセス

Kubernetesには膨大な量のコードがありますが、各モジュールの起動プロセスを詳しく見てみると、比較的似たルーチンがあります。

  1. KubeletServer kubelet操作に必要なすべての構成情報を保持する構成オブジェクトを作成し  ます
  2. コマンドラインを解析し、コマンドラインのパラメーターに従って更新する KubeletServer
  3. KubeletServer 構成に基づいて実際のkubelet ランタイムオブジェクトを  作成する 
  4. Start()メソッドを介しkubelet ランタイムオブジェクト開始する 

DeviceMangerの初期化は、ステップ3とステップ4で行われます。

deviceManagerInit.png

  • app.kubeletに対応cmd/kubelet/kubelet.go
  • serverに対応cmd/kubelet/app/server.go
  • kubeletに対応pkg/kubelet/kubelet.go
  • container_manager_linuxに対応pkg/kubelet/cm/container_manager_linux.go
  • device.managerに対応pkg/kubelet/cm/deviceplugin/manager.go

上記のシーケンス図は、KubeletがDeviceManagerを初期化して開始するプロセスのプロセスです(理解を容易にするために、DeviceManagerとは関係のないメソッドはここでは無視されます)。

それがあることがわかるserver中国のrun()メソッドは、2つのことを行いますNewMainKubeletstartKubelet、デバイスマネージャの初期化と起動はまた、この2つのステップで完了し、そしてgrpc登録サービスを同時に開始され、その後、デバイスプラグインを登録することができます。

  1. DeviceManger初期化は、オブジェクトの作成時にContainerManager行われ、オブジェクトはランタイムオブジェクトを作成するためのパラメーターContainerManagerとして使用されます。NewMainKubeletKubelet

実際に定義されているのは:pkg/kubelet/cm/container_manager_linux.go

func NewContainerManager(mountUtil mount.Interface, cadvisorInterface cadvisor.Interface, nodeConfig NodeConfig, failSwapOn bool, devicePluginEnabled bool, recorder record.EventRecorder) (ContainerManager, error) {
...

glog.Infof("Creating device plugin manager: %t", devicePluginEnabled)
    if devicePluginEnabled {
        cm.devicePluginManager, err = deviceplugin.NewManagerImpl()
    } else {
        cm.devicePluginManager, err = deviceplugin.NewManagerStub()
    }

...
}

この機能はまだ比較的新しいため、機能ゲート、つまりconfigure --feature-gates = DevicePlugins = trueを介して有効にする必要があり、この機能はデフォルトで無効になっています。この関数がオンのときに呼び出さdeviceplugin.NewManagerImpl()れます。それ以外の場合は、スタブ実装があり、何も行われません。

deviceplugin.NewManagerImpl()pkg/kubelet/cm/deviceplugin/manager.go内で定義され、

// NewManagerImpl creates a new manager.
func NewManagerImpl() (*ManagerImpl, error) {
    return newManagerImpl(pluginapi.KubeletSocket)
}

実際、実際の初期作業は次の方法で行われます

func newManagerImpl(socketPath string) (*ManagerImpl, error) {
    glog.V(2).Infof("Creating Device Plugin manager at %s", socketPath)

    if socketPath == "" || !filepath.IsAbs(socketPath) {
        return nil, fmt.Errorf(errBadSocket+" %v", socketPath)
    }

    dir, file := filepath.Split(socketPath)
    manager := &ManagerImpl{
        endpoints:        make(map[string]endpoint),
        socketname:       file,
        socketdir:        dir,
        allDevices:       make(map[string]sets.String),
        allocatedDevices: make(map[string]sets.String),
        podDevices:       make(podDevices),
    }
    manager.callback = manager.genericDeviceUpdateCallback

    // The following structs are populated with real implementations in manager.Start()
    // Before that, initializes them to perform no-op operations.
    manager.activePods = func() []*v1.Pod { return []*v1.Pod{} }
    manager.sourcesReady = &sourcesReadyStub{}

    return manager, nil
}

これはManagerImplの初期化に過ぎず、意味のあるタスクは2つだけです

  • socketPathDeviceManagerとデバイスプラグインは同じノードにデプロイされるため、Unixソケットモードの通信のみを使用する必要があるため、DeviceManager組み込みgrpcサービスのリスニングファイルを設定し  ます。
  • デバイスステータスのコールバック関数を設定する genericDeviceUpdateCallback

コメントThe following structs are populated with real implementations in manager.Start()述べように  、実際には初期化フェーズでは、

  1. DeviceMangerそれはあるStart()Kubeletの起動の一環运行时initializeModules 调用的,具体还是ContainerManager`起動。
func (cm *containerManagerImpl) Start(node *v1.Node,
    activePods ActivePodsFunc,
    sourcesReady config.SourcesReady,
    podStatusProvider status.PodStatusProvider,
    runtimeService internalapi.RuntimeService) error {

...

// Starts device plugin manager.
    if err := cm.devicePluginManager.Start(deviceplugin.ActivePodsFunc(activePods), sourcesReady); err != nil {
        return err
    }
    return nil

}

ここでは、アクティブなポッドリストとポッドメタデータのソース(FILE、URL、api-server)が、DeviceManagerを起動するための入力として使用されます。これらの2つのパラメーターは、起動時に使用されません。

func (m *ManagerImpl) Start(activePods ActivePodsFunc, sourcesReady config.SourcesReady) error {
    glog.V(2).Infof("Starting Device Plugin manager")

    m.activePods = activePods
    m.sourcesReady = sourcesReady

    // Loads in allocatedDevices information from disk.
    err := m.readCheckpoint()
    if err != nil {
        glog.Warningf("Continue after failing to read checkpoint file. Device allocation info may NOT be up-to-date. Err: %v", err)
    }

    socketPath := filepath.Join(m.socketdir, m.socketname)
    os.MkdirAll(m.socketdir, 0755)

    // Removes all stale sockets in m.socketdir. Device plugins can monitor
    // this and use it as a signal to re-register with the new Kubelet.
    if err := m.removeContents(m.socketdir); err != nil {
        glog.Errorf("Fail to clean up stale contents under %s: %+v", m.socketdir, err)
    }

    s, err := net.Listen("unix", socketPath)
    if err != nil {
        glog.Errorf(errListenSocket+" %+v", err)
        return err
    }

    m.server = grpc.NewServer([]grpc.ServerOption{}...)

    pluginapi.RegisterRegistrationServer(m.server, m)
    go m.server.Serve(s)

    glog.V(2).Infof("Serving device plugin registration server on %q", socketPath)

    return nil
}

Startメインコアは2つのことを行います。

  • m.readCheckpoint() 登録および割り当てられたデバイス情報をローカルチェックポイント(/ var / lib / kubelet / device-plugins / kubelet_internal_checkpoint)から取得する責任がありますが、なぜこれを実行するのですか?これは主に、Kubeletが機器の割り当てと管理を担当し、この情報がKubeletのメモリにのみ存在するためです。Kubeletが再起動されると、割り当てられたデバイス、および割り当てられたデバイスが具体的に関連付けられているポッド

DeviceManagerは、各デバイスがポッドに割り当てられた後、ポッドとデバイス間のマッピング関係をjson形式でローカルファイルに記録します。

  • go m.server.Serve(s) デバイスプラグインの登録が完了するように、バックグラウンドグラウトモードでgrpcサービスを開始しますgrpcによって開かれたサービスがデバイスプラグインとどのように相互作用するかについては後で紹介します。

概要:

オープンソースコードを読むことで、技術レベルの向上に役立つだけでなく、技術の基本原理を深く理解し、技術アーキテクチャをすばやく理解できるだけでなく、優れたコードスタイルやデザインパターンを学ぶのにも役立ちます。この記事はデバイスマネージャーの初期化シナリオの紹介にすぎませんが、Kubernetesのデバイスプラグインメカニズムの理解を深めるために、今後も他のシナリオを検討していきます。

おすすめ

転載: blog.csdn.net/zhangge3663/article/details/108290030