O problema da limpeza “inteligente” de imagens de containers e sua solução no werf

O problema da limpeza “inteligente” de imagens de containers e sua solução no werf

O artigo discute os problemas de limpeza de imagens que se acumulam em registros de contêineres (Docker Registry e seus análogos) na realidade dos pipelines modernos de CI/CD para aplicativos nativos da nuvem entregues ao Kubernetes. São apresentados os principais critérios de relevância das imagens e as consequentes dificuldades em automatizar a limpeza, poupar espaço e satisfazer as necessidades das equipas. Por fim, usando o exemplo de um projeto específico de Open Source, contaremos como essas dificuldades podem ser superadas.

Introdução

O número de imagens em um registro de contêiner pode crescer rapidamente, ocupando mais espaço de armazenamento e aumentando significativamente seu custo. Para controlar, limitar ou manter o crescimento aceitável do espaço ocupado no cadastro, aceita-se:

  1. use um número fixo de tags para imagens;
  2. limpe as imagens de alguma forma.


A primeira limitação às vezes é aceitável para equipes pequenas. Se os desenvolvedores tiverem tags permanentes suficientes (latest, main, test, boris etc.), o registro não aumentará de tamanho e por muito tempo você não precisará pensar em limpá-lo. Afinal, todas as imagens irrelevantes são apagadas e simplesmente não sobra trabalho para limpeza (tudo é feito por um coletor de lixo comum).

No entanto, esta abordagem limita enormemente o desenvolvimento e raramente é aplicável a projetos modernos de CI/CD. Uma parte integrante do desenvolvimento foi automação, que permite testar, implantar e fornecer novas funcionalidades aos usuários com muito mais rapidez. Por exemplo, em todos os nossos projetos, um pipeline de CI é criado automaticamente a cada commit. Nele, a imagem é montada, testada, lançada em diversos circuitos Kubernetes para depuração e demais verificações e, se tudo correr bem, as alterações chegam ao usuário final. E isso não é mais ciência de foguetes, mas uma ocorrência cotidiana para muitos - provavelmente para você, já que está lendo este artigo.

Como a correção de bugs e o desenvolvimento de novas funcionalidades são realizados em paralelo, e os lançamentos podem ser realizados várias vezes ao dia, é óbvio que o processo de desenvolvimento é acompanhado por um número significativo de commits, o que significa um grande número de imagens no registro. Como resultado, surge a questão de organizar uma limpeza eficaz do registro, ou seja, removendo imagens irrelevantes.

Mas como você determina se uma imagem é relevante?

Critérios para a relevância da imagem

Na grande maioria dos casos, os principais critérios serão:

1. A primeira (a mais óbvia e mais crítica de todas) são as imagens que atualmente usado no Kubernetes. A remoção dessas imagens pode resultar em custos significativos de tempo de inatividade de produção (por exemplo, as imagens podem ser necessárias para replicação) ou anular os esforços da equipe de depuração em qualquer um dos loops. (Por esse motivo fizemos até um especial Exportadores de Prometheus, que rastreia a ausência de tais imagens em qualquer cluster do Kubernetes.)

2. Segundo (menos óbvio, mas também muito importante e novamente relacionado com a exploração) - imagens que necessário para reversão em caso de detecção de problemas graves na versão atual. Por exemplo, no caso do Helm, são imagens usadas em versões salvas do lançamento. (A propósito, por padrão no Helm o limite é de 256 revisões, mas é improvável que alguém realmente precise salvar este um grande número de versões?..) Afinal, nós, em particular, armazenamos versões para que possamos utilizá-las posteriormente, ou seja, “reverter” para eles, se necessário.

3. Terceiro - necessidades do desenvolvedor: Todas as imagens relacionadas ao seu trabalho atual. Por exemplo, se estamos considerando um PR, faz sentido deixar uma imagem correspondente ao último commit e, digamos, ao commit anterior: desta forma o desenvolvedor pode retornar rapidamente a qualquer tarefa e trabalhar com as alterações mais recentes.

4. Quarto - imagens que correspondem às versões do nosso aplicativo, ou seja são o produto final: v1.0.0, 20.04.01/XNUMX/XNUMX, serra, etc.

NB: Os critérios aqui definidos foram formulados com base na experiência de interação com dezenas de equipes de desenvolvimento de diferentes empresas. Porém, é claro, dependendo das especificidades dos processos de desenvolvimento e da infraestrutura utilizada (por exemplo, Kubernetes não é utilizado), esses critérios podem ser diferentes.

Elegibilidade e soluções existentes

Serviços populares com registros de contêineres, via de regra, oferecem suas próprias políticas de limpeza de imagens: nelas você pode definir as condições sob as quais uma tag é removida do registro. No entanto, essas condições são limitadas por parâmetros como nomes, horário de criação e número de tags*.

