Post Mortem na indisponibilidade do Quay.io

Observação. trad.: no início de agosto, a Red Hat falou publicamente sobre a solução de problemas de acessibilidade que os usuários de seu serviço encontraram nos meses anteriores Quay.io (é baseado em um cadastro de imagens container, que a empresa recebeu junto com a compra do CoreOS). Independentemente do seu interesse neste serviço como tal, é instrutivo o caminho que os engenheiros do SRE da empresa percorreram para diagnosticar e eliminar as causas do acidente.

Post Mortem na indisponibilidade do Quay.io

No dia 19 de maio, no início da manhã (horário de verão do leste, EDT), o serviço quay.io travou. O acidente afetou tanto os consumidores do quay.io quanto os projetos de código aberto que usam o quay.io como plataforma para construção e distribuição de software. A Red Hat valoriza a confiança de ambos.

Uma equipe de engenheiros do SRE se envolveu imediatamente e tentou estabilizar o serviço do Cais o mais rápido possível. No entanto, enquanto faziam isso, os clientes perdiam a capacidade de enviar novas imagens e apenas ocasionalmente conseguiam extrair as existentes. Por alguma razão desconhecida, o banco de dados quay.io foi bloqueado após dimensionar o serviço para capacidade total.

«O que mudou?" - esta é a primeira pergunta que geralmente é feita nesses casos. Percebemos que pouco antes do problema, o cluster OpenShift Dedicated (que executa quay.io) começou a ser atualizado para a versão 4.3.19. Como o quay.io é executado no Red Hat OpenShift Dedicated (OSD), as atualizações regulares eram rotineiras e nunca causavam problemas. Além disso, nos últimos seis meses, atualizamos os clusters do Quay diversas vezes sem qualquer interrupção no serviço.

Enquanto tentávamos restaurar o serviço, outros engenheiros começaram a preparar um novo cluster OSD com a versão anterior do software, para que, se algo acontecesse, pudessem implantar tudo nele.

Análise de causa raiz

O principal sintoma da falha foi uma avalanche de dezenas de milhares de conexões de banco de dados, que tornou a instância do MySQL efetivamente inoperante. Isso dificultou o diagnóstico do problema. Estabelecemos um limite para o número máximo de conexões de clientes para ajudar a equipe do SRE a avaliar o problema. Não notamos nenhum tráfego incomum no banco de dados: na verdade, a maioria das solicitações foi lida e apenas algumas foram escritas.

Também tentamos identificar um padrão no tráfego do banco de dados que pudesse causar essa avalanche. No entanto, não encontramos nenhum padrão nos logs. Enquanto esperávamos que o novo cluster com OSD 4.3.18 estivesse pronto, continuamos tentando lançar pods quay.io. Cada vez que o cluster atingisse a capacidade total, o banco de dados congelava. Isso significava que era necessário reiniciar a instância do RDS, além de todos os pods quay.io.

À noite, estabilizamos o serviço no modo somente leitura e desabilitamos o máximo possível de funções não essenciais (por exemplo, coleta de lixo do namespace) para reduzir a carga no banco de dados. Os congelamentos pararam mas o motivo nunca foi encontrado. O novo cluster OSD ficou pronto e migramos o serviço, conectamos o tráfego e continuamos o monitoramento.

Quay.io funcionou de forma estável no novo cluster OSD, então voltamos aos logs do banco de dados, mas não conseguimos encontrar uma correlação que explicasse os bloqueios. Os engenheiros do OpenShift trabalharam conosco para entender se as alterações no Red Hat OpenShift 4.3.19 poderiam causar problemas no Quay. Contudo, nada foi encontrado e Não foi possível reproduzir o problema em condições de laboratório.

Segunda falha

Em 28 de maio, pouco antes do meio-dia EDT, o quay.io travou novamente com o mesmo sintoma: o banco de dados foi bloqueado. E novamente colocamos todos os nossos esforços na investigação. Em primeiro lugar, foi necessário restabelecer o serviço. No entanto desta vez, reiniciar o RDS e reiniciar os pods quay.io não fez nada: outra avalanche de conexões sobrecarregou a base. Mas por que?

Quay é escrito em Python e cada pod opera como um único contêiner monolítico. O tempo de execução do contêiner executa muitas tarefas paralelas simultaneamente. Usamos a biblioteca gevent em gunicorn para processar solicitações da web. Quando uma solicitação chega ao Quay (por meio de nossa própria API ou por meio da API do Docker), é atribuído a ela um trabalhador gevent. Normalmente, esse trabalhador deve entrar em contato com o banco de dados. Após a primeira falha, descobrimos que os trabalhadores gevent estavam se conectando ao banco de dados usando configurações padrão.

