Pular para o conteúdo
10 min de leitura

A Arquitetura do Kubernetes Explicada de Verdade

Por Equipe Nebular ·

Control plane, kubelet, o loop de reconciliação e o modelo declarativo: entenda como o Kubernetes realmente funciona por dentro, sem decorar comandos.

Neste artigo

O Kubernetes tem fama de complexo, e parte dessa fama é merecida. Mas boa parte da dificuldade que as pessoas sentem não vem da complexidade real e sim de aprenderem o Kubernetes pela ponta errada: decorando comandos kubectl, colando manifestos YAML de exemplos e torcendo para funcionar. Aprender assim é como memorizar frases em um idioma sem entender a gramática. Funciona até a primeira situação nova, e aí desmorona.

A verdade é que o Kubernetes é construído sobre um punhado de ideias coerentes. Quando você entende essas ideias, o resto se encaixa. O comportamento do sistema deixa de ser um conjunto de regras arbitrárias e vira consequência lógica de um desenho. Este artigo apresenta essa gramática: o modelo declarativo, o loop de reconciliação, e as peças do control plane e dos nós que fazem tudo funcionar.

A ideia central: declarar o estado desejado#

Antes de qualquer componente, entenda a filosofia. O Kubernetes é declarativo. Você não diz a ele o que fazer, passo a passo. Você diz qual é o estado desejado do mundo, e o sistema trabalha continuamente para tornar a realidade igual a essa declaração.

A diferença é profunda. Numa abordagem imperativa, você diria: "crie três containers, coloque-os nestes nós, configure o balanceador apontando para eles". Se um container cair, o sistema não faz nada, porque você mandou criar, não mandou manter. Na abordagem declarativa, você diz: "quero três réplicas deste serviço rodando, sempre". Se uma réplica cai, o sistema percebe a divergência entre o desejado (três) e o real (duas) e cria uma nova, sem ninguém pedir. Você descreve o destino, não o caminho.

Essa é a razão de o Kubernetes ser resiliente por natureza. Ele não executa um roteiro que termina; ele mantém uma condição de forma perpétua. Cada recurso que você cria, um Deployment, um Service, um ConfigMap, é uma peça do estado desejado, guardada no sistema, e há sempre alguém observando para garantir que a realidade a honre.

O loop de reconciliação: o coração que nunca para#

O mecanismo que torna o modelo declarativo real é o loop de reconciliação, também chamado de control loop. É a ideia mais importante do Kubernetes inteiro, e cabe em três passos que se repetem para sempre:

  1. Observar o estado atual do sistema.
  2. Comparar com o estado desejado que foi declarado.
  3. Agir para reduzir a diferença entre os dois.

E então volta ao passo um, indefinidamente. Cada tipo de recurso tem um controlador que roda esse loop. O controlador de Deployment observa quantos Pods existem, compara com quantos deveriam existir, e cria ou remove Pods para bater o número. O controlador de nós observa se os nós estão respondendo e reage quando um some. Não há um orquestrador central dando ordens; há uma coleção de controladores, cada um responsável por reconciliar um pedaço do mundo, todos rodando o mesmo loop.

Compreender isso muda tudo. Quando você faz kubectl apply de um manifesto, você não está mandando o Kubernetes executar uma ação. Está atualizando o registro do estado desejado. Os controladores relevantes percebem a mudança e reconciliam. Por isso aplicar o mesmo manifesto duas vezes não causa problema: o estado desejado é o mesmo, não há divergência para corrigir, nada acontece. A idempotência não é um recurso adicionado; é consequência do desenho.

O control plane: o cérebro do cluster#

Um cluster Kubernetes se divide em duas partes: o control plane, que toma decisões, e os nós de trabalho, que executam as cargas. Comecemos pelo cérebro. O control plane é composto por alguns componentes com papéis bem definidos.

