Análise do código-fonte do mecanismo de plug-in do dispositivo do Kubernetes

Introdução:  O mecanismo de Plug-in de Dispositivo introduzido pelo Kubernetes 1.8 pode suportar a integração de vários dispositivos, como GPU, FPGA, NIC de alto desempenho, InfiniBand, etc. por meio de expansão. Device Manager é o módulo responsável pela interação do Device Plugin e gerenciamento do ciclo de vida do dispositivo no Kubelet.Após entender seu design básico, este artigo analisa o código-fonte do Device Manager e entende seu modo de operação.

O mecanismo de plug-in de dispositivo introduzido no Kubernetes 1.8 oferece suporte à integração de vários dispositivos, como GPU, FPGA, NIC de alto desempenho e InfiniBand de maneira estendida. Device Manager é o módulo responsável pela interação do Device Plugin e gerenciamento do ciclo de vida do dispositivo no Kubelet.Após entender seu design básico , precisamos analisar o código fonte do Device Manager para entender seu modo de operação.

O princípio básico

Primeiro esclareça o objetivo:

Não é para entender todas as implementações do Kubelet, mas para entender como o Device Manager funciona na descoberta de recursos, criação de pods, verificação de integridade do dispositivo e como ele interage com Kubelet, então iremos ignorar operações que não têm nada a ver com o Device Manager.

Aqui estão meus princípios e alguma experiência ao ler o código:

  • Compreenda a interface e descubra a interação com módulos externos
  • Compreenda a estrutura que implementa a interface
  • Associar chamadas de método e estruturas de dados da perspectiva dos cenários do usuário é como conectar gráficos e caracteres. Depois de entender as configurações da tarefa, você pode interromper rapidamente o processo de chamada de código; a leitura de chamadas de código também pode aprofundar a estrutura de dados Compreensão de design
  • O código do Kubernetes é mais complicado e é difícil descobrir a finalidade e a finalidade de cada definição de estrutura de dados de relance. Neste momento, podemos anotar as perguntas e suposições. Não fique muito confuso, você pode verificar isso mais tarde. Ler um livro pode mudar seu significado e o código é o mesmo.Quando você se familiarizar com o contexto do código, alguns problemas serão resolvidos.
  • Como o Device Manager funciona no Kubelet, uma compreensão do código-fonte do Kubelet é a base para a compreensão do mecanismo de operação de módulos específicos

PS A versão do código-fonte do Kubernetes analisada neste artigo é 1.9.3

O código principal do DeviceManager está  pkg/kubelet/cm/devicepluginsob

devicePlugin.png

Definição da interface DeviceManager

Arquivo

pkg / kubelet / cm / deviceplugin / types.go

Definição específica:

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

A partir dos comentários, você pode ver que DeviceManager é responsável por gerenciar todos os plug-ins de dispositivo em execução no nó. Aqui estão 6 métodos que podem interagir com o mundo externo:

  • Start () e stop () são para iniciar o registro do plug-in do dispositivo e parar o serviço, respectivamente, o que na verdade é uma rotina comum no K8S
  • Dispositivos () lista a lista de dispositivos na forma de um mapa

Os 3 métodos a seguir são o trabalho principal:

  • Allocate () aloca os dispositivos disponíveis para o Pod e chama o plug-in do dispositivo para realizar a inicialização necessária do dispositivo
  • GetDeviceRunContainerOptions () obtém os parâmetros necessários para configurar o dispositivo para o container, como Environment, Volume e Device.Este método será usado no processo de criação do container
  • GetCapacity () é usado pelo nó para relatar o número de Recursos estendidos para o servidor API

Claro, para entender mais claramente, você também precisa entender o link de chamada em um cenário específico. Existem duas implementações da interface DeviceManager aqui: MangerImpl e  ManagerStub, ManagerStub é na verdade uma implementação vazia, portanto, não há necessidade de olhar atentamente. Vamos entender brevemente  MangerImpla implementação

Implementação da interface DeviceManager

Arquivo

pkg/kubelet/cm/deviceplugin/manager.go

Definição específica:

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

Na definição e comentários de ManagerImpl, você pode adivinhar aproximadamente que ele está fazendo três coisas:

  • Fornece serviço grpc e suporta o registro de múltiplos dispositivos plug-in
  • Fornece uma função de retorno de chamada para o Plug-in do Dispositivo monitorCallback. Quando o status do dispositivo muda, o Gerenciador de Dispositivos pode ser notificado para fazer algum processamento correspondente. Por exemplo, quando um dispositivo não pode funcionar normalmente, é necessário subtrair um do número total de recursos disponíveis no nó
  • A atribuição e gestão de equipamentos, especificamente, consiste em registar o número total de um determinado equipamento e qual o número que foi atribuído. Deste ponto de vista, o Plug-in do Dispositivo precisa fornecer um UUID para cada dispositivo. Este UUID precisa ser único e imutável neste nó. O que o Gerenciador de Dispositivos precisa fazer é manter o conjunto de UUIDs e ser responsável pela atualização e distribuição do dispositivo.

Classificação de cena

