Los servicios se pueden diseñar con todo tipo de fiabilidad y resiliencia integradas. Sin embargo, para que sean confiables en la práctica, también deben lidiar con los errores predecibles cuando se producen. En Amazon, creamos servicios para que sean redundantes y escalables de manera horizontal, ya que el hardware puede llegar a fallar con el tiempo. Todo disco duro tiene una vida útil máxima esperada y toda pieza de software es susceptible de sufrir una falla en algún momento. El estado de un servidor puede parecer binario: o funciona o no funciona en absoluto y se deja de lado. Lamentablemente, este no es el caso. Descubrimos que en lugar de simplemente apagarse, los servidores que fallan pueden producir daños impredecibles y, muchas veces, desproporcionados a un sistema. Las comprobaciones de estado detectan este tipo de problemas y responden a ellos de forma automática.

En este artículo, se describe cómo utilizamos las comprobaciones de estado para detectar y solucionar los errores de los servidores únicos, qué es lo que sucede cuando no se utilizan y cómo los sistemas, que reaccionan exageradamente a los errores de las comprobaciones de estado, pueden convertir los problemas pequeños en interrupciones totales. También proporcionamos información acerca de nuestra experiencia en Amazon sobre cómo equilibrar las diferencias entre los distintos tipos de implementaciones de comprobaciones de estado.

Errores menores con efectos muy grandes

Cuando era un nuevo desarrollador de software en Amazon, trabajaba en el sitio web renderizando la flota detrás de Amazon.com. Mientras trabajaba en un cambio para agregar algo de instrumentación y obtener visibilidad de lo bien que funcionaba el software, desafortunadamente, escribí un error. El error se desencadenó de forma extraña, pero cuando lo hizo, provocó que un servidor web determinado generara páginas de error en blanco en cada solicitud. El problema se solucionó solo al reiniciar el proceso del servidor web. Detectamos el error y restauramos el cambio rápidamente, agregamos varias pruebas y mejoramos los procesos para poder identificar situaciones como estas en el futuro. Pero mientras el error se producía, algunos de los servidores de una gran flota terminaron en este estado de fallas.
 
Una característica que hizo que este error fuera especialmente difícil de encontrar fue que el servidor no se daba cuenta de que estaba dañado. Además, el servidor perdió la capacidad de informar su estado a los sistemas de monitoreo, por lo que no desencadenó las alarmas habituales ni se lo puso fuera de servicio de forma automática. Para empeorar aún más la situación, el servidor se volvió muy rápido y comenzó a producir páginas de error en blanco mucho más rápido que las páginas web sin errores que producían los “servidores sanos”. La tecnología de equilibrio de carga que usábamos en ese momento favorecía a los servidores rápidos por sobre los lentos, por lo que dirigía una cantidad desproporcionada de tráfico a los servidores con fallas, lo que incrementaba aún más los efectos del problema.

Se dispararon otras alarmas también, dado que el monitoreo implica medir las tasas de error y latencia de varios puntos del sistema. Mientras que este tipo de sistemas de monitoreo y procesos operativos pueden servir como una barrera para contener el problema, las comprobaciones de estado adecuadas pueden minimizar los efectos de toda esta clase de errores de forma significativa al detectarlos y actuar en función de ellos rápidamente.

Desventajas de las comprobaciones de estado

Las comprobaciones de estado son una forma de preguntar a los servicios de un servidor en particular si es capaz o no de realizar un trabajo de manera correcta. Los balanceadores de carga hacen esta pregunta a cada servidor de manera periódica para determinar a qué servidores es seguro dirigir el tráfico. El servicio que obtiene mensajes de una cola puede preguntarse si está funcionando correctamente antes de decidir si desea recibir más trabajo de la cola. Los agentes de monitoreo, que se ejecutan en cada servidor o en una flota de monitoreo externa, pueden preguntar a los servidores si están en buen estado a fin de poder encender una alarma u ocuparse de forma automática de los servidores que están fallando.

Como se puede ver en mi ejemplo del error en el sitio web, cuando un servidor que no funciona correctamente sigue en servicio, puede disminuir de manera desproporcionada la disponibilidad del servicio en su totalidad. Con una flota de diez servidores, un servidor defectuoso significa que la disponibilidad de la flota será del 90 % o menos. Para empeorar la situación, algunos algoritmos de equilibrio de carga, como el de “menos solicitudes”, dan más trabajo a los servidores más rápidos. Cuando un servidor falla, a menudo comienza a producir errores en las solicitudes de forma rápida, lo que genera un “agujero negro” en la flota de servicio y atrae más solicitudes que los servidores en buen estado. En algunos casos, agregamos protección adicional para prevenir estos agujeros negros disminuyendo la velocidad de las solicitudes fallidas para que coincida con la latencia promedio de las solicitudes exitosas. Sin embargo, existen otras situaciones, como las de los evaluadores de cola, en las que este problema es más difícil de solucionar. Por ejemplo, si un evaluador de cola obtiene mensajes tan rápido como puede recibirlos, un servidor con fallas también se convertirá en un agujero negro. Con un conjunto de entornos tan diversos para distribuir el trabajo, la forma en la que pensamos sobre la protección de un servidor con fallas parciales varía de un sistema a otro.

