Kubernetes: do zero a decente ainda hoje

real helm

Se você está começando agora nesse tal Kubernetes e já está naquele ponto em que seu navegador tem +200 abas abertas em materiais diversos, desde o site oficial até artigos diversos por aí, bem-vindo! A meu ver, você já pode deixar isso tudo de lado e ficar tranquilo, porque por meio da leitura deste artigo, você conseguirá ainda nesse dia de hoje (a não ser que você esteja lendo-o próximo da meia-noite, claro) adquirir conhecimento suficiente para tornar-se um profissional muito decentemente capacitado a trabalhar com essa tecnologia bacana que é o Kubernetes.

Então, por favor, leve numa boa os parágrafos um tanto mais “filosóficos” do início, porque ainda teremos uma abordagem bastante prática para deixá-lo a par e, se possível, acostumado com o instrumental todo (que nem é muito, mesmo).

0: Disclaimer

Este artigo é longo e é um esforço e tanto escrever tudo de uma vez. Ele certamente será melhorado com o tempo. Quero começar focando no que é mais essencial e ir adicionando melhorias aos poucos.

1: As dores

(Você pode pular essa parte, se estiver com pressa.)

O mais importante ao adotar uma nova ferramenta é entender e, especialmente, sentir as dores que ela promete sanar. Quando comecei a tentar lidar com Docker, por exemplo, eu simplesmente não tinha nenhuma das necessidades que a ferramenta busca suprir, então a via como algo supérfluo e, portanto, nunca me engajava realmente no aprendizado da mesma, mais ou menos como o famoso cálculo de massa molar do Ensino Médio, que por mais que o professor tente ensinar da melhor maneira possível, não passa de uma enorme encheção de linguiça e, portanto, ninguém dá muita bola ou aprende direito.

Você lembra como era antigamente? Antigamente nós tínhamos O Servidor. Não que isso tenha desaparecido (não mesmo!), mas antigamente meio que era a única opção disponível. Depois surgiram as public cloud e as pessoas demoraram a entender como usá-las e passaram muito tempo reclamando que eram caras sem perceber que estavam usando uma tecnologia nova ainda sob um ponto de vista antigo.

Eventualmente, com a containerização das coisas, surgiram outras alternativas e escalar uma aplicação horizontalmente tornou-se mais ou menos trivial, embora a configuração desse tipo de arquitetura ainda dependesse de muitos conceitos particulares de cada provedor de serviços. Configurar um sistema assim acabava sendo trabalhoso, de qualquer forma, e exigia um investimento e um certo compromisso com aquele provedor de serviços específico, o que terminava amarrando você ou sua empresa a este.

Bom mesmo é simplicidade, não? Se você tem um projeto simples, pode pegar qualquer máquina barata, como Linode ou Vultr, e instalar o Dokku, que é um software open source que busca simular uma espécide de Heroku, e passar a vida sendo feliz fazendo deploys simplesmente com git push dokku! Eu faço isso com uma porção de projetos (incluindo esse blog) e é uma experiência magnífica.

É tão legal que eu queria usar esse tipo de coisa em todo lugar! Quer criar uma nova aplicação? dokku app:create nova-app; dokku git:init nova-app e depois é configurar o novo remoto no repositório local, dar o primeiro push e pronto. Precisa de TLS? Tem um plugin para gerenciar tudo automaticamente. Precisa de um banco de dados? Tem um plugin para isso. Redis? Plugin. Precisa de um volume? Tem um comando. Quer configurar algo muito específico do Docker? Edita um arquivo, é bem fácil!

Mas existe uma limitação, e é essa dor que me ajudou a entrar a fundo no aprendizado de Kubernetes: isso tudo é limitado aO Servidor. Quer fazer isso tudo dividindo a carga entre duas máquinas? Usando Dokku? É... Boa sorte...

