Publique aplicações nativas do Kubernetes com confiança
Amir Moualem
14 de novembro de 2019
0 minutos de leituraHá alguns meses, nossa equipe começou a desenvolver um novo produto.
Esse novo produto tem algumas características que o diferenciam de outros projetos de software que desenvolvemos até agora:
Ele é nativo do Kubernetes, ou seja, está fortemente integrado à API do Kubernetes e precisa dessa API específica para funcionar (ou até mesmo para ser testado… falaremos mais sobre isso adiante).
Ele precisa de determinadas configurações para funcionar corretamente. Isso significa que precisamos distribuir a imagem com os arquivos de configuração .yaml do Kubernetes, para que nossos usuários possam instalá-la sem complicações. O desejo de simplificar ao máximo a integração inicial ao produto torna isso ainda mais complexo: por isso, oferecemos instalação tanto por meio de charts do Helm quanto de arquivos .yaml “vanilla”, e ambos precisam ser testados e publicados.
É uma aplicação do lado do cliente; assim que a publicamos, perdemos o controle sobre ela, e não há como voltar atrás. Podemos publicar hotfixes para nossos microsserviços em poucos minutos, mas pedir repetidamente que nossos usuários atualizem a instalação só vai complicar a integração inicial, na melhor das hipóteses.
Isso nos levou a dedicar um pouco mais de tempo ao planejamento de um pipeline de CI e CD que nos permitisse distribuir um produto como esse com confiança, sem desacelerar demais nosso trabalho.
O pipeline de CI/CD que a Snyk já usa para microsserviços gira em torno da interação dos desenvolvedores com o GitHub:
Ao abrir pull requests, instalamos, executamos e testamos o serviço em um ambiente de testes.
A mesclagem de pull requests começa da mesma forma, seguida por um lançamento semântico do serviço, a criação de uma imagem Docker, sua execução e implantação no cluster Kubernetes do nosso ambiente de produção.
Queríamos interações semelhantes, para que o processo parecesse o mais natural possível.
Então, como abordamos esse projeto?
Testes de integração com Kubernetes usando Kind
Para nós, que ainda não tínhamos muita experiência, a primeira dúvida era como testar um software tão dependente da API do Kubernetes. Simular todos os fluxos relacionados à API do Kubernetes seria extremamente difícil de escrever e manter, além de provavelmente deixar de fora alguns fluxos essenciais que deveriam ser testados. Uma abordagem ingênua seria usar um cluster Kubernetes real, seja configurando um para cada etapa de testes, seja mantendo um ambiente em execução por longos períodos. Embora isso certamente funcionasse, traria complicações demais para lidarmos neste estágio inicial do desenvolvimento do software:
Criar novos ambientes Kubernetes a cada teste levaria tempo para desenvolver e aumentaria muito o tempo de configuração dos testes.
Manter um ambiente em execução por longos períodos significa cuidar da limpeza e pode tornar os testes bastante instáveis.
Felizmente, nos indicaram oprojeto Kind. Kind é uma ferramenta baseada em Docker para executar clusters locais do Kubernetes compatíveis com a API do Kubernetes. Ela atendeu perfeitamente às nossas necessidades, pois combina as vantagens de um ambiente limpo para cada teste com uma configuração muito rápida.
Agora, na etapa de testes — seja no ambiente de CI ou mesmo localmente —, basta criar um cluster Kind, carregar nele a imagem recém-criada e verificar se o contêiner executa a tarefa para a qual foi desenvolvido.
Teste o que você entrega; entregue o que você testa
O próximo desafio era garantir que não testássemos apenas nosso código, mas também o produto completo que entregamos.
O código (e o serviço) é instalado em uma imagem Docker. A imagem Docker pode ser implantada em clusters Kubernetes usando charts do Helm ou arquivos .yaml “vanilla”, e os clusters Kubernetes podem oferecer suporte a diferentes versões das APIs do Kubernetes.
Nosso objetivo é testar e oferecer suporte a essa ampla variedade de cenários.
Assim que criamos uma imagem, adicionamos uma tag para identificá-la ao longo das etapas de teste. Instalamos essa imagem usando tanto charts do Helm quanto arquivos .yaml “vanilla”, em clusters Kubernetes com diferentes versões de API, antes de marcá-la como “aprovada”.
Também é nessa etapa que usamos o semantic-release para versionar nossas imagens. Em vez de acompanhar nossos produtos pelos SHAs do Git ou por versões arbitrárias atribuídas manualmente, optamos pelo versionamento semântico. Assim, oferecemos versões simples e fáceis de entender, com notas de lançamento atribuídas automaticamente e publicadas no GitHub. O resultado é uma imagem com uma tag versionada, como 1.2.3.
Nesta etapa, basta apontar nossos arquivos .yaml de implantação para essa nova versão...
Publicação fácil com charts do Helm e GitHub Pages
Helm é o gerenciador de pacotes do Kubernetes. Uma breve pesquisa revelou duas abordagens principais para publicar charts do Helm:
Hospedar o chart do Helm norepositório público de charts selecionados do Helm.
Hospedá-los por conta própria com oGitHub Pages em nosso repositório.
A princípio, a segunda abordagem fez mais sentido para nós, pois é independente e não cria dependência de terceiros. À medida que nosso produto e pipeline de CI e CD amadurecerem, talvez decidamos contribuir com nosso chart para o repositório público também.
Integrando tudo com o GitHub
Parece que resolvemos a maioria dos nossos problemas: executamos testes de integração com Kind para simular ambientes Kubernetes reais em diferentes cenários e usamos o versionamento semântico para marcar nossos produtos, que publicamos no GitHub Pages para simplificar o processo.
Então, como tudo isso se encaixa no nosso processo de desenvolvimento?
Definimos duas branches principais em nosso repositório Git:
staging, nossa branch padrão, é o ponto de ramificação e mesclagem de todas as mudanças no produto (funcionalidades, correções de bugs, tarefas de manutenção etc.).masteré o ponto de decisão para publicar nosso produto testado.
O pipeline de CI e CD tem quatro etapas no total, distribuídas entre essas duas branches e as duas principais interações dos nossos desenvolvedores com as branches do GitHub (abrir e mesclar um pull request). Cada etapa serve para aumentar nossa confiança no produto desenvolvido e nos aproximar da entrega aos usuários.
Vale mencionar que a orquestração é feita pelo Travis CI (a migração para o CircleCI está em andamento), mas, para todos os efeitos, poderia ser um pequeno script interno em Python.
A tabela a seguir resume as quatro etapas e o que realizamos em cada uma.
Gatilho | Ação | Objetivo |
|---|---|---|
Abrir um pull request de uma branch de funcionalidade para | Lint, compilação e testes unitários. | Testar em um ambiente limpo. |
Mesclar um pull request de uma branch de funcionalidade para | Criar uma imagem Docker. Marcá-la como | Garantir que as branches mescladas não entrem em conflito. Testar e compilar em um ambiente limpo. Criar uma única vez o produto que planejamos publicar. |
Abrir um pull request de | Nada. Este é o momento para realizar quaisquer etapas manuais antes da publicação do produto, como dogfooding. | Última oportunidade para receber feedback. Lembrete para inspecionar manualmente determinados ambientes de teste, se necessário. |
Mesclar um pull request de | Alterar a tag da imagem para sua versão pública, | Publicar o produto que já foi criado e testado — sabemos exatamente o que estamos publicando. |
Sem mais um resumo chato de sempre
Brincadeira, é completamente chato. Mas vou ser breve.
Criamos um excelente pipeline de CI e CD e aprendemos muito ao longo do caminho. Equilibramos a automação com a quantidade certa de cliques manuais, de um jeito que parece natural para nós. Cada etapa aumenta nossa confiança no que estamos entregando, e a última torna a entrega pública.
Como em qualquer outro projeto de software, ainda há espaço para melhorias. Poderíamos começar a publicar em um repositório público de charts do Helm. Também poderíamos ampliar nossa infraestrutura de testes para incluir mais ambientes de longa duração e testar atualizações. Quando tivermos confiança suficiente nos testes, talvez até eliminemos algumas etapas manuais antes do lançamento — quem sabe?
Segurança de contêineres que prioriza os desenvolvedores
O Snyk encontra e corrige automaticamente vulnerabilidades em imagens de contêiner e workloads do Kubernetes.