Observamos que los servidores fallan independientemente por diversas razones, como los discos que no se pueden escribir y que provocan que las solicitudes fallen de inmediato, los relojes que se distorsionan abruptamente y provocan que las llamadas a las dependencias fallen en la autenticación, los servidores que no consiguen recuperar el material criptográfico actualizado y provocan que el descifrado y la cifrado fallen, los procesos de soporte importantes que colapsan a causa de sus propios errores, las pérdidas de memoria y los interbloqueos que congelan el procesamiento.

Los servidores también fallan por razones correlacionadas que provocan que muchos o todos los servidores de una flota fallen al mismo tiempo. Las razones correlacionadas incluyen la interrupción de una dependencia compartida y los problemas de red a gran escala. La comprobación de estado ideal evaluará cada aspecto del estado del servidor y de las aplicaciones, tal vez incluso verificará que los procesos de soporte no importantes se estén ejecutando. Sin embargo, los problemas surgen cuando se producen errores en la comprobación de estado por alguna causa que no es importante y cuando esos errores se correlacionan entre los servidores. Si la automatización pone a los servidores fuera de servicio cuando todavía podrían haber llevado a cabo trabajo útil, la automatización produce más daños que beneficios.

La dificultad que presentan las comprobaciones de estado es esta tensión entre, por un lado, los beneficios de las comprobaciones de estado exhaustivas y la rápida mitigación de los errores de un solo servidor y, por el otro lado, el daño que causa un error falso positivo en toda la flota. Por consiguiente, uno de los desafíos de crear una buena comprobación de estado es la protección cuidadosa contra los falsos positivos. En general, esto significa que la automatización que rodea a las comprobaciones de estado debe dejar de dirigir tráfico a un único servidor defectuoso, pero seguir permitiendo el tráfico si toda la flota parece estar teniendo problemas.

Formas de medir el estado

Existen varias razones por las que se puede romper un servidor, y existen muchos lugares de nuestros sistemas en donde medimos su estado. Algunas comprobaciones de estado pueden indicar definitivamente que un servidor en particular está dañado de forma independiente, mientras que otros son más confusos y muestran falsos positivos en el caso de errores correlacionados. Algunas comprobaciones de estado son difíciles de implementar. Otras se implementan en la configuración con servicios como Amazon Elastic Compute Cloud (Amazon EC2) y Elastic Load Balancing. Cada tipo de comprobación de estado tiene sus fortalezas.

Comprobaciones de actividad

Las comprobaciones de actividad evalúan la conectividad básica a un servicio y la presencia de un proceso del servidor. Por lo general, las lleva a cabo un balanceador de carga o un agente de monitoreo externo, que desconocen los detalles sobre cómo funciona una aplicación. Las comprobaciones de actividad se suelen incluir con el servicio y no es necesario que el autor de la aplicación implemente nada. Algunos ejemplos de las comprobaciones de actividad que usamos en Amazon incluyen los siguientes:

• Las pruebas que confirman que un servidor está escuchando en su puerto esperado y que acepta nuevas conexiones TCP.
• Las pruebas que ejecutan solicitudes HTTP básicas y se aseguran de que el servidor responda con un código de estado 200.
• Las comprobaciones de estado para Amazon EC2 que verifican los elementos básicos que son necesarios para que cualquier sistema funcione, como la capacidad de acceso a la red.

Comprobaciones de estado locales

Las comprobaciones de estado locales van más allá que las comprobaciones de actividad a fin de verificar que la aplicación puede funcionar. Estas comprobaciones de estado evalúan los recursos que no se comparten con los otros servidores. Por lo tanto, es poco probable que fallen en muchos servidores de la flota al mismo tiempo. Estas comprobaciones de estado evalúan los siguientes aspectos:

