kubernetes: controllers/operators e custom resources

Primeiramente uma das capacidades mais exuberantes do Kubernetes ou k8s é a sua extensibilidade. Toda vez que aplicamos um objeto no kubernetes através de um kind específico algum mecanismo interno dele atua para que desempenhe a função correspondente. Por exemplo: um deployment age criando um replicaset que por sua vez atua controlando replicas de pods. Portanto, acontece que há no kubernetes: controllers/operators e custom resources, que são formas diferentes de pensar essa questão. Assim esse artigo falará um pouco melhor sobre eles.

Principais Conceitos

Custom Resource Definition – CRD

Só para ilustrar, é possível criar novos kinds no kubernetes com campos e características específicas: para fazer isso é necessário criar um Custom Resource Definition ou CRD. Em seguida, ao fazer isso um novo resource ficará disponível através do api-resources do kubernetes, acessível via HTTP por aplicações.

Kubectl api-resources

Controller

Além disso, a ideia de um controller vem da eletrônica: algo com a característica de loop infinito com um estado desejado a perseguir. Por exemplo: um ar-condicionado com o termostato tentando perseguir a temperatura configurada. Desse modo se a temperatura ficar acima do desejado, o resfriamento é acionado, se ficar abaixo o resfriamento é desligado. Similarmente o kubernetes possui controllers que visam garantir o estado desejado do ambiente a depender da configuração definida.

Da mesma forma veja também que um CRD não faz nada sozinho. Ele depende da existência de um controller para agir com base nas características ambientais do kubernetes.

Operator

Em contrapartida, técnicamente não há uma literatura oficial do kubernetes sobre o conceito de operator. Mas esse conceito é forte na comunidade e deve ser explorado: é um tipo específico de controller que objetiva facilitar a manitenção feita pelos sysadmins. Com um operator é possível, por exemplo, subir um cluster de Cassandra com um yaml muito pequeno, como o exemplo a seguir:

apiVersion: cassandraoperator.instaclustr.com/v1alpha1
kind: CassandraCluster
"metadata":
  name: test-dc-cassandra
spec:
  size: 3

Do mesmo modo há uma rede social que serve como um repositório de operators chamada OperatorHub. Note que na data de hoje há cerca de 300 operators disponíveis. Ao mesmo tempo qualquer pessoa pode criar um operator específico e publicar nele. Assim essas são algumas aplicações específicas comumente feitas através de operators:

  • Monitoramento e alertas automáticos
  • Atualizações automáticas de versão ao longo do tempo
  • Instalação de recursos personalizados
  • Fornecer escalonamento automático
  • Gerenciamento do ciclo de vida
  • Gerenciamento de armazenamento e backups

Manipulando um CRD

De fato criar um novo custom resource é algo bastante simples: Há um modelo de referência que utiliza o kind CustomResourceDefinition que traz características como o kind específico a ser criado, nome da versão, campos, etc. Veja abaixo um exemplo simples.

apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
"metadata":
  name: foos.samplecontroller.k8s.io

spec:
  group: samplecontroller.k8s.io
  versions:
    - name: v1alpha1
      served: true
      storage: true
      schema:
        # schema used for validation
        openAPIV3Schema:
          type: object
          properties:
            spec:
              type: object
              properties:
                deploymentName:
                  type: string
                replicas:
                  type: integer
                  minimum: 1
                  maximum: 10
            status:
              type: object
              properties:
                availableReplicas:
                  type: integer
  names:
    kind: Foo
    plural: foos
  scope: Namespaced

Após o CustomResourceDefinition ter sido aplicado com sucesso no ambiente do kubernetes é possível criar objetos desse novo kind obedecendo todos os campos e critério definidos no CRD. Veja a seguir um exemplo de uso.

apiVersion: samplecontroller.k8s.io/v1alpha1
kind: Foo
"metadata":
  name: example-foo
spec:
  deploymentName: example-foo
  replicas: 1

A partir da aplicação desse yaml é possível fazer a manipulação comum desse objeto com comandos como os seguintes:

kubectl get foos
kubectl get foo
kubectl describe foo xpto
kubectl delete xpto

Estrutura interna de um CRD

Esse é um esquema teórico que mostra como funciona internamente um CRD e a relação que tem com estruturas internas do kubernetes. De modo geral controllers customizados são construídos em Go Lang, mas isso não é uma obrigação. Entretanto o esquema exemplifica o cenário em Go Lang através do framework Operations Framework.

Esquema das estruturas internas de um custom controller para um CRD do Kubernetes
Esquema das estruturas internas de um custom controller para um CRD do Kubernetes

Client-go: Reflector