* Depende de implementações específicas de registro de contêiner. Consideramos as possibilidades das seguintes soluções: Azure CR, Docker Hub, ECR, GCR, Pacotes GitHub, GitLab Container Registry, Harbor Registry, JFrog Artifactory, Quay.io - a partir de setembro de 2020.

Este conjunto de parâmetros é suficiente para satisfazer o quarto critério - ou seja, selecionar imagens que correspondam às versões. No entanto, para todos os outros critérios, é necessário escolher algum tipo de solução de compromisso (uma política mais dura ou, inversamente, mais branda) - dependendo das expectativas e das capacidades financeiras.

Por exemplo, o terceiro critério – relacionado às necessidades dos desenvolvedores – pode ser resolvido organizando processos dentro das equipes: nomeação específica de imagens, manutenção de listas de permissões especiais e acordos internos. Mas, em última análise, ainda precisa ser automatizado. E se as capacidades das soluções prontas não forem suficientes, você terá que fazer algo por conta própria.

A situação com os dois primeiros critérios é semelhante: eles não podem ser satisfeitos sem receber dados de um sistema externo - aquele onde as aplicações são implantadas (no nosso caso, Kubernetes).

Ilustração do fluxo de trabalho no Git

Digamos que você esteja trabalhando algo assim no Git:

O problema da limpeza “inteligente” de imagens de containers e sua solução no werf

O ícone com um cabeçalho no diagrama indica imagens de contêiner que estão atualmente implantadas no Kubernetes para qualquer usuário (usuários finais, testadores, gerentes, etc.) ou são usadas por desenvolvedores para depuração e fins semelhantes.

O que acontece se as políticas de limpeza permitirem apenas que as imagens sejam retidas (não excluídas) por nomes de tags fornecidos?

O problema da limpeza “inteligente” de imagens de containers e sua solução no werf

Obviamente, tal cenário não deixará ninguém feliz.

O que mudará se as políticas permitirem que as imagens não sejam excluídas? de acordo com um determinado intervalo de tempo/número de últimos commits?

O problema da limpeza “inteligente” de imagens de containers e sua solução no werf

O resultado ficou muito melhor, mas ainda está longe do ideal. Afinal, ainda temos desenvolvedores que precisam de imagens no registro (ou mesmo implantadas em K8s) para depurar bugs...

Resumindo a situação atual do mercado: as funções disponíveis nos registros de contêineres não oferecem flexibilidade suficiente na hora da limpeza, e a principal razão para isso é nenhuma maneira de interagir com o mundo exterior. Acontece que as equipes que exigem tal flexibilidade são forçadas a implementar de forma independente a exclusão de imagens “de fora”, usando a API Docker Registry (ou a API nativa da implementação correspondente).

No entanto, estávamos procurando uma solução universal que automatizasse a limpeza de imagens para diferentes equipes usando diferentes registros...

Nosso caminho para a limpeza universal de imagens

De onde vem essa necessidade? O fato é que não somos um grupo separado de desenvolvedores, mas uma equipe que atende muitos deles ao mesmo tempo, ajudando a resolver problemas de CI/CD de forma abrangente. E a principal ferramenta técnica para isso é o utilitário Open Source bem. Sua peculiaridade é que não desempenha uma função única, mas acompanha os processos de entrega contínua em todas as etapas: da montagem à implantação.

Publicar imagens no registro* (imediatamente após serem construídas) é uma função óbvia desse utilitário. E como as imagens são colocadas lá para armazenamento, então - se o seu armazenamento não for ilimitado - você precisa ser responsável pela limpeza posterior. Como alcançamos sucesso nisso, satisfazendo todos os critérios especificados, será discutido mais adiante.

* Embora os próprios registros possam ser diferentes (Docker Registry, GitLab Container Registry, Harbor, etc.), seus usuários enfrentam os mesmos problemas. A solução universal no nosso caso não depende da implementação do cadastro, pois funciona fora dos próprios registros e oferece o mesmo comportamento para todos.

Embora estejamos usando o werf como exemplo de implementação, esperamos que as abordagens utilizadas sejam úteis para outras equipes que enfrentam dificuldades semelhantes.

Então ficamos ocupados externo implementação de um mecanismo de limpeza de imagens - em vez dos recursos que já estão integrados aos registros de contêineres. O primeiro passo foi usar a API Docker Registry para criar as mesmas políticas primitivas para o número de tags e o horário de sua criação (mencionadas acima). Adicionado a eles lista de permissões com base em imagens usadas na infraestrutura implantada, ou seja Kubernetes. Para este último, foi suficiente usar a API Kubernetes para iterar todos os recursos implantados e obter uma lista de valores image.

Esta solução trivial resolveu o problema mais crítico (critério nº 1), mas foi apenas o começo de nossa jornada para melhorar o mecanismo de limpeza. O passo seguinte – e muito mais interessante – foi a decisão associar imagens publicadas ao histórico do Git.