Dado o número significativo de pods do Quay e milhares de solicitações recebidas por segundo, um grande número de conexões de banco de dados poderia, teoricamente, sobrecarregar a instância do MySQL. Graças ao monitoramento, soube-se que o Quay processa em média 5 mil solicitações por segundo. O número de conexões com o banco de dados foi aproximadamente o mesmo. 5 mil conexões estavam dentro das capacidades de nossa instância RDS (o que não pode ser dito sobre dezenas de milhares). Por alguma razão, houve picos inesperados no número de conexões, no entanto, não notamos nenhuma correlação com as solicitações recebidas.

Desta vez estávamos determinados a encontrar e eliminar a origem do problema, e não nos limitar a reiniciar. Para a base de código do Quay alterações foram feitas para limitar o número de conexões ao banco de dados para cada trabalhador gevent. Esse número se tornou um parâmetro na configuração: tornou-se possível alterá-lo na hora, sem construir uma nova imagem de contêiner. Para descobrir quantas conexões poderiam ser tratadas de forma realista, executamos vários testes em um ambiente de teste, definindo valores diferentes para ver como isso afetaria os cenários de teste de carga. Como resultado, descobriu-se que Quay começa a gerar erros 502 quando o número de conexões ultrapassa 10 mil.

Implantamos imediatamente esta nova versão em produção e começamos a monitorar o cronograma de conexão do banco de dados. No passado, a base era bloqueada após cerca de 20 minutos. Após 30 minutos sem problemas, tínhamos esperança e, uma hora depois, tínhamos confiança. Restauramos o tráfego do site e iniciamos a análise post-mortem.

Tendo conseguido contornar o problema que leva ao bloqueio, não descobrimos seus reais motivos. Foi confirmado que não está relacionado a nenhuma alteração no OpenShift 4.3.19, já que o mesmo aconteceu na versão 4.3.18, que anteriormente funcionava com o Quay sem problemas.

Havia claramente algo mais escondido no aglomerado.

Estudo Detalhado

Quay.io usou as configurações padrão para se conectar ao banco de dados por seis anos sem problemas. O que mudou? É claro que durante todo esse tempo o tráfego no quay.io tem crescido de forma constante. No nosso caso, parecia que algum valor limite havia sido atingido, o que serviu de gatilho para uma avalanche de conexões. Continuamos estudando os logs do banco de dados após a segunda falha, mas não encontramos nenhum padrão ou relacionamento óbvio.

Enquanto isso, a equipe do SRE tem trabalhado em melhorias na observabilidade de solicitações e na integridade geral do serviço do Quay. Novas métricas e painéis foram implantados, mostrando quais partes do Cais são mais procuradas pelos clientes.

Quay.io funcionou bem até 9 de junho. Esta manhã (EDT) vimos novamente um aumento significativo no número de conexões de banco de dados. Desta vez não houve tempo de inatividade, já que o novo parâmetro limitou seu número e não permitiu que excedessem a taxa de transferência do MySQL. No entanto, por cerca de meia hora, muitos usuários notaram um desempenho lento do quay.io. Coletamos rapidamente todos os dados possíveis usando as ferramentas de monitoramento adicionadas. De repente, um padrão surgiu.

Pouco antes do aumento nas conexões, um grande número de solicitações foram feitas à API App Registry. App Registry é um recurso pouco conhecido do quay.io. Ele permite armazenar coisas como gráficos Helm e contêineres com metadados ricos. A maioria dos usuários do quay.io não trabalha com esse recurso, mas o Red Hat OpenShift o utiliza ativamente. O OperatorHub, como parte do OpenShift, armazena todos os operadores no App Registry. Esses operadores formam a base para o ecossistema de carga de trabalho do OpenShift e para o modelo operacional centrado no parceiro (operações do segundo dia).

Cada cluster do OpenShift 4 usa operadores do OperatorHub integrado para publicar um catálogo de operadores disponíveis para instalação e fornecer atualizações para aqueles já instalados. Com a crescente popularidade do OpenShift 4, o número de clusters nele em todo o mundo também aumentou. Cada um desses clusters baixa o conteúdo do operador para executar o OperatorHub integrado, usando o App Registry dentro do quay.io como back-end. Em nossa busca pela origem do problema, perdemos o fato de que, à medida que a popularidade do OpenShift crescia gradualmente, a carga em uma das funções raramente usadas do quay.io também aumentava..