• La incapacidad de escribir o leer desde un disco. Puede ser tentador pensar que los servicios sin estado no necesitan un disco en el que se pueda grabar. Sin embargo, en Amazon, los servicios tienden a usar sus discos para actividades como el monitoreo, el registro y la publicación de datos de medición asíncrona.
• Los procesos importantes que fallan o se dañan. Algunos servicios toman solicitudes mediante un proxy del servidor (similar a NGINX) y ejecutan su lógica de negocio en otro proceso del servidor. Una comprobación de actividad solo puede evaluar si el proceso del proxy se está ejecutando. El proceso de una comprobación de estado local puede pasar del proxy a la aplicación para verificar que se ejecuten y respondan a las solicitudes de manera correcta. Curiosamente, en el ejemplo del sitio web desde el principio del artículo, la comprobación de estado existente era lo suficientemente exhaustiva como para garantizar que el proceso de renderización se ejecutaba y respondía, pero no tan exhaustiva como para garantizar que respondiera correctamente.
• Los procesos de soporte faltantes. Los alojamientos a los que les faltan sus servicios de monitoreo pueden dejar “ciegos” a los operadores y sin conocer el estado de sus servicios. Otros procesos de soporte impulsan los registros de uso de medición y facturación o reciben las actualizaciones de las credenciales. Los servidores con procesos de soporte dañados ponen la funcionalidad en riesgo de forma sutil y difícil de detectar.

Comprobaciones de estado de dependencia

Las comprobaciones de estado de dependencia consisten en una inspección exhaustiva de la capacidad que tiene una aplicación para interactuar con los sistemas adyacentes. Estas comprobaciones idealmente detectan los problemas locales del servidor, como las credenciales vencidas, que evitan que el servidor interactúe con una dependencia. Pero también pueden arrojar falsos positivos cuando existen problemas en la misma dependencia. Debido a estos falsos positivos, debemos tener cuidado con la forma en que reaccionamos ante los errores de las comprobaciones de estado de dependencia. Las comprobaciones de estado de dependencia pueden evaluar lo siguiente:

• Las malas configuraciones o los metadatos obsoletos: si un proceso busca actualizaciones de manera asíncrona para los metadatos o la configuración pero el mecanismo de actualización está dañado en un servidor, este puede quedar significativamente fuera de sincronización en relación con sus pares y comportarse de manera inadecuada en una forma impredecible que no se haya evaluado. Sin embargo, cuando un servidor no observa una actualización por un tiempo, no sabe si el mecanismo de actualización está dañado o si el sistema central de actualizaciones dejó de publicar actualizaciones para todos los servidores.
• La incapacidad para comunicarse con sus compañeros servidores o las dependencias: se sabe que los comportamientos de red extraños han afectado la capacidad de un subconjunto de servidores en una flota para comunicarse con las dependencias sin afectar la capacidad de enviar tráfico a ese servidor. Los problemas del software, como los interbloqueos o los errores en los grupos de conexión, también pueden dificultar la comunicación de la red.
• Otros errores de software inusuales que requieren un rebote de procesos: los interbloqueos, las pérdidas de memoria o los errores de corrupción de estado pueden hacer que un servidor arroje errores. 

Detección de anomalías

La detección de anomalías examina todos los servidores de una flota para determinar si algún servidor se comporta de forma extraña en comparación con sus pares. Si se agregan datos de monitoreo en cada servidor, podemos comparar continuamente tasas de errores , datos de latencia u otros atributos para poder encontrar los servidores anómalos y ponerlos fuera de servicio de manera automática. La detección de anomalías puede encontrar divergencias en la flota que un servidor no puede detectar en sí mismo, como las siguientes:

• Los relojes distorsionados. Especialmente cuando los servidores están con grandes cargas, se sabe que sus relojes se distorsionan de forma drástica y abrupta. Las medidas de seguridad, como las que se utilizan para evaluar las solicitudes firmadas a AWS, requieren que la hora en el reloj de un cliente esté dentro de los cinco minutos de la hora real. Si no es así, las solicitudes no llegan a los servicios de AWS.
• Los códigos antiguos. Si un servidor se desconecta de la red o se apaga por un periodo prolongado y, luego, vuelve a ponerse en línea, podría ejecutar códigos peligrosamente desactualizados, que son incompatibles con el resto de la flota.
• Cualquier modo de error inesperado. Algunas veces los servidores fallan de tal modo que devuelven errores que se identifican como errores del cliente y no propios (HTTP 400 en lugar de 500). Los servidores pueden ralentizarse en lugar de fallar, o pueden responder más rápido que sus compañeros, lo que es una señal de que están devolviendo respuestas falsas a sus intermediarios. La detección de anomalías es una función completa increíble que detecta modos de errores inesperados.

Existen algunas condiciones que deben cumplirse para que la detección de anomalías funcione en la práctica:

• Los servidores deberían hacer aproximadamente lo mismo. En los casos en los que dirijamos de forma explícita diferentes tipos de tráfico hacia distintos tipos de servidores, es posible que los servidores no se comporten una forma lo suficientemente similar como para detectar valores atípicos. Sin embargo, cuando utilizamos los balanceadores de carga para dirigir el tráfico hacia los servidores, es probable que estos respondan de formas similares.
• Las flotas deben ser relativamente homogéneas. En flotas que incluyen diferentes tipos de instancias, algunas instancias pueden ser más lentas que otras, lo que puede activar falsamente la detección pasiva de servidores defectuosos. Para poder trabajar en este escenario, cotejamos las métricas por tipo de instancia.
• Los errores o diferencias de comportamiento se deben informar. Debido a que dependemos de los propios servidores para que informen de los errores, ¿qué sucede cuando sus sistemas de monitoreo también se dañan? Afortunadamente, el cliente de un servicio es un excelente lugar para agregar instrumentación. Los balanceadores de carga, como el Balanceador de carga de aplicaciones, publican los registros de acceso que muestran qué servidor backend se contactó en cada solicitud, el tiempo de respuesta y si la solicitud fue exitosa o falló. 

Reacciones seguras a los errores de las comprobaciones de estado

Cuando un servidor determina que no está en buen estado, puede tomar dos tipos de medidas. En el caso más extremo, puede decidir de forma local que no se le debe asignar ningún trabajo y ponerse fuera de servicio si no supera una comprobación de estado del balanceador de carga o si interrumpe el sondeo de una cola de espera. Otra forma en la que el servidor podría reaccionar es informar a una autoridad central sobre un problema y dejar que el sistema central decida cómo resolverlo. El sistema central puede abordar el problema de forma segura sin dejar que la automatización afecte a toda la flota.

Existen diversas formas de implementar las comprobaciones de estado y de responder a ellas. En esta sección, se describen algunos patrones que utilizamos en Amazon.

Falla de apertura

Algunos balanceadores de carga pueden actuar como una autoridad central inteligente. Cuando un servidor individual no supera una comprobación de estado, el balanceador de carga deja de enviarle tráfico. Pero cuando todos los servidores no superan la comprobación de estado al mismo tiempo, el balanceador de carga presenta una falla de apertura (fail-open) y permite el tráfico hacia todos los servidores. Podemos usar los balanceadores de carga para admitir la implementación segura de una comprobación de estado de dependencia, y quizás incluir uno que consulte su base de datos y compruebe que sus procesos de soporte que no son importantes se estén ejecutando.

Por ejemplo, el balanceador de carga de red de AWS presenta una falla de apertura si ningún servidor se muestra en buen estado. También deja de funcionar en las zonas de disponibilidad dañadas si todos los servidores de una zona de disponibilidad informan que no están en buen estado. (Para obtener más información acerca de cómo utilizar los balanceadores de carga de red para realizar comprobaciones de estado, consulte la documentación de Elastic Load Balancing). Nuestro balanceador de carga de aplicaciones también es compatible con la falla de apertura, al igual que Amazon Route 53. (Para obtener más información acerca de cómo configurar las comprobaciones de estado con Route 53, consulte la documentación de Route 53).

Cuando confiamos en el comportamiento de falla de apertura, nos aseguramos de evaluar los modos de error de las comprobaciones de estado de las dependencias. Por ejemplo, piense en un servicio en el que los servidores se conectan a un almacén de datos compartido. Si el almacenamiento de datos se vuelve lento o responde con una baja tasa de errores, es posible que los servidores no superen las comprobaciones de estado de dependencia en algunos casos. Esta condición hace que los servidores entren y salgan del servicio pero no activa el umbral de falla de apertura. El razonamiento y la prueba de las fallas parciales de las dependencias con estas comprobaciones de estado son importantes para evitar una situación en la que una falla podría causar que las comprobaciones de estado exhaustivas empeoren las cosas.

Mientras que la falla d apertura es un comportamiento útil, en Amazon tendemos a ser escépticos de las cuestiones que no podemos deducir o evaluar del todo en cada situación. Todavía no hemos encontrado pruebas generales de que la falla de apertura se comportará como esperamos en todos los tipos de sobrecargas, errores parciales o errores sutiles en un sistema o en las dependencias de ese sistema. Debido a esta limitación, los equipos de Amazon tienden a restringir sus comprobaciones de estado del balanceador de carga de acción rápida a comprobaciones de estado locales y dependen de sistemas centralizados para reaccionar con cuidado a las comprobaciones de estado de dependencia más exhaustivas. Esto no quiere decir que no utilicemos el comportamiento de falla de apertura o probemos que funciona en casos particulares. Pero cuando la lógica puede actuar en un gran número de servidores de forma rápida, somos extremadamente cautelosos con respecto a esa lógica.

