BLOG | NGINX

Mejores prácticas para configurar aplicaciones de microservicios

NGINX - Parte de F5 - horizontal, negro, tipo RGB
Miniatura de Javier Evans
Javier Evans
Publicado el 2 de marzo de 2023

Las directrices conocidas como la aplicación de los doce factores se publicaron por primera vez hace más de diez años. Desde entonces, casi todas sus prácticas obligatorias se han convertido en la forma estándar de facto de escribir e implementar aplicaciones web. Y si bien han seguido siendo aplicables frente a los cambios en la forma en que se organizan e implementan las aplicaciones, en algunos casos se requieren matices adicionales para comprender cómo se aplican las prácticas a los patrones de microservicios para desarrollar e implementar aplicaciones.

Este blog se centra en el Factor 3, Configuración de la tienda en el entorno , que establece:

  • La configuración es todo lo que varía entre los entornos de implementación (que la aplicación de doce factores denomina implementaciones ).
  • La configuración debe estar estrictamente separada del código de la aplicación; de lo contrario, ¿cómo puede variar entre implementaciones?
  • Los datos de configuración se almacenan en variables de entorno.

Al avanzar hacia los microservicios, aún se pueden respetar estas directrices, pero no siempre de forma que se correspondan con la interpretación literal de la aplicación de doce factores. Algunas directrices, como proporcionar datos de configuración como variables de entorno, se aplican correctamente. Otras prácticas comunes de microservicios, si bien respetan los principios básicos de la aplicación de doce factores, son más bien extensiones de ella. En esta publicación, analizaremos tres conceptos fundamentales de la gestión de configuración para microservicios a través de la lente del Factor 3:

Terminología y conceptos clave de microservicios

Antes de adentrarnos en la discusión sobre la adaptación de Factor 3 para microservicios, es útil comprender algunos términos y conceptos clave.

  • Arquitectura de aplicación monolítica : un modelo arquitectónico tradicional que separa las funciones de la aplicación en módulos de componentes, pero incluye todos los módulos en una única base de código.
  • Arquitectura de aplicaciones de microservicios : un modelo arquitectónico que crea una aplicación grande y compleja a partir de múltiples componentes pequeños, cada uno de los cuales realiza un conjunto de operaciones bien delimitado (como autenticación, notificación o procesamiento de pagos). “Microservicio” es también el nombre de los propios componentes pequeños. En la práctica, algunos “microservicios” pueden llegar a ser bastante grandes.
  • Servicio : término general para una sola aplicação o microservicio en un sistema.
  • Sistema : en el contexto de este blog, el conjunto completo de microservicios e infraestructura de soporte que se unen para crear la funcionalidad completa proporcionada por la organización.
  • Artefacto : Un objeto creado por una secuencia de pruebas y compilación. Puede adoptar muchas formas, como una imagen Docker que contiene el código de una aplicación.
  • Implementación : una “instancia” en ejecución de un artefacto, que se ejecuta en un entorno como ensayo, integración o producción.

Microservicios versus monolitos

Con una aplicação monolítica, todos los equipos de la organización trabajan en la misma aplicação y la misma infraestructura circundante. Aunque las aplicaciones monolíticas generalmente parecen más simples que los microservicios en el papel, existen varias razones comunes por las que las organizaciones deciden migrar a microservicios:

  • Autonomía del equipo : puede ser complicado definir la propiedad de la funcionalidad y los subsistemas en un monolito. A medida que las organizaciones crecen y maduran, la responsabilidad de la funcionalidad de las aplicaciones suele distribuirse entre cada vez más equipos. Esto crea dependencias entre equipos porque el equipo que posee una parte de la funcionalidad no posee todos los subsistemas relacionados en el monolito.
  • Disminuir el “radio de explosión” : cuando una aplicação grande se desarrolla e implementa como una sola unidad, un error en un subsistema puede degradar la funcionalidad de toda la aplicación.
  • Escalabilidad independiente de la funcionalidad : incluso si solo un módulo en una aplicación monolítica está bajo una carga pesada, la organización debe implementar muchas instancias de toda la aplicación para evitar fallas o degradación del sistema.