Esquemas de marcação

Para começar, escolhemos uma abordagem em que a imagem final deveria armazenar as informações necessárias para a limpeza e construímos o processo em esquemas de marcação. Ao publicar uma imagem, o usuário selecionou uma opção específica de marcação (git-branch, git-commit ou git-tag) e usou o valor correspondente. Nos sistemas CI, esses valores eram definidos automaticamente com base nas variáveis ​​de ambiente. Na verdade a imagem final foi associada a uma primitiva Git específica, armazenando os dados necessários para limpeza em etiquetas.

Essa abordagem resultou em um conjunto de políticas que permitiram que o Git fosse usado como a única fonte da verdade:

  • Ao excluir uma ramificação/tag no Git, as imagens associadas no registro foram excluídas automaticamente.
  • O número de imagens associadas às tags e commits do Git pode ser controlado pelo número de tags usadas no esquema selecionado e pelo horário em que o commit associado foi criado.

No geral, a implementação resultante satisfez as nossas necessidades, mas em breve um novo desafio nos esperava. O fato é que ao usar esquemas de marcação baseados nas primitivas do Git, encontramos uma série de deficiências. (Como sua descrição está além do escopo deste artigo, todos podem se familiarizar com os detalhes aqui.) Portanto, tendo decidido mudar para uma abordagem mais eficiente de marcação (marcação baseada em conteúdo), tivemos que reconsiderar a implementação da limpeza de imagens.

Novo algoritmo

Por que? Com a marcação baseada em conteúdo, cada tag pode satisfazer vários commits no Git. Ao limpar imagens, você não pode mais presumir apenas do commit onde a nova tag foi adicionada ao registro.

Para o novo algoritmo de limpeza, foi decidido abandonar os esquemas de marcação e construir processo de meta-imagem, cada um dos quais armazena um monte de:

  • o commit no qual foi realizada a publicação (não importa se a imagem foi adicionada, alterada ou permaneceu a mesma no registro do container);
  • e nosso identificador interno correspondente à imagem montada.

Em outras palavras, foi fornecido vinculando tags publicadas com commits no Git.

Configuração final e algoritmo geral

Ao configurar a limpeza, os usuários agora têm acesso a políticas que selecionam as imagens atuais. Cada uma dessas políticas é definida:

  • muitas referências, ou seja, Tags Git ou ramificações Git usadas durante a verificação;
  • e o limite de imagens pesquisadas para cada referência do conjunto.

Para ilustrar, a configuração da política padrão começou a ficar assim:

cleanup:
  keepPolicies:
  - references:
      tag: /.*/
      limit:
        last: 10
  - references:
      branch: /.*/
      limit:
        last: 10
        in: 168h
        operator: And
    imagesPerReference:
      last: 2
      in: 168h
      operator: And
  - references:  
      branch: /^(main|staging|production)$/
    imagesPerReference:
      last: 10

Esta configuração contém três políticas que cumprem as seguintes regras:

  1. Salve a imagem das últimas 10 tags Git (por data de criação da tag).
  2. Salve no máximo 2 imagens publicadas na última semana em no máximo 10 tópicos com atividade na última semana.
  3. Salve 10 imagens para filiais main, staging и production.

O algoritmo final se resume às seguintes etapas:

  • Recuperando manifestos do registro de contêiner.
  • Excluindo imagens usadas no Kubernetes, porque Já os pré-selecionamos pesquisando a API K8s.
  • Verificando o histórico do Git e excluindo imagens com base em políticas especificadas.
  • Removendo imagens restantes.

Voltando à nossa ilustração, isto é o que acontece com o werf:

O problema da limpeza “inteligente” de imagens de containers e sua solução no werf

No entanto, mesmo que você não use o werf, uma abordagem semelhante à limpeza avançada de imagens - em uma implementação ou outra (de acordo com a abordagem preferida para marcação de imagens) - pode ser aplicada a outros sistemas/utilitários. Para fazer isso, basta lembrar os problemas que surgem e encontrar em sua pilha as oportunidades que permitem integrar sua solução da maneira mais tranquila possível. Esperamos que o caminho que percorremos o ajude a olhar para o seu caso particular com novos detalhes e reflexões.

Conclusão

  • Mais cedo ou mais tarde, a maioria das equipes enfrenta o problema de estouro de registro.
  • Na busca por soluções, é necessário primeiro determinar os critérios de relevância da imagem.
  • As ferramentas oferecidas pelos populares serviços de registro de contêineres permitem organizar uma limpeza muito simples que não leva em conta o “mundo exterior”: as imagens utilizadas no Kubernetes e as peculiaridades dos fluxos de trabalho da equipe.
  • Um algoritmo flexível e eficiente deve compreender os processos de CI/CD e operar não apenas com dados de imagem Docker.

PS

Leia também em nosso blog:

Fonte: habr.com

Adicionar um comentário