Kubernetes & Elastic Container Registry

Kubernetes

O plano desse hands-on é basicamente utilizar o ECR da AWS para armazenar as imagens do Docker e fazer o pull e deploy dessas imagens num cluster Kubernetes.

Antes de tudo acho que vale a pena explicar brevemente o que é o Kubernetes e qual o propósito da ferramenta.

O Kubernetes funciona como um verdadeiro capitão dos containers, pois permite gerenciar os diversos recursos vinculados aos nossos microsserviços. O Kubernetes torna o deploy e scaling dos microsserviços trivial, feito de forma automática.

À grosso modo, temos um cluster de compute/workers nodes que são gerenciados por um master node. Obviamente, existem diversos recursos, componentes e detalhes que serão omitido aqui porque não é o propósito do tutorial.

Acima temos uma representação básica da arquitetura do Kubernetes e os seus componentes.

Imagine que os nodes são apenas máquinas cujo único propósito é prover recursos aos pods, esses recursos são basicamente:

  • Compute
  • Storage
  • Networking

Então temos o seguinte, uma pool de máquinas trabalhadoras cujo único propósito de existência é prover recursos aos pods que ali residem. Os pods são a alma da aplicação, tudo no kubernetes gira literalmente em torno dos pods, é o recurso mais básico, mais essencial do Kubernetes.

Pods são quase containers. Pods podem ter mais de 1 container, englobam a aplicação em um ambiente de recursos compartilháveis. Esse esquema de empacotar os containers é justamente o que torna possível o gerenciamento automático da infraestrutura de uma aplicação.

Em momentos de picos de acessos o deploy devidamente configurado é capaz de aguentar as requisições e escalar imediatamente os pods horizontalmente. Além disso, é possível configurar um esquema de auto-healing que faz a recuperação automática dos containers nessas ocasiões.

Outra coisa bastante interessante que essa ferramenta permite é a atualização da aplicação pra uma nova versão sem precisar tirá-la do ar e se a atualização estiver com problemas é possível fazer o rollback da aplicação automaticamente também. Além disso, permite-nos declarar os recursos do cluster utilizando o YAML. para cada tipo de recurso devemos declarar os metadados, especificações, labels e seletores.

Tendo dito isso, fica claro o motivo pelo qual Kubernetes não é concorrente do Docker. Quem liga, desliga, cria e remove os containers dos pods é o container runtime do Docker, então o Kubernetes “se beneficia” com isso.

Helm Charts

Pense no seguinte cenário, precisamos ter um deployment.yaml para vários ambientes (kubernetes clusters): staging, development, production. Poderíamos criar um yaml file para cada ambiente e para cada ambiente teríamos que modificar os valores no yaml manualmente caso uma image guardada em um registry remoto fosse modificada.

Isso pode ser resolvido colocando placeholders dentro desses arquivos yaml e substituindo de acordo com arquivos de configurações… Porém, existe uma solução muito mais eficiente e produtiva: Helm. Com Helm podemos declarar um arquivo values.yaml que irá guardar apenas os valores que serão transferidos para um arquivo yaml de template. Muito mais produtivo.

Elastic Container Registry