Comprobaciones de estado sin interruptor de circuito

La posibilidad de que los servidores reaccionen a sus propios problemas puede parecer el camino más rápido y sencillo para la recuperación. Sin embargo, también es el camino más arriesgado si el servidor está equivocado sobre su estado o si no tiene una visión completa de lo que está sucediendo en toda la flota. Cuando todos los servidores de la flota toman la misma decisión equivocada al mismo tiempo, se pueden producir errores en cascada en todos los servicios adyacentes. Este riesgo nos presenta una desventaja. Si se produce una falta de datos en la comprobación de estado y el monitoreo, el servidor podría reducir la disponibilidad de un servicio hasta que se detecte el problema. Sin embargo, este escenario evita una interrupción total del servicio debido al comportamiento inesperado de la comprobación de estado en toda la flota.

A continuación, se muestran prácticas recomendadas que seguimos para implementar las comprobaciones de estado cuando no tenemos un interruptor de circuito integrado:

• Configurar el productor de trabajo (balanceador de carga, subproceso de sondeo en cola) para ejecutar las comprobaciones de estado locales y de actividad. El balanceador de carga pone a los servidores fuera de servicio de forma automática solo si tienen algún problema que es definitivamente local para ese servidor, como un disco defectuoso.
• Configurar otros sistemas de monitoreo externo para llevar a cabo comprobaciones de estado de dependencia y detecciones de anomalías. Estos sistemas podrían intentar terminar las instancias de manera automática o alarmar a un operador o ponerse en contacto con él.

Cuando creamos sistemas para que reaccionen de forma automática a los errores de las comprobaciones de estado de dependencia, debemos crear la cantidad adecuada de umbrales para evitar que los sistemas automatizados tomen medidas drásticas de forma inesperada. Los equipos de Amazon que operan los servidores con estado como Amazon DynamoDB, Amazon S3 y Amazon Relational Database Service (Amazon RDS) tienen importantes requisitos de durabilidad respecto del reemplazo de los servidores. También han creado cuidadosos bucles de retroalimentación de control y de limitación de la velocidad para que la automatización se detenga y se comunique con las personas cuando se cruzan los umbrales. Cuando se crea dicha automatización, debemos estar seguros de que nos damos cuenta cuándo un servidor no supera una comprobación del estado de dependencia. Para algunas métricas, dependemos de que los servidores informen su estado individual a un sistema de monitoreo central. Para compensar los casos en los que el servidor está tan dañado que no puede proporcionar información sobre su estado, también efectuamos controles activos en ellos para verificarlos. 

Priorizar el estado

Especialmente en las situaciones de sobrecarga, es importante que los servidores prioricen sus comprobaciones de estado por encima de su trabajo habitual. En esta situación, no superar las comprobaciones de estado o responder con lentitud puede empeorar aún más una mala situación de caídas de tensión. 

Cuando un servidor no supera la comprobación de estado de un balanceador de carga, está pidiendo que este lo ponga fuera de servicio inmediatamente y por un periodo de tiempo que no sea corto. Cuando un solo servidor falla, no hay problema, pero en un aumento repentino del tráfico hacia el servicio, lo último que queremos es reducir su tamaño. Poner fuera de servicio a los servidores durante una sobrecarga puede provocar una caída en espiral. Si se obliga a los servidores restantes a recibir aún más tráfico, es más probable que se sobrecarguen, que no superen una comprobación de estado y que reduzcan aún más la flota.

El problema no es que los servidores sobrecargados arrojen errores cuando están sobrecargados. El problema es que los servidores no respondan a la solicitud de ping del balanceador de carga a tiempo. Después de todo, las comprobaciones de estado del balanceador de carga están configuradas con tiempos de espera, tal como cualquier otra llamada remota al servicio. Los servidores que han sufrido una caída de tensión tardan en responder por una serie de razones, incluidos la contención alta de la CPU, los ciclos largos de recolección de basura o, simplemente, la falta de subprocesos de empleados. Los servicios deben configurarse de manera que se reserven recursos para que respondan a las comprobaciones de estado de manera oportuna, en lugar de tener que atender demasiadas solicitudes adicionales.

Afortunadamente, existen algunas prácticas recomendadas de configuración simple que seguimos para ayudar a prevenir este tipo de caídas en espiral. Las herramientas como los iptables, e incluso algunos balanceadores de carga, respaldan la noción de “conexiones máximas”. En este caso, el sistema operativo (o el balanceador de carga) limita la cantidad de conexiones al servidor para que su proceso no se desborde con solicitudes simultáneas que disminuirían su velocidad.

