Una republicación fallida nunca borra un backend que funciona
El 25 de septiembre, una republicación fallida borró el backend de una app que había estado en vivo y funcionando durante unas dos horas y media. El código que deshace una publicación fallida eliminó todas las apps de servidor que la publicación había tocado, incluidas las que ya estaban sirviendo antes de que empezara. Ahora solo se borran las apps que una publicación ha creado; una app que ya existía vuelve exactamente a la versión que ejecutaba antes. Dos correcciones posteriores ese mismo día garantizaron que la vuelta atrás también se ejecute cuando falla la herramienta de despliegue de la propia plataforma de hosting, y que una app sana no se dé por fallida solo porque estaba dormida.
Cómo se publica un backend
Cuando tu app de Mythex tiene un backend (una API o un servidor, y no solo archivos estáticos), la publicación lo construye a partir de un Dockerfile (una receta para empaquetar la app en una imagen) y despliega esa imagen en máquinas de nuestra plataforma de hosting. Después, un health check espera a que la nueva versión arranque de verdad y se mantenga en pie antes de marcar la publicación como activa.
Si algo falla por el camino, se ejecuta una vuelta atrás (rollback) para deshacerlo. Acertar con esa vuelta atrás importa más que casi cualquier otra cosa en la publicación, porque se ejecuta justo cuando algo ya va mal.
Problema 1: la vuelta atrás borraba lo que debía restaurar
La vuelta atrás llevaba una lista de las apps de servidor a las que la publicación había desplegado algo y, en caso de fallo, destruía cada una de ellas. Eso es correcto en una primera publicación: la app se acaba de crear, nada depende de ella, y borrarla deja las cosas como estaban.
En una republicación es exactamente lo contrario. La app ya existía y estaba atendiendo a visitantes reales. El 25 de septiembre, la API de una app que llevaba unas dos horas y media en vivo fue borrada por una republicación fallida.
Había un segundo fallo escondido en el mismo código. Cuando el borrado no se completaba, la imagen nueva y rota se quedaba en las máquinas, mientras al propietario le decíamos que su sitio actual seguía activo. Cuando el health check puede fallar, la imagen nueva ya se ha desplegado en todas las máquinas. No hacer nada no es lo mismo que deshacer.
La solución: destruir solo lo que creaste, restaurar lo demás
Ahora cada publicación registra, para cada app de servidor que toca:
- si esta publicación la creó, y
- si no, qué imagen estaba ejecutando justo antes.
En caso de fallo, cada app recibe uno de tres tratamientos:
- Creada por esta publicación: se destruye, como antes.
- Ya existía y se conoce la imagen anterior: se vuelve a desplegar la imagen anterior con la misma configuración. Sin construcción; solo se mueve de nuevo la imagen.
- Ya existía y no se conoce la imagen anterior: no se toca. Una app existente nunca se borra, aunque no podamos restaurarla.
Estas reglas son una pequeña función pura con sus propias pruebas, de modo que la decisión no puede volver a "destruir todo" sin que falle una prueba.
Problema 2: la herramienta de despliegue podía fallar antes de que hubiéramos anotado cómo deshacer
Poco después de esa corrección encontramos un hueco. La herramienta de despliegue de la plataforma ejecuta sus propias comprobaciones de humo: si las máquinas nuevas se caen, sale con un error. Pero lo hace después de que la imagen nueva ya esté en las máquinas.
Nuestro código solo registraba una app para la vuelta atrás cuando la herramienta terminaba con éxito. Cuando la propia herramienta hacía fallar el despliegue, la app todavía no estaba en la lista de vuelta atrás, así que no se restauraba nada. En nuestro entorno de staging, una republicación rota dejó la imagen rota sirviendo.
La solución: registrar la forma de deshacer antes de empezar
La app ahora se registra para la vuelta atrás —con si fue creada y qué imagen ejecutaba— en cuanto se prepara y antes de desplegar nada encima. Falle lo que falle después, la vuelta atrás sabe qué poner de nuevo. Lo verificamos en local: una republicación rota falló en las propias comprobaciones de humo de la herramienta de despliegue, y la versión anterior siguió respondiendo.
Problema 3: una app dormida se daba por fallida como "nunca arrancó"
Las apps publicadas en Mythex se duermen cuando están inactivas para ahorrar tus créditos. Una republicación deja una máquina dormida parada, así que el health check tiene que despertarla. Pedía a la plataforma que arrancara la máquina una sola vez, e ignoraba la respuesta.
El 25 de septiembre, esa única petición de arranque no llegó a ninguna parte. Una API sana permaneció parada durante los cinco minutos completos del health check y se dio por fallida con el mensaje "1 machine never started (last state: stopped)".
La plataforma a veces rechaza un arranque. Mientras reproducíamos el problema, devolvió un conflicto "lease currently held" sin que lo provocáramos, y rechaza los arranques mientras una máquina se está actualizando.
La solución: seguir insistiendo y llamar a la puerta
Ahora el health check:
- vuelve a pedir el arranque de una máquina parada cada 20 segundos mientras espera,
- registra lo que respondió la plataforma cuando rechaza la petición, y
- envía una petición normal a la app, igual que haría un visitante: el proxy de la plataforma despierta una máquina dormida cuando llega una petición.
Lo reproducimos en la plataforma real con una app de prueba desechable cuyo primer arranque era rechazado. Antes de la corrección, la publicación fallaba a los 91 segundos. Después, se publicaba en 20 segundos. Una app que de verdad se cae al arrancar sigue fallando, como debe ser.
Lo que aprendimos
- La vuelta atrás tiene que distinguir entre lo nuevo y lo existente. "Deshacer" algo que creaste es borrarlo. "Deshacer" algo que ya estaba ahí es restaurarlo. Tratarlos igual es la forma en que un mecanismo de seguridad acaba borrando una app que funcionaba.
- Anota cómo deshacer antes de actuar. Si la forma de deshacer solo se registra después de que un paso tenga éxito, un paso que falla a mitad de camino no deja nada con lo que deshacer.
- Nunca ignores la respuesta a una petición de la que dependes. Un "no" ignorado convirtió una app sana en una publicación fallida.
Una corrección relacionada del mismo día: atrapar apps que se caen justo después de publicarse. Para lo que pasa cuando una publicación falla por tu lado, consulta solución de problemas de publicación en la documentación.