Analyse du code source du mécanisme du plugin de périphérique de Kubernetes

Introduction:  Le mécanisme Device Plugin introduit par Kubernetes 1.8 peut prendre en charge l'intégration de divers périphériques tels que GPU, FPGA, NIC haute performance, InfiniBand, etc. via une extension. Device Manager est le module responsable de l'interaction Device Plugin et de la gestion du cycle de vie des périphériques dans Kubelet. Après avoir compris sa conception de base, cet article analyse le code source de Device Manager et comprend son mode de fonctionnement.

Le mécanisme Device Plugin introduit dans Kubernetes 1.8 prend en charge l'intégration de divers périphériques tels que GPU, FPGA, NIC haute performance et InfiniBand de manière étendue. Device Manager est le module responsable de l'interaction Device Plugin et de la gestion du cycle de vie des périphériques dans Kubelet. Après avoir compris sa conception de base , nous devons analyser le code source de Device Manager pour comprendre son mode de fonctionnement.

Le principe de base

Clarifiez d'abord l'objectif:

Il ne s'agit pas de comprendre toutes les implémentations de Kubelet, mais de comprendre comment Device Manager fonctionne dans la découverte de ressources, la création de pod, la vérification de l'état de l'appareil et comment il interagit avec Kubelet, nous ignorerons donc les opérations qui n'ont rien à voir avec Device Manager.

Voici mes principes et quelques expériences lors de la lecture du code:

  • Comprendre l'interface et comprendre l'interaction avec les modules externes
  • Comprendre la structure qui implémente l'interface
  • Associer des appels de méthode et des structures de données du point de vue des scénarios utilisateur revient à connecter des graphiques et des caractères. Après avoir compris les paramètres de la tâche, vous pouvez rapidement couper dans le processus d'appel de code; et la lecture des appels de code peut également approfondir la structure des données. Compréhension de la conception
  • Le code de Kubernetes est plus compliqué et il est difficile de comprendre d'un coup d'œil le but et le but de chaque définition de structure de données. À ce stade, nous pouvons noter les questions et les hypothèses. Ne vous embêtez pas trop, vous pourrez vérifier plus tard. La lecture d'un livre peut changer sa signification, et le code est le même. Lorsque vous vous familiarisez avec le contexte du code, certains problèmes seront résolus.
  • Étant donné que le Gestionnaire de périphériques fonctionne dans Kubelet, une compréhension du code source de Kubelet est la base pour comprendre le mécanisme de fonctionnement de modules spécifiques

PS La version du code source de Kubernetes analysée dans cet article est la 1.9.3

Le code principal de DeviceManager est  pkg/kubelet/cm/devicepluginsous

devicePlugin.png

Définition de l'interface DeviceManager

Fichier

pkg / kubelet / cm / deviceplugin / types.go

Définition spécifique:

// 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)
}

À partir des commentaires, vous pouvez voir que DeviceManager est responsable de la gestion de tous les plug-ins de périphérique exécutés sur le nœud. Voici 6 méthodes qui peuvent interagir avec le monde extérieur:

  • Start () et stop () doivent respectivement démarrer l'enregistrement du plug-in de périphérique et arrêter le service, ce qui est en fait une routine courante dans K8S
  • Périphériques () répertorie la liste des périphériques sous la forme d'une carte

Les 3 méthodes suivantes constituent le travail de base:

  • Allocate () alloue les périphériques disponibles pour le pod et appelle le plug-in de périphérique pour effectuer l'initialisation de périphérique requise
  • GetDeviceRunContainerOptions () obtient les paramètres nécessaires à la configuration de l'appareil pour le conteneur, tels que l'environnement, le volume et l'appareil. Cette méthode sera utilisée dans le processus de création du conteneur.
  • GetCapacity () est utilisé par le nœud pour signaler le nombre de ressources étendues au serveur API

Bien sûr, pour mieux comprendre, vous devez également comprendre le lien d'appel dans un scénario spécifique. Il existe deux implémentations de l'interface DeviceManager ici: MangerImpl et  ManagerStub, ManagerStub est en fait une implémentation vide, il n'est donc pas nécessaire de regarder de près. Comprenons brièvement  MangerImplla mise en œuvre

Implémentation de l'interface DeviceManager

Fichier

pkg/kubelet/cm/deviceplugin/manager.go

Définition spécifique:

// 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
}

Dans la définition et les commentaires de ManagerImpl, vous pouvez à peu près deviner qu'il fait trois choses:

  • Fournir un service grpc et prendre en charge l'enregistrement de plusieurs plug-ins de périphérique
  • Fournit une fonction de rappel pour le module d'extension de périphérique monitorCallback. Lorsque l'état du périphérique change, le gestionnaire de périphériques peut être averti pour effectuer un traitement correspondant. Par exemple, lorsqu'un périphérique ne peut pas fonctionner normalement, il est nécessaire de soustraire un du nombre total de ressources disponibles sur le nœud
  • L'attribution et la gestion de l'équipement, en particulier, consiste à enregistrer le nombre total d'un certain équipement et quel numéro a été attribué. De ce point de vue, Device Plugin doit fournir un UUID pour chaque périphérique. Cet UUID doit être unique et immuable sur ce nœud. Ce que Device Manager doit faire est de maintenir l'ensemble des UUID et d'être responsable de la mise à jour et de la distribution des périphériques.

Classification des scènes