Cuando un proxy o un balanceador de carga que admite conexiones máximas se encargan de un servicio, parece lógico hacer que la cantidad de subprocesos del empleado en el servidor HTTP coincida con la cantidad máxima de conexiones en el proxy. Sin embargo, esta configuración establecería el servicio para una caída en espiral durante una caída de tensión. Las comprobaciones de estado del proxy también necesitan conexiones, por lo que es importante hacer que el grupo de trabajo del servidor sea lo suficientemente grande como para poder admitir solicitudes de comprobaciones de estado adicionales. Los trabajadores inactivos son económicos, por lo que solemos configurar otros adicionales: desde unos cuantos trabajadores adicionales hasta duplicar las conexiones máximas de proxy configuradas.

Otra estrategia que utilizamos para priorizar las comprobaciones de estado es que los servidores implementen sus propias imposiciones máximas de solicitudes simultáneas. En este caso, las comprobaciones de estado del balanceador de carga se permiten siempre, pero las solicitudes habituales se rechazan si el servidor ya está trabajando en algún umbral. Las implementaciones en Amazon van desde el simple uso de semáforos en Java hasta el análisis más complejo de las tendencias en la utilización de la CPU.

Otra manera de ayudar a asegurar que los servicios respondan a tiempo a una solicitud de ping de la comprobación de estado es realizar la lógica de comprobación de estado de dependencia en un subproceso en segundo plano y actualizar un indicador isHealthy que comprueba la lógica de ping. En este caso, los servidores responden de inmediato a las comprobaciones de estado, y las comprobaciones de estado de dependencia producen una carga predecible en el sistema externo con el que interactúan. Cuando los equipos hacen esto, son extremadamente cuidadosos con la detección de errores en los subprocesos de las comprobaciones de estado. Si ese subproceso en segundo plano sale, el servidor no detecta un error futuro del servidor (o una recuperación).

Equilibrio de las comprobaciones de estado de dependencia con el alcance del impacto

Las comprobaciones de estado de dependencia son interesantes debido a que actúan como una evaluación exhaustiva del estado del servidor. Desafortunadamente, pueden ser peligrosas porque una dependencia puede causar errores en cascada en todo el sistema.

Se puede obtener información acerca de cómo manejar las comprobaciones de estado de dependencia si se observa la arquitectura orientada al servicio de Amazon. Cada servicio de Amazon está diseñado para hacer una pequeña cantidad de cosas, no existe ningún monolito que se encargue de todo. Existen muchas razones por las que nos gusta crear servicios de esta manera, como una innovación más rápida con equipos pequeños y un menor alcance de impacto si hay un problema con un servicio. Este diseño de la arquitectura también se puede aplicar a las comprobaciones de estado.

Cuando un servicio llama a otro servicio, toma una dependencia con ese servicio. Si un servicio solo llama a la dependencia algunas veces, podemos considerar que esa dependencia es una “dependencia liviana”, dado que el servicio todavía puede realizar algunos tipos de trabajos aun si no puede comunicarse con la dependencia. Sin la protección de la falla de apertura, la implementación de las comprobaciones de estado que evalúan una dependencia la convierten en una “dependencia pesada”. Si la dependencia está inactiva, el servicio también deja de funcionar, lo que produce errores en cascada con un mayor alcance de impacto.

Aunque separamos la funcionalidad en servicios diferentes, es probable que cada servicio sirva a múltiples API. A veces, las API del servicio tienen sus propias dependencias. Si una API se ve afectada, preferimos que el servicio continúe sirviendo a las otras API. Por ejemplo, un servicio puede ser tanto un plano de control (como las ocasionalmente llamadas API CRUD de los recursos de larga duración) como un plano de datos (API fundamentales para los negocios de alto rendimiento). Quisiéramos que las API del plano de datos siguieran funcionando incluso si las API del plano de control tienen problemas para comunicarse con sus dependencias.

De manera similar, incluso una sola API puede comportarse diferente según la entrada o el estado de los datos. Un patrón común es la API de lectura que efectúa consultas a la base de datos pero que almacena las respuestas en la memoria caché de forma local por algún tiempo. Si la base de datos está inactiva, el servicio todavía puede proporcionar las lecturas guardadas en la memoria caché hasta que la base de datos esté de vuelta en línea. La falla en las comprobaciones de estado si una sola ruta de código está dañada aumenta el alcance del impacto de un problema relacionado con una dependencia.

Este debate sobre qué dependencia necesita una comprobación de estado plantea una pregunta interesante sobre los intercambios entre los microservicios y los servicios relativamente monolíticos. Rara vez existe una regla clara sobre la cantidad de unidades o puntos de enlace que se pueden implementar para interrumpir un servicio, pero los interrogantes sobre “qué dependencias necesitan una comprobación del estado” y “si los errores incrementan el alcance del impacto”, son ópticas interesantes que se pueden utilizar para determinar la forma en que se puede crear un micro o macroservicio. 