Fizemos algumas análises do tráfego de solicitação do App Registry e demos uma olhada no código do registro. Imediatamente foram reveladas deficiências, devido às quais as consultas ao banco de dados não foram formadas de forma otimizada. Quando a carga estava baixa, eles não causavam nenhum problema, mas quando a carga aumentava, tornavam-se uma fonte de problemas. O App Registry revelou ter dois endpoints problemáticos que não respondiam bem ao aumento da carga: o primeiro fornecia uma lista de todos os pacotes no repositório, o segundo retornava todos os blobs do pacote.

Eliminação de causas

Durante a semana seguinte, passamos a otimizar o código do próprio App Registry e de seu ambiente. Consultas SQL claramente ineficazes foram reformuladas e chamadas de comando desnecessárias foram eliminadas tar (era executado sempre que os blobs eram recuperados), o cache era adicionado sempre que possível. Em seguida, realizamos extensos testes de desempenho e comparamos a velocidade do App Registry antes e depois das alterações.

Solicitações de API que antes demoravam meio minuto agora são concluídas em milissegundos. Na semana seguinte implantamos as mudanças na produção e, desde então, o quay.io tem funcionado de forma estável. Durante esse período, ocorreram vários picos acentuados no tráfego no endpoint do App Registry, mas as melhorias feitas evitaram interrupções no banco de dados.

O que aprendemos?

É claro que qualquer serviço tenta evitar o tempo de inatividade. No nosso caso, acreditamos que as interrupções recentes ajudaram a melhorar o quay.io. Aprendemos algumas lições importantes que gostaríamos de compartilhar:

  1. Dados sobre quem usa seu serviço e como nunca são supérfluos. Como o Quay “simplesmente funcionou”, nunca tivemos que perder tempo otimizando o tráfego e gerenciando a carga. Tudo isso criou uma falsa sensação de segurança de que o serviço poderia ser escalonado indefinidamente.
  2. Quando o serviço cai, colocá-lo de volta em funcionamento é uma prioridade máxima.. Como o Quay continuou a sofrer com um banco de dados bloqueado durante a primeira interrupção, nossos procedimentos padrão não surtiram o efeito pretendido e não foi possível restaurar o serviço usando-os. Isto levou a uma situação em que era necessário gastar tempo analisando e coletando dados na esperança de encontrar a causa raiz - em vez de concentrar todos os esforços na restauração da funcionalidade.
  3. Avalie o impacto de cada recurso do serviço. Os clientes raramente usavam o App Registry, então isso não era uma prioridade para nossa equipe. Quando alguns recursos do produto são pouco usados, seus bugs raramente aparecem e os desenvolvedores param de monitorar o código. É fácil cair no equívoco de que é assim que deveria ser – até que, de repente, essa função se encontra no centro de um grande incidente.

Qual é o próximo?

O trabalho para garantir a estabilidade do serviço nunca para e estamos constantemente melhorando. À medida que os volumes de tráfego continuam a crescer no quay.io, reconhecemos que temos a responsabilidade de fazer tudo o que estiver ao nosso alcance para corresponder à confiança dos nossos clientes. Portanto, estamos atualmente trabalhando nas seguintes tarefas:

  1. Implante réplicas de banco de dados somente leitura para ajudar o serviço a lidar com o tráfego apropriado em caso de problemas com a instância primária do RDS.
  2. Atualizando uma instância do RDS. A versão atual em si não é o problema. Em vez disso, queremos simplesmente remover a trilha falsa (que seguimos durante a falha); Manter o software atualizado eliminará outro fator no caso de interrupções futuras.
  3. Cache adicional em todo o cluster. Continuamos procurando áreas onde o cache possa reduzir a carga no banco de dados.
  4. Adicionando um firewall de aplicativo web (WAF) para ver quem está se conectando ao quay.io e por quê.
  5. A partir da próxima versão, os clusters Red Hat OpenShift abandonarão o App Registry em favor dos Catálogos do Operador baseados em imagens de contêiner disponíveis em quay.io.
  6. Um substituto de longo prazo para o App Registry poderia ser o suporte para especificações de artefatos da Open Container Initiative (OCI). Atualmente está implementado como funcionalidade nativa do Quay e estará disponível aos usuários quando a especificação em si for finalizada.

Todos os itens acima fazem parte do investimento contínuo da Red Hat no quay.io, à medida que passamos de uma pequena equipe “estilo startup” para uma plataforma madura orientada por SRE. Sabemos que muitos de nossos clientes confiam no quay.io em seu trabalho diário (incluindo a Red Hat!) e tentamos ser o mais transparentes possível sobre interrupções recentes e esforços contínuos para melhorar.

PS do tradutor

Leia também em nosso blog:

Fonte: habr.com

Adicionar um comentário