Cinq scénarios sont principalement impliqués ici:

  • Initialisation et démarrage du Gestionnaire de périphériques
  • Recevez l'enregistrement du point de terminaison du plug-in de périphérique et interrogez le point de terminaison pour la liste des ID de périphérique
  • Rapportez régulièrement les informations de l'appareil sur le nœud
  • Lors de la création d'un pod, combinez les informations de l'appareil avec le pod pour générer la configuration (environnement, appareil, volume) nécessaire pour créer le conteneur
  • Lorsque l'état de l'appareil n'est pas sain, informez Kubelet de mettre à jour l'état des appareils disponibles

Cet article analyse d'abord le premier scénario: le processus d'initialisation et de démarrage du Gestionnaire de périphériques

Processus d'initialisation et de démarrage du gestionnaire de périphériques

Kubernetes a une énorme quantité de code, mais un examen plus approfondi du processus de démarrage de chaque module a une routine relativement similaire. Prenez Kubelet comme exemple:

  1. Créez un  KubeletServer objet de configuration contenant toutes les informations de configuration nécessaires à l'opération de kubelet
  2. Analyser la ligne de commande et mettre à jour en fonction des paramètres de la ligne de commande KubeletServer
  3. KubeletServer Créez de vrais kubelet objets d'exécution en fonction de  la configuration 
  4. Start()Démarrez l' kubelet objet d'exécution via une méthode 

L'initialisation de DeviceManger se produit aux étapes 3 et 4.

deviceManagerInit.png

  • app.kubeletCorrespond àcmd/kubelet/kubelet.go
  • serverCorrespond àcmd/kubelet/app/server.go
  • kubeletCorrespond àpkg/kubelet/kubelet.go
  • container_manager_linuxCorrespond àpkg/kubelet/cm/container_manager_linux.go
  • device.managerCorrespond àpkg/kubelet/cm/deviceplugin/manager.go

Le diagramme de séquence ci-dessus est le processus d'initialisation et de démarrage de DeviceManager par Kubelet (pour faciliter la compréhension, les méthodes qui n'ont rien à voir avec DeviceManager seront ignorées ici)

On peut voir que la méthode serverchinoise run()fait deux choses: NewMainKubeletet startKubelet, et l'initialisation et le démarrage du Gestionnaire de périphériques sont également terminés dans ces deux étapes, et le service d'enregistrement grpc est démarré en même temps, alors le Plugin de périphérique peut être enregistré.

  1. DeviceMangerL'initialisation se ContainerManagerfait lors de la création de l'objet , et l' ContainerManagerobjet est utilisé comme paramètre pour NewMainKubeletcréer l' Kubeletobjet d'exécution,

En fait défini dans: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()
    }

...
}

Étant donné que cette fonctionnalité est encore relativement nouvelle, elle doit être activée via la porte de fonctionnalité, c'est-à-dire configure --feature-gates = DevicePlugins = true, et cette fonctionnalité est désactivée par défaut. Il sera appelé lorsque cette fonction sera activée deviceplugin.NewManagerImpl(), sinon il y aura une implémentation de stub et rien ne sera fait.

deviceplugin.NewManagerImpl()Défini à l' pkg/kubelet/cm/deviceplugin/manager.gointérieur,

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

En fait, le travail initial réel est effectué selon les méthodes suivantes

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
}

Ceci est juste l'initialisation de ManagerImpl, il n'y a que deux tâches significatives

  • Configurez le fichier d'écoute du service grpc intégré de DeviceManager  socketPath, car DeviceManager et Device Plugin sont déployés sur le même nœud, il suffit donc d'utiliser la communication en mode Unix Socket
  • Définir la fonction de rappel de l'état de l'appareil genericDeviceUpdateCallback

Comme mentionné The following structs are populated with real implementations in manager.Start()dans les commentaires  , en fait dans la phase d'initialisation, il n'y a pas

  1. DeviceMangerIl fait Start()partie du démarrage du démarrage de 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

}

Ici, la liste des pods actifs et la source des métadonnées du pod (FILE, URL, api-server) seront utilisées comme entrée pour démarrer le DeviceManager. Ces deux paramètres ne sont pas utilisés au démarrage.

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
}

StartLe noyau principal fait deux choses:

  • m.readCheckpoint() Responsable de l'obtention des informations sur les périphériques enregistrés et alloués à partir du point de contrôle local (/ var / lib / kubelet / device-plugins / kubelet_internal_checkpoint), pourquoi voulez-vous faire cela? Ceci est principalement dû au fait que Kubelet est responsable de l'allocation et de la gestion des équipements, et cette information n'existe que dans la mémoire de Kubelet. Une fois le Kubelet redémarré, quels appareils ont été alloués et à quel pod les appareils alloués sont spécifiquement associés

DeviceManager enregistre la relation de mappage entre le pod et le périphérique au format json dans un fichier local une fois que chaque périphérique est alloué au pod.

  • go m.server.Serve(s) Démarrez le service grpc en mode de jointoiement en arrière-plan, afin que l'enregistrement du plug-in de périphérique puisse être terminé.Nous présenterons plus tard comment les services ouverts par grpc interagissent avec le plug-in de périphérique.

résumé:

La lecture de code open source peut nous aider à améliorer notre niveau technique, non seulement nous pouvons approfondir les principes sous-jacents de la technologie et comprendre rapidement l'architecture technique; cela peut également nous aider à apprendre d'excellents styles de code et modèles de conception. Cet article n'est qu'une introduction au scénario d'initialisation du gestionnaire de périphériques. Nous continuerons d'étudier d'autres scénarios à l'avenir pour approfondir la compréhension du mécanisme de plug-in de périphérique de Kubernetes.

Je suppose que tu aimes

Origine blog.csdn.net/zhangge3663/article/details/108290030
conseillé
Classement