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/deviceplugin
sob
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 MangerImpl
a 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:
- Crie um
KubeletServer
objeto de configuração que contenha todas as informações de configuração necessárias para a operação do kubelet - Analise a linha de comando e atualize de acordo com os parâmetros da linha de comando
KubeletServer
KubeletServer
Criekubelet
objetos de tempo de execução real com base na configuraçãoStart()
Inicie okubelet
objeto runtime por meio de um método
A inicialização do DeviceManger acontece na etapa 3 e na etapa 4.
app.kubelet
Corresponde acmd/kubelet/kubelet.go
server
Corresponde acmd/kubelet/app/server.go
kubelet
Corresponde apkg/kubelet/kubelet.go
container_manager_linux
Corresponde apkg/kubelet/cm/container_manager_linux.go
device.manager
Corresponde 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 server
chinês run()
faz duas coisas: NewMainKubelet
e 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.
DeviceManger
A inicialização éContainerManager
feita quando o objeto é criado , e oContainerManager
objeto é usado como um parâmetro paraNewMainKubelet
criar oKubelet
objeto 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.go
dentro,
// 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á
DeviceManger
FazStart()
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
}
Start
O 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.