Então, para mim, Kubernetes é um Dokku que funciona em N >= 1 máquinas: já tem tudo meio pronto, não precisa pensar demais nas coisas e não precisa se amarrar com algum cloud provider específico. Isso tudo e mais um bônus: IaaC, ou infrastructure as code, na maior parte do tempo.

No último mês eu terminei de configurar, com Kubernetes e de maneira muito simples, um “gitops”, que é mais ou menos como eu faço com Dokku, mas sem a parte de ter que mandar um push para um remoto separado: um push no repositório de infra já faz toda a mágica acontecer e eu vejo o que anda rolando por um bot no Discord. É magnífico!

2: O que realmente importa

Para mim, a parte mais complicada e que ninguém se deu ao trabalho de explicar adequadamente foi entender, no meio de tanta coisa que há para aprender, no que realmente eu devo focar no começo e o que eu posso deixar para depois.

Veja só, Kubernetes é um conjunto de padrões, não exatamente uma implementação. Então qualquer componente do sistema pode ser implementado por quem bem entender, contanto que adeque-se às especificações, e esse componente se integrará sem problemas no “ecossistema” do Kubernetes e, portanto, para algumas áreas específicas existem várias opções de componentes e é muito fácil para um iniciante perder-se completamente nisso e ficar estancado tentando fazer uma porção de escolhas que absolutamente não são importantes (novamente: para um iniciante).

Assim, talvez a parte mais importante deste artigo seja justamente essa aqui, a parte na qual eu te ajudo a entender o que é importante mesmo e o que você absolutamente deveria deixar para depois!

As coisas com as quais você não deve se preocupar agora: * “Control plane”: eu nem sei porque insistem tanto em falar disso... Meus clusters estão rodando de boas por todo esse tempo e em momento nenhum eu parei para sequer considerar o que fosse um tal de control plane. Não perca seu tempo pensando nisso agora. * Rede: é só questão de abrir algumas portas nas máquinas que farão parte do seu cluster e pronto. * “Distribuição”: depois que as configs do seu cluster estiverem em arquivos, alterar a implementação de Kubernetes ou mudar o próprio cluster de lugar passa a ser quase trivial. * Sistema Operacional: não fique de “nojinho”, porque o sistema operacional só servirá para rodar o Kubernetes e mais nada além do bash e do vim . * Ingress: a não ser que já de começo sua aplicação tenha alguma necessidade extremamente específica e absolutamente importante, qualquer ingress serve, então não gaste muita energia comparando as alternativas, meramente pegue o que já estiver pronto para uso. * Outras implementações específicas: já vi um par de gente dizendo que “X não é Kuberntetes de verdade, porque não tem o etcd”, o que é, até onde posso ver, uma baita bobagem, porque Kubernetes é uma série de padrões e não as implementações: qualquer implementação que siga as especificações é válida. * Helm e seus Charts: não agora! Helm ajuda muito em setups um pouco mais complexos, mas ao mesmo tempo meio que tira um pouco do seu controle sobre as coisas, e se você ainda não é familiar com Kubernetes, acredito que isso possa te prejudicar, então o meu conselho é que por agora evitemos lidar com Helm o quanto pudermos.

As coisas que realmente importam: * Conceitos: diferenciar um container de um pod, diferenciar um pod de um daemonset, saber o que cada coisa faz, etc, etc. * kubectl: usar a linha de comando para conseguir entender o estado geral do seu cluster é bem importante. Eventualmente você pode lidar numa dashboard e há várias excelentes opções, mas acredito que o melhor para o aprendizado é começar digitando os comandos, mesmo. * yamls de configuração: enquanto usar o kubectl bem te mostra o estado atual do sistema, ler os yaml de configuração te mostrará o estado desejado do sistema.

Okay?

Tendo dito isso, já podemos partir para as questões práticas.

3: Os pogramas

Algumas ferramentas, como Minikube, permite que se trabalhe com uma espécie de cluster-de-uma-máquina-só-que-é-o-teu-PC. Minha recomendação: não.

