Provámos que cada alerta podia disparar. A maioria não conseguia.
A monitorização tem uma propriedade que a torna perigosa como nenhuma outra: é o código que só corre durante um desastre. Todas as outras partes do teu sistema correm constantemente, à frente dos utilizadores, a lançar erros que reparas. O teu sistema de alertas corre no dia em que a base de dados enche, e não antes, e se estiver avariado descobre-lo no exato momento em que estavas a contar com ele, e precisamente da forma que garante que ninguém estava a olhar. Um alerta silencioso é pior do que nenhum alerta, porque de um alerta ausente sabes que não o tens, ao passo que de um silencioso acreditas que o tens.
Por isso deixámos de confiar que os nossos alertas funcionavam só porque os tínhamos escrito, e construímos um banco de ensaio para o provar. Para cada regra de alerta que implantamos, o banco de ensaio envia um sinal real que a deveria disparar, e depois faz aquilo que realmente importa: espera que a notificação chegue fisicamente a um relé de captura, um pequeno serviço que faz as vezes do telemóvel que, de outro modo, vibraria. Disparar na própria interface da ferramenta de monitorização não conta. O que conta é a entrega. O caminho completo, do sinal até uma mensagem na mão, ou é uma falha.
A primeira execução real reprovou a maioria das regras. Eis o que se escondia por trás de painéis verdes.
O alerta principal estava morto
A regra mais importante que temos é a que dispara quando a taxa de erros sobe: demasiados HTTP 500, avisa alguém. O banco de ensaio lançou-lhe quatrocentos e cinquenta erros de servidor reais, verificou que pelo menos noventa por cento deles devolviam mesmo um 500, e esperou. Não chegou nada. O único alerta em que mais quereríamos confiar nunca tinha disparado uma única vez, e não teria disparado num incidente real.
A causa era um nome. As nossas métricas saem do OpenTelemetry, onde os nomes levam pontos, como http.server.request.duration. A dado momento, entre ler um tutorial e escrever a consulta, tínhamos assumido que o backend de monitorização normalizaria esses pontos para underscores, como faz muito do ferramental do Prometheus, e escrevemos a consulta do alerta contra a forma com underscores. O backend não normaliza. Mantém os nomes com pontos exatamente como são enviados. Por isso o alerta consultava uma métrica que não existia, não correspondia a nada, calculava uma taxa de erros de zero para sempre, e ficava ali, calmo e verde, enquanto a métrica real, sob o seu verdadeiro nome com pontos, registava cada um daqueles quinhentos. A consulta não estava errada de uma forma que gera um erro. Estava errada de uma forma que devolve em silêncio o conjunto vazio, que a aritmética transforma depois num zero perfeitamente plausível.
A que arrastou consigo todos os alertas de registos
Esse era o mau. O seguinte era pior, e o banco de ensaio só o apanhou porque insiste na entrega e não em a regra parecer correta.
Toda uma categoria dos nossos alertas baseia-se em registos: encontra uma linha nos registos, dispara. Cada um deles avaliava sobre o nada, e a razão não tinha nada a ver com as regras de alerta. Era uma definição de retenção.
Reconciliamos a retenção dos nossos registos através da API de definições do backend. Essa API tinha passado para uma nova versão com uma nova forma de pedido, e nós continuávamos a enviar a forma antiga. O corpo antigo não foi rejeitado. O descodificador do outro lado ignora em silêncio os campos que não reconhece, por isso leu os nossos campos agora desconhecidos como ausentes, tomou o valor por omissão da retenção que não conseguia encontrar, e esse valor por omissão era zero. A chamada devolvia um alegre 200. E uma retenção de zero dias significa expiração instantânea: os dados de registo acabados de escrever eram marcados para expirar de imediato, de modo que o armazém que os alertas de registos consultavam estava, na prática, sempre vazio. A ingestão parecia completamente saudável todo o tempo, porque os registos chegavam. Só que envelheciam e desapareciam no instante exato em que aterravam.
Dois sistemas fizeram, cada um, algo defensável. A API de definições aceitou um pedido que só compreendia em parte em vez de falhar, o que é uma escolha comum e muitas vezes razoável. A nossa implantação enviou um corpo que se tinha tornado obsoleto perante uma mudança de versão. Nenhum dos dois sinalizou o que quer que fosse. O resultado foi que o número mais consequente de todo o nosso pipeline de registos, quanto tempo vive um registo, tinha sido posto em silêncio a zero, e o único sintoma visível era cada alerta de registos permanecer calado de forma permanente, com um ar saudável. Se tivéssemos testado que as regras pareciam certas, pareciam perfeitas. Só testar que um evento real registado produz um aviso entregue o podia revelar, porque só esse teste lê mesmo de volta do armazém que tinha sido esvaziado.
Os alertas que disparavam para sempre, o que é o mesmo que nunca
Uma terceira falha ia na direção oposta, e é a subtil. Um punhado de alertas de auditoria disparava sem parar. Isso soa ao oposto de um alerta silencioso, mas tem o efeito idêntico, e perceber porquê vale o desvio.
Quando um destes alertas disparava, o backend de monitorização escrevia uma linha de registo sobre a avaliação, uma migalha, e essa linha continha a própria expressão de filtro do alerta como texto. Essa migalha era ela própria ingerida como um registo, tal como qualquer outra coisa. Por isso, da vez seguinte em que a regra avaliava, encontrava uma linha correspondente: a sua própria pegada da vez anterior. A regra tinha-se tornado autossustentada. Correspondia à sua própria sombra, a cada avaliação, para sempre.
Um alerta que está sempre a disparar nunca transita de não disparado para disparado, e é essa transição que envia uma notificação. Por isso a camada de alertas, ao ver uma condição que já era verdadeira e assim permanecia, suprimia as notificações seguintes, exatamente como foi concebida, para te poupar um aviso a cada minuto sobre uma coisa de que já foste informado. A consequência é que um evento real, uma genuína ação de auditoria digna de um aviso, chegava a uma regra que já estava presa em ligada e por isso não dizia nada. Um alerta preso permanentemente em ligado é tão mudo como um preso permanentemente em desligado. A correção foi restringir essas regras às linhas de registo emitidas pelos nossos próprios serviços, para que deixem de corresponder à conversa do backend sobre si mesmo, com uma exceção deliberada cujo sinal real vem mesmo de fora dos nossos serviços.
A regra que tratava o primeiro fôlego de um pod como algo normal
A última é uma lição pequena e afiada sobre cálculos de taxa. Um alerta construído sobre a taxa de variação de uma métrica não consegue calcular uma taxa a partir da primeiríssima amostra de uma série, porque não há nada antes dela com que comparar. Assim, quando um pod acabado de nascer emitiu a sua primeira rajada de erros, essa rajada tornou-se a linha de base em vez de um pico, e a regra que o devia ter apanhado viu apenas um ponto de partida. Corrigimo-lo no banco de ensaio enviando esses sinais em duas vagas, uma pequena para estabelecer a série e uma real a seguir, o que é também uma descrição justa do que o tráfego de produção faz por si só e do que um teste sintético tem de imitar de forma deliberada.
Cada falha escondia-se numa costura que só encontras ao exercitá-la
Cada uma destas era invisível à inspeção. As consultas liam-se corretamente. As regras estavam implantadas. Os painéis estavam verdes. A ingestão estava saudável. Cada falha vivia numa costura entre duas coisas que, isoladamente, estavam bem: um nome de métrica e um pressuposto sobre normalização, um corpo de pedido e uma mudança de versão, um alerta e o registo que o seu próprio disparo produz, uma função de taxa e uma série que acabou de começar. Não consegues ler uma costura. Tens de a exercitar.
Por isso trata a monitorização por aquilo que é, que é código, e em concreto código cuja única execução em produção é a emergência. O código que só corre na emergência precisa que os seus testes corram em todos os outros dias, porque não há uma primeira falha suave que te avise. Envia um sinal real de ponta a ponta, e verifica o artefacto mesmo no fim da cadeia, a notificação entregue, e não a regra com ar saudável no meio. Qualquer coisa aquém disso é testar que escreveste um alerta, o que já sabias, em vez de testar que ele dispara, que é a única coisa que realmente querias.
Se preferes que o teu fornecedor de identidade já estivesse a vigiar-se a si próprio desta forma, o Authagonal faz passar o seu próprio sistema de alertas exatamente por este banco de ensaio, para que no dia em que algo se parta, o aviso que devia disparar o faça mesmo.