Nota do tradutor.: no início de agosto, a Red Hat anunciou publicamente a resolução de problemas de disponibilidade que surgiram nos meses anteriores para os usuários do seu serviço (a base é um registro para imagens de contêineres, que veio à empresa junto com a compra da CoreOS). Independentemente do seu interesse neste serviço, o caminho que os engenheiros SRE da empresa percorreram para diagnosticar e resolver as causas da falha é instrutivo.

Na manhã de 19 de maio (horário de verão do leste da América do Norte, EDT), o serviço quay.io caiu. A falha afetou tanto os consumidores do quay.io quanto os projetos de código aberto que utilizam o quay.io como plataforma para a construção e distribuição de software. A Red Hat valoriza a confiança de ambos.
A equipe de engenheiros SRE imediatamente se mobilizou para estabilizar o serviço Quay o mais rápido possível. No entanto, enquanto trabalhavam nisso, os clientes ficaram impossibilitados de enviar novas imagens e só conseguiam baixar as existentes de forma esporádica. Por motivos desconhecidos, o banco de dados do quay.io travava após a escalabilidade do serviço para a capacidade total.
«O que mudou?» — esta é a primeira pergunta que costuma ser feita em situações como esta. Notamos que pouco antes do problema, o cluster OpenShift Dedicated (no qual o quay.io opera) começou a ser atualizado para a versão 4.3.19. Como o quay.io opera no Red Hat OpenShift Dedicated (OSD), as atualizações regulares eram comuns e nunca causaram problemas. Além disso, nos seis meses anteriores, atualizamos os clusters Quay várias vezes sem qualquer interrupção no serviço.
Enquanto tentávamos restaurar o funcionamento do serviço, outros engenheiros começaram a preparar um novo cluster OSD com a versão anterior do software, para que, se necessário, pudéssemos implantar tudo nele.
Análise da causa raiz
O principal sintoma da falha foi uma avalanche de dezenas de milhares de conexões ao banco de dados, que tornou a instância do MySQL praticamente inoperante. Isso dificultou o diagnóstico do problema. Estabelecemos um limite no número máximo de conexões dos clientes para ajudar a equipe SRE a avaliar a questão. Nenhum tráfego incomum para o banco de dados foi notado: na verdade, a maioria das solicitações era de leitura, e apenas algumas eram de gravação.
Nós também tentamos identificar um padrão no tráfego do banco de dados que poderia ter causado essa avalanche. No entanto, não conseguimos encontrar nenhuma regularidade nos logs. Enquanto aguardávamos a conclusão do novo cluster com OSD 4.3.18, continuamos as tentativas de iniciar os pods quay.io. Sempre que o cluster atingia a capacidade total, o banco de dados travava. Isso significava que era necessário reiniciar a instância do RDS além de todos os pods quay.io.
Na noite, estabilizamos o serviço no modo somente leitura e desativamos o máximo de funções não essenciais (como a coleta de lixo no namespace) para reduzir a carga no banco de dados. As interrupções cessaram, mas a causa não foi encontrada. O novo cluster OSD estava pronto, migramos o serviço, conectamos o tráfego e continuamos a monitoramento.
Quay.io funcionava 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, tentando entender se as mudanças na Red Hat OpenShift 4.3.19 poderiam ter causado problemas com o Quay. No entanto, nada foi descoberto, e não conseguimos reproduzir o problema em condições de laboratório.
A segunda falha
28 de maio, pouco antes do meio-dia EDT, quay.io caiu novamente com o mesmo sintoma: o funcionamento do banco de dados foi bloqueado. E mais uma vez, reunimos todos os recursos para investigar. Antes de tudo, precisávamos restaurar o funcionamento do serviço. No entanto, desta vez, reiniciar o RDS e reiniciar os pods quay.io não trouxe resultado: uma nova avalanche de conexões inundou o banco de dados. Mas por quê?Quay é escrito em Python e cada pod funciona como um único contêiner monolítico. No ambiente de execução do contêiner, várias tarefas paralelas são executadas ao mesmo tempo. Utilizamos a biblioteca
gevent gunicorn sob para processar as requisições da web. Quando uma solicitação chega ao Quay (através de nossa própria API, ou através da API do Docker), um trabalhador gevent é atribuído a ele. Normalmente, esse trabalhador deve se conectar ao banco de dados. Após a primeira falha, descobrimos que os trabalhadores gevent estavam se conectando ao banco de dados, utilizando as configurações padrão. para processar solicitações da web. Quando uma solicitação chega ao Quay (através de nossa própria API ou pela API do Docker), um worker gevent é atribuído a ela. Normalmente, esse worker deve se conectar ao banco de dados. Após a primeira falha, descobrimos que os workers gevent se conectavam ao banco de dados usando as configurações padrão.
Dado o número significativo de pods do Quay e milhares de solicitações recebendo a cada segundo, o grande número de conexões ao banco de dados poderia teoricamente sobrecarregar a instância do MySQL. Através da monitoração, ficou claro que o Quay processava em média 5 mil solicitações por segundo. Aproximadamente esse também era o número de conexões ao banco de dados. 5 mil conexões estavam confortavelmente dentro das capacidades da nossa instância RDS (o que não se pode dizer de dezenas de milhares). Por alguma razão, ocorreram picos inesperados no número de conexões, no entanto, não notamos qualquer correlação com as solicitações de entrada.
Desta vez, decidimos firmemente encontrar e eliminar a fonte do problema, em vez de nos contentar com uma reinicialização. No código do Quay foram feitas alterações que limitam o número de conexões ao banco de dados para cada worker gevent. Esse número se tornou um parâmetro na configuração: agora era possível alterá-lo 'em tempo real', sem a necessidade de criar uma nova imagem de contêiner. Para descobrir quantas conexões poderiam ser realmente processadas, foram realizados vários testes no ambiente de staging, variando os valores para observar como isso afetaria os cenários de teste de carga. No final, descobriu-se que o Quay começa a retornar erros 502 quando o número de conexões excede 10 mil.
Imediatamente implementamos essa nova versão em produção e começamos a monitorar o gráfico de conexões ao banco de dados. No passado, o banco travava em cerca de 20 minutos. Após 30 minutos sem problemas, começamos a ter esperança, e após uma hora, confiança. Recuperamos o tráfego de gravação no site e iniciamos a análise postmortem.
Conseguimos contornar o problema que levava ao bloqueio, mas não identificamos suas verdadeiras causas. Confirmou-se que não estava relacionada a quaisquer mudanças na versão 4.3.19 do OpenShift, uma vez que o mesmo aconteceu na versão 4.3.18, que anteriormente funcionou com o Quay sem quaisquer problemas.
Claramente havia algo mais oculto no cluster.
Uma investigação detalhada
O Quay.io usou por seis anos as configurações padrão para se conectar ao banco de dados sem quaisquer problemas. O que mudou? É claro que, durante todo esse tempo, o tráfego no quay.io cresceu constantemente. No nosso caso, parecia que um certo limite havia sido atingido, servindo como um gatilho para uma avalanche de conexões. Continuamos a analisar os logs do banco de dados após a segunda falha, mas não encontramos padrões ou relações óbvias.
Enquanto isso, a equipe de SRE estava fazendo melhorias na observabilidade das solicitações no Quay e na saúde geral do serviço. Novas métricas e painéis de monitoramento foram implementados, mostrando quais partes do Quay estão em maior demanda pelos clientes.
O Quay.io funcionou normalmente até 9 de junho. Na manhã (horário EDT), testemunhamos novamente um aumento significativo no número de conexões ao banco de dados. Desta vez, não ocorreu downtime, uma vez que uma nova configuração limitava seu número e impedia que superasse a capacidade 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, utilizando as ferramentas de monitoramento adicionadas. De repente, um padrão se manifestou.
Antes do aumento no número de conexões, um grande número de solicitações foi feito à API do App Registry. O App Registry é uma função pouco conhecida do quay.io. Ele permite armazenar itens como gráficos Helm e contêineres com metadados ricos. A maioria dos usuários do quay.io não utiliza essa função, mas ela é amplamente usada pelo Red Hat OpenShift. O OperatorHub no OpenShift armazena todos os operadores no App Registry. Esses operadores formam a base do ecossistema de cargas de trabalho do OpenShift e do modelo operacional (no contexto das operações do "segundo dia", Day 2) voltado para parceiros.
Cada cluster OpenShift 4 usa operadores do OperatorHub integrado para publicar o catálogo de operadores disponíveis para instalação e fornecer atualizações para os já instalados. Com o aumento da popularidade do OpenShift 4, o número de clusters em todo o mundo também cresceu. Cada um desses clusters carrega o conteúdo dos operadores para iniciar o OperatorHub integrado, usando o App Registry dentro do quay.io como back-end. Na busca pela origem do problema, perdemos de vista o fato de que, com o crescimento gradual da popularidade do OpenShift, a carga sobre uma das funções raramente utilizadas do quay.io também aumentava..
Realizamos uma análise do tráfego de consultas do App Registry e examinamos o código do registro. Logo ficaram evidentes as falhas que causavam a formação de consultas ao banco de dados de forma não otimizada. Com uma carga baixa, elas não causavam problemas, mas, à medida que aumentava, se tornavam uma fonte de problemas. O App Registry tinha dois endpoints problemáticos, que respondiam mal ao aumento da carga: o primeiro retornava a lista de todos os pacotes no repositório, o segundo retornava todos os blobs para um pacote.
Eliminação das causas
Durante toda a semana seguinte, nos dedicamos à otimização do código do App Registry e de seu ambiente. As consultas SQL claramente ineficazes foram reformuladas, chamadas desnecessárias de comando foram eliminadas tar (ela era executada a cada recuperação de blobs), e o cache foi adicionado sempre que possível. Em seguida, um extenso teste de desempenho foi realizado e a velocidade de operação do App Registry foi comparada antes e depois das modificações.
As consultas da API, que antes levavam até meio minuto, agora eram executadas em milissegundos.Na semana seguinte, implementamos as alterações em produção, e desde então o quay.io funciona de maneira estável. Durante esse tempo, observamos alguns picos acentuados de tráfego no endpoint do App Registry, mas as melhorias que fizemos impediram interrupções no banco de dados.
O que aprendemos?
É claro que qualquer serviço procura evitar interrupções. No nosso caso, acreditamos que as recentes falhas ajudaram a tornar o quay.io melhor. Aprendemos algumas lições importantes que gostaríamos de compartilhar:
- As informações sobre quem e como utiliza seu serviço nunca são desnecessárias.Como o Quay "simplesmente funcionava", nunca sentimos a necessidade de gastar 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 escalar indefinidamente.
- Quando o serviço falha, restaurar seu funcionamento é a principal prioridade.Como o Quay continuou a sofrer com um banco de dados bloqueado durante a primeira falha, nossos procedimentos padrão não tiveram o efeito esperado e não conseguimos restaurar o serviço com a ajuda deles. Isso levou a uma situação em que tivemos que gastar tempo analisando e coletando dados na esperança de encontrar a causa raiz — em vez de direcionar todos os esforços para a restauração do funcionamento.
- Avalie o impacto de cada uma das funções do serviço.Os clientes raramente utilizavam o App Registry, portanto, ele não era uma prioridade para nossa equipe. Quando algumas funções do produto são quase não utilizadas, seus bugs aparecem raramente e os desenvolvedores param de acompanhar o código. É fácil ser vítima da ilusão de que deve ser assim — até que repentinamente essa função se torne o centro de um grande incidente.
E agora?
O trabalho para garantir a estabilidade do serviço nunca para e estamos constantemente melhorando-o. Os volumes de tráfego em quay.io continuam a crescer, e estamos cientes de que devemos fazer todo o possível para justificar a confiança dos clientes. Portanto, atualmente estamos trabalhando nas seguintes tarefas:
- Implantação de réplicas de banco de dados apenas para leitura, para ajudar o serviço a lidar com o tráfego correspondente em caso de problemas com a instância primária do RDS.
- Atualização da instância do RDS. A versão atual por si só não é um problema. Em vez disso, queremos remover um falso diagnóstico (que seguimos durante a falha); manter o software atualizado eliminará mais um fator em caso de futuras interrupções.
- Cache adicional em todo o cluster. Continuamos a buscar áreas onde o cache pode ajudar a reduzir a carga no banco de dados.
- Adição de um firewall de aplicativos da web (WAF), para ver quem está se conectando ao quay.io e por quê.
- A partir da próxima versão, os clusters do Red Hat OpenShift abandonarão o App Registry em favor de catálogos de operadores (Operator Catalogs), baseados em imagens de contêineres disponíveis em quay.io.
- Uma substituição a longo prazo para o App Registry pode ser o suporte às especificações de artefatos da Open Container Initiative (OCI). No momento, isso está sendo implementado como uma funcionalidade nativa do Quay e estará disponível para os usuários assim que a especificação for finalizada.
Tudo o que foi mencionado acima faz parte dos contínuos investimentos da Red Hat na quay.io à medida que fazemos a transição de uma pequena equipe "no espírito de uma startup" para uma plataforma madura, gerida por SRE. Sabemos que muitos de nossos clientes contam com a quay.io em seu dia a dia (incluindo a Red Hat!) e nos esforçamos para sermos o mais transparentes possível sobre as falhas recentes e os esforços contínuos para melhorar.
P.S. do tradutor
Confira também em nosso blog:
- «»;
- «»;
- «».
Fonte: habr.com
