4dim / Notas
Desplegar sin caídas, medido: 16 de 150 contra 0 de 455
Un reinicio directo dejó 1,6 segundos sin servicio y 16 peticiones de 150 fallidas. Con dos ranuras y un proxy que conmuta, 455 peticiones durante un despliegue y ninguna falló. Cómo funciona en un VPS con bash, y el chequeo de salud del que depende todo.
Lo que medimos
Si tu negocio depende de una aplicación web, cada actualización abre un rato en el que nadie puede usarla. Esta nota mide ese rato en un sitio real, cuenta a cuánta gente le falló algo, y explica cómo lo dejamos en nada.
Un sitio pequeño en un servidor pequeño se despliega casi siempre así: se para el proceso, se copia el código nuevo, se arranca. Durante ese rato no hay nadie atendiendo. Quisimos saber cuánto dura «ese rato» y qué le pasa a quien está en la página.
| Forma de desplegar | Tiempo sin servicio | Peticiones fallidas |
|---|---|---|
| Reinicio directo del proceso | 1,6 segundos | 16 de 150 (conexión rechazada) |
| Azul/verde con dos ranuras | 0 | 0 de 455 |
Segundo y medio parece poco. Pero un 10 % de las peticiones que llegan en ese instante falla. Y para quien estaba enviando un formulario, una respuesta a un cliente o una campaña, el despliegue se ve como un error suyo que no entiende. Con dos ranuras pasaron 455 peticiones durante un despliegue completo, y no falló ninguna.
Cómo funciona, sin Docker ni orquestador
Hay dos carpetas, azul y verde, cada una con su proceso en un puerto distinto. Una está viva y recibe el tráfico. La otra está apagada.
Desplegar es traer el código a la ranura apagada, instalar dependencias, compilar y arrancar el proceso. Después, esperar a que responda sano, hasta cuarenta intentos de un segundo.
Solo entonces se cambia una línea de configuración de nginx, el programa que reparte el tráfico, para que apunte al puerto nuevo. Nginx se recarga sin cortar conexiones. La ranura vieja se deja quince segundos terminando lo que tuviera a medias, y se apaga.
Si la versión nueva no pasa la comprobación de salud, se descarta y la vieja sigue sirviendo. No hay que revertir nada, porque nunca se movió nada. Todo eso es un script de bash de cien líneas, un servidor alquilado con nginx y pm2, y ningún componente más.
Un chequeo de salud que vale algo
Todo el mecanismo descansa en una pregunta: «¿está sana la versión nueva?». Un chequeo que solo mira si el proceso responde en su puerto le da verde a una versión que no puede hablar con la base de datos. Es decir, a una que no puede dejar entrar a nadie. El nuestro comprueba tres cosas:
- Que alcanza a Postgres, la base de datos.
- Que las migraciones aplicadas en la base coinciden con los archivos de migración que trae el código. Una migración es un cambio de estructura de la base: una tabla nueva, una columna más. La primera versión del chequeo tenía una lista fija de tablas requeridas, y nada obligaba a actualizarla cuando una migración creaba una tabla nueva: daba verde con la estructura incompleta. Comparar contra los archivos no se puede olvidar, porque los archivos vienen con el despliegue.
- Qué versión es. Durante meses el chequeo devolvió
"build": nullporque leía una variable de entorno que nadie definía. Un campo siempre vacío es peor que no tenerlo: aparenta ser una respuesta.
Ese chequeo detectó, antes de mover el tráfico, una migración pendiente que habría reventado cualquier página.
También nos dio un falso rojo. La tabla que registra las migraciones nació antes de que otra migración fijara los permisos por defecto, así que quedó sin permiso de lectura. El chequeo decía «sin migrar» con todo migrado. El diagnóstico más alarmante posible, y falso. Lo cazó el propio chequeo el día que se estrenó.
El despliegue sin caídas no lo hace el script. Lo hace la pregunta que el script sabe hacer antes de mover el tráfico.
Los archivos con hash se retienen siete días
Un sitio moderno sirve sus scripts y estilos con un hash del contenido en el nombre del archivo. El hash es una huella: si el contenido cambia, el nombre cambia. Si el despliegue borra los archivos de la versión anterior, quien tenía la página abierta pide al navegar un archivo que ya no existe, y la interfaz se le rompe a medio camino.
Los nuestros se copian a una carpeta compartida fuera de las ranuras, sin sobrescribir, y se borran por antigüedad a los siete días. Acumularlos es seguro porque el hash garantiza que dos archivos con el mismo nombre son el mismo archivo.
Tres trampas del propio script
- Dos despliegues a la vez. Con varias consolas trabajando, dos despliegues simultáneos eligen la misma ranura apagada y uno pisa la compilación del otro a medias. Un cerrojo con
flock -nhace que el segundo falle en el acto con un mensaje, en vez de esperar. ln -sfsobre un enlace que ya existe. Si el enlace «vivo» apunta a un directorio y se rehace sin-n, no se reemplaza el enlace: se crea otro dentro del directorio al que apunta. Esa-nes la diferencia entre un despliegue y un misterio.- El orden. Primero migrar, después desplegar, y nunca al revés. El código nuevo saldría a servir contra una estructura que no existe. Y para que migrar antes sea seguro, cada migración tiene que seguir sirviendo a la versión vieja durante los segundos del cambio: se expande primero, se contrae después.
Qué hacer entonces
- Medir el despliegue actual: lanzar peticiones durante un reinicio y contar las que fallan. Sin el número no hay decisión.
- Dos ranuras y un proxy que conmuta. No hace falta más infraestructura que la que ya se tiene.
- Un chequeo de salud que pregunte por la base y por las migraciones, no por el puerto.
- Retener unos días los archivos con hash de la versión anterior.
- Cerrojo,
ln -sfn, y migrar antes que desplegar.
Esta nota sale de nuestro trabajo en Software a medida.