Por supuesto, los microservicios vienen con sus propios desafíos, que incluyen mayor complejidad, menor observabilidad y la necesidad de nuevos modelos de seguridad, pero muchas organizaciones, especialmente las grandes o de rápido crecimiento, deciden que los desafíos valen la pena para dar a sus equipos más autonomía y flexibilidad para crear bases confiables y estables para las experiencias que brindan a sus clientes.

Cambios necesarios para las arquitecturas de microservicios

Al refactorizar una aplicación monolítica en microservicios, sus servicios deben:

  • Aceptar cambios de configuración de forma predecible
  • Darse a conocer al sistema más amplio de una manera predecible
  • Estar bien documentado

Para una aplicación monolítica, pequeñas inconsistencias en los procesos y la dependencia de suposiciones compartidas no son críticas. Sin embargo, al haber muchos microservicios separados, esas inconsistencias y suposiciones pueden generar mucho dolor y caos. Muchos de los cambios que es necesario realizar con los microservicios son necesidades técnicas, pero una cantidad sorprendente tiene que ver con cómo los equipos trabajan internamente e interactúan con otros equipos.

Los cambios organizativos notables con una arquitectura de microservicios incluyen:

  • En lugar de trabajar juntos en la misma base de código, los equipos se vuelven totalmente separados y cada uno es completamente responsable de uno o más servicios. En la implementación más común de microservicios, los equipos también se reorganizan para ser “multifuncionales”, lo que significa que tienen miembros con todas las competencias necesarias para completar los objetivos del equipo con dependencias mínimas de otros equipos.
  • Los equipos de plataforma (responsables de la salud general del sistema) ahora deben coordinar múltiples servicios propiedad de diferentes equipos en lugar de lidiar con una sola aplicação.
  • Los equipos de herramientas deben seguir siendo capaces de proporcionar herramientas y orientación a los diversos equipos propietarios de servicios para ayudarlos a lograr sus objetivos rápidamente, al mismo tiempo que mantienen estable el sistema.

Diagrama que compara la organización de los equipos de desarrolladores para aplicaciones monolíticas y de microservicios

Definir claramente la configuración de su servicio

Un área de la arquitectura de microservicios en la que necesitamos ampliar el Factor 3 tiene que ver con la necesidad de definir claramente cierta información vital sobre un servicio, incluida su configuración, y asumir un mínimo de contexto compartido con otros servicios. El factor 3 no aborda esto directamente, pero es especialmente importante con un gran número de microservicios separados que contribuyen a la funcionalidad de la aplicação .

Como propietario de un servicio en una arquitectura de microservicios, su equipo posee servicios que desempeñan funciones específicas en el sistema en su conjunto. Otros equipos cuyos servicios interactúan con el suyo necesitan acceder al repositorio de su servicio para leer el código y la documentación, así como para realizar contribuciones.

Además, es una lamentable realidad en el campo del desarrollo de software que la composición del equipo cambia a menudo, no solo porque los desarrolladores se unen y abandonan la empresa, sino también debido a la reorganización interna. Además, la responsabilidad de un determinado servicio a menudo también se transfiere entre equipos.

En vista de estas realidades, su base de código y documentación deben ser extremadamente claros y consistentes, lo que se logra mediante lo siguiente:

  • Definir claramente el propósito de cada opción de configuración
  • Definir claramente el formato esperado del valor de configuración
  • Definir claramente cómo la aplicação espera que se proporcionen los valores de configuración
  • Registrar esta información en un número limitado de archivos

Muchos marcos de aplicação proporcionan un medio para definir la configuración requerida. Por ejemplo, el paquete NPM convict para aplicações Node.js utiliza un “esquema” de configuración completo almacenado en un solo archivo. Actúa como fuente de verdad para toda la configuración que una aplicación Node.js requiere para ejecutarse.