Esse serviço da AWS permite a hospedagem de imagens de containers da nossa aplicação num repositório dentro do nosso registry privado. Dentre as features desse serviço podemos listar: integração com o Amazon Elastic Kubernetes (EKS), suporte aos padrões da Open Container Initiative (OCI) e ao Docker Registry HTTP API V2 (permite a utilização da CLI do docker para interagir com o ECR, alta disponibilidade pois as imagens são armazenadas no S3 (várias cópias armazenadas em diversos sistemas).

Além disso, podemos definir políticas de controle de acesso às imagens e transferir as imagens via HTTPS, o que nos dá uma camada a mais de segurança na rede. Nesse Hands-on iremos criar um repositório dentro de um registry privado. Posteriormente nos autenticaremos com a CLI do Docker ao registry e pusharemos a imagem.

Hands-on

O propósito do tutorial é demonstrar a configuração de um ambiente hipotético do desenvolvimento de uma aplicação, com a agilidade de - em casos em que precisaremos fazer rollback para uma versão anterior, pois a versão atual crashou em algum momento - sermos capazes de fazê-lo sem afetar a experiência do usuário da aplicação ocasionados por erros de desenvolvimento.

Vamos buildar uma imagem de uma aplicação Flask mínima que a única coisa que faz é renderizar um Hello World no browser.

Para isso criaremos um Dockerfile e buildaremos a imagem, posteriomente pusharemos para o ECR. Abaixo nós temos o Dockerfile, que utiliza como base para o build da imagem o python:3.8-slim-buster. Também setamos uma variável de ambiente que o comando Flask aponta (estamos dizendo qual arquivo é o entrypoint da nossa aplicação flask).

Após isso, copiamos o requirements.txt contendo as dependências para a aplicação funcionar (nesse caso apenas o Flask mesmo), e instalamos as dependências. Copiamos o resto da aplicação pra dentro do WORKDIR da imagem e rodamos o comando que inicia a API Flask.

Para efetuar o build da imagem utilizamos o comando build do docker (criaremos uma docker image) especificamos o diretório onde está o Dockerfile (nesse caso como é o diretório atual usamos .) e criamos uma tag para imagem.

docker build . --tag "app:v1"

Para confirmar o build da imagem:

docker images

Agora podemos pushar a imagem para o repositório da AWS. Para isso precisamos autenticar a CLI do Docker.

aws ecr get-login-password — region us-east-1 | docker login --username AWS --password-stdin 846140221160.dkr.ecr.us-east-1.amazonaws.com/handson

Agora precisamos “taggear” a imagem para apontar pro repositório da AWS:

docker tag cde26b811e7b 84610221160.dkr.ecr.us-east-1.amazonaws.com/handson

Agora basta pushar a imagem pro repositório da ECR:

docker push 84610221160.dkr.ecr.us-east-1.amazonaws.com/handson:v1

Para fazer o deploy da imagem do registry num cluster kubernetes precisamos configurar um secret (recurso do kubernetes). Para isso utilizaremos um pequeno script que já configura (ou atualiza) esse recurso:

Como eu já havia criado esse secret antes, o kubectl acusará que não teve nenhuma mudança no recurso pois o .dockerconfigjson já foi setado.

Deploy

Como mencionado anteriormente, para facilitar a manutenção dos recursos do Kubernetes usaremos o Helm. Recapitulando, temos arquivos yaml de templates que especificam os recursos que queremos no nosso cluster e temos um arquivo de valores que serão atribuídos aos specs desses recursos. Para criarmos o esqueleto de um helm-chart é bem simples: helm create app-helm

Estrutura do diretório helm chart

Por exemplo, dentro da pasta templates, no arquivo deployment.yaml notamos que os valores do deployment são armazenados no arquivo values.yaml .

deployment.yaml

Perceba a spec imagePullSecrets , é esse item que devemos alterar para apontar pro nosso recurso secret que criamos anteriormente no cluster.

Para isso, vamos no values.yaml e atribuímos esse valor:

values.yaml

Nesse momento nós já somos capazes de fazer o pull da imagem armazenada no registry usando o kubernetes (helm). Porém o serviço atribuído ao nosso pod nesse deploy é o ClusterIP, que atribui um endereço interno ao nosso pod. Porém não é isso que queremos, precisamos de um serviço que mapeie o processo do nosso pod (aplicação) a uma porta do node que está armazenado no cluster. Para isso temos o serviço NodePort, que mapeia a porta do pod para uma porta alta do nosso node (que nesse caso é a minha máquina host).

values.yaml

Acima estamos mapeando o ClusterIP service do nosso pod para uma porta alta do node. A porta alvo (targetPort) do nosso pod é a porta 5000, que é a porta que o processo da aplicação Flask está rodando. Nesse momento podemos dar um helm install ./app-helm e acessarmos a aplicação usando o IP do node + a porta que foi atribuída ao NodePort. Podemos ver isso com clareza usando o comando para listar os serviços kubectl get svc .

Acima ignore o serviço kubernetes. dummy-chart está rodando na porta 32373 do host (node).

192.168.1.6: IP do node

Concluindo…

Tudo isso pra que? Bom, imagine que temos um repositório com os helm charts de uma aplicação em que os desenvolvedores podem fazer o pull e rodar localmente num cluster kubernetes. O único trabalho desse desenvolvedor seria obter os helm charts e executar o comando para instalar a aplicação no cluster (tirando a etapa de configurar o secret que só acontece única vez).

Podemos tirar proveito de várias features desse package manager, como os hooks que podem ser usados num pipeline CI/CD (poderíamos ter feito um para setar o secret antes da instalação do chart), rollback release (helm mantém um histórico dos deploys), alteração dos valores no values.yaml por parte do desenvolvedor em ambiente de desenvolvimento via CLI, etc.

programmer