O kube-apiserver é a porta de entrada de tudo. Toda comunicação com o cluster, seja do kubectl, de um controlador ou de um nó, passa por ele. Ele expõe a API REST do Kubernetes, autentica e autoriza cada requisição, valida os objetos e os persiste. Nenhum componente fala diretamente com outro; todos falam com o apiserver. Essa centralização da comunicação, em vez de acoplar componentes uns aos outros, é o que mantém o sistema modular.

O etcd é o banco de dados do cluster, um armazenamento chave-valor distribuído e consistente. É ali, e somente ali, que o estado do cluster vive: todo Deployment, todo Service, todo Secret. Se o etcd for perdido sem backup, o cluster é perdido. Por isso, em produção, o etcd roda replicado e com backups regulares. Ele é a única fonte da verdade, e o apiserver é o único que fala com ele.

O kube-scheduler decide em qual nó cada novo Pod deve rodar. Quando um Pod é criado sem nó atribuído, o scheduler avalia os candidatos considerando recursos disponíveis, restrições declaradas, afinidades e políticas, e escolhe o melhor encaixe. Ele não executa o Pod; apenas decide o destino e registra essa decisão, que os nós então cumprem.

O kube-controller-manager hospeda os vários controladores que rodam os loops de reconciliação: o de Deployment, o de ReplicaSet, o de nós, o de endpoints, entre outros. É o lar dos control loops que discutimos.

Em clusters na nuvem, existe ainda o cloud-controller-manager, que integra o Kubernetes com o provedor de infraestrutura, provisionando balanceadores de carga, volumes e rotas de rede conforme os recursos exigem.

Os nós de trabalho: onde a carga roda#

Se o control plane decide, os nós de trabalho executam. Cada nó roda dois componentes essenciais.

O kubelet é o agente que vive em cada nó e conversa com o apiserver. Ele recebe a lista de Pods que devem rodar naquele nó e faz acontecer: pede ao runtime de containers que baixe as imagens e inicie os containers, monitora a saúde deles, reporta o status de volta ao apiserver. O kubelet é o braço do control plane em cada máquina, o executor local do estado desejado.

Abaixo do kubelet está o container runtime, o software que de fato roda os containers, tipicamente o containerd. O kubelet fala com ele por uma interface padronizada, a CRI, o que permite trocar o runtime sem mexer no resto. É esse desacoplamento que tornou possível a transição para longe do Docker como runtime sem quebrar as cargas existentes.

O terceiro componente do nó é o kube-proxy, responsável por parte da mágica de rede que faz um Service ser acessível por um endereço estável mesmo quando os Pods por trás dele nascem e morrem. Ele programa as regras que roteiam o tráfego para os Pods certos. A rede do Kubernetes é um mundo próprio, com CNI, Services e Ingress, e merece atenção dedicada; se essa camada te interessa, o funcionamento das redes no Kubernetes aprofunda como o tráfego realmente flui.

Pod: a unidade que importa#

Uma peça de vocabulário costuma confundir quem chega: o Kubernetes não roda containers diretamente, ele roda Pods. Um Pod é a menor unidade que o Kubernetes agenda, e é uma cápsula que contém um ou mais containers que compartilham o mesmo network namespace e podem compartilhar volumes.

Na maioria dos casos, um Pod tem um único container principal. Mas o Pod existe como abstração para permitir que containers estreitamente acoplados vivam juntos: um container principal e um sidecar que coleta logs, por exemplo, ou um proxy que intercepta o tráfego. Como compartilham o network namespace, os containers de um Pod se enxergam por localhost, exatamente como discutimos ao ver que namespaces de rede podem ser compartilhados.

Pods são efêmeros por natureza. Nascem, podem morrer, e são substituídos. Você quase nunca cria Pods diretamente; cria abstrações de nível mais alto, como Deployments, que gerenciam ReplicaSets, que por sua vez mantêm o número certo de Pods. Cada nível é um controlador reconciliando o nível abaixo, num encadeamento elegante de loops.

Como o estado se propaga: o watch em vez do polling#