Un esquema sólido y fácilmente detectable permite que los miembros de su equipo y otras personas interactúen con confianza con su servicio.

Cómo se proporciona la configuración a un servicio

Una vez que haya definido claramente qué valores de configuración necesita su aplicação , también debe respetar la importante distinción entre las dos fuentes principales de las que una aplicação de microservicios implementada extrae su configuración:

  • Scripts de implementación que definen explícitamente las opciones de configuración y acompañan al código fuente de la aplicação
  • Fuentes externas consultadas en el momento de la implementación

Los scripts de implementación son un patrón común de organización de código en las arquitecturas de microservicios. Como son nuevos desde la publicación original de la aplicación de doce factores, necesariamente representan una extensión de ella.

Patrón: Implementación y configuración de infraestructura junto a la aplicação

En los últimos años, se ha vuelto común tener una carpeta llamada infraestructura (o alguna variante de ese nombre) en el mismo repositorio que el código de la aplicação . Generalmente contiene:

  • Infraestructura como código ( Terraform es un ejemplo común) que describe la infraestructura de la que depende el servicio, como una base de datos
  • Configuración para su sistema de orquestación de contenedores, como gráficos de Helm y manifiestos de Kubernetes
  • Cualquier otro archivo relacionado con la implementación de la aplicação

A primera vista, esto podría parecer una violación de la prescripción de Factor 3 de que la configuración esté estrictamente separada del código.

De hecho, su ubicación junto a su aplicação significa que una carpeta de infraestructura realmente respeta la regla y al mismo tiempo permite mejoras de proceso valiosas que son fundamentales para los equipos que trabajan en entornos de microservicios.

Los beneficios de este patrón incluyen:

  • El equipo propietario del servicio también es propietario de la implementación del servicio y de la implementación de la infraestructura específica del servicio (como bases de datos).
  • El equipo propietario puede asegurarse de que los cambios en cualquiera de estos elementos pasen por su proceso de desarrollo (revisión de código, CI).
  • El equipo puede cambiar fácilmente la forma en que se implementan sus servicios y su infraestructura de soporte sin depender de equipos externos que realicen el trabajo por ellos.

Tenga en cuenta que los beneficios que ofrece este patrón refuerzan la autonomía individual del equipo y, al mismo tiempo, garantizan que se aplique un rigor adicional al proceso de implementación y configuración.

¿Qué tipo de configuración va dónde?

En la práctica, utiliza los scripts de implementación almacenados en su carpeta de infraestructura para administrar tanto la configuración definida explícitamente en los mismos scripts como la recuperación de la configuración desde fuentes externas en el momento de la implementación, al tener el script de implementación para un servicio:

  1. Definir ciertos valores de configuración directamente
  2. Define dónde el proceso que ejecuta el script de implementación puede buscar los valores de configuración deseados en fuentes externas

Los valores de configuración que son específicos de una determinada implementación de su servicio y que están totalmente bajo el control de su equipo se pueden especificar directamente en los archivos de la carpeta de infraestructura. Un ejemplo podría ser algo así como un límite en el tiempo durante el cual se permite ejecutar una consulta de base de datos iniciada por la aplicación. Este valor se puede cambiar modificando el archivo de implementación y volviendo a implementar la aplicação.

Un beneficio de este esquema es que los cambios en dicha configuración necesariamente pasan por revisión de código y pruebas automatizadas, lo que reduce la probabilidad de que un valor mal configurado provoque una interrupción. Los cambios en los valores que pasan por la revisión de código y los valores de las claves de configuración en un momento determinado se pueden descubrir en el historial de sus herramientas de control de código fuente.

Los valores que son necesarios para que la aplicação se ejecute, pero que no están bajo el control de su equipo, deben ser proporcionados por el entorno en el que se implementa la aplicação . Un ejemplo es el nombre de host y el puerto en el que el servicio se conecta a otro microservicio del que depende.

