5.3 Orquestração com Kubernetes
Last updated
Last updated
Na seção anterior pudemos entender as complexidades de gerenciar aplicações multi-containers e como o uso do Docker Compose facilita a orquestração dessas aplicações.
Porém, no mundo corporativo, temos empresas que necessitam atender demandas cada vez maiores, com alta disponibilidade e com a disponibilização de novas funcionalidades de forma cada vez mais ágil. Para atender esse cenário, uma infraestrutura composta por múltiplos servidores que é utilizada por várias equipes que produzem aplicações cloud native se torna necessária.
Nesse contexto, o Kubernetes é uma ferramenta que veio para simplificar e resolver diversos problemas:
Como dar maior escala para as aplicações com ajuste automático de cargas e escalabilidade horizontal?
Como melhorar a produtividade do desenvolvedor com abstração da operação e infraestrutura?
Como garantir alta disponibilidade entre deployments da aplicação e com self-healing?
Esse cenário é um dos principais motivadores do uso de ferramentas de orquestração mais completas e eficientes como o Kubernetes. O Kubernetes é um sistema de automação de deployments e gerenciamento de aplicações de larga escala e alta complexidade composta por containers (Luksa, M. (2018). Kubernetes in action. Manning.).
O projeto do Kubernetes foi iniciado pelo Google que reporta o uso de containers desde 2004 e em 2014 tinha a execução de 2 bilhões de containers por semana conforme apresentação na Gluecon 2014.
Dado esse cenário de uso massivo de containers em aplicações produtivas, o Google já investia em pesquisa e desenvolvimento de soluções internas de orquestração. Primeiramente, contava com o sistema Borg e na sequência evoluiu para outra solução interna denominada Omega.
Aproveitando todo o conhecimento interno desenvolvido ao longo de muitos anos, o projeto do Kubernetes foi anunciado como open source em 2014. O contexto de lançamento da plataforma envolvia também a grande adoção e disputa dos serviços de nuvem pública. Em entrevista com o product manager do Google Martin Buhr, foi mencionada a estratégia do produto de tornar a API do Kubernetes em um padrão do mercado para orquestração de containers devido a facilidade do uso, fazendo com que o Google se posicionasse como especialista no assunto para dar maior popularidade ao seu próprio serviço de nuvem pública. Tendo um padrão de mercado para workloads containerizados, também haveria uma facilidade de migração dessas aplicações entre datacenters on premises e os diversos serviços de nuvem, facilitando assim a adoção dos serviços da Google Cloud Platform (GCP).
Objetos são entidades persistidas dentro do sistema do Kubernetes. Basicamente, eles descrevem como deveria se comportar todos os componentes orquestrados pelo cluster.
Para entendermos melhor esse conceito, podemos começar com dois objetos básicos:
Pod
: é um grupo de um ou mais containers e consistem na menor unidade de gerenciamento do Kubernetes
Service
: Uma forma de expor as aplicações rodando em pods.
Nesse contexto, quando executamos um container de nginx e expomos essa aplicação para conexões externas, estamos definindo dois objetos dentro de um cluster kubernetes. Um pod
que especifica como esse container deve ser executado e um service
que especifica como esse container será exposto e acessado.
Apesar do objeto pod
ser uma camada que especifica os containers, dentro do Kubernetes os pods são gerenciados por outros objetos que controlam os workloads dentro do cluster. Abaixo alguns exemplos:
Deployment
: Determina o estado de pods e replicasets, permitindo rollback, scale up e outras funcionalidades
Job
e Cronjob
: Definem tarefas que possuem um tempo para serem finalizadas. Tarefas que são executadas apenas uma vez são definidas por job. Já os cronjobs, definem tarefas recorrentes.
Outros exemplos de workload são encontrados na documentação oficial.
Outro objeto que utilizaremos na nossa demonstração é o Namespace
que serve como um mecanismo de isolamento e organização de outros objetos dentro de um mesmo cluster.
Além dos objetos citados, o Kubernetes conta com várias outras definições que, apesar de não ser o escopo do curso, podem ser estudados com mais detalhes:
Fonte: jayendrapatil.com
Para entender melhor as capacidades do Kubernetes, precisamos conhecer os seus componentes e como a plataforma gerencia a sua infraestrutura. Aproveitando o contexto do curso na utilização do Docker Compose, a imagem abaixo ilustra um comparativo entre as ferramentas e apresenta os dois papéis que um servidor pode exercer dentro de um cluster Kubernetes: master node
ou worker node
.
Fonte: Serverside
O primeiro ponto a ser observado é que o Kubernetes administra um cluster de servidores. Essa característica facilita a implementação de vários benefícios dentro de um sistema containerizado como a escalabilidade horizontal e alta disponibilidade.
Fonte: c-sharpcorner
Os servidores com papel de master executam as funções de controle e gerenciamento do cluster. Basicamente, eles possuem 4 componentes:
API Server
:
Responsável por servir a API do control plane. É por meio dessa API que todas as requisições para o controle do cluster são gerenciadas.
Etcd
:
Responsável pela persistência de todos os objetos do cluster
Scheduler
:
Responsável pela alocação de todos os pods dentro dos worker nodes.
Controller Manager
:
Aplicações responsáveis por gerenciar todo o ciclo de vida dos objetos Kubernetes.
Já os worker nodes são os servidores responsáveis por de fato executar as aplicações. Possuem os seguintes componentes:
Kubelet
:
Agente executado em cada worker responsável por garantir a execução o estado solicitado de cada container
Kube-proxy
:
Proxy de rede responsável por todas as regras e comunicação entre os pods.
Container Runtime
:
Engine responsável por executar os containers.
Entendidos os principais conceitos de arquitetura e objetos do Kubernetes, podemos analisar tudo junto e entender como ocorrem as interações com a ferramenta.
Dado que dentro dos master nodes o api-server
é a peça responsável por receber os pedidos de criação, atualização e deleção dos objetos, é importante entender as principais formas de interação:
kubectl
: Esse é o utilitário de linha de comando do Kubernetes e possibilita a interação com a ferramenta por meio do terminal.
Interface Web
: O Kubernetes conta com uma UI que possibilita o monitoramento e execução de comandos de forma visual.
API Rest
: Também é possível interagir com o cluster por meio de clients da API em diversas linguagens.
Na interação com o cluster, enviamos um payload que pode ser especificado em yaml ou json que contém a definição dos resources que queremos modificar no cluster. A especificação contém alguns blocos de informação:
API e Type
: Especifica com qual api estamos interagindo e qual resource estamos definindo
Metadata
: Determina informações de metadados como nome do objeto, namespace, labels etc.
Spec
: Contém a especificação do objeto e varia de acordo com o tipo de resource que estamos trabalhando.
Status
: Contém o estado atual do objeto. É dessa forma que o Kubernetes persiste as informações e compara o estado declarado com o estado atual para determinar as ações necessárias dentro do cluster.
Por fim, com base nos conceitos de interação, arquitetura e objetos, é possível compreender o ciclo de vida dos objetos dentro do sistema do Kubernetes e que ações geralmente são executadas durante a criação de um resource:
O desenvolvedor submete um manifesto (yaml/json) para a api do Kubernetes
Os objetos especificados nos manifestos são persistidos no Etcd
Os controllers monitoram o estado desejado dos objetos especificados e se comunica novamente com o API server
O scheduler avalia os servidores do cluster e decide onde os novos pods irão ser executados.
O kubelet percebe que o seu servidor foi designado para executar uma tarefa
Se serviços foram especificados, o kube-proxy implementa as regras de comunicação
Os controllers e o kubelet continuam monitorando os estados dos objetos constantemente.
Para colocar os conceitos estudas em prática, vamos executar um primeiro exemplo de aplicação implantada no Kubernetes. Para isso, precisamos de uma versão local da ferramenta para testes e desenvolvimento. Abaixo temos algumas das principais distribuições Kubernetes para desenvolvimento:
Nessa aula utilizaremos a distribuição do Microk8s devido a boa integração que a ferramenta possui com o Ubuntu. Todo o tutorial de instalação atualizado pode ser encontrado na página do produto. Abaixo temos o passo a passo para instalação:
Instalando o Microk8s:
Conseguindo acesso ao grupo da aplicação:
Na sequência, validamos se temos a disponibilidade da aplicação:
Após o start da aplicação, iremos habilitar alguns plugins básicos:
Por fim, podemos ver as instruções de acesso a aplicação pelo comando abaixo:
A instalação do microk8s já é o suficiente para termos um ambiente de testes Kubernetes local. Caso seja necessário acessar outros cluster remotos, podemos instalar o utilitário de linha de comando do Kubernetes: kubectl
(Tutorial de instalação). Também podemos configurar o acesso ao Microk8s via linha de comando.
Para os primeiros passos na plataforma, iremos utilizar uma aplicação web exposta pela porta 8080 que simplesmente exibe algumas informações do servidor no qual ela está hospedada.
Inicialmente vamos criar um namespace para organizar todos os workloads desse exemplo:
Para indicarmos que todos os próximos comandos serão executados no namespace criado, atualizamos o contexto de trabalho:
Na sequência, iremos criar o nosso primeiro deployment:
Podemos verificar o status do deployment da nossa aplicação via dashboard ou pelo seguinte comando:
Uma vez que nossa aplicação estiver pronta, temos que expô-la para acesso externo:
Por fim, basta verificar em qual porta nossa aplicação está disponível e acessá-la pelo navegador:
Para demonstrar como funciona o padrão declarativo dos manifestos do Kubernetes, iremos adaptar o exemplo da aplicação de classificação de produtos com 2 containers vista na seção 3.2.
O primeiro passo é reconstruir as imagens do web server nginx e da API de classificação de produtos. Para isso, utilizaremos o diretório aplicativos/http-api-classificacao-produtos-k8s
contido no repositório desse livro. Esse repositório contém as modificações necessárias para executarmos o nosso exemplo em cluster Kubernetes.
A partir do diretório mencionado, realize o build das duas imagens necessárias para o nosso exemplo. Atenção para as tags utilizadas durante o build.
Após realizado o build, iremos publicar as imagens em um registry privado local que está sendo servido na porta 32000 via Microk8s. Caso ainda não tenha o registry habilitado, realize o setup via comando abaixo:
Utilize o comando de push do Docker para publicar as imagens:
Inicialmente a aplicação trazia dois containers separados que se comunicavam entre si por meio de uma rede virtual do Docker. No exemplo orquestrado pelo Kubernetes, iremos utilizar cada container definido dentro de um POD
. Cada pod será definido dentro de um objeto de deployment
.
Para viabilizar a comunicação entre o frontend e backend, será utilizado um objeto de service
para expor o pod da API Python dentro da rede virtual do Cluster. Por último, será utilizado um outro service para expor o frontend para comunicação externa ao cluster.
Para criar todos os resources especificados no yaml acima:
Agora, basta acessar a aplicação no servidor local via porta 32767: http://127.0.0.1:32767/cadastro.html.
Para deletar todos os objetos criados:
Para demonstrar como o Kubernetes nos ajuda a lidar com uma maior demanda de requisições na API de produtos, vamos modificar o deployment da API Python no nosso arquivo yaml:
Essas alterações estão disponíveis no arquivo yaml k8s-manifests/classificador-scale.yaml
.
Com apenas uma alteração possibilitamos a escalabilidade horizontal de forma independente para uma das aplicações que estamos orquestrando.
Para aplicar as alterações podemos executar o comando de apply:
Se acessarmos o dashboard do nosso cluster Kubernetes, é possível observar que a aplicação wsgi possui 3 pods em funcionamento.
Por fim, nosso próximo exemplo demonstra como o Kubernetes facilita deploy de aplicações sem downtime e como realizar rollbacks.
Vamos provocar uma falha na nova versão da nossa aplicação alterando a imagem utilizada pelo frontend para uma imagem não existente. Essa alteração está disponível no arquivo k8s-manifests/classificador-wrong-tag.yaml
.
Agora, realizaremos o deployment de uma nova versão por meio do comando de apply:
Se inspecionarmos o status da nossa aplicação no dashboard do Kubernetes, veremos que um novo pod foi lançando com a nova versão de imagem. Esse novo pod encontra-se em falha pois especificamos uma imagem inexistente.
Apesar disso, podemos conferir na url de cadastro da aplicação que ela ainda está totalmente funcional. Isso acontece devido a estratégia de deploy do Kubernetes que lança os pods da nova versão sem deletar os pods antigos. Os pods da versão mais antiga só são excluídos e o tráfego só é redirecionado para a nova versão se os pods da última versão se reportarem como saudáveis.
Além de possibilitar o deploy sem indisponibilizar a aplicação, o Kubernetes facilita o rollback para a nossa última versão estável. Podemos listar o histórico de deployments da nossa aplicação:
Na sequência, podemos escolher uma versão estável como ponto de rollback.
Após o rollback, nossa aplicação estará estável novamente e sem pendências de alterações.
Para limpar o ambiente novamente, basta realizarmos a deleção dos objetos declarados no nosso arquivo de deployment:
Esses exemplos ajudam a nos familiarizamos com alguns conceitos básicos de orquestração com o Kubernetes, mas a plataforma oferece ainda mais possibilidades em termos de gerenciamento de configuração, storage, secrets, workloads batch e outros.
Para se aprofundar ainda mais no tema, podemos seguir a documentação oficial.