Cinco cenários estão envolvidos principalmente aqui:

  • Inicialização e inicialização do Device Manager
  • Receba o registro do endpoint do Plug-in do Dispositivo e consulte o endpoint para obter a lista de ID do Dispositivo
  • Reportar informações do dispositivo no nó regularmente
  • Ao criar um pod, combine as informações do dispositivo com o pod para gerar a configuração (ambiente, dispositivo, volume) necessária para criar o contêiner
  • Quando o status do dispositivo não estiver íntegro, notifique Kubelet para atualizar o status dos dispositivos disponíveis

Este artigo analisa primeiro o cenário um: a inicialização e o processo de inicialização do Gerenciador de dispositivos

Inicialização do gerenciador de dispositivos e processo de inicialização

O Kubernetes tem uma grande quantidade de código, mas uma análise mais detalhada do processo de inicialização de cada módulo tem uma rotina relativamente semelhante. Veja o Kubelet como exemplo:

  1. Crie um  KubeletServer objeto de configuração que contenha todas as informações de configuração necessárias para a operação do kubelet
  2. Analise a linha de comando e atualize de acordo com os parâmetros da linha de comando KubeletServer
  3. KubeletServer Crie kubelet objetos de tempo de execução real com base na  configuração 
  4. Start()Inicie o kubelet objeto runtime por meio de um método 

A inicialização do DeviceManger acontece na etapa 3 e na etapa 4.

deviceManagerInit.png

  • app.kubeletCorresponde acmd/kubelet/kubelet.go
  • serverCorresponde acmd/kubelet/app/server.go
  • kubeletCorresponde apkg/kubelet/kubelet.go
  • container_manager_linuxCorresponde apkg/kubelet/cm/container_manager_linux.go
  • device.managerCorresponde apkg/kubelet/cm/deviceplugin/manager.go

O diagrama de sequência acima é o processo de como o Kubelet inicializa e inicia o DeviceManager (para facilitar o entendimento, métodos que não têm nada a ver com o DeviceManager serão ignorados aqui)

Pode-se ver que o método serverchinês run()faz duas coisas: NewMainKubelete startKubelet, e a inicialização e inicialização do Device Manager também são concluídas nessas duas etapas, e o serviço de registro grpc é iniciado ao mesmo tempo, então o Device Plugin pode ser registrado.

  1. DeviceMangerA inicialização é ContainerManagerfeita quando o objeto é criado , e o ContainerManagerobjeto é usado como um parâmetro para NewMainKubeletcriar o Kubeletobjeto de tempo de execução,

Definido na verdade em: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()
    }

...
}

Como esse recurso ainda é relativamente novo, ele precisa ser ativado por meio da porta de recurso, ou seja, configure --feature-gates = DevicePlugins = true, e esse recurso é desativado por padrão. Ele será chamado quando esta função for ativada deviceplugin.NewManagerImpl(), caso contrário, haverá uma implementação de stub e nada será feito.

deviceplugin.NewManagerImpl()Definido pkg/kubelet/cm/deviceplugin/manager.godentro,

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

Na verdade, o trabalho inicial real é feito nos seguintes métodos

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
}

Esta é apenas a inicialização do ManagerImpl, existem apenas duas tarefas significativas

  • Configure o arquivo de escuta do serviço grpc integrado do DeviceManager  socketPath, porque DeviceManager e Device Plugin são implantados no mesmo nó, então só precisa usar a comunicação no modo Unix Socket
  • Defina a função de retorno de chamada do status do dispositivo genericDeviceUpdateCallback

Conforme mencionado The following structs are populated with real implementations in manager.Start()nos comentários  , na verdade, na fase de inicialização, não há

  1. DeviceMangerFaz Start()parte da inicialização do 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

}

Aqui, a lista de pod ativos e a origem dos metadados do pod (FILE, URL, api-server) serão usados ​​como entrada para iniciar o DeviceManager. Esses dois parâmetros não são usados ​​na inicialização.

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
}

StartO núcleo principal faz duas coisas:

  • m.readCheckpoint() Responsável por obter as informações do dispositivo registrado e alocado do ponto de verificação local (/ var / lib / kubelet / device-plugins / kubelet_internal_checkpoint), por que você deseja fazer isso? Principalmente porque Kubelet é o responsável pela alocação e gerenciamento dos equipamentos, e essa informação só existe na memória de Kubelet. Depois que o Kubelet é reiniciado, quais dispositivos foram alocados e a qual pod os dispositivos alocados estão especificamente associados

DeviceManager registra a relação de mapeamento entre o pod e o dispositivo no formato json para um arquivo local depois que cada dispositivo é alocado para o pod.

  • go m.server.Serve(s) Inicie o serviço grpc no modo de junção de fundo, para que o registro do Plugin do Dispositivo possa ser concluído. Apresentaremos como os serviços abertos pelo grpc interagem com o Plugin do Dispositivo posteriormente.

resumo:

Ler código-fonte aberto pode nos ajudar a melhorar nosso nível técnico, não só podemos nos aprofundar nos princípios básicos da tecnologia e entender rapidamente a arquitetura técnica, mas também pode nos ajudar a aprender excelentes estilos de código e padrões de design. Este artigo é apenas uma introdução ao cenário de inicialização do Device Manager. Continuaremos a estudar outros cenários no futuro para aprofundar a compreensão do mecanismo de plug-in do dispositivo do Kubernetes.

Acho que você gosta

Origin blog.csdn.net/zhangge3663/article/details/108290030
Recomendado
Clasificación