Como ese servicio no es propiedad de su equipo, no puede hacer suposiciones sobre valores como el número de puerto. Estos valores pueden cambiar en cualquier momento y es necesario registrarlos en algún almacenamiento de configuración central cuando se modifican, ya sea que el cambio se realice manualmente o mediante algún proceso automático. Luego podrán ser consultados por aplicações que dependen de ellos.

Podemos resumir estas pautas en dos prácticas recomendadas para la configuración de microservicios.

Una configuración de microservicios no: Confíe en valores codificados o acordados mutuamente

Puede parecer más sencillo codificar ciertos valores en sus scripts de implementación, por ejemplo, la ubicación de un servicio con el que interactúa su servicio. En la práctica, codificar ese tipo de configuración es peligroso, especialmente en entornos modernos donde las ubicaciones de los servicios suelen cambiar con frecuencia. Y es especialmente peligroso si no tienes el segundo servicio.

Puede pensar que puede confiar en su propia diligencia para mantener la ubicación del servicio actualizada en sus scripts o, peor aún, que puede confiar en que el equipo propietario le informará cuando la ubicación cambie. La diligencia a menudo falla en momentos de estrés y, dependiendo del rigor humano, su sistema corre el riesgo de fallar sin previo aviso.

Una configuración de microservicios hace lo siguiente: Haga que el servicio pregunte "¿Dónde está mi base de datos?"

Independientemente de que la información de ubicación esté codificada o no, su aplicação no debe depender de que la infraestructura crítica se encuentre en una ubicación determinada. En cambio, un servicio recién implementado necesita formular preguntas a alguna fuente común dentro del sistema como "¿dónde está mi base de datos?" y recibir una respuesta precisa sobre la ubicación actual de ese recurso externo. Hacer que cada servicio se registre en el sistema a medida que se implementa hace que las cosas sean mucho más sencillas.

Poner un servicio a disposición como configuración

Así como el sistema debe proporcionar respuestas a las preguntas "¿dónde está mi base de datos?" y "¿dónde está el 'servicio X' del que dependo?", un servicio debe estar expuesto al sistema de tal manera que otros servicios puedan encontrarlo y comunicarse con él fácilmente sin saber nada acerca de cómo está implementado.

Una práctica de configuración clave en las arquitecturas de microservicios es el descubrimiento de servicios: el registro de nueva información de servicio y la actualización dinámica de esa información a medida que acceden otros servicios. Después de explicar por qué el descubrimiento de servicios es necesario para los microservicios, exploremos un ejemplo de cómo lograrlo con NGINX Open Source y Consul.

Es una práctica común tener varias instancias (implementaciones) de un servicio ejecutándose al mismo tiempo. Esto permite no solo gestionar tráfico adicional, sino también actualizar un servicio sin tiempo de inactividad mediante el lanzamiento de una nueva implementación. Al actuar como proxy inverso y equilibrador de carga, herramientas como NGINX procesan el tráfico entrante y lo dirigen a la instancia más adecuada. Este es un patrón agradable, porque los servicios que dependen de su servicio envían solicitudes solo a NGINX y no necesitan saber nada sobre sus implementaciones.

A modo de ejemplo, supongamos que tiene una única instancia de un servicio llamado messenger ejecutándose detrás de NGINX que actúa como un proxy inverso.

Diagrama de una única instancia del microservicio 'mensajero' que NGINX utiliza como proxy inverso

¿Y ahora qué pasa si tu aplicación se vuelve popular? Eso se considera una buena noticia, pero luego te das cuenta de que debido al aumento del tráfico, la instancia de mensajería consume mucha CPU y tarda más en procesar las solicitudes, mientras que la base de datos parece estar funcionando bien. Esto indica que es posible que puedas resolver el problema implementando otra instancia del servicio de mensajería .

Cuando se implementa la segunda instancia del servicio de mensajería , ¿cómo sabe NGINX que está activo y comienza a enviarle tráfico? Agregar manualmente nuevas instancias a su configuración de NGINX es un enfoque, pero rápidamente se vuelve inmanejable a medida que más servicios aumentan o reducen su escala.

