Uma Republicação Falhada Nunca Apaga um Backend que Funciona
No dia 25 de setembro, uma republicação falhada apagou o backend de uma app que esteve ativo e a funcionar durante cerca de duas horas e meia. O código que desfaz uma publicação falhada removeu todas as apps de servidor que a publicação tinha tocado, incluindo as que já estavam a responder antes de ela começar. Agora, só são apagadas as apps que uma publicação criou, e uma app que já existia volta exatamente à versão que estava a correr antes. Duas correções seguintes, no mesmo dia, garantiram que o desfazer também corre quando a própria ferramenta de deploy da plataforma de alojamento falha, e que uma app saudável não é dada como falhada só porque estava a dormir.
Como é publicado um backend
Quando a sua app Mythex tem um backend (uma API ou servidor, e não apenas ficheiros estáticos), a publicação constrói-o a partir de um Dockerfile (uma receita para empacotar a app numa imagem) e instala essa imagem em máquinas da nossa plataforma de alojamento. Depois, um health check aguarda que a nova versão arranque de facto e se mantenha em funcionamento antes de a publicação ser marcada como ativa.
Se alguma coisa falhar pelo caminho, é executado um rollback para a desfazer. Acertar nesse rollback é mais importante do que quase tudo o resto na publicação, porque corre precisamente quando algo já está a correr mal.
Problema 1: o rollback apagava o que devia restaurar
O rollback mantinha uma lista das apps de servidor onde uma publicação tinha feito deploy e, em caso de falha, destruía todas. Isso está certo numa primeira publicação: a app foi criada há um momento, nada depende dela, e apagá-la deixa tudo como estava.
Esteve exatamente errado numa republicação. A app já existia e estava a servir visitantes reais. A 25 de setembro, a API de uma app que estava ativa há cerca de duas horas e meia foi apagada por uma republicação falhada.
Havia um segundo modo de falha escondido no mesmo código. Quando o apagamento não chegava a acontecer, a nova imagem avariada ficava nas máquinas, enquanto dizíamos ao dono que o seu site atual continuava ativo. No momento em que o health check pode falhar, a nova imagem já foi instalada em todas as máquinas. Não fazer nada não é o mesmo que desfazer.
A correção: destruir apenas o que foi criado e restaurar o resto
Cada publicação regista agora, para cada app de servidor que toca:
- se esta publicação a criou, e
- se não a criou, que imagem estava a correr imediatamente antes.
Em caso de falha, cada app recebe um de três tratamentos:
- Criada por esta publicação: é destruída, como antes.
- Já existia, e a imagem anterior é conhecida: é feito novo deploy da imagem anterior com a mesma configuração. Não há build; só a imagem volta atrás.
- Já existia, e a imagem anterior é desconhecida: fica como está. Uma app existente nunca é apagada, mesmo quando não a conseguimos restaurar.
Estas regras são uma pequena função pura com testes próprios, para que a decisão não volte a ser "destruir tudo" sem que um teste falhe.
Problema 2: a ferramenta de deploy podia falhar antes de termos registado como desfazer
Logo depois dessa correção, encontrámos uma lacuna. A ferramenta de deploy da plataforma de alojamento executa os seus próprios testes de funcionamento: se as novas máquinas se avariarem, termina com erro. Mas fá-lo depois de a nova imagem já estar nas máquinas.
O nosso código só registava uma app para rollback quando a ferramenta devolvia sucesso. Quando a própria ferramenta falhava o deploy, a app ainda não estava na lista de rollback, por isso nada era restaurado. No nosso ambiente de staging, uma republicação avariada deixou a imagem avariada a servir.
A correção: registar o desfazer antes de começar
A app fica agora registada para rollback, com a indicação de se foi criada e da imagem que corria, assim que é preparada e antes de qualquer coisa ser instalada nela. Aconteça o que acontecer a seguir, o rollback sabe o que repor. Verificámos localmente: uma republicação avariada falhou nos próprios testes de funcionamento da ferramenta de deploy, e a versão anterior continuou a responder.
Problema 3: uma app a dormir foi dada como "nunca arrancou"
As apps publicadas na Mythex entram em modo de espera quando estão inativas, para poupar créditos. Uma republicação deixa uma máquina parada que estava a dormir, por isso o health check tem de a acordar. Pedia à plataforma para arrancar a máquina uma única vez, e ignorava a resposta.
A 25 de setembro, esse único pedido de arranque não surtiu efeito. Uma API saudável ficou parada durante todo o health check de cinco minutos e foi dada como falhada com "1 máquina nunca arrancou (último estado: parada)".
A plataforma por vezes recusa um arranque. Durante a reprodução do problema, devolveu um conflito "lease currently held" sem que o tivéssemos provocado, e recusa arranques enquanto uma máquina está a ser atualizada.
A correção: continuar a insistir e bater à porta
O health check passa agora a:
- voltar a pedir o arranque de uma máquina parada a cada 20 segundos enquanto espera,
- registar o que a plataforma respondeu quando recusou, e
- enviar um pedido normal à app, tal como faria um visitante. O proxy da plataforma acorda uma máquina a dormir quando chega um pedido.
Reproduzimos o problema na plataforma real com uma app descartável cujo primeiro arranque era recusado. Antes da correção, a publicação falhava ao fim de 91 segundos. Depois, publicou-se em 20 segundos. Uma app que realmente se avaria ao arrancar continua a falhar, como deve ser.
O que retirámos disto
- O rollback tem de distinguir o que é novo do que já existia. "Desfazer" algo que criaste é apagar. "Desfazer" algo que já lá estava é repor. Tratar as duas coisas da mesma forma é como um mecanismo de segurança acaba por apagar uma app que funciona.
- Escreve como desfazer antes de agir. Se o desfazer só é registado depois de um passo ter sucesso, um passo que falha a meio deixa nada para desfazer.
- Nunca ignores a resposta a um pedido de que dependes. Um "não" ignorado transformou uma app saudável numa publicação falhada.
Uma correção relacionada, do mesmo dia: apanhar apps que se avariam logo após a publicação. Para o que acontece quando uma publicação falha do teu lado, consulta a resolução de problemas de publicação na documentação.