Minikube e similares são ferramentas que serão muito úteis depois que você já tiver dominado Kubernetes, já tiver um cluster de produção e outro de desenvolvimento rodando e, então, quiser implantar Kubernetes no localdev da galera, para unificar as coisas. Agora no começo? Não boto fé que vão te ajudar muito.

Minha recomendação para iniciantes é o microk8s, por uma série de motivos:

  1. Já empacota meio que tudo que você vai precisar;
  2. Já vem com umas coisinhas extremamente úteis prontas para uso (que você só vai descobrir mais tarde, mas ficará muito grato que já estão configuradas);
  3. Usa poucos recursos das máquinas;
  4. É muito fácil de rodar local, mas também é production-ready.

Como eu disse antes, tem aí uma meia dúzia de gringos dizendo que “microk8s are not really Kubernetes”, mas isso é bobagem e não vai te ajudar em nada. Quer dar a eles o benefício da dúvida? Bom, primeiro faça uso do microk8s para aprender Kubernetes e em seguida procure o que quer que seja “real Kubernetes”. De um jeito ou de outro, você ficará grato por esse conselho.

Microk8s tem um drawback, porém, que é o seguinte: os desenvolvedores distribuem oficialmente somente via snap, o que meio que te amarra ao Ubuntu e seus derivados. Efeito prático? Dar um nojinho em quem não curte Ubuntu, por qualquer que seja o motivo, mas mais nada. Novamente: o sistema operacional está aí apenas para rodar o Kubernetes, o bash e o vim. Então não faz muita diferença.

E você pode usar qualquer outra implementação (eu chamo de “distribuição¨, porque meio que é o que é, mas não é bem um termo comum da literatura a respeito), inclusive porque os comandos específicos destas que você vai querer usar são pouquíssimos (e são: habilitar plugins e adicionar ou remover nós).

4: microk8s

Você não precisa de “um cluster” para começar a lidar com microk8s! Pode fazê-lo no seu próprio computador ou alguma outra máquina local (eu acho que pode funcionar de boas dentro de um container Docker e não vejo por que não funcionaria, mas nunca tentei).

Se seu cluster tiver apenas 1 nó, os pods e containers simplesmente serão executados nesse nó. E o efeito é simplesmente esse: vai tudo rodar na mesma máquina.

Enquanto isso não é exatamente o comportamento esperado de um cluster na vida real, é perfeitamente suficiente para você dar seus primeiros passos com Kubernetes, conhecer os comandos, aprender a averiguar o que anda acontecendo no cluster, etc.

Para instalar:

$ sudo snap install microk8s --classic

Na primeira execução, recomendo mandar um “wait ready”, porque pode ser que demore mais alguns segundos até tudo ficar pronto para uso:

$ sudo microk8s status --wait-ready

E para não precisar usar sudo para tudo:

# Adicionar o usuário atual ao grupo com permissão de rodar o comando:
$ sudo usermod -a -G microk8s $USER
# Atualizar a lista de grupos do usuário corrente:
$ exec sudo su $USER

5: Cluster

(Você pode pular esta parte se ainda não vai configurar várias máquinas num mesmo cluster.)

Um cluster é um conjunto de máquinas que compartilham alguma coisa. No nosso caso, chamamos cada máquina de “nó” (em inglês: node). Este nó em que você instalou o microk8s é um nó mestre (master). Você pode adicionar mais nós-mestres para ter alta disponibilidade (3 ou mais) ou simplesmente workers, de onde não se espera que o cluster seja controlado, mas simplesmente que tarefas sejam executadas.

Caso você esteja já em um “setup fechado”, em que somente as portas necessárias são abertas, a lista das que precisam estar ouvindo em cada nós está aqui:

https://microk8s.io/docs/ports

na seção “Services binding to the default Host interface”. Estas devem estar prontas para receber conexões externas.