Um detalhe de implementação merece destaque porque explica como o sistema inteiro se mantém reativo sem sobrecarregar o apiserver. Poderíamos imaginar que cada controlador fica perguntando ao apiserver, repetidamente, "algo mudou?", num polling constante. Não é assim que funciona, e a diferença importa.

O apiserver oferece um mecanismo de watch: um controlador declara interesse num tipo de recurso e passa a receber notificações quando qualquer objeto daquele tipo muda, sem precisar perguntar. Quando você cria um Deployment, o apiserver persiste a mudança no etcd e notifica todos que estão observando Deployments. O controlador de Deployment recebe o evento e reage na hora, iniciando a reconciliação. É um modelo orientado a eventos, não a sondagem.

Isso tem duas consequências valiosas. A primeira é eficiência: o sistema não desperdiça recursos perguntando o tempo todo, ele é acordado apenas quando há de fato uma mudança. A segunda é a baixa latência: a reação a uma mudança é quase imediata, porque a notificação chega no instante em que o estado muda, e não na próxima rodada de um polling. O watch é o que faz o loop de reconciliação parecer instantâneo na prática, apesar de conceitualmente ser um ciclo que se repete. Ele também é resiliente: se um controlador cai e volta, ele primeiro relista o estado atual completo para se sincronizar, e depois retoma o watch a partir dali, garantindo que não perdeu nada durante a ausência.

Namespaces lógicos: organizando o cluster#

Uma palavra de cuidado com vocabulário, porque um mesmo termo aparece em dois contextos diferentes. Além dos namespaces do kernel Linux que isolam containers, o Kubernetes tem seus próprios namespaces lógicos, que são uma coisa distinta: uma forma de particionar o cluster em espaços virtuais para organizar recursos.

Um namespace do Kubernetes agrupa recursos e cria um escopo para nomes, de modo que dois Deployments com o mesmo nome podem coexistir em namespaces diferentes sem colidir. Times ou ambientes distintos podem viver em namespaces separados no mesmo cluster, com cotas de recurso e políticas de acesso próprias por namespace. É uma fronteira lógica de organização e governança, não o isolamento de baixo nível do kernel. Confundir os dois é comum e vale manter a distinção clara: o namespace do kernel isola um processo do sistema operacional; o namespace do Kubernetes organiza recursos dentro da abstração do cluster.

Por que essa arquitetura vence#

Amarrando o desenho: você declara o estado desejado ao apiserver, que o persiste no etcd. Controladores no controller-manager observam esse estado e rodam loops de reconciliação. O scheduler decide onde colocar novos Pods. Os kubelets em cada nó executam os Pods que lhes cabem, apoiados no runtime de containers, enquanto o kube-proxy trata a rede. Tudo isso girando continuamente, sempre puxando a realidade na direção da declaração.

Essa arquitetura entrega três propriedades difíceis de obter de outro jeito. Ela é auto-corretiva, porque o loop de reconciliação restaura o estado desejado sozinho quando algo desvia. Ela é declarativa, o que torna a infraestrutura versionável e reproduzível: seus manifestos são a descrição completa do que deve existir. E ela é extensível, porque o próprio mecanismo de controladores e recursos pode ser estendido com recursos customizados e operadores que rodam seus próprios loops, aplicando a mesma gramática a domínios novos.

O Kubernetes é complexo, sim. Mas a complexidade dele é composicional, não caótica. São poucas ideias, aplicadas com consistência, empilhadas em camadas. Quando você para de decorar comandos e passa a enxergar o loop de reconciliação por trás de cada comportamento, o sistema deixa de ser um labirinto e vira o que sempre foi: uma máquina de manter estados desejados, incansável e previsível.

Leituras relacionadas

Nenhum comentário ainda

Seja o primeiro a comentar.

Deixe seu comentário

Entre com sua conta Canverly para comentar. Você pode usar a mesma conta em qualquer site da rede.

Entrar com Canverly