Cuestiones reales que han salido mal con las comprobaciones de estado

Todo esto puede tener sentido en teoría, pero, en la práctica, ¿qué les sucede a los sistemas cuando no se realizan las comprobaciones de estado adecuadas? Buscamos patrones en las historias de los clientes de AWS y de todo Amazon para ayudar a ilustrar la imagen completa. También analizamos los factores de compensación, los tipos de factores “seguros” que los equipos implementan para evitar que una deficiencia en las comprobaciones de estado provoque un problema generalizado.

Implementaciones

Uno de los patrones de los problemas de las comprobaciones de estado involucra a las implementaciones. Los sistemas de implementación como AWS CodeDeploy impulsan los códigos nuevos hacia un subconjunto de la flota a la vez, y así esperan que se complete una ronda de implementación antes de continuar con la que sigue. Este proceso depende de servidores que informan al sistema de implementación una vez que están funcionando con el nuevo código. Si no envían el informe, el sistema de implementación detecta que hay algún problema con el nuevo código y restaura la implementación.

El script de implementación más básico para el inicio del servicio simplemente bifurcará el proceso del servidor e inmediatamente responderá “implementación realizada” al sistema de implementación. Sin embargo, esto es peligroso porque muchas cosas pueden salir mal con el nuevo código: este podría fallar justo después del lanzamiento, bloquearse o no iniciar la escucha en un conector del servidor, fallar en la carga de la configuración necesaria para procesar las solicitudes correctamente, o encontrarse con un error. Cuando un sistema de implementación no está configurado para someterse a una prueba de comprobación de estado de dependencia, no se da cuenta de que está impulsando una implementación con problemas. Continúa dañando un servidor tras otro.

Afortunadamente, en la práctica, los equipos de Amazon implementan múltiples sistemas de mitigación para evitar que esta situación ponga fuera de funcionamiento a toda su flota. Una de estas medidas de mitigación es configurar alarmas que se activan cuando el tamaño total de la flota es demasiado pequeño o cuando funciona con una carga elevada, o cuando hay una alta tasa de latencia o error. Si se activa alguna de estas alarmas, el sistema de implementación detiene la implementación y la restaura.

Otro tipo de mitigación es la utilización de implementaciones por etapas. En lugar de implementar toda la flota de una sola vez, el servicio puede configurarse para implementar un subconjunto, tal vez una zona de disponibilidad, antes de pausar y ejecutar un conjunto completo de pruebas de integración en esa zona. Esta alineación de implementación por zona de disponibilidad es conveniente porque los servicios ya están diseñados para poder seguir funcionando si hay problemas con una sola zona de disponibilidad.

Y, por supuesto, antes de la implementación para la producción, los equipos de Amazon impulsan esos cambios a través de entornos de prueba y ejecutan pruebas de integración automatizadas que podrían detectar este tipo de errores. Sin embargo, pueden existir diferencias sutiles e inevitables entre los entornos de prueba y de producción, por lo que es importante combinar varias capas de seguridad de implementación a fin de detectar todo tipo de problemas antes de que causen un impacto en la producción. Mientras las comprobaciones de estado son importantes para proteger los servicios frente a las implementaciones con fallas, nos aseguramos de no detenernos ahí. Pensamos acerca de las estrategias “seguras” que sirven como barreras para proteger a las flotas de estos y otros errores.

Procesadores asíncronos

Otro patrón de errores se ubica alrededor del procesamiento asíncrono de mensajes, como un servicio que obtiene su trabajo al sondear una cola SQS o Amazon Kinesis Stream. A diferencia de los sistemas que aceptan solicitudes de los balanceadores de carga, en estos no hay algo que realice comprobaciones de estado automáticas para poner a los servidores fuera de servicio.

Cuando los servicios no tienen comprobaciones de estado lo suficientemente exhaustivas, los servidores de los trabajadores de colas individuales pueden tener errores, como discos que se llenan o se quedan sin descriptores de archivos. Este problema no hará que el servidor deje de recibir trabajo de la cola, pero impedirá que el servidor pueda procesar los mensajes correctamente. También ha provocado retrasos en el procesamiento de los mensajes, ya que el servidor defectuoso retira rápidamente el trabajo de la cola y no puede ocuparse de él.

En este tipo de situaciones, a menudo existen varios factores de compensación que contribuyen a contener el impacto. Por ejemplo, si un servidor no puede procesar el mensaje que saca de SQS, entonces SQS reenvía ese mensaje a otro servidor luego de un tiempo de espera de visibilidad de los mensajes ya configurado. La latencia integral aumenta, pero los mensajes no se eliminan. Otro factor de compensación es la alarma que suena cuando hay demasiados errores procesando mensajes, y que alerta al operador para que investigue.