Tendo isso pronto, você vai rodar esse comando no nó mestre:

$ microk8s add-node

E a saída deste irá indicar o comando que você deve rodar no nó que será adicionado. Leia com atenção o que está escrito ali.

Esse add-node gera um token com tempo de expiração que serve como uma forma de a outra máquina autenticar-se junto ao cluster. Se esse tempo expirar, não se preocupe, simplesmente rode o add-node novamente e copie o novo comando.

6: kubectl

A implementação do kubectl, que é o comando usado para operar o cluster, está “dentro” do microk8s, e é chamada como um subcomando deste:

$ microk8s kubectl

Mas isso é muito para ser digitado. É bastante comum criar-se um alias para isso:

$ alias k="microk8s kubectl"

Então, doravante, toda vez que você vir o comando k, considere que é microk8s kubectl sendo invocado.

O próprio kubectl tem também seus subcomandos. O mais comuns são:

Uma dica legal que te permite visualizar facilmente o que anda acontecendo no cluster é a seguinte: deixe o seguinte comando rodando num terminal à parte (se você usa zellij, tmux ou similares, deixe num “pane” facilmente visível).

$  k events -w

O “w” vem de “watch”: esse comando ficará mostrando os eventos que acontecem no cluster, como “baixando imagem”, “pod criado”, “pod deletado”, etc. Assim você consegue ver os efeitos de cada outro comando que rodar.

7: Pods e containers

Um container, em qualquer que seja o contexto, é basicamente “uma aplicação em execução”. Tudo bem que haja diversos processos rodando, mas, por convenção, um container representa uma aplicação ou serviço específicos.

Se você tem uma aplicação que precisa de Postgres e Redis, por exemplo, poderia rodá-la usando 3 containers:

  1. Redis
  2. Postgres
  3. App

Mas acontece, algumas vezes, que a aplicação necessita de algum tipo de sidecar, como um coletor de métricas ou de logs. Localmente, é só questão de rodar outro container. Mas conceitualmente esses dois-ou-mais containers (App e coletor de métricas) fazem parte de uma coisa só, certo?

Por isso existe o conceito de pods no Kubernetes. Um pod é como se fosse um “lugar” onde rodam N >= 1 containers e, dentro dele, todos podem se comunicar baseados no localhost, ou seja, é como se todos estivessem na mesma máquina — inclusive por que é assim mesmo. A menor divisão do que se pode rodar em um nó dentro do cluster é justamente o pod (ou seja: dentro do pod há a garantia, de fato, que todos estão efetivamente na mesma máquina).

Então, nesse caso, App e sidecars fariam parte de um pod só, enquanto Postgres e Redis seriam pods à parte (quando não serviços externos).

E é só isso. Não precisa pirar muito a respeito. Um pod é a menor divisão inter-nós e dentro de um pod roda-se 1-ou-mais containers. Mais nada.

Para listar os pods que estão rodando no seu cluster:

$ k get pods

8: Namespaces

O legal do Kubernetes é que, apesar do que pode parecer, não tem muita mágica. Por exemplo: existe uma aplicação, que no microk8s é a tal calico, que gerencia a rede do cluster. Inclusive é por isso que eu disse antes que não precisava se preocupar com configurações de rede: contanto que aquelas portas estejam abertas, o calico cuida de fazer com que os pods consigam comunicar-se entre si.

Mas onde está essa aplicação? Oras, num pod!

Mas por que não a vejo quando listo os pods? Ora, porque seria terrível ficar vendo boilerplate o tempo todo enquanto gerencia-se um cluster e por isso o Kubernetes implementa namespaces.

Quando você roda um k get pods, está listando os pods do namespace default, que é onde você vai querer rodar suas próprias aplicações. Mas as aplicações “internas” do Kubernetes aparecerão em outros namespaces.

