Prácticas tecnológicas para sobrevivir contingencias
Conocen el dicho de que a medida que los policías se van modernizando los ladrones se van poniendo más inteligentes? Bueno, lo mismo aplica para los sistemas informáticos. A medida que uno agrega medidas de seguridad y confianza a un sistema, aparecen combinaciones extrañísimas de bugs que provocan de igual manera la caída de los sistemas.
Un sábado mi empresa fue víctima de estos viles problemas donde producto de un script mal configurado, el servidor se cayó súbitamente y luego no partió más (¡Cuando te sale la ventana del Grub Rescue y los File System aparecen como desconocidos sabes que estás en verdaderos problemas!) perdiendo en su totalidad el contenido almacenado en este servidor. Pese a que cumplimos todos los estándares clásicos (RAID 10, Memorias ECC, fuentes de poder redundantes, etc) estos duendes siempre son capaces de botar el mejor de los hardwares. ¡Definitivamente no se confíen de que por tener RAID están salvados! Incluso empresas grandes como Amazon Web Services han sido victimas de estas combinaciones malvadas de duendes.
En mi empresa no somos extraños a estos malvados duendes y en los últimos cuatro años hemos robustecido nuestros planes para sobrellevar contingencias. Hoy puedo decir con orgullo que, pese a haber perdido súbitamente el servidor que corría toda la carga de mi empresa, no perdimos ningún byte de los servicios principales por la caída, y el proceso de re-establecer los servicios tomó aproximadamente 15 minutos. ¡Lo cual no es menor si uno considera que tenemos 2TB+ de datos, 15+ aplicaciones distintas y cientos de miles de archivos!
El problema que le dio el knock-out a nuestro servidor, pese a ser curioso, no es lo importante de la historia, sino cómo llegamos a estar estar preparados para este tipo de problemas. Esta historia resume las principales prácticas tecnológicas que hemos adoptado estos años las cuales fueron instrumentales para sobrevivir esta falla sin consecuencias mayores. Hay muchas cosas que quedan fuera que quizás merecen mención, pero en virtud de su tiempo solo expongo las que seleccioné como más importantes.
Pruebas de continuidad de negocios
Sin lugar a dudas este es el punto más crítico que debe cumplir cualquier empresa que quiera estar realmente preparada para enfrentar contingencias. La política de realizar simulaciones de contingencias es esencial para madurar las arquitecturas informáticas.
Deben contar con un documento que detalle las pruebas que se harán cada año. Cada prueba debe quedar registrada en un templado sencillo (¡No se entierren en papeles de puro gusto!), donde lo más importante es la sección de Sugerencias de Mejoras. Si no tienen un templado les dejo a disposición el que yo uso.
Su empresa debe tener un responsable (AKA: arquitecto de soluciones) quien planea las mejoras a las arquitecturas y supervisa su implementación en el tiempo. El arquitecto debe estar al tanto de los desarrollos que están realizando los ingenieros, de manera de aconsejarlos y guiarlos para que el nuevo código que produzcan ya esté listo para la nueva arquitectura y no haya que re-hacerlo después. Ser arquitecto no es un trabajo full-time, pero si requiere sentarse a reflexionar cada cierto tiempo sobre la arquitectura que esta implementada y cuales son sus vulnerabilidades.
Para las empresas que están recién empezando deben plantearse metas incrementales acotadas y accesibles, nadie va a transformar toda su arquitectura en 1 mes. Primero se hacen las pruebas de concepto con los servicios menos importantes, se pasan a producción y luego se empieza con los servicios principales. Deben siempre tender a unificar el manejo de sus software usando el mismo ecosistema (IE: todos los deployments vía GitLab).
Monitoreo externo
La primera clave para poder enfrentar cualquier problema es enterarse que existe un problema. Para esto nosotros usamos un servicio externo que se llama Pingdom, donde tenemos configurado que nos monitoree externamente nuestros servicios, y en caso que alguno de ellos no responda adecuadamente, que envíe notificaciones a los encargados. Es importante que el monitoreo sea externo, ya que como es la ironía, todo nuestro monitoreo interno se cayó junto al servidor primario.
Aprender de los errores
Otra práctica esencial es la de analizar los errores de forma científica y aprender de ellos, nadie quiere tropezarse dos veces con el mismo problema.
La gente que maneja la contingencia debe ir anotando en un block de notas hints de los problemas que encontraron, para luego juntarse todos los responsables y analizar las situaciones y problemas, viendo qué cosas salieron bien y qué cosas salieron mal, qué mejoras se pueden hacer y cómo se podrían encolar dentro de los desarrollos futuros. Este análisis se plasma en un documento llamado post-mortem, el cual luego se puede distribuir entre la gente TI o bien a los mismos clientes (Las empresas maduras realizan esto para construir confianza). En general la gente que realiza estos reportes se les conoce como Site Reliability Engineers.
En nuestro caso por ejemplo, pese a que salió todo mayormente bien, nos dimos cuenta que nos faltaba algo básico y esencial: una checklist de todos los servicios que teníamos que revisar, lo cual nos provocó que se nos pasaran algunos servicios secundarios menores en el manejo de la contingencia. Además varios scripts de administración fallaron de forma menor agregando tiempo a nuestro manejo de la contingencia, lo cual se transformó en 10 tickets distintos de mejorar los distintos aspectos que fallaron.
Aplicaciones en contenedores
Los container Docker nos permiten encapsular todas las librerías y el código ejecutable necesarias dentro de estos containers las cuales nos aseguran que tienen todo lo necesario para correr su aplicación. Previamente a esto teníamos todo instalado “a mano” en los servidores y cada vez que trasladábamos la carga a otro lado nos dábamos cuenta que faltaban algunas librerías, dependencias, o las versiones no eran las mismas y funcionaban distinto. Con Docker eliminamos de raíz todos estos problemas, lo que si es importante que los Docker sigan las buenas prácticas, teniendo tanto sus paquetes y librerías como su código. Una imagen Docker debe representa una versión de su aplicación, y si hay que actualizar algo de código, se debe generar una nueva imagen que contenga este cambio y desplegar esa imagen, ¡no hacer un git pull dentro del Docker!
Deployment automático y completo
Una vez tenemos todas nuestras aplicaciones contenidas, necesitamos que a la hora de levantarlas en producción, esta se despliegue en todos nuestros servidores de manera que no exista la posibilidad que distintos servidores tengan distintas imágenes de nuestras aplicaciones Docker. Para esto nuestros deployments funcionan por medio de un Pipeline de Gitlab, el cual se encarga (usando Ansible) de bajar las nuevas versiones en cada uno de los servidores y luego genera la actualización del servicio.
Desacoplar las conexiones entre servicios
Otro de los problemas que solíamos tener era que los servidores se conectaban por medio de IPs y cuando un servidor se caía había que actualizar las IPs en diversos lugares y configuraciones en nuestros diversos servicios. La medida que adoptamos tiene dos componentes:
Primero creamos un DNS interno que sirve un dominio ficticio (internal) el cual tiene los nombres de nuestros servicios (servicio1.internal.ra, servicio2.internal.ra, etc). Luego todos nuestros servicios usan estos nombres de dominio para comunicarse ( http://servicio1.internal.ra/algo.php ). De esta manera cuando queremos hacer un cambio de servidor solo vamos a este DNS y actualizamos todas las IP de un solo update para todos los servicios afectados. Si usan algún Cloud DNS (Route 53 por ejemplo) pueden incluso tener sus servicios en dos partes y configuran el DNS para hacer elfail-over de forma automática.
Luego, usando las herramientas de docker, creamos docker networks en donde los container se pueden encontrar entre ellos por medio del nombre del container. (Si docker1 y docker2 están en la misma docker network, que no sea la default, se pueden ver por su dirección directamente, IE: http://docker2/algo.php ). Esto nos permite manejar menos DNS ya que la mayoría de nuestros micro-servicios necesitan comunicarse solo con sus servicios aledaños.
Minimizar la cantidad de componentes
Mientras más piezas tiene un sistema más compleja se vuelve su administración. Esto es especialmente importante en la continuidad de negocios, haciendo que asegurarse que todo este sincronizado sea más difícil. Por esto es importante tratar de reducir las soluciones ad-hoc e ir con soluciones genéricas probadas.
Por ejemplo en las DBs relacionales tener distintas versiones y motores que mantener sincronizados puede ser un caos técnico requiriendo mucho conocimiento experto para su mantenimiento. Por esto en mi empresa decidimos minimizar la cantidad de bases de datos relacionales y tenemos una sola gran base de datos MariaDB con distintos schemas los cuales sirven distintas aplicaciones (Lo podemos hacer ya que los servicios son poco intensivos en IOPS) manteniéndola sincronizada con un esquema multi-master usando GTID. Para los archivos lo mismo, tenemos un gran repositorio de archivos que mantenemos sincronizado y mandamos a a cold-storage los archivos antiguos para evitar estar sincronizando archivos que nadie usa.
Disclaimer: Esto es desde el punto de vista no-cloud, una aplicación Cloud-Native tiene otras alternativas para evitar que la continuidad de negocios con muchos componentes sea difícil (IE: Well Architected Framework de AWS ). ¡Para bases de datos no dejen de ver herramientas como AWS RDS o GCP Cloud SQL !
Conclusiones
Estar preparado para diversas contingencias no es algo fácil y es un trabajo que toma persistencia y constancia en el tiempo. Cuando deben tomar decisiones de qué arquitectura implementar, ¡siempre escojan la solución más simple! Los sistemas simples funcionan mucho mejor y son más mantenibles al largo plazo (Si lo puedes hacer con un RSYNC dale, por ningún motivo uses DRBD o Gluster para una tarea sencilla).
Les dejo además un resumen de nuestro stack tecnológico con el cual mantenemos nuestra continuidad de negocios.
- Docker & Docker Swarm para orquestación de containers.
- AWS Log Driver para Docker, guardando todos los log en AWS CloudWatch
- Ansible para ejecutar scripts o deployments en varios servidores simultaneamente
- GitLab para Ticketing, Repositorio, Testing y Deployment
- RSYNC para sincronización de archivos
- AWS/GCP SDKs para cold-storage (AWS S3 o GCP Cloud Storage)
- MariaDB MultiMaster con GTID para la base de datos
- Bind9 como servidor DNS
- Monitoreo con Nagios y Grafana
Espero que les haya ayudado este blog y agradeceré cualquier comentario que quieran dejarme. Si hay algún tema que les gustaría que desarrollara por favor ¡háganmelo saber!
Además, para agradecerles el tiempo de lectura, les dejo esta imagen, que detalla una situación bastante similar a lo que nos pasó, es chistoso leerlo desde un punto de vista externo, también lo es vivirlo cuando uno esta preparado, sin embargo no tiene nada de humor si uno no estaba preparado.