Una solución común es rastrear los servicios en un sistema con un registro de servicios de alta disponibilidad como Consul . Las nuevas instancias de servicio se registran en Consul a medida que se implementan. Consul monitorea el estado de las instancias enviándoles periódicamente controles de salud. Cuando una instancia no pasa las comprobaciones de estado, se elimina de la lista de servicios disponibles.

Diagrama de dos instancias del microservicio 'mensajero' que NGINX utiliza como proxy inverso, con Consul para el descubrimiento de servicios

NGINX puede consultar un registro como Consul usando una variedad de métodos y ajustar su enrutamiento en consecuencia. Recuerde que cuando actúa como proxy inverso o balanceador de carga, NGINX enruta el tráfico a servidores “ascendentes”. Considere esta sencilla configuración:


# Define un grupo ascendente llamado "messenger_service"
upstream messenger_service {
servidor 172.18.0.7:4000;
servidor 172.18.0.8:4000;
}

servidor {
escuchar 80;

ubicación /api {
# Proxy del tráfico HTTP con rutas que empiezan por '/api' al bloque 'upstream' anterior. El algoritmo de balanceo de carga predeterminado, Round-Robin, alterna las solicitudes entre los dos servidores del bloque.

proxy_pass http://messenger_service;
proxy_set_header X-Forwarded-For $remote_addr;
}
}


De forma predeterminada, NGINX necesita saber la dirección IP precisa y el puerto de cada instancia de mensajería para enrutar el tráfico hacia ella. En este caso, es el puerto 4000 tanto en 172.18.0.7 como en 172.18.0.8.

Aquí es donde entran en juego el Cónsul y la plantilla Cónsul . La plantilla de Consul se ejecuta en el mismo contenedor que NGINX y se comunica con el cliente de Consul que mantiene el registro del servicio.

Cuando cambia la información de registro, la plantilla Consul genera una nueva versión del archivo de configuración NGINX con las direcciones IP y los puertos correctos, la escribe en el directorio de configuración NGINX y le indica a NGINX que recargue su configuración. No hay tiempo de inactividad cuando NGINX recarga su configuración y la nueva instancia comienza a recibir tráfico tan pronto como se completa la recarga.

Con un proxy inverso como NGINX en este tipo de situación, hay un único punto de contacto para registrarse en el sistema como el lugar al que pueden acceder otros servicios. Su equipo tiene la flexibilidad de administrar instancias de servicio individuales sin tener que preocuparse de que otros servicios pierdan acceso al servicio en su totalidad.

Experiencia práctica con NGINX y microservicios en marzo

Es cierto que los microservicios aumentan la complejidad, tanto en términos técnicos para sus servicios como en términos organizacionales para sus relaciones con otros equipos. Para disfrutar de los beneficios de una arquitectura de microservicios es importante reexaminar críticamente las prácticas diseñadas para monolitos para asegurarse de que todavía brinden los mismos beneficios cuando se aplican a un entorno muy diferente. En este blog, exploramos cómo el Factor 3 de la aplicación de doce factores aún aporta valor en un contexto de microservicios, pero puede beneficiarse de pequeños cambios en cómo se aplica concretamente.

Para obtener más información sobre cómo aplicar la aplicación de doce factores a las arquitecturas de microservicios, consulte la Unidad 1 de Microservicios de marzo de 2023 ( próximamente en el blog ). Regístrese gratuitamente para obtener acceso a un seminario web sobre este tema y a un laboratorio práctico.


"Esta publicación de blog puede hacer referencia a productos que ya no están disponibles o que ya no reciben soporte. Para obtener la información más actualizada sobre los productos y soluciones F5 NGINX disponibles, explore nuestra familia de productos NGINX . NGINX ahora es parte de F5. Todos los enlaces anteriores de NGINX.com redirigirán a contenido similar de NGINX en F5.com.