Você tanto pode adicionar a opção -n $namespace para ver os pods de um namespace específico quanto pode mandar um -A para ver de todos os namespaces:

$ k get pods -A

Legal, né?

9: O pod de depuração

Um dos comandos mais úteis que tenho anotado aqui comigo é o seguinte:

$ kubectl run -i --tty --rm shell --image=jrecord/nettools -- /bin/bash

O que esse comando faz é iniciar um container baseado na imagem jrecord/nettools e cujo pod se chamará shell (experimente rodar isso e, em outro terminal, listar os pods). Você então terá uma linha de comando com os comandos mais úteis para se depurar a rede e, se quiser, já fazer uns testes nos pods (e serviços) que tem rodando.

Por ora não parece muito útil, mas pelo menos você já consegue (i) rodar um container/pod baseado numa imagem, (ii) rodar testes de rede dentro do cluster, se precisar, (iii) testar a listagem de pods e (iv) ver os eventos naquela janela que mostra os eventos mencionada anteriormente (k events -w, lembra?).

Uma opção interessante para o k get pods é a -o wide. Isso faz com que o comando mostra mais coisas para você, sendo uma delas em qual nó cada pod está rodando.

No Kubernetes você *pode, mas não precisa* definir previamente em qual nó cada pod deve rodar. O que é excelente, já que do contrário você teria que planejar tudo de antemão e a própria extensibilidade do cluster seria prejudicada. Existe a opção de definir “node affinity” e outras formas de afinidade, mas por default o Kubernetes tenta distribuir os pods dentro do cluster da melhor maneira possível (ou “mais distribuída possível”, até onde percebi).

10: Serviços e Endpoints

Imagine que você tenha um serviço que ouve requisições HTTP e que esse serviço usa um banco de dados. É fácil imaginar que basta que o container exponha uma porta e, pronto, você tem um serviço rodando.

Mas o que acontece quando você quiser escalar esse seu serviço? Cada um dos 25 containers ouvirá naquela porta... e como as requisições serão distribuídas? Você vai implementar um load balancer, um proxy reverso ou algo assim?

Absolutamente não. Escalar serviços é uma prática tão comum que já existe um padrão para fazer-se isso. São os serviços.

Você pode listar os serviços rodando no seu cluster assim:

$ k get services
$ k get svc  # para os preguiçosos
$ k get svc -A  # para listar os serviços de todos os namespaces

A arquitetura de um serviço é assim: um serviço conecta-se a um endpoint, que lista N IP:porta de N pods.

Okay?

1 Service –> 1 Endpoint –> N IP:port –> Pods

Toda vez que você roda um pod que contenha algum container que expõe uma porta (imagine uma funçãozinha HTTP que ouve na porta 5000) e cria um Serviço relacionado a esse container nessa porta (imagine que você quer expor essa função na porta 80 dentro do cluster), o Kubernetes cria lá uma lista de IPs:portas para onde as requisições a esse serviço serão roteadas.

Imagine que dentro do teu sistema há um “create-user”, que é um serviço HTTP que será chamado por outros componentes. Por default, o container expõe a porta 5000. E você espera ter pelo menos 10 instâncias desse serviço rodando simultaneamente dentro do cluster. O que isso significa na prática?

Menciono os endpoints porque é uma ferramenta muito útil de depuração: algumas vezes eu criei Serviços que “não funcionavam” porque os containers ouviam em portas erradas, por exemplo. Um bom k get endpoints mostra facilmente se há mesmo algum endpoint escutando ou não.

Mão na massa: o yaml de um “deploy”

// TODO

ConfigMaps

// TODO

Secrets

// TODO

Ingress

// TODO

TLS

// TODO

TCP Proxy

// TODO

Volumes

// TODO

Helm

BotKube

Apesar do que a documentação pode parecer requerer, não é preciso rodar o Elastic Search para ter o botkube rodando.

https://botkube.io/

// TODO

Webhooks

// TODO