Discos que se llenan

Otra clase de errores que solemos observar es cuando los discos de los servidores se llenan, lo que hace que el procesamiento y el registro fallen. Este error lleva a una deficiencia en la visibilidad del monitoreo, dado que el servidor puede no ser capaz de informar sus errores al sistema de monitoreo.

Otra vez, varios controles de mitigación evitan que los servicios queden “ciegos” y mitigan el impacto de forma rápida. Los sistemas que están a cargo de un proxy, como un balanceador de carga de aplicaciones o una gateway de la API, tendrán métricas de latencia y tasa de error que producirá ese proxy. En este caso, las alarmas se dispararán incluso si el servidor no informa los errores. Para los sistemas basados en colas, los servicios como Amazon Simple Queue Service (Amazon SQS) informan las métricas que indican que el procesamiento está demorado para algunos mensajes.

Lo que estas soluciones tienen en común es que hay múltiples niveles de monitoreo. El propio servidor informa los errores, pero también lo hace un sistema externo. El mismo principio es importante con las comprobaciones de estado. Un sistema externo puede evaluar el estado de un sistema determinado con más precisión de la que puede evaluarse a sí mismo. Es por esto que con AWS Auto Scaling, los equipos configuran un balanceador de carga para que realice comprobaciones de estado externas de ping.

Los equipos también escriben sus propios sistemas de comprobaciones de estado personalizados para preguntar de forma periódica a cada servidor si está en buen estado e informar a AWS Auto Scaling cuándo un servidor no está en buen estado. Una implementación común de este sistema involucra una función Lambda que se ejecuta cada minuto y que evalúa el estado de cada servidor. Estas comprobaciones de estado incluso pueden guardar sus estados entre cada ejecución en un lugar como DynamoDB para que no marquen demasiados servidores como dañados de una vez involuntariamente.

Zombis

Otro patrón de problemas incluye a los servidores zombis. Los servidores se pueden desconectar de la red durante ciertos periodos de tiempo pero permanecer funcionando, o se pueden apagar por periodos prolongados y luego reiniciarse.

Cuando los servidores zombis vuelven a activarse pueden estar significativamente fuera de sincronización respecto del resto de la flota, lo que puede causar serios problemas. Por ejemplo, si un servidor zombi ejecuta una versión del software mucho más antigua e incompatible, puede causar errores cuando intente interactuar con una base de datos con un esquema diferente o puede utilizar la configuración incorrecta.

Para hacer frente a los zombis, los sistemas suelen responden a las comprobaciones de estado con la versión del software que se está ejecutando en ese momento. Luego, un agente de monitoreo central compara las respuestas de toda la flota para buscar todo lo que se esté ejecutando de forma inesperada en una versión obsoleta, e impide que estos servidores vuelvan a entrar en servicio.

Conclusión

Los servidores y el software que se ejecuta en ellos fallan por todo tipo de razones extrañas. Con el tiempo, el hardware se rompe físicamente. Como desarrolladores de software, eventualmente escribimos un error como el que se describe más arriba que pone el software en un estado defectuoso. Se necesitan múltiples capas de comprobaciones, desde las comprobaciones de actividad ligeras hasta el monitoreo pasivos de las métricas por servidor, para poder detectar todo tipo de modos de error inesperados.

Cuando se producen estos errores, es importante detectarlos y poner a los servidores afectados fuera de servicio de inmediato. Sin embargo, como con cualquier automatización de la flota, agregamos interruptores de limitación de velocidad, de umbrales y de circuitos que desactivan la automatización e involucran a las personas en situaciones de incertidumbre o en situaciones extremas. La falta de apertura y la creación de actores centralizados son estrategias para aprovechar los beneficios de la comprobación de estado exhaustiva con la seguridad de la automatización limitada en función de la velocidad.

Laboratorio práctico

Pruebe algunos de los principios que aprendió aquí con un laboratorio práctico.


Acerca del autor

David Yanacek es ingeniero jefe sénior en AWS Lambda. David ha sido desarrollador de software en Amazon desde el año 2006. Anteriormente, trabajó en Amazon DynamoDB y AWS IoT, así como en los marcos de servicios web internos y en los sistemas de automatización de las operaciones de la flota. Una de las actividades preferidas de David en el trabajo es llevar a cabo análisis de registros y examinar las métricas operativas para encontrar formas de mejorar el funcionamiento de los sistemas con el paso del tiempo.

Tiempos de espera, reintentos y retardo con fluctuación