Daniel Pous Montardit

Daniel Pous Montardit

Principal Architect en la unidad CTO de TCCT. Ingeniero Informático y emprendedor con más de 20 años de experiencia en desarrollo de software.
Cloud
Resiliencia, clave en sistemas Cloud-Native
En el primer post de la serie Cloud-Native, ¿Qué significa que mi software sea Cloud Native?, presentamos la resiliencia como uno de los atributos fundamentales que nos ayudan a conseguir que nuestros sistemas sean fiables y funcionen prácticamente sin interrupciones de servicio. Empecemos por definir qué es resiliencia: Es la capacidad de reaccionar ante un fallo y a su vez recuperarse del mismo para seguir funcionando, minimizando cualquier tipo de afectación sobre el negocio. La resiliencia por tanto no trata de evitar fallos, sino de aceptarlos y construir un servicio de forma que sea capaz de recuperarse y volver a un estado de funcionamiento completo lo antes posible. Los sistemas Cloud-Native se fundamentan en arquitecturas distribuidas y, por tanto, se exponen a un mayor conjunto de casuísticas de error en comparación al modelo clásico de aplicación en monolito. Algunos ejemplos de situaciones de fallo son: Crecimientos inesperados en las latencias de red que pueden incurrir en timeouts de comunicación entre componentes y llegar a reducir la calidad del servicio. Microcortes de red causantes de errores de conectividad. Caída de un componente, con reinicio o cambio de localización, que debe gestionarse transparentemente al servicio. Sobrecarga de un componente que desencadena un incremento progresivo en su tiempo de respuesta y puede desencadenar finalmente errores de conexión. Orquestación de operaciones tales como rolling updates (estrategia de actualización del sistema que evita cualquier pérdida de servicio) o escalado/desescalado de servicios. Fallos de hardware. A pesar de que las plataformas cloud pueden detectar y mitigar muchos de los fallos en la capa de infraestructura sobre la que se ejecutan las aplicaciones, para obtener un nivel de resiliencia adecuado de nuestro sistema, es necesario implementar ciertas prácticas o patrones a nivel de la aplicación o sistema software desplegado. Hablemos ahora de qué técnicas o tecnologías nos ayudan a conseguir resiliencia en cada una de las capas presentadas: capa infraestructura y capa software. Infraestructura Resiliente La resiliencia, a nivel hardware, puede conseguirse vía soluciones como por ejemplo fuentes de alimentación redundantes, unidades de almacenamiento con escritura duplicada (RAIDs), etc. Sin embargo, sólo ciertos fallos quedarán cubiertos con estas protecciones, y tendremos que recurrir a otras técnicas para alcanzar los niveles de resiliencia deseados, como son la redundancia y la escalabilidad. Redundancia La redundancia consiste en, tal y como indica la misma palabra, replicar cada uno de los elementos que conforman el servicio, de manera que cualquier tarea o parte de una tarea siempre pueda ser realizada por más de un componente. Para ello hemos de añadir un mecanismo que permite repartir la carga de trabajo entre estas "copias" duplicadas dentro de cada grupo de trabajo, como por ejemplo, podría ser un balanceador de carga. Por otro lado, determinar el nivel de replicación necesario en un servicio dependerá de los requerimientos de negocio del mismo, y afectará tanto a su coste como a la complejidad del mismo. Se recomienda identificar los flujos críticos dentro del servicio y añadir redundancia en cada punto de los flujos, evitando así crear "puntos únicos de fallo" (single point of failure). Estos puntos se refieren a aquellos componentes de nuestro sistema que en caso de fallo provocaría una caída total del mismo. Es también común añadir redundancia multiregión con geo-replicación de la información y distribuir la carga mediante balanceo por DNS, dirigiendo así cada petición a la región adecuada en función de la distancia a su origen geográfico. Escalabilidad Diseñar sistemas escalables es fundamental también para conseguir resiliencia. La escalabilidad o capacidad de ajustar los recursos a la carga de trabajo, ya sea incrementando o decrementando su número, es fundamental para evitar situaciones de fallo como timeouts de comunicación por tiempos de respuesta demasiado elevados, caídas de servicio por colapso de trabajo, o la degradación de los subsistemas de almacenamiento por ingestión masiva de información,... Existen dos tipos de escalado: Escalado vertical o scale up, que consiste en incrementar la potencia de una máquina (ya sea en CPU, memoria, especio de almacenamiento...) Escalado horizontal o scale out, que supone añadir más máquinas. La capacidad de escalar horizontalmente un sistema está muy interrelacionada con disponer de redundancia. Podríamos ver la primera como un plano superior a la segunda, es decir, un sistema no-redundante no puede ser escalable horizontalmente. A su vez, podemos conseguir escalabilidad horizontal sobre redundancia si añadimos una retroalimentación que permita determinar a partir de la carga en tiempo real del sistema en qué medida se debe crecer o decrecer en recursos para ajustarse óptimamente a las necesidades demandadas en cada momento. Fijémonos que en este punto estamos también estableciendo una relación con la capacidad de observabilidad, que será la encargada de proporcionar las métricas necesarias para monitorizar la carga y automatizar los sistemas de autoescalado. Existen librerías en muchos lenguajes para implementar estas técnicas y también se pueden recurrir a soluciones más ortogonales como los Service Mesh para facilitarnos esta labor y desacoplar completamente nuestra lógica de negocio. Cyber Security Cómo ser una organización ciberresiliente 15 de septiembre de 2022 Software Resiliente Cómo ya se avanzaba al inicio de este post, es imprescindible incorporar la resiliencia dentro del diseño del propio software para poder afrontar exitosamente todos los retos de los sistemas distribuidos. La lógica del servicio debe tratar al fallo como un caso más y no como una excepción, debe definir cómo actuar en caso de fallo, y determinar la acción de contingencia cuando el camino preferente no esté disponible. Esto último se conoce cómo acción fallback o configuración de respaldo para ese caso de error. Patrones arquitecturales A parte del patrón fallback, existen un conjunto de patrones de arquitectura orientados a proveer de resiliencia un sistema distribuido, como por ejemplo son: Cortocircuito (Circuit Breaker): este patrón contribuye a que un servicio pueda recuperarse o desacoplarse tanto de caídas de rendimiento por sobrecargas en un subsistema como ante apagones completos de partes de la aplicación. Cuando el número de fallos continuados que un componente reporta supera un cierto nivel, es la antesala de que algo más grave está a punto de suceder: la caída total del subsistema afectado. Mediante el bloqueo temporal de nuevas peticiones el componente en apuros tendrá la oportunidad de recuperarse y evitar males mayores. Este colchón temporal puede ser suficiente para que el sistema de autoescalado haya podido intervenir y replicar el componente en sobrecarga, evitando así cualquier pérdida de servicio a sus clientes. Timeouts: el mero hecho de limitar el tiempo en qué el emisor de una petición va esperar su respuesta, puede ser la clave que evite sobrecargas por acumulación de recursos facilitando así la resiliencia del sistema. Si un microservicio A requiere del microservicio B y éste no responde dentro del timeout definido, al no producirse ninguna espera indefinida, el microservicio A recuperará el control pudiendo decidir si sigue intentando o no. Si el problema ha sido provocado por un microcorte en la red o una sobrecarga del microservicio B, un reintento puede ser suficiente para dirigir de nuevo la petición hacia la instancia de B ya recuperada o hacia una nueva instancia libre de carga.. Y en caso de no realizar más intentos, el microservicio A puede liberar recursos y ejecutar el fallback definido. Reintentos: las dos técnicas anteriores, cortocircuito y timeouts, han introducido ya indirectamente la importancia de realizar reintentos como concepto base para obtener resiliencia. Pero, ¿es posible incorporar reintentos en las comunicaciones entre componentes de forma gratuita? Imaginemos siguiendo con el ejemplo anterior que un microservicio A hace una petición al B, y por un corte puntual de red la respuesta de B no llega a A. Si A incorpora reintentos, lo que sucederá es que cuando finalice el tiempo de espera de esa llamada (el timeout), éste recuperará el control y volverá a realizar la petición a B, con lo que B realizará el trabajo por duplicado con las consecuencia que puedan derivar. Por ejemplo, si esa petición fuera restar una compra del stock de productos, se estaría registrando por duplicado la salida y por tanto dejando un saldo incorrecto en los libros de stock. Es por esta situación, que se introduce el concepto de "idempotencia". Un servicio idempotente se caracteriza por ser inmune a peticiones duplicadas, es decir, el procesar repetidamente una misma petición no provoca incoherencias en el resultado final, dando pie a conseguir "reintentos seguros". La inmunidad se obtiene en base a un diseño que contemple la idempotencia desde su inicio, por ejemplo, en el caso anterior de la actualización del stock, la petición debería incluir un identificador de compra, y el microservicio B debería registrar y validar que dicho identificador no ha sido procesado completamente antes de intentarlo de nuevo. Caché: ahora que conocemos el porqué de incorporar reintentos, podemos entender los poderes de este nuevo patrón que son las cachés aplicadas a resiliencia. Si se usa una caché para almacenar automáticamente las respuestas de un microservicio, se estará ayudando tanto a reducir la presión sobre él como a generar una alternativa (fallback) en caso de ciertas anomalías. En el caso de un reintento, la caché ayuda a que el componente no tenga que volver a realizar un trabajo ya completado anteriormente, y así pueda devolver el resultado directamente. Bulkhead: este último patrón consiste en dividir el sistema distribuido en partes "aisladas" e independientes, también llamadas pools, de forma que en caso que falle una de ellas las demás puedan seguir funcionando normalmente. Esta herramienta de arquitectura puede verse cómo una técnica de contingencia, equiparable a un cortafuegos o a los compartimientos estancos que dividen en partes los barcos y evitan que el agua salte entre ellos. Es aconsejable, por ejemplo, para aislar a un conjunto de componentes críticos de otros estándares. Hay qué valorar también que estás divisiones a veces pueden provocar pérdidas de eficiencia en el uso de recursos, a parte de añadir más complejidad a la solución. Tests de resiliencia Como hemos dicho anteriormente, en un sistema distribuido hay tantos componentes interactuando entre ellos que la probabilidad de cosas que pueden salir mal es muy grande. Hardware, red, sobrecarga de tráfico, etc. pueden fallar. Hemos comentado varias técnicas para conseguir que nuestro software sea resiliente y se minimice el impacto de esos fallos. Pero bien, ¿tenemos alguna forma de comprobar la resiliencia de nuestro sistema? La respuesta es sí, y se llama “Chaos Engineering”. ¿Qué es Chaos Engineering? Es una disciplina de experimentación en infraestructura que saca a la luz las debilidades sistémicas. Este proceso empírico de verificación conduce a sistemas más resistentes y genera confianza sobre la capacidad de éstos a resistir situaciones turbulentas. Experimentar en Chaos Engineering puede ser tan simple como ejecutar manualmente kill -9 (orden para finalizar inmediatamente un proceso en sistemas unix/linux) en una caja dentro de un entorno de pruebas para simular el fallo de un servicio. O puede ser tan sofisticado como diseñar y realizar experimentos automáticamente en un entorno de producción contra una fracción pequeña pero estadísticamente significativa del tráfico en vivo. Existen además librerías y frameworks de soporte, como por ejemplo, Chaos-monkey que es un framework creado por Netflix que permite finalizar aleatoriamente máquinas virtuales o contenedores en entornos productivos, y que cumple los principios de Ingeniería del Caos. Es necesario identificar las debilidades del sistema antes de que se manifiesten en comportamientos aberrantes con afectación generalizada a todo el sistema. Las debilidades sistémicas pueden tomar la forma de: configuraciones de respaldo incorrectas cuando un servicio no está disponible; reintentos desmesurados por tiempos de espera mal ajustados; cortes de servicio cuando un componente de la cadena de procesado colapsa por saturación de tráfico; fallos en cascada masivos consecuencia de un único componente (permite detectar single-point of failure); etc Cyber Security ¿Dónde sitúas a tu empresa en el camino hacia la ciberseguridad? 20 de abril de 2022 Conclusiones El enfoque más tradicional a la hora de construir sistemas era tratar al fallo como un evento excepcional fuera del camino exitoso de ejecución, y por lo tanto no se contemplaba en el diseño base del corazón del servicio. Esto ha cambiado radicalmente en el mundo Cloud-native, dado que en arquitecturas distribuídas las situaciones de fallo aparecen con normalidad y recurrentemente en alguna pieza del conjunto y eso debe considerarse y asumirse desde el primer momento y dentro del propio diseño. Así cuando hablamos de resiliencia nos referimos a esta característica que permite a los servicios responder y recuperarse a fallos limitando al máximo los efectos sobre el conjunto del sistema y reduciendo a la mínima expresión la afectación sobre el mismo. Conseguir sistemas resilientes no únicamente impacta en la calidad del servicio o aplicación, sino que también permite ganar más eficiencia en costes y, sobre todo, no perder oportunidades de negocio por pérdidas de servicio.
30 de enero de 2023
Cloud
Observabilidad: qué es y qué nos ofrece
¿Qué es la observabilidad? El término "observabilidad" proviene de la teoría de control de Rudolf Kalman y se refiere a la capacidad de inferir el estado interno de un sistema en función de sus salidas externas. Este concepto aplicado a sistemas software se refiere a la capacidad de comprender el estado interno de una aplicación en función de su telemetría. No todos los sistemas permiten o dan suficiente información para ser 'observados', así pues clasificaremos como observables aquellos que lo permitan. Ser observable es uno de los atributos fundamentales de los sistemas cloud-native. La información de telemetría se puede clasificar en tres categorías principales: Logs: probablemente el mecanismo más común y extendido de emitir información de lo acontecido internamente que disponen los procesos o servicios de un sistema software. Históricamente son la fuente más detallada de lo ocurrido y siguen un orden temporal. Su aportación es clave para depurar y comprender lo sucedido dentro de un sistema, aunque algunos apuntan que podrían llegar a ser relevados por las trazas en este rol principal. Son fáciles de recolectar, pero muy voluminosos y consecuentemente caros de retener. Existen tanto logs estructurados como no estructurados (texto libre), y entre los formatos comunes tenemos json y logfmt. También se encuentran propuestas de estandarización semántica como por ejemplo la de Open Telemetry o Elastic Common Schema. Métricas: son información cuantitativa (datos numéricos) relativa a procesos o máquinas a lo largo del tiempo. Como por ejemplo podría ser el porcentaje de uso de CPU, Disco o Memoria de una máquina cada 30 segundos o el contador del número total de errores devueltos por una API, etiquetado con el HTTP-Status devuelto y el nombre del contenedor de Kubernetes, por ejemplo, que ha procesado la petición. Así pues estas series temporales pueden ser determinadas por un conjunto de etiquetas con valores, y que además, sirven de punto de entrada de exploración de la información de telemetría. Las métricas se caracterizan por ser sencillas de recolectar, económicas de almacenar, dimensionales para permitir un análisis rápido y una excelente manera de medir la salud general del sistema. Más adelante en otro post veremos también que los valores de una métrica pueden tener datos adjuntos llamados exemplars, también en forma de clave/valor, que sirven entre otras razones para correlacionar fácilmente dicho valor con otras fuentes de información. Por ejemplo en la anterior métrica del contador de errores de una API un exemplar adjunto podría permitirnos saltar directamente de la métrica a las trazas de la petición que originó el error. Esto facilita enormemente la operación del sistema. Trazas: hablamos de datos detallados sobre el camino ejecutado en el interior de un sistema en respuesta a un estímulo exterior (como podría ser una petición HTTP, un mensaje en una cola, o una ejecución programada). Este tipo de información es muy valiosa dado que nos muestra la latencia de un extremo a otro del recorrido ejecutado y para cada una de las llamadas individuales en él realizadas, aunque se trate de una arquitectura distribuida y por lo tanto, la ejecución pueda afectar a múltiples componentes o procesos. La clave de este poder reside en la propagación del contexto entre los componentes del sistema que trabajan conjuntamente, por ejemplo, en un sistema distribuido de micro-servicios los componentes pueden usar cabeceras HTTP para propagar la información de estado requerida y así conseguir los datos entrelazados de un extremo al otro. En conclusión, las trazas nos permiten comprender las rutas de ejecución, encontrar cuellos de botella y optimizarlos eficientemente e identificar errores facilitando su comprensión y su solución. Estos tres verticales de información se denominan los "tres pilares" de la observabilidad, y hacerlos trabajar conjuntamente es fundamental para maximizar los beneficios obtenidos. Por ejemplo, las métricas pueden ser alarmadas para notificar un mal funcionamiento, y sus exemplars asociados nos van a permitir identificar el subconjunto de trazas asociadas a la aparición del problema subyacente. Finalmente, seleccionaremos los logs relacionados con dichas trazas, accediendo así a todo el contexto disponible y necesario para identificar y corregir eficientemente la causa raíz del problema. Una vez resuelto el incidente podemos enriquecer nuestra observabilidad mediante nuevas métricas, consolas o alarmas para anticipar de manera más proactiva problemas similares en un futuro. ¿Por qué la monitorización no es suficiente? ¿Qué nos ofrece la observabilidad? La monitorización nos permite detectar si algo no está funcionando adecuadamente, pero no nos proporciona las razones. Además, sólo es posible monitorizar situaciones de antemano previstas (known knowns). La observabilidad, en cambio, parte de la integración y relación de múltiples fuentes de datos de telemetría que, en conjunto, nos ayudan a comprender mejor cómo funciona el sistema software bajo observación y no sólo a identificar problemáticas. Sin embargo, el aspecto más crítico es lo que se hace con los datos una vez recopilados, por ejemplo, ¿por qué confiar en umbrales predefinidos cuando podemos detectar automáticamente 'puntos de cambio' inusuales? Este tipo de 'inteligencia' es lo que habilita al descubrimiento de incógnitas desconocidas (unknown unknowns). La elaboración de mapas de topología en tiempo real (real time topology) es otra de las capacidades que nos ofrece la observabilidad, y nos permite establecer relaciones automáticas entre toda la información de telemetría ingestada llegando mucho más allá que una simple correlación por tiempo. Un ejemplo de gran impacto de lo que pueden aportar estas topologías sería conseguir mecanismos de resolución automática de incidentes en tiempo real sin intervención humana. La observabilidad nos facilita también la integración de la performance (rendimiento) como una actividad de primer nivel en el desarrollo software, al permitirnos disponer de información de profiling (detalle paso a paso de una ejecución) de forma continua (algo que sin los mecanismos adecuados requiere de mucho esfuerzo en sistemas distribuidos) y ofrecernos la posibilidad de detectar cuellos de botella en tiempo real, etc. Además, el mero hecho de hacernos entender a fondo que sucede dentro de un sistema a lo largo del tiempo nos permite, tanto maximizar el beneficio de la realización de pruebas de carga (y en general de cualquier tipo de test e2e) como abrir las puertas a la puesta en práctica de técnicas de ingeniería del caos, y a su vez aunque no menos importante, reduce el tiempo medio de resolución (MTTR - mean-time to resolution) de incidencias al reducir el tiempo de dedicación al diagnóstico permitiendo centrar el foco en la resolución del problema. Podemos concluir que cuando un sistema abraza una solución de observabilidad madura, los beneficios para el negocio se agudizan. No únicamente dando pie a innovar de forma más eficiente sino que la reducción de tiempos para implementación se traslada como incremento en eficiencia a los equipos generando todo ello consecuentes reducciones de costes. Por todos estos motivos os podéis imaginar que la observabilidad tampoco es una preocupación puramente operativa, sino una responsabilidad transversal de todo el equipo además de considerarse una práctica base dentro de las recomendaciones de la ingeniería software más actual y avanzada. Conclusión La clave para comprender los problemas de los sistemas distribuidos, problemas que aparecen de forma recurrente pero con variabilidad acentuada, es poder depurarlas armados con evidencias en lugar de conjeturas o hipótesis. Debemos interiorizar que los 'errores' forman parte de la nueva normalidad que acompaña a sistemas complejos distribuidos. El grado de observabilidad de un sistema es el grado en que se puede depurar, así mismo podemos asimilar el aporte de la observabilidad en un sistema distribuido, a lo que nos ofrece un depurador (debugger) sobre un sólo proceso en ejecución. Fialmente, resaltar que un sistema observable permite ser optimizado, tanto a nivel técnico como a nivel de negocio, con mucha más facilidad que el resto. Referencias https://newrelic.com/resources/ebooks/what-is-observability https://www.splunk.com/en_us/blog/devops/observability-it-s-not-what-you-think.html https://www.alibabacloud.com/blog/a-unified-solution-for-observability---make-sls-compatible-with-opentelemetry_597157 https://containerjournal.com/kubeconcnc/the-future-of-observability/ https://www.infracloud.io/blogs/tracing-grafana-tempo-jaeger/ https://blog.paessler.com/the-future-of-monitoring-the-rise-of-observability * * * Observabilidad es una de las 10 grandes tendencias tecnológicas de 2023 identificadas por José Cerdán, CEO de Telefónica Tech. Foto principal: Mohammad Metri / Unsplash
12 de enero de 2023