Disponibilidad operacional
Escribir código puede ser divertido, pero ejecutar código de manera segura, escalable y confiable es un conjunto de habilidades completamente diferente.
Read or listen to this story on Medium.
Con Agile y DevOps llegó una era de desarrollo de software rápido y dinámico, como se explica en Creación de una cultura ágil .
La creación rápida de prototipos ahora se puede realizar de forma económica y sencilla, pero a menudo se pasa por alto la preparación operativa y el paso de hacer que el prototipo esté "listo para la producción". A menudo es demasiado fácil tomar el prototipo en ejecución que les gusta a las partes interesadas y llamarlo producción, el proyecto se cierra y los desarrolladores pasan al siguiente proyecto. Espera, ¿quién va a apoyar esa nueva solución?
Se espera que el primer desafío pueda ser respondido por el concepto de equipos de servicio DevOps y propiedad del producto. Si una aplicación o un servicio se está ejecutando para su organización, debe ser importante para alguien y debe tener un propietario consciente. Sí, ejecutar muchas aplicaciones requiere recursos humanos, por lo tanto, hágalo adecuadamente. Los equipos de servicio pueden admitir absolutamente más de un servicio, pero tenga en cuenta que gradualmente su carga de trabajo aumentará a más de lo que pueden admitir adecuadamente.
La preparación operativa es un proceso estructurado para garantizar que el equipo de operaciones adquiera las herramientas, las habilidades y la documentación necesarias para operar y mantener una solución recién completada y una infraestructura de soporte . Por lo tanto, es crucial comenzar el proceso de preparación operativa temprano en la fase de planificación y ejecución del proyecto.
Tenga en cuenta que siempre será necesario aplicar parches. Incluso con soluciones sin servidor, las versiones antiguas de los intérpretes siempre quedarán obsoletas y requerirán cambios y pruebas. Seamos realistas, esto no es magia, no es realmente sin servidor, solo se ejecuta en el servidor de otra persona. La barra en el modelo de responsabilidad compartida puede moverse, pero en última instancia, la responsabilidad del cumplimiento de la seguridad cibernética de la solución es suya.
Prácticas DevOps
Hace algunos años, me encargaron el mantenimiento de la infraestructura de Tomcat para una empresa y comencé a notar que los archivos .war que un desarrollador me dio para implementar eran casi el doble del tamaño de lo que otros desarrolladores me enviaban. Resultó que la configuración del compilador en su estación de trabajo no estaba configurada para comprimir, a diferencia de los otros desarrolladores. ¿Qué más era diferente en su configuración?
La conclusión es: no compile el código localmente, si no puede usar una canalización de compilación, al menos, haga que sus desarrolladores compilen el código en un sistema compartido.
Regla n.º 1: no compile el código de producción en su estación de trabajo local
En la misma línea, no me importa si no cambió una sola línea de código, si vuelve a compilar un nuevo binario, ahora es una versión completamente nueva que debe probarse nuevamente. Con ese fin, compilar código para un entorno específico (como una compilación Dev que sea diferente a la compilación Prod) es definitivamente un antipatrón que debe evitarse. Compile un paquete genérico y pase parámetros específicos del entorno durante la implementación. El mismo paquete debe implementarse en todos los entornos.
Regla n.º 2: no compile código para un entorno específico
Incluso si implementa el mismo paquete, la desviación del servidor es otro problema común. Con el tiempo, no importa cuán grande y dedicado sea su equipo de OPS, los servidores de desarrollo no serán exactamente como los servidores de producción. Son parcheados en diferentes momentos, tal vez por diferentes personas, la configuración de la infraestructura es diferente, etc. Estas pequeñas diferencias pueden afectar la estabilidad y el comportamiento de la solución en formas a veces inexplicables.
La mejor herramienta para combatir la deriva del servidor es en realidad "Dockerizar" su implementación. Crear imágenes de Docker (o cualquier solución equivalente) es en realidad una de las mejores formas de incluir bibliotecas y dependencias en su implementación. Ya no importa si el servidor Dev tiene una versión de Java diferente a la del servidor Prod, la versión de Java que necesita está empaquetada dentro de su imagen de contenedor, junto con su código. Esto realmente lo convierte en una implementación portátil y, para ese fin, los servidores importan mucho menos. Nunca debe confiar en un servidor con nombre (si el nombre del servidor comienza con 'prd_' entonces...), los servidores virtuales son dinámicos y se pueden reconstruir o escalar según sea necesario como parte de un ASG. Los microservicios son dinámicos, los parámetros de implementación son el pegamento que mantiene unido el entorno.
Regla #3: Los meseros son ganado, no mascotas, no puedes nombrarlos
Una vez trabajé con un desarrollador que colocó una "actualización apt-get" cerca del comienzo de su archivo Docker-compose. Si bien aplaudo su esfuerzo desde la perspectiva de la seguridad cibernética, este acto me aterrorizó desde la perspectiva de DevOps, porque va en contra del principio central de "construir una vez, implementar en todas partes". Puedo garantizarle que exactamente la misma imagen de Docker, creada una vez y almacenada en un registro de Docker, no tendrá las mismas bibliotecas cuando se implemente en Dev hoy y en Producción el próximo mes. Si bien puede parecer exagerado, la previsibilidad y la repetibilidad son dos de los pilares de la preparación operativa. No importa quién compiló el código ese día y quién lo implementó, el resultado debe ser el mismo. Si puede, automatice sus compilaciones, pruebas e implementaciones como parte de una canalización de integración continua e implementación continua (CI/CD).
Regla n.º 4: esfuércese por la previsibilidad y la repetibilidad en su proceso de creación e implementación
No culpes a Murphy
La "ley de Murphy" establece que "cualquier cosa que pueda salir mal, saldrá mal y en el peor momento posible". Este adagio, que se puede aplicar a muchas cosas diferentes, definitivamente se aplica a la ejecución de soluciones de TI en un entorno de producción.
"La esperanza no es una estrategia"
Espere que las cosas se rompan y que se cometan errores. Antes de llamar a su solución "producción", las partes interesadas, así como los equipos Dev y Ops (o DevOps) deben realizar una "Revisión de preparación operativa" formal de la solución. Hay varias listas de verificación disponibles en Internet, y AWS también puso su lista de verificación a disposición de los clientes . En pocas palabras, los equipos deben tomar decisiones sobre la alta disponibilidad de la solución propuesta, así como las opciones de recuperación ante desastres. No hay respuestas "únicas para todos" a estas preguntas, qué tan importante es esa solución para su organización y cuál es el costo del tiempo de inactividad son algo subjetivos y propios de su cultura. Los servidores se caen, los centros de datos completos pueden inundarse, regiones enteras pueden verse afectadas por un ataque nuclear y continentes enteros pueden desconectarse después de un impacto de meteorito... necesita encontrar el equilibrio entre su tolerancia al riesgo y el costo y complejidad de la solución alternativa, sabiendo que la complejidad adicional viene a costa de un riesgo adicional. La respuesta no es fácil de obtener, y esto puede necesitar ser una discusión recurrente, pero vale la pena tener la conversación.
La alta disponibilidad es el arte de diseñar soluciones que instantáneamente conmutarán por error o escalarán frente a la adversidad, la recuperación ante desastres es el arte de implementar copias de seguridad y poder restaurar la solución de alguna manera dentro de un tiempo aceptable. Ambos no son mutuamente excluyentes, una solución de alta disponibilidad no lo protegerá de una base de datos malintencionada o accidental, el cambio simplemente se replicaría en todos los nodos; en cuyo caso sería necesaria una restauración desde una copia de seguridad o un sitio retrasado. ¿Cuántos datos puede permitirse perder entre copias de seguridad? ¿Cuánto tiempo de inactividad afectaría a su marca y enviaría clientes a la competencia?
Tener una canalización de implementación estable y bien definida, así como una solución bien documentada (consulte Crear diagramas a partir de texto ) y una estructura de soporte bien dotada de personal son factores extremadamente importantes en esta ecuación.
Las pruebas funcionales y de regresión siempre deben ser parte de su proceso de implementación, y las pruebas de carga y resistencia también deben estar presentes. He visto máquinas virtuales implementadas exactamente a partir de la misma plantilla que se comportan de manera muy diferente al resto, lamentablemente esto no es una ciencia exacta, implemente comprobaciones de estado adecuadas antes de permitir que cualquier sistema nuevo acepte una carga de trabajo real. Pruebe temprano, pruebe con frecuencia, pero genere alertas significativas. Tenga cuidado con los efectos en cascada de las dependencias posteriores, ¿debería alertar a su equipo si falla debido a una interrupción causada por otro equipo?
Le recomiendo que piense en implementar dos niveles diferentes de monitoreo y alerta, para que no abrume a su personal de guardia. Monitorear cada instancia de cada componente es útil, pero especialmente con sistemas distribuidos donde se ejecutan múltiples instancias de un componente, perder una instancia puede no valer la pena despertar a alguien en medio de la noche, tal vez un informe nocturno que se puede abordar en la mañana será suficiente. Sin embargo, si la prueba de extremo a extremo está fallando, la prueba que imita de cerca las acciones que un usuario aleatorio realizaría de forma rutinaria, entonces la alerta es adecuada.
Regla #5: Espera lo inesperado
"Simplicidad es la máxima sofisticación."
Ha decidido qué tan altamente disponible desea que sea su solución, escalando a través de múltiples zonas de disponibilidad y centros de datos, con equilibrio de carga regional y réplicas de bases de datos de solo lectura con almacenamiento en caché. Las cosas seguirán yendo mal.
También debe implementar métricas y monitoreo para obtener visibilidad de su solución y su comportamiento y rendimiento. Supervise los componentes clave y también la experiencia del usuario de extremo a extremo. Obtenga informes diarios de cuántos códigos de retorno 200, 400 y 500 devuelve su servicio todos los días e investigue cualquier anomalía.
Una vez implementé una nueva versión de un servicio de procesamiento que instantáneamente comenzó a arrojar errores. Retiramos la nueva versión e investigamos los registros de errores. Dos de los contenedores de procesamiento emitían ID de transacción únicos duplicados, lo que confundía a otro servicio dependiente posterior. ¿Cómo fue esto posible? El desarrollador había usado un algoritmo para emitir identificaciones únicas que dependían del tiempo de implementación del contenedor... y dos contenedores de alguna manera se implementaron exactamente en el mismo milisegundo. La solución fue fácil, use la identificación del contenedor así como la marca de tiempo de implementación para emitir identificaciones únicas, pero ¿cuáles son las probabilidades de que esto suceda?
Al diseñar microservicios, debe diseñar para fallar. Espere siempre que los servicios de los que depende puedan fallar. Incluso peor que la falla, los servicios pueden devolver una respuesta inválida o confusa solo una parte del tiempo, tal vez solo una instancia de cada 100 está dando malas respuestas. Espere que las cosas fallen, implemente reintentos e implemente un retroceso exponencial. He visto demasiados problemas con reintentos rápidos que abruman un servicio que ya tiene problemas. Vuelva a intentarlo durante mucho tiempo, y las interrupciones en los servicios dependientes pueden durar horas, si no días. Necesita tomar decisiones, bajo estrés, ¿es mejor atender todas las solicitudes de manera deficiente o es mejor atender solo un número limitado de solicitudes correctamente y entregar a otros usuarios un mensaje de error?
Regla #6: Construir para fallar
Las cosas se romperán, eliminar un cuello de botella en su infraestructura solo revelará el siguiente, y su equipo aprenderá y mejorará. Pero esto solo puede suceder si su cultura permite que ese fracaso suceda y se convierta en una oportunidad de aprendizaje. Necesita construir una cultura intachable, donde las personas puedan chocar con una pared y superar ese obstáculo, aprender y crecer. Si cada error, prevenible o no, es visto como un fracaso individual, entonces tendrá lugar una cultura de "esto nunca sucedió" y la sospecha se apoderará de todo. Sea abierto sobre sus fallas y errores, sea vulnerable y admita sus defectos, y todos crecerán juntos como organización. La transparencia es clave.
Por supuesto, se deben investigar todas y cada una de las interrupciones y se debe realizar un análisis de causa raíz (RCA) adecuado, pero a menos que un humano haya cometido un error obvio y deliberado, el enfoque nunca debe estar en "¿quién hizo esto?" sino en "¿cómo permitió el proceso que un humano cometiera este error?". El objetivo real de la RCA es asegurarse de que no se pueda cometer el mismo error o uno similar en el futuro, es parte del proceso de mejora continua. Y, por supuesto, si la interrupción fue causada por un error de software o infraestructura, es imprescindible enviar comentarios al equipo de desarrollo.
Regla #7: Construya una cultura sin culpa
Si no comete errores, no está trabajando en problemas lo suficientemente difíciles. Y eso es un gran error.
Sabiendo que las cosas se romperán, también debes abrazar lo inesperado. Netflix fue pionera en la ingeniería del caos , un método de prueba de software distribuido que introduce deliberadamente escenarios fallidos y defectuosos para verificar su resiliencia frente a interrupciones aleatorias. No todo el mundo puede "ejecutar a los simios" en la producción de la forma en que lo hace Netflix, el impacto para ellos puede ser mostrar algunos fotogramas de una película a una resolución más baja, y el impacto en un sistema financiero en tiempo real podría ser devastador, así que por favor adaptar esto a su propio entorno.
Si bien no menciona específicamente los sistemas de TI, vale la pena leer el libro Antifragile: Things That Gain from Disorder, de Nassim Nicholas Taleb , que definitivamente se puede aplicar a la creación de microservicios resilientes.
Regla #8: Acepta lo inesperado
“La antifragilidad va más allá de la resiliencia o la robustez. Lo resiliente resiste los golpes y se mantiene igual; el antifrágil mejora.”
Mitigar la fragilidad no es una opción sino un requisito. Puede sonar obvio, pero parece que se pierde el punto. Porque la fragilidad es muy penosa, como una enfermedad terminal. Un paquete no se rompe en condiciones adversas, luego logra repararse cuando se restauran las condiciones adecuadas. La fragilidad tiene una propiedad similar a la de un trinquete, la irreversibilidad del daño.