Um refletor observa a API Kubernetes para o tipo de recurso especificado (kind). Isso pode ser um recurso interno ou pode ser um recurso personalizado. Ao receber uma notificação sobre a existência de uma nova instância de recurso por meio da API watch, ele obtém o objeto recém-criado usando a API correspondente. Em seguida, ele coloca o objeto em uma fila Delta Fifo.

Client-go: Informer

Um informer remove objetos da fila Delta Fifo. Seu trabalho é salvar o objeto para recuperação posterior e invocar o código do controlador passando o objeto.

Client-go: Indexer

Um indexer fornece funcionalidade de indexação sobre objetos. Um caso de uso típico de indexação é criar um índice baseado em rótulos de objeto. O Indexador pode manter índices com base em várias funções de indexação. Indexador usa um armazenamento de dados thread-safe para armazenar objetos e suas chaves. Há uma função padrão que gera a chave de um objeto como uma combinação de <namespace>/<name> para esse objeto.

Custom-controller: Informer Reference

É a referência à instância do informer que sabe como trabalhar com seus objetos de CRD. Seu código de controlador personalizado precisa criar o Informer apropriado.

Custom-controller: Indexer Reference

Essa é a referência à instância do Indexer que sabe como trabalhar com seus objetos de CRDs. Seu código de controlador personalizado precisa ser criado. Você usará essa referência para recuperar objetos para processamento posterior. O client-go oferece funções para criar Informer e Indexer de acordo com suas necessidades. No seu código você pode invocar diretamente essas funções ou usar métodos de factory para criar um informer.

Custom-controller: Resource Event Handler

O padrão típico para escrever essas funções é obter a chave do objeto despachado e pôr essa chave em uma fila de trabalho para processamento adicional.

Custom-controller: Work Queue

Essa é a fila que você cria no código do controlador para dissociar a entrega de um objeto de seu processamento. As funções do manipulador de eventos de recurso são gravadas para extrair a chave do objeto entregue e adicioná-la à fila de trabalho.

Custom-controller: Process Item

Esta é a função que você cria em seu código que processa itens da fila de trabalho. Pode haver uma ou mais outras funções que fazem o processamento real. Essas funções normalmente usam a referência do Indexador ou um wrapper de listagem para recuperar o objeto correspondente à chave.

Operators Hub

O OperatorHub é um site que agrega repositórios com códigos de controllers formatados para serem de simples instalação. O site define 5 níveis de maturidade da instalação de aplicações no ambiente kubernetes: 1 – Instalação básica, 2 – atualização perfeita, 3 – ciclo de vida completo, 4 – com profundidade e 5 – piloto automático. O diagrama a seguir exibe os 5 níveis: até onde Helm Packages podem atender; até onde o ansible pode atender; e até onde esses operators (controllers) do OperatorHub podem atender.

Níveis de maturidade de instalações no Kubernetes
Níveis de maturidade de instalações no Kubernetes

Boas práticas

Construir um operator exije conhecimento de programação e há práticas que devem ser levadas em consideração para que a construção desse controller tenha sucesso. Selecionamos um conjunto de ações ou considerações relevantes para o tema:

  • Entenda que os operators são automatizadores de tarefas de Sysadmins
  • Operators podem ser utilizados no lugar de um helm package complexo
  • Eles são softwares e possuem dinâmica particular para garantir sua qualidade
  • Sempre se preocupe com a idempotência ao criar controllers
  • Considerações específicas para o desenvolvimento
    • Faça a correta definição dos objetos na primeira volta
    • Na segunda volta valide a idempotência
    • Garanta que a estrutura seja absolutamente stateless
    • Crie finalizers (delete em cascata)
    • Muito cuidado com as consultada para descobrir o estado do cluster (isso pode sobrecarregar o cluster)
  • Considerações específicas de plataforma de desenvolvimento
    • Utilize o Operator SDK (Ele já implementa boas práticas e facilita o trabalho)
    • Utilizar o Operator Utils
    • Prefira programar em Go Lang
  • Só crie controllers se você tiver certeza da necessidade
    • Ele é a melhor solução técnica para o cenário?
    • Não é over-engineering ir por esse caminho?
    • Não há algo mais fácil para a necessidade?
    • Há na equipe pessoal para manter?
  • Saiba que Controllers não são bala de prata – No Silver Bullet
    • Ele podem ajudar muito se bem produzidos
    • Mas sua produção é muito cuidadosa e pode gerar problemas que devem ser colocados na balança

Thiago Anselme
Thiago Anselme - Gerente de TI - Arquiteto de Soluções

Ele atua/atuou como Dev Full Stack C# .NET / Angular / Kubernetes e afins. Ele possui certificações Microsoft MCTS (6x), MCPD em Web, ITIL v3 e CKAD (Kubernetes) . Thiago é apaixonado por tecnologia, entusiasta de TI desde a infância bem como amante de aprendizado contínuo.

Deixe um comentário