Tutorial de NGINX: cómo desplegar y configurar microservicios

NGINX | 07 de marzo de 2023

Esta publicación es uno de los cuatro tutoriales que te ayudarán a poner en práctica los conceptos de Microservices March 2023: Empieza a implementar microservicios:

Todas las aplicaciones requieren configuración, pero los aspectos que deben tenerse en cuenta al configurar un microservicio pueden no ser los mismos que para una aplicación monolítica. Podemos consultar el Factor 3 (Almacenar la configuración en el entorno) de la aplicación de doce factores para obtener directrices aplicables a ambos tipos de aplicaciones, aunque pueden adaptarse a las aplicaciones de microservicios. En concreto, podemos adaptar la forma en que definimos la configuración del servicio, proporcionamos dicha configuración a un servicio y hacemos que un servicio esté disponible como valor de configuración para otros servicios que puedan depender de él.

Para comprender conceptualmente cómo adaptar el Factor 3 a los microservicios —en concreto, las prácticas recomendadas para los archivos de configuración, las bases de datos y la detección de servicios—, consulta Prácticas recomendadas para configurar aplicaciones de microservicios en nuestro blog. Este artículo es una excelente manera de poner en práctica esos conocimientos.

Nota: Nuestra intención en este tutorial es ilustrar algunos conceptos fundamentales, no mostrar la forma correcta de implementar microservicios en producción. Aunque utiliza una arquitectura de «microservicios» real, hay algunas salvedades importantes:

  • El tutorial no utiliza ningún framework de orquestación de contenedores, como Kubernetes o Nomad. De este modo, podrás aprender los conceptos relacionados con los microservicios sin perderte en los detalles específicos de un framework concreto. Los patrones que se presentan aquí se pueden trasladar a un sistema que ejecute uno de estos frameworks.
  • Los servicios están optimizados para facilitar su comprensión, en lugar de priorizar el rigor de la ingeniería de software. El objetivo es analizar la función de cada servicio en el sistema y sus patrones de comunicación, no los detalles específicos del código. Para obtener más información, consulte los archivos README de cada servicio.

Descripción general del tutorial

Este tutorial muestra cómo se aplican los conceptos del Factor 3 a las aplicaciones de microservicios. A lo largo de cuatro retos, explorarás algunos patrones habituales de configuración de microservicios e implementarás y configurarás un servicio utilizando dichos patrones:

  • En el Reto 1 y el Reto 2 se explora el primer patrón, que se refiere a dónde se ubica la configuración de una aplicación de microservicios. Hay tres ubicaciones habituales:
    • El código de la aplicación
    • El script de despliegue de la aplicación
    • Fuentes externas a las que accede el script de despliegue
  • En el Reto 3 configuraste dos patrones más: exponer la aplicación al mundo exterior mediante NGINX como proxy inverso y habilitar el descubrimiento de servicios mediante Consul.
  • En el Reto 4 implementas el patrón final: usar una instancia de tu microservicio como «ejecutor de tareas» para realizar una acción puntual distinta de su función habitual (en este caso, simular una migración de base de datos).

El tutorial utiliza cuatro tecnologías:

  • messenger – Una API de chat sencilla con capacidad para almacenar mensajes, creada para este tutorial
  • NGINX Open Source – Un punto de entrada al servicio messenger y al sistema general en su conjunto
  • Consul – Un registro dinámico de servicios y almacén de pares clave-valor
  • RabbitMQ – Un popular bróker de mensajes de código abierto que permite que los servicios se comuniquen de forma asíncrona

Diagrama de topología que muestra NGINX Open Source y la plantilla de Consul ejecutándose conjuntamente en un contenedor. La plantilla de Consul se comunica con el cliente de Consul. NGINX Open Source actúa como proxy inverso para el servicio de mensajería, que almacena datos en messenger_db y se comunica con Rabbit MQ.

Vea este vídeo para obtener una visión general del tutorial. Los pasos no coinciden exactamente con los de esta publicación, pero el vídeo ayuda a comprender los conceptos.

 

Requisitos previos y configuración

Requisitos previos

Para completar el tutorial en tu propio entorno, necesitas:

  • Un entorno compatible con Linux/Unix
  • Conocimientos básicos de la línea de comandos de Linux, JavaScript y bash (no obstante, se proporciona y explica todo el código y los comandos, por lo que podrás completar el tutorial aunque tus conocimientos sean limitados)
  • Docker y Docker Compose
  • Node.js 19.x o posterior
    • Hemos probado la versión 19.x, pero esperamos que las versiones más recientes de Node.js también funcionen.
    • Para obtener información detallada sobre la instalación de Node.js, consulta el archivo README del repositorio del servicio messenger. También puedes instalar asdf para obtener exactamente la misma versión de Node.js que se utiliza en los contenedores.
  • curl (ya está instalado en la mayoría de los sistemas)
  • Las cuatro tecnologías enumeradas en Descripción general del tutorial: messenger (que descargarás en la siguiente sección), NGINX Open Source, Consul y RabbitMQ.

Configuración

  1. Inicia una sesión de terminal (en las instrucciones posteriores se hará referencia a ella como la terminal de la aplicación).
  2. En tu directorio personal, crea el directorio microservices-march y clona en él los repositorios de GitHub de este tutorial. (También puedes usar otro nombre de directorio y adaptar las instrucciones según corresponda). Nota: A lo largo del tutorial se omite el indicador de la línea de comandos de Linux para facilitar que puedas copiar y pegar los comandos en tu terminal. La virgulilla (~) representa tu directorio personal.
mkdir ~/microservices-marchcd ~/microservices-march git clone https://github.com/microservices-march/platform.git --branch mm23-twelve-factor-start git clone https://github.com/microservices-march/messenger.git --branch mm23-twelve-factor-start
  1. Ve al repositorio platform e inicia Docker Compose:
cd platformdocker compose up -d --build
  1. Esto inicia tanto RabbitMQ como Consul, que se utilizarán en los siguientes desafíos.
    • La opción -d indica a Docker Compose que se desvincule de los contenedores una vez que se hayan iniciado (de lo contrario, los contenedores permanecerán vinculados a tu terminal).
    • La opción --build indica a Docker Compose que vuelva a compilar todas las imágenes al iniciarse. Esto garantiza que las imágenes que se están ejecutando se mantengan actualizadas ante cualquier posible cambio en los archivos.
  2. Cambia al repositorio messenger e inicia Docker Compose:
cd ../messengerdocker compose up -d --build
  1. Esto inicia la base de datos PostgreSQL del servicio messenger, a la que nos referiremos como messenger-database durante el resto del tutorial.

Reto 1: Definir la configuración de los microservicios a nivel de aplicación

En este reto, configurarás la primera de las tres ubicaciones que veremos en el tutorial: el nivel de aplicación. (El reto 2 muestra la segunda y la tercera ubicación: los scripts de despliegue y las fuentes externas.)

La aplicación de doce factores excluye específicamente la configuración a nivel de aplicación, ya que este tipo de configuración no necesita cambiar entre distintos entornos de despliegue (que la aplicación de doce factores denomina «despliegues»). No obstante, abordamos los tres tipos para ofrecer una visión completa, ya que la forma de gestionar cada categoría al desarrollar, compilar y desplegar un servicio es diferente.

El servicio messenger está escrito en Node.js y tiene el punto de entrada en app/index.mjs dentro del repositorio messenger. Esta línea del archivo:

app.use(express.json());

es un ejemplo de configuración a nivel de aplicación. Configura el framework Express para deserializar los cuerpos de las solicitudes de tipo application/json y convertirlos en objetos JavaScript.

Esta lógica está estrechamente ligada al código de tu aplicación y no es lo que la metodología de aplicaciones de doce factores considera «configuración». Sin embargo, en el desarrollo de software todo depende de las circunstancias, ¿no?

En las dos secciones siguientes, modificará esta línea para implementar dos ejemplos de configuración a nivel de aplicación.

Ejemplo 1

En este ejemplo, se establece el tamaño máximo del cuerpo de una solicitud que acepta el servicio messenger. Este límite de tamaño se establece mediante el argumento limit de la función express.json, tal como se explica en la documentación de la API de Express. Aquí se añade el argumento limit a la configuración del middleware JSON del framework Express descrita anteriormente.

  1. En su editor de texto preferido, abra app/index.mjs y sustituya:
app.use(express.json())
  1. con:
app.use(express.json({ limit: "20b" }));
  1. En el terminal de la aplicación (el que utilizaste en Configuración), ve al directorio app e inicia el servicio messenger:
cd app npm install node index.mjs
messenger_service listening on port 4000
  1. Inicie una segunda sesión de terminal independiente (a la que las instrucciones posteriores denominan «terminal del cliente») y envíe una solicitud POST al servicio messenger. El mensaje de error indica que la solicitud se ha procesado correctamente porque el cuerpo de la solicitud no superaba el límite de 20 bytes establecido en el paso 1, pero que el contenido de la carga útil JSON es incorrecto:
curl -d '{ "text": "hello" }' -H "Content-Type: application/json" -X POST http://localhost:4000/conversations...
{ "error": "Conversation must have 2 unique users" }
  1. Envía un cuerpo de mensaje ligeramente más largo (de nuevo, en el terminal del cliente). Se muestra mucha más información que en el paso 3, incluido un mensaje de error que indica que, esta vez, el cuerpo de la solicitud supera los 20 bytes:
curl -d '{ "text": "hello, world" }' -H "Content-Type: application/json" -X POST http://localhost:4000/conversations...
\”PayloadTooLargeError: request entity too large"

Ejemplo 2

Este ejemplo utiliza convict, una biblioteca que permite definir un «esquema» de configuración completo en un único archivo. También ilustra dos directrices del factor 3 de la aplicación de doce factores:

  • Almacena la configuración en variables de entorno – Modifica la aplicación para que el tamaño máximo del cuerpo se establezca mediante una variable de entorno (JSON_BODY_LIMIT) en lugar de estar codificado de forma fija en el código de la aplicación.
  • Define claramente la configuración de tu servicio – Se trata de una adaptación del Factor 3 para microservicios. Si no conoces este concepto, te recomendamos que dediques un momento a leer sobre él en Prácticas recomendadas para configurar aplicaciones de microservicios en nuestro blog.

El ejemplo también prepara cierta «infraestructura» que aprovecharás en el Reto 2: el script de despliegue de messenger que crearás en ese reto establece la variable de entorno JSON_BODY_LIMIT, que aquí insertas en el código de la aplicación para ilustrar cómo se especifica la configuración en un script de despliegue.

  1. Abre el archivo de configuración de convict, app/config/config.mjs, y añade lo siguiente como una nueva clave después de la clave amqpport:
jsonBodyLimit: {  doc: `The max size (with unit included) that will be parsed by the
        JSON middleware. Unit parsing is done by the
        https://www.npmjs.com/package/bytes library.
        ex: "100kb"`,
  format: String,
  default: null,
  env: "JSON_BODY_LIMIT",
},
  1. La biblioteca convict se encarga de analizar la variable de entorno JSON_BODY_LIMIT cuando la utilice para establecer el tamaño máximo del cuerpo mediante la línea de comandos en el paso 3 que aparece a continuación:
    • Obtiene el valor de la variable de entorno correcta
    • Comprueba el tipo de la variable (String)
    • Permite acceder a ella en la aplicación mediante la clave jsonBodyLimit key
  2. En app/index.mjs sustituye:
app.use(express.json({ limit: "20b" }));
  1. con
app.use(express.json({ limit: config.get("jsonBodyLimit") }));
  1. En el terminal de la aplicación (donde inició el servicio messenger en el paso 2 del Ejemplo 1), pulse Ctrl+c para detenerlo. A continuación, vuelva a iniciarlo utilizando la variable de entorno JSON_BODY_LIMIT para establecer el tamaño máximo del cuerpo en 27 bytes:
^cJSON_BODY_LIMIT=27b node index.mjs
  1. Este es un ejemplo de cómo modificar el método de configuración cuando resulta adecuado para tu caso de uso: has pasado de codificar un valor de forma fija (en este caso, un límite de tamaño) en el código de la aplicación a establecerlo mediante una variable de entorno, tal como recomienda la metodología de aplicaciones de doce factores. Como se ha mencionado anteriormente, en el Reto 2 el uso de la variable de entorno JSON_BODY_LIMIT se convertirá en un ejemplo de la segunda ubicación de la configuración, cuando utilices el script de despliegue del servicio messenger para establecer la variable de entorno en lugar de hacerlo en la línea de comandos.
  2. En el terminal del cliente, repita el comando curl del paso 4 del ejemplo 1 (con el cuerpo de solicitud de mayor tamaño). Como ahora ha aumentado el límite de tamaño a 27 bytes, el cuerpo de la solicitud ya no supera el límite y aparece un mensaje de error que indica que la solicitud se ha procesado, pero que el contenido de la carga útil JSON es incorrecto:
curl -d '{ "text": "hello, world" }' -H "Content-Type: application/json" -X POST http://localhost:4000/conversations{ "error": "Conversation must have 2 unique users" }
  1. Si lo deseas, puedes cerrar el terminal del cliente. El resto de los comandos del tutorial se ejecutarán en el terminal de la aplicación.
  2. En el terminal de la aplicación, pulsa Ctrl+c para detener el servicio messenger (anteriormente, en el paso 3, detuviste y reiniciaste el servicio en este terminal).
^c
  1. Detén messenger-database. Puedes ignorar sin problema el mensaje de error que aparece, ya que los elementos de infraestructura definidos en el repositorio platform siguen utilizando la red. Ejecuta este comando en la raíz del repositorio messenger.
docker compose down
...failed to remove network mm_2023....

Desafío 2: Crear scripts de despliegue para un servicio

«La configuración debe estar estrictamente separada del código (de lo contrario, ¿cómo podría variar entre despliegues?)». — Del factor 3 de la aplicación de doce factores

A primera vista, podría interpretarse que esto significa «no incluir la configuración en el control de código fuente». En este reto, implementarás un patrón habitual para entornos de microservicios que puede parecer que incumple esta regla, pero que en realidad la respeta y, al mismo tiempo, aporta valiosas mejoras en los procesos que son fundamentales para dichos entornos.

En este reto, creará scripts de despliegue para imitar la funcionalidad de la infraestructura como código y de los manifiestos de despliegue que proporcionan configuración a un microservicio, modificará los scripts para utilizar fuentes de configuración externas, establecerá un secreto, y después ejecutará los scripts para desplegar los servicios y su infraestructura.

Los scripts de despliegue se crean en un directorio infrastructure recién creado en el repositorio messenger. Un directorio llamado infrastructure (o alguna variante de ese nombre) es un patrón habitual en las arquitecturas modernas de microservicios y se utiliza para almacenar elementos como:

Entre las ventajas de este patrón se incluyen:

  • Asigna la responsabilidad del despliegue del servicio y de la infraestructura específica del servicio (como las bases de datos) al equipo responsable del servicio.
  • El equipo puede garantizar que los cambios en cualquiera de estos elementos pasen por su proceso de desarrollo (revisión de código, integración continua, etc.).
  • El equipo puede modificar fácilmente la forma en que se despliegan el servicio y la infraestructura que lo sustenta, sin depender de que otros equipos hagan el trabajo por ellos.

Como se ha mencionado anteriormente, el objetivo de este tutorial no es mostrar cómo configurar un sistema real, y los scripts que se despliegan en este reto no se asemejan a los de un sistema de producción real. Más bien, ilustran algunos conceptos fundamentales y problemas que resuelve la configuración específica de cada herramienta al abordar el despliegue de infraestructura relacionada con microservicios, al tiempo que abstraen los scripts para reducir al mínimo posible la dependencia de herramientas específicas.

Crear los scripts de despliegue iniciales

  1. En el terminal de la aplicación, crea un directorio infrastructure en la raíz del repositorio messenger y crea archivos que contengan los scripts de despliegue del servicio messenger y de messenger-database. En función de tu entorno, puede que tengas que anteponer sudo a los comandos chmod:
mkdir infrastructurecd infrastructure touch messenger-deploy.sh chmod +x messenger-deploy.sh touch messenger-db-deploy.sh chmod +x messenger-db-deploy.sh
  1. En el editor de texto que prefiera, abra messenger-deploy.sh y añada lo siguiente para crear un script de despliegue inicial para el servicio messenger:
#!/bin/bashset -e

JSON_BODY_LIMIT=20b

docker run \
  --rm \
  -e JSON_BODY_LIMIT="${JSON_BODY_LIMIT}" \
  messenger

Este script aún no está completo, pero ilustra un par de conceptos:

  • Asigna un valor a las variables de entorno incluyendo esa configuración directamente en el script de despliegue.
  • Utiliza la opción -e en el comando docker run para inyectar variables de entorno en el contenedor durante la ejecución.

Puede parecer redundante establecer así el valor de las variables de entorno, pero esto significa que, por muy complejo que llegue a ser este script de despliegue, basta con echar un vistazo al principio del script para entender cómo se proporcionan los datos de configuración al despliegue.

Además, aunque un script de despliegue real puede no invocar explícitamente el comando docker run, este script de ejemplo pretende mostrar los principales problemas que resuelve algo como un manifiesto de Kubernetes. Cuando se utiliza un sistema de orquestación de contenedores como Kubernetes, un despliegue inicia un contenedor y pone a disposición de este la configuración de la aplicación derivada de los archivos de configuración de Kubernetes. Por tanto, podemos considerar que este archivo de despliegue de ejemplo es una versión mínima de un script de despliegue que desempeña la misma función que los archivos de despliegue específicos de un framework, como los manifiestos de Kubernetes.

En un entorno de desarrollo real, podrías incluir este archivo en el sistema de control de versiones y someterlo a una revisión de código. Esto permite que el resto del equipo comente tus ajustes y ayuda así a evitar incidentes en los que unos valores mal configurados provoquen un comportamiento inesperado. Por ejemplo, en esta captura de pantalla, un miembro del equipo señala acertadamente que un límite de 20 bytes para los cuerpos de las solicitudes JSON entrantes (establecido mediante JSON_BODY_LIMIT) es demasiado bajo.

Captura de pantalla de una revisión de código en la que se indica que un límite de 20 bytes para el cuerpo del mensaje es demasiado bajo

Modificar los scripts de despliegue para consultar valores de configuración de fuentes externas

En esta parte del reto, configurará la tercera ubicación para la configuración de un microservicio: una fuente externa que se consulta durante la implementación. Registrar valores dinámicamente y obtenerlos de una fuente externa durante la implementación es una práctica mucho más recomendable que codificarlos de forma fija, ya que deben actualizarse constantemente y pueden provocar fallos. Para obtener más información, consulte Prácticas recomendadas para configurar aplicaciones de microservicios en nuestro blog.

En este punto, hay dos componentes de infraestructura ejecutándose en segundo plano para proporcionar los servicios auxiliares que necesita el servicio messenger:

  1. RabbitMQ, gestionado por el equipo de Plataforma en un despliegue real (iniciado en el paso 3 de «Configuración»)
  2. La base de datos messenger-database, que en un despliegue real es responsabilidad de tu equipo (iniciada en el paso 4 de Configuración)

El esquema de convict del servicio messenger en app/config/config.mjs define las variables de entorno obligatorias correspondientes a estos elementos de configuración externa. En esta sección, configurará estos dos componentes para que proporcionen la configuración. Para ello, establecerá los valores de las variables en una ubicación accesible para todos, de modo que el servicio messenger pueda consultarlos cuando se despliegue.

La información de conexión necesaria para RabbitMQ y messenger-database se registra en el almacén de claves/valores (KV) de Consul, una ubicación común a la que pueden acceder todos los servicios a medida que se implementan. El almacén KV de Consul no es un lugar habitual para guardar este tipo de datos, pero en este tutorial se utiliza para simplificar.

  1. Sustituye el contenido de infrastructure/messenger-deploy.sh (creado en el paso 2 de la sección anterior) por lo siguiente:
#!/bin/bashset -e

# This configuration requires a new commit to change
NODE_ENV=production
PORT=4000
JSON_BODY_LIMIT=100kb

# Postgres database configuration by pulling information from 
# the system
POSTGRES_USER=$(curl -X GET http://localhost:8500/v1/kv/messenger-db-application-user?raw=true)
PGPORT=$(curl -X GET http://localhost:8500/v1/kv/messenger-db-port?raw=true)
PGHOST=$(curl -X GET http://localhost:8500/v1/kv/messenger-db-host?raw=true)

# RabbitMQ configuration by pulling from the system
AMQPHOST=$(curl -X GET http://localhost:8500/v1/kv/amqp-host?raw=true)
AMQPPORT=$(curl -X GET http://localhost:8500/v1/kv/amqp-port?raw=true)

docker run \
  --rm \
  -e NODE_ENV="${NODE_ENV}" \
  -e PORT="${PORT}" \
  -e JSON_BODY_LIMIT="${JSON_BODY_LIMIT}" \
  -e PGUSER="${POSTGRES_USER}" \
  -e PGPORT="${PGPORT}" \
  -e PGHOST="${PGHOST}" \
  -e AMQPPORT="${AMQPPORT}" \
  -e AMQPHOST="${AMQPHOST}" \
  messenger
  1. Este script ejemplifica dos tipos de configuración:
    • Configuración especificada directamente en el script de despliegue – Establece el entorno de despliegue (NODE_ENV) y el puerto (PORT), y cambia JSON_BODY_LIMIT a 100 KB, un valor más realista que 20 bytes.
    • Configuración consultada desde fuentes externas – Obtiene los valores de las variables de entorno POSTGRES_USER, PGPORT, PGHOST, AMQPHOST y AMQPPORT del almacén KV de Consul. Los valores de las variables de entorno se establecen en el almacén KV de Consul mediante los dos pasos siguientes.
  2. Abre messenger-db-deploy.sh y añade lo siguiente para crear un script de despliegue inicial para messenger-database:
#!/bin/bashset -e

PORT=5432
POSTGRES_USER=postgres

docker run \
  -d \
  --rm \
  --name messenger-db \
  -v db-data:/var/lib/postgresql/data/pgdata \
  -e POSTGRES_USER="${POSTGRES_USER}" \
  -e POSTGRES_PASSWORD="${POSTGRES_PASSWORD}" \
  -e PGPORT="${PORT}" \
  -e PGDATA=/var/lib/postgresql/data/pgdata \
  --network mm_2023 \
  postgres:15.1

# Register details about the database with Consul
curl -X PUT http://localhost:8500/v1/kv/messenger-db-port \
  -H "Content-Type: application/json" \
  -d "${PORT}"

curl -X PUT http://localhost:8500/v1/kv/messenger-db-host \
  -H "Content-Type: application/json" \
  -d 'messenger-db' # This matches the "--name" flag above
                    # (the hostname)

curl -X PUT http://localhost:8500/v1/kv/messenger-db-application-user \
  -H "Content-Type: application/json" \
  -d "${POSTGRES_USER}"
  1. Además de definir la configuración que puede consultar el servicio messenger durante el despliegue, el script ilustra los mismos dos conceptos que el script inicial del servicio messenger de Crear scripts de despliegue iniciales):
    • Especifica determinados parámetros de configuración directamente en el script de despliegue; en este caso, indica a la base de datos PostgreSQL el puerto en el que debe ejecutarse y el nombre de usuario del usuario predeterminado.
    • Ejecuta Docker con la marca -e para inyectar variables de entorno en el contenedor durante la ejecución. También establece el nombre del contenedor en ejecución como messenger-db, que pasa a ser el nombre de host de la base de datos en la red de Docker que creó al iniciar el servicio platform en el paso 2 de Configuración.
  2. En un despliegue real, suele ser el equipo de Plataforma (o uno similar) el que se encarga del despliegue y el mantenimiento de un servicio como RabbitMQ en el repositorio platform, al igual que haces con messenger-database en el repositorio messenger. A continuación, el equipo de Plataforma se asegura de que los servicios que dependen de esa infraestructura puedan localizarla. Para este tutorial, configura tú mismo los valores de RabbitMQ:
curl -X PUT --silent --output /dev/null --show-error --fail \ -H "Content-Type: application/json" \ -d "rabbitmq" \ http://localhost:8500/v1/kv/amqp-host curl -X PUT --silent --output /dev/null --show-error --fail \ -H "Content-Type: application/json" \ -d "5672" \ http://localhost:8500/v1/kv/amqp-port
  1. (Quizá te preguntes por qué se utiliza amqp para definir las variables de RabbitMQ; se debe a que AMQP es el protocolo que utiliza RabbitMQ.)

Configurar un secreto en los scripts de despliegue

Solo falta un dato (crítico) en los scripts de despliegue del servicio messenger: la contraseña de messenger-database.

Nota: La gestión de secretos no es el objetivo de este tutorial, por lo que, para simplificar, el secreto se define en los archivos de despliegue. No hagas nunca esto en un entorno real —ya sea de desarrollo, pruebas o producción—, pues supone un enorme riesgo para la seguridad.

Para obtener más información sobre cómo gestionar secretos correctamente, consulta la Unidad 2: Introducción a la gestión de secretos en microservicios de Microservices March 2023. (Spoiler: una herramienta de gestión de secretos es el único método realmente seguro para almacenarlos).

  1. Sustituye el contenido de infrastructure/messenger-db-deploy.sh por lo siguiente para almacenar la contraseña secreta de messenger-database en el almacén de clave-valor de Consul:
#!/bin/bashset -e

PORT=5432
POSTGRES_USER=postgres
# NOTE: Never do this in a real-world deployment. Store passwords
# only in an encrypted secrets store.
POSTGRES_PASSWORD=postgres

docker run \
  --rm \
  --name messenger-db-primary \
  -d \
  -v db-data:/var/lib/postgresql/data/pgdata \
  -e POSTGRES_USER="${POSTGRES_USER}" \
  -e POSTGRES_PASSWORD="${POSTGRES_PASSWORD}" \
  -e PGPORT="${PORT}" \
  -e PGDATA=/var/lib/postgresql/data/pgdata \
  --network mm_2023 \
  postgres:15.1

echo "Register key messenger-db-port\n"
curl -X PUT --silent --output /dev/null --show-error --fail http://localhost:8500/v1/kv/messenger-db-port \
  -H "Content-Type: application/json" \
  -d "${PORT}"

echo "Register key messenger-db-host\n"
curl -X PUT --silent --output /dev/null --show-error --fail http://localhost:8500/v1/kv/messenger-db-host \
  -H "Content-Type: application/json" \
  -d 'messenger-db-primary' # This matches the "--name" flag above
                            # which for our setup means the hostname

echo "Register key messenger-db-application-user\n"
curl -X PUT --silent --output /dev/null --show-error --fail http://localhost:8500/v1/kv/messenger-db-application-user \
  -H "Content-Type: application/json" \
  -d "${POSTGRES_USER}"

echo "Register key messenger-db-password-never-do-this\n"
curl -X PUT --silent --output /dev/null --show-error --fail http://localhost:8500/v1/kv/messenger-db-password-never-do-this \
  -H "Content-Type: application/json" \
  -d "${POSTGRES_PASSWORD}"

printf "\nDone registering postgres details with Consul\n"
  1. Sustituye el contenido de infrastructure/messenger-deploy.sh por lo siguiente para obtener el secreto de contraseña de messenger-database del almacén KV de Consul:
#!/bin/bashset -e

# This configuration requires a new commit to change
NODE_ENV=production
PORT=4000
JSON_BODY_LIMIT=100kb

# Postgres database configuration by pulling information from 
# the system
POSTGRES_USER=$(curl -X GET http://localhost:8500/v1/kv/messenger-db-application-user?raw=true)
PGPORT=$(curl -X GET http://localhost:8500/v1/kv/messenger-db-port?raw=true)
PGHOST=$(curl -X GET http://localhost:8500/v1/kv/messenger-db-host?raw=true)
# NOTE: Never do this in a real-world deployment. Store passwords
# only in an encrypted secrets store.
PGPASSWORD=$(curl -X GET http://localhost:8500/v1/kv/messenger-db-password-never-do-this?raw=true)

# RabbitMQ configuration by pulling from the system
AMQPHOST=$(curl -X GET http://localhost:8500/v1/kv/amqp-host?raw=true)
AMQPPORT=$(curl -X GET http://localhost:8500/v1/kv/amqp-port?raw=true)

docker run \
  --rm \
  -d \
  -e NODE_ENV="${NODE_ENV}" \
  -e PORT="${PORT}" \
  -e JSON_BODY_LIMIT="${JSON_BODY_LIMIT}" \
  -e PGUSER="${POSTGRES_USER}" \
  -e PGPORT="${PGPORT}" \
  -e PGHOST="${PGHOST}" \
  -e PGPASSWORD="${PGPASSWORD}" \
  -e AMQPPORT="${AMQPPORT}" \
  -e AMQPHOST="${AMQPHOST}" \
  --network mm_2023 \
  messenger

Ejecutar los scripts de despliegue

  1. Ve al directorio app del repositorio messenger y crea la imagen de Docker para el servicio messenger:
cd ../appdocker build -t messenger .
  1. Compruebe que solo se estén ejecutando los contenedores que pertenecen al servicio platform:
docker ps --format '{{.Names}}'consul-server
consul-client
rabbitmq
  1. Ve al directorio raíz del repositorio messenger e implementa messenger-database y el servicio messenger:
cd .../infrastructure/messenger-db-deploy.sh ./infrastructure/messenger-deploy.sh
  1. El script messenger-db-deploy.sh inicia messenger-database y registra la información correspondiente en el sistema (que, en este caso, es el almacén de claves y valores de Consul). A continuación, el script messenger-deploy.sh inicia la aplicación y obtiene del sistema la configuración registrada por messenger-db-deploy.sh (de nuevo, el almacén de claves y valores de Consul). Sugerencia: Si un contenedor no se inicia, elimine el segundo parámetro del comando docker run (la línea -d \) del script de despliegue y vuelva a ejecutar el script. El contenedor se iniciará entonces en primer plano, lo que hará que sus registros aparezcan en el terminal y puedan ayudar a identificar el problema. Cuando lo resuelva, restaure la línea -d \ para que el contenedor se ejecute en segundo plano.
  2. Envía una solicitud sencilla de comprobación de estado a la aplicación para verificar que el despliegue se haya realizado correctamente:
curl localhost:4000/healthcurl: (7) Failed to connect to localhost port 4000 after 11 ms: Connection refused
  1. ¡Vaya, ha fallado! Resulta que aún falta un elemento de configuración fundamental y el servicio messenger no está expuesto al resto del sistema. Se está ejecutando sin problemas dentro de la red mm_2023, pero solo se puede acceder a esa red desde Docker.
  2. Detén el contenedor en ejecución para preparar la creación de una nueva imagen en el siguiente reto:
docker rm $(docker stop $(docker ps -a -q --filter ancestor=messenger --format="{{.ID}}"))

Desafío 3: Exponer un servicio al mundo exterior

En un entorno de producción, por lo general no se exponen los servicios directamente. En su lugar, se sigue un patrón habitual de microservicios y se coloca un servicio de proxy inverso delante del servicio principal.

En este reto, expondrás el servicio messenger al exterior mediante la configuración de la detección de servicios, que consiste en registrar la información de los nuevos servicios y actualizarla dinámicamente cuando otros servicios acceden a ella. Para ello, utilizarás las siguientes tecnologías:

  • Consul, un registro dinámico de servicios, y Consul template, una herramienta para actualizar dinámicamente un archivo a partir de datos de Consul
  • NGINX Open Source, como proxy inverso y equilibrador de carga que proporciona un único punto de entrada para tu servicio messenger, que estará compuesto por varias instancias individuales de la aplicación ejecutadas en contenedores

Para obtener más información sobre la detección de servicios, consulte «Cómo hacer que un servicio esté disponible como configuración» en «Prácticas recomendadas para configurar aplicaciones de microservicios» en nuestro blog.

Configurar Consul

El archivo app/consul/index.mjs del repositorio messenger contiene todo el código necesario para registrar el servicio messenger en Consul al iniciarse y anular su registro durante un cierre ordenado. Expone una única función, register, que registra cualquier servicio recién desplegado en el registro de servicios de Consul.

  1. En el editor de texto que prefiera, abra app/index.mjs y añada el siguiente fragmento después de las demás instrucciones import, para importar la función register desde app/consul/index.mjs:
import { register as registerConsul } from "./consul/index.mjs";
  1. A continuación, modifica la sección SERVER START situada al final del script, tal como se muestra, para llamar a registerConsul() después de que se haya iniciado la aplicación:
/* =================  SERVER START
================== */
app.listen(port, async () => {
  console.log(`messenger_service listening on port ${port}`);
  registerConsul();
});

export default app;
  1. Abre el esquema de convict en app/config/config.mjs y añade los siguientes valores de configuración después de la clave jsonBodyLimit que añadiste en el paso 1 del Ejemplo 2.
  consulServiceName: {    doc: "The name by which the service is registered in Consul. If not specified, the service is not registered",
    format: "*",
    default: null,
    env: "CONSUL_SERVICE_NAME",
  },
  consulHost: {
    doc: "The host where the Consul client runs",
    format: String,
    default: "consul-client",
    env: "CONSUL_HOST",
  },
  consulPort: {
    doc: "The port for the Consul client",
    format: "port",
    default: 8500,
    env: "CONSUL_PORT",
  },
  1. Esto configura el nombre con el que se registra un nuevo servicio y define el nombre de host y el puerto del cliente de Consul. En el siguiente paso, modificará el script de despliegue del servicio messenger para incluir esta nueva información de conexión a Consul y de registro del servicio.
  2. Abre infrastructure/messenger-deploy.sh y sustituye su contenido por lo siguiente para incluir en la configuración del servicio messenger la información de conexión y registro del servicio de Consul que configuraste en el paso anterior:
#!/bin/bashset -e

# This configuration requires a new commit to change
NODE_ENV=production
PORT=4000
JSON_BODY_LIMIT=100kb

CONSUL_SERVICE_NAME="messenger"

# Consul host and port are included in each host since we
# cannot query Consul until we know them
CONSUL_HOST="${CONSUL_HOST}"
CONSUL_PORT="${CONSUL_PORT}"

# Postgres database configuration by pulling information from 
# the system
POSTGRES_USER=$(curl -X GET "http://localhost:8500/v1/kv/messenger-db-application-user?raw=true")
PGPORT=$(curl -X GET "http://localhost:8500/v1/kv/messenger-db-port?raw=true")
PGHOST=$(curl -X GET "http://localhost:8500/v1/kv/messenger-db-host?raw=true")
# NOTE: Never do this in a real-world deployment. Store passwords
# only in an encrypted secrets store.
PGPASSWORD=$(curl -X GET "http://localhost:8500/v1/kv/messenger-db-password-never-do-this?raw=true")

# RabbitMQ configuration by pulling from the system
AMQPHOST=$(curl -X GET "http://localhost:8500/v1/kv/amqp-host?raw=true")
AMQPPORT=$(curl -X GET "http://localhost:8500/v1/kv/amqp-port?raw=true")

docker run \
  --rm \
  -d \
  -e NODE_ENV="${NODE_ENV}" \
  -e PORT="${PORT}" \
  -e JSON_BODY_LIMIT="${JSON_BODY_LIMIT}" \
  -e PGUSER="${POSTGRES_USER}" \
  -e PGPORT="${PGPORT}" \
  -e PGHOST="${PGHOST}" \
  -e PGPASSWORD="${PGPASSWORD}" \
  -e AMQPPORT="${AMQPPORT}" \
  -e AMQPHOST="${AMQPHOST}" \
  -e CONSUL_HOST="${CONSUL_HOST}" \
  -e CONSUL_PORT="${CONSUL_PORT}" \
  -e CONSUL_SERVICE_NAME="${CONSUL_SERVICE_NAME}" \
  --network mm_2023 \
  messenger
  1. Los aspectos principales que hay que tener en cuenta son: Nota: En un despliegue real, este es un ejemplo de configuración que debe acordarse entre los equipos: el equipo responsable de Consul debe proporcionar las variables de entorno CONSUL_HOST y CONSUL_PORT en todos los entornos, ya que un servicio no puede consultar Consul sin esta información de conexión.
    • La variable de entorno «CONSUL_SERVICE_NAME» indica a la instancia del servicio messenger qué nombre debe usar al registrarse en Consul.
    • Las variables de entorno CONSUL_HOST y CONSUL_PORT se utilizan para el cliente de Consul que se ejecuta en la ubicación donde se ejecuta el script de despliegue.
  2. En el terminal de la aplicación, cambia al directorio app, detén todas las instancias en ejecución del servicio messenger y vuelve a compilar la imagen de Docker para incorporar el nuevo código de registro del servicio:
cd appdocker rm $(docker stop $(docker ps -a -q --filter ancestor=messenger --format="{{.ID}}")) docker build -t messenger .
  1. Accede a http://localhost:8500 desde un navegador para ver la interfaz de usuario de Consul en funcionamiento (aunque todavía no ocurre nada interesante).
  2. En la raíz del repositorio messenger, ejecuta el script de despliegue para iniciar una instancia del servicio messenger:
CONSUL_HOST=consul-client CONSUL_PORT=8500 ./infrastructure/messenger-deploy.sh
  1. En la interfaz de usuario de Consul del navegador, haz clic en Servicios en la barra de encabezado para comprobar que se está ejecutando un único servicio messenger.
    Pestaña Servicios de la interfaz de usuario de Consul con una instancia de cada uno de los servicios consul y messenger
  2. Ejecuta el script de despliegue unas cuantas veces más para iniciar más instancias del servicio messenger. Comprueba en la interfaz de usuario de Consul que estén en ejecución.
CONSUL_HOST=consul-client CONSUL_PORT=8500 ./infrastructure/messenger-deploy.sh
  1. Pestaña «messenger» de la interfaz de usuario de Consul con cinco instancias del servicio

Configurar NGINX

El siguiente paso es añadir NGINX Open Source como proxy inverso y equilibrador de carga para dirigir el tráfico entrante a todas las instancias de messenger que estén en ejecución.

  1. En el terminal de la aplicación, cambie al directorio raíz del repositorio messenger y cree un directorio llamado load-balancer y tres archivos:
mkdir load-balancercd load-balancer touch nginx.ctmpl touch consul-template-config.hcl touch Dockerfile
  1. El archivo Dockerfile define el contenedor en el que se ejecutan NGINX y Consul Template. Consul Template utiliza los otros dos archivos para actualizar dinámicamente los servidores upstream de NGINX cuando cambia el servicio messenger (cuando se inician o se detienen instancias del servicio) en su registro de servicios.
  2. Abra el archivo nginx.ctmpl creado en el paso 1 y añada el siguiente fragmento de configuración de NGINX, que Consul Template utiliza para actualizar dinámicamente el grupo de servidores upstream de NGINX:
upstream messenger_service {    {{- range service "messenger" }}
    server {{ .Address }}:{{ .Port }};
    {{- end }}
}

server {
    listen 8085;
    server_name localhost;

    location / {
        proxy_pass http://messenger_service;
        add_header Upstream-Host $upstream_addr;
    }
}
  1. Este fragmento añade la dirección IP y el número de puerto de cada instancia del servicio messenger registrada en Consul al grupo de servidores ascendentes messenger_service de NGINX. NGINX redirige las solicitudes entrantes al conjunto de instancias de servicio ascendentes definido dinámicamente.
  2. Abra el archivo consul-template-config.hcl creado en el paso 1 y añada la siguiente configuración:
consul {  address = "consul-client:8500"

  retry {
    enabled  = true
    attempts = 12
    backoff  = "250ms"
  }
}
template {
  source      = "/usr/templates/nginx.ctmpl"
  destination = "/etc/nginx/conf.d/default.conf"
  perms       = 0600
  command     = "if [ -e /var/run/nginx.pid ]; then nginx -s reload; else nginx; fi"
}
  1. Esta configuración de Consul Template le indica que vuelva a generar la plantilla source (el fragmento de configuración de NGINX creado en el paso anterior), que la guarde en el destination especificado y, por último, que ejecute el command indicado (que ordena a NGINX recargar su configuración). En la práctica, esto significa que se crea un nuevo archivo default.conf cada vez que se registra, actualiza o elimina del registro una instancia de servicio en Consul. A continuación, NGINX recarga su configuración sin interrumpir el servicio, lo que garantiza que disponga de un conjunto actualizado y en buen estado de servidores (instancias del servicio messenger) a los que pueda enviar tráfico.
  2. Abra el archivo Dockerfile creado en el paso 1 y añada el siguiente contenido, que compila el servicio NGINX. (No es necesario que entienda el archivo Dockerfile para seguir este tutorial, pero el código incluye comentarios explicativos para su comodidad).
FROM nginx:1.23.1
ARG CONSUL_TEMPLATE_VERSION=0.30.0

# Set an environment variable for the location of the Consul
# cluster. By default, it tries to resolve to consul-client:8500
# which is the behavior if Consul is running as a container in the 
# same host and linked to this NGINX container (with the alias 
# consul, of course). But this environment variable can also be
# overridden as the container starts if we want to resolve to
# another address.

ENV CONSUL_URL consul-client:8500

# Download the specified version of Consul template
ADD https://releases.hashicorp.com/consul-template/${CONSUL_TEMPLATE_VERSION}/consul-template_${CONSUL_TEMPLATE_VERSION}_linux_amd64.zip /tmp

RUN apt-get update \
  && apt-get install -y --no-install-recommends dumb-init unzip \
  && unzip /tmp/consul-template_${CONSUL_TEMPLATE_VERSION}_linux_amd64.zip -d /usr/local/bin \
  && rm -rf /tmp/consul-template_${CONSUL_TEMPLATE_VERSION}_linux_amd64.zip

COPY consul-template-config.hcl ./consul-template-config.hcl
COPY nginx.ctmpl /usr/templates/nginx.ctmpl

EXPOSE 8085

STOPSIGNAL SIGQUIT

CMD ["dumb-init", "consul-template", "-config=consul-template-config.hcl"]
  1. Crea una imagen de Docker:
docker build -t messenger-lb .
  1. Ve al directorio raíz de messenger, crea un archivo llamado messenger-load-balancer-deploy.sh como archivo de despliegue para el servicio NGINX (al igual que con el resto de los servicios que has desplegado a lo largo del tutorial). Dependiendo de tu entorno, puede que tengas que anteponer chmod al comando sudo:
cd .. touch infrastructure/messenger-load-balancer-deploy.sh chmod +x infrastructure/messenger-load-balancer-deploy.sh
  1. Abre «messenger-load-balancer-deploy.sh» y añade el siguiente contenido:
#!/bin/bashset -e

# Consul host and port are included in each host since we
# cannot query Consul until we know them
CONSUL_HOST="${CONSUL_HOST}"
CONSUL_PORT="${CONSUL_PORT}"

docker run \
  --rm \
  -d \
  --name messenger-lb \
  -e CONSUL_URL="${CONSUL_HOST}:${CONSUL_PORT}"  \
  -p 8085:8085 \
  --network mm_2023 \
  messenger-lb
  1. Ahora que ya lo tienes todo listo, despliega el servicio NGINX:
CONSUL_HOST=consul-client CONSUL_PORT=8500 ./infrastructure/messenger-load-balancer-deploy.sh
  1. Comprueba si puedes acceder al servicio messenger desde el exterior:
curl -X GET http://localhost:8085/health
OK
  1. ¡Funciona! Ahora NGINX distribuye la carga entre todas las instancias creadas del servicio messenger. Puedes comprobarlo porque el encabezado X-Forwarded-For muestra las mismas direcciones IP del servicio messenger que aparecen en la interfaz de usuario de Consul en el paso 8 de la sección anterior.

Desafío 4: Migrar una base de datos usando un servicio como ejecutor de tareas

Las aplicaciones grandes suelen utilizar «ejecutores de tareas» con pequeños procesos de trabajo que permiten realizar tareas puntuales, como modificar datos (algunos ejemplos son Sidekiq y Celery). Estas herramientas suelen requerir infraestructura adicional, como Redis o RabbitMQ. En este caso, se utiliza el propio servicio messenger como «ejecutor de tareas» para realizar tareas puntuales. Esto tiene sentido porque ya es muy pequeño, puede interactuar perfectamente con la base de datos y con los demás componentes de infraestructura de los que depende, y se ejecuta de forma totalmente independiente de la aplicación que atiende el tráfico.

Hacerlo así ofrece tres ventajas:

  1. El ejecutor de tareas (incluidos los scripts que ejecuta) se somete exactamente a las mismas comprobaciones y al mismo proceso de revisión que el servicio de producción.
  2. Los valores de configuración, como los usuarios de la base de datos, se pueden cambiar fácilmente para aumentar la seguridad del despliegue en producción. Por ejemplo, puede ejecutar el servicio de producción con un usuario de «privilegios reducidos» que solo pueda escribir y realizar consultas en las tablas existentes. Puede configurar otra instancia del servicio para que modifique la estructura de la base de datos con un usuario con privilegios más elevados que pueda crear y eliminar tablas.
  3. Algunos equipos ejecutan tareas desde instancias que también gestionan el tráfico de producción del servicio. Esto es peligroso porque los problemas con la tarea pueden afectar a las demás funciones que realiza la aplicación del contenedor. Evitar situaciones como esta es precisamente el motivo por el que usamos microservicios, ¿no?

En este reto, explorarás cómo se puede modificar un artefacto para que desempeñe una nueva función cambiando algunos valores de configuración de la base de datos y migrando la messenger base de datos para que utilice los nuevos valores y probando su rendimiento.

Migrar la base de datos messenger

Para un despliegue de producción real, podrías crear dos usuarios distintos con permisos diferentes: un «usuario de la aplicación» y un «usuario de migración». Para simplificar, en este ejemplo se utiliza el usuario predeterminado como usuario de la aplicación y se crea un usuario de migración con privilegios de superusuario. En una situación real, conviene dedicar más tiempo a determinar qué permisos mínimos específicos necesita cada usuario en función de su rol.

  1. En el terminal de la aplicación, crea un nuevo usuario de PostgreSQL con privilegios de superusuario:
echo "CREATE USER messenger_migrator WITH SUPERUSER PASSWORD 'migrator_password';" | docker exec -i messenger-db-primary psql -U postgres
  1. Abre el script de despliegue de la base de datos (infrastructure/messenger-db-deploy.sh) y sustituye su contenido para añadir las credenciales del nuevo usuario. Nota: Conviene insistir en que, en un despliegue real, NUNCA se deben incluir secretos, como las credenciales de la base de datos, en un script de despliegue ni en ningún otro lugar que no sea una herramienta de gestión de secretos. Para obtener más información, consulta Unidad 2: Introducción a la gestión de secretos en microservicios de Microservices March 2023.
#!/bin/bash
set -e

PORT=5432
POSTGRES_USER=postgres
# NOTE: Never do this in a real-world deployment. Store passwords
# only in an encrypted secrets store.
# Because we’re focusing on other concepts in this tutorial, we
# set the password this way here for convenience.
POSTGRES_PASSWORD=postgres

# Migration user
POSTGRES_MIGRATOR_USER=messenger_migrator
# NOTE: As above, never do this in a real deployment.
POSTGRES_MIGRATOR_PASSWORD=migrator_password

docker run \
  --rm \
  --name messenger-db-primary \
  -d \
  -v db-data:/var/lib/postgresql/data/pgdata \
  -e POSTGRES_USER="${POSTGRES_USER}" \
  -e POSTGRES_PASSWORD="${POSTGRES_PASSWORD}" \
  -e PGPORT="${PORT}" \
  -e PGDATA=/var/lib/postgresql/data/pgdata \
  --network mm_2023 \
  postgres:15.1

echo "Register key messenger-db-port\n"
curl -X PUT --silent --output /dev/null --show-error --fail http://localhost:8500/v1/kv/messenger-db-port \
  -H "Content-Type: application/json" \
  -d "${PORT}"

echo "Register key messenger-db-host\n"
curl -X PUT --silent --output /dev/null --show-error --fail http://localhost:8500/v1/kv/messenger-db-host \
  -H "Content-Type: application/json" \
  -d 'messenger-db-primary' # This matches the "--name" flag above
                            # which for our setup means the hostname

echo "Register key messenger-db-application-user\n"
curl -X PUT --silent --output /dev/null --show-error --fail http://localhost:8500/v1/kv/messenger-db-application-user \
  -H "Content-Type: application/json" \
  -d "${POSTGRES_USER}"

curl -X PUT --silent --output /dev/null --show-error --fail http://localhost:8500/v1/kv/messenger-db-password-never-do-this \
  -H "Content-Type: application/json" \
  -d "${POSTGRES_PASSWORD}"

echo "Register key messenger-db-application-user\n"
curl -X PUT --silent --output /dev/null --show-error --fail http://localhost:8500/v1/kv/messenger-db-migrator-user \
  -H "Content-Type: application/json" \
  -d "${POSTGRES_MIGRATOR_USER}"

curl -X PUT --silent --output /dev/null --show-error --fail http://localhost:8500/v1/kv/messenger-db-migrator-password-never-do-this \
  -H "Content-Type: application/json" \
  -d "${POSTGRES_MIGRATOR_PASSWORD}"

printf "\nDone registering postgres details with Consul\n"
  1. Este cambio simplemente añade el usuario de migración al conjunto de usuarios que se configura en Consul después de desplegar la base de datos.
  2. Crea un archivo nuevo en el directorio infrastructure llamado messenger-db-migrator-deploy.sh (de nuevo, puede que tengas que anteponer sudo al comando chmod):
touch infrastructure/messenger-db-migrator-deploy.shchmod +x infrastructure/messenger-db-migrator-deploy.sh
  1. Abre messenger-db-migrator-deploy.sh y añade lo siguiente:
#!/bin/bashset -e

# This configuration requires a new commit to change
NODE_ENV=production
PORT=4000
JSON_BODY_LIMIT=100kb

CONSUL_SERVICE_NAME="messenger-migrator"

# Consul host and port are included in each host since we
# cannot query Consul until we know them
CONSUL_HOST="${CONSUL_HOST}"
CONSUL_PORT="${CONSUL_PORT}"

# Get the migrator user name and password
POSTGRES_USER=$(curl -X GET "http://localhost:8500/v1/kv/messenger-db-migrator-user?raw=true")
PGPORT=$(curl -X GET "http://localhost:8500/v1/kv/messenger-db-port?raw=true")
PGHOST=$(curl -X GET http://localhost:8500/v1/kv/messenger-db-host?raw=true)
# NOTE: Never do this in a real-world deployment. Store passwords
# only in an encrypted secrets store.
PGPASSWORD=$(curl -X GET "http://localhost:8500/v1/kv/messenger-db-migrator-password-never-do-this?raw=true")

# RabbitMQ configuration by pulling from the system
AMQPHOST=$(curl -X GET "http://localhost:8500/v1/kv/amqp-host?raw=true")
AMQPPORT=$(curl -X GET "http://localhost:8500/v1/kv/amqp-port?raw=true")

docker run \--rm \
  -d \
  --name messenger-migrator \
  -e NODE_ENV="${NODE_ENV}" \
  -e PORT="${PORT}" \
  -e JSON_BODY_LIMIT="${JSON_BODY_LIMIT}" \
  -e PGUSER="${POSTGRES_USER}" \
  -e PGPORT="${PGPORT}" \
  -e PGHOST="${PGHOST}" \
  -e PGPASSWORD="${PGPASSWORD}" \
  -e AMQPPORT="${AMQPPORT}" \
  -e AMQPHOST="${AMQPHOST}" \
  -e CONSUL_HOST="${CONSUL_HOST}" \
  -e CONSUL_PORT="${CONSUL_PORT}" \
  -e CONSUL_SERVICE_NAME="${CONSUL_SERVICE_NAME}" \
  --network mm_2023 \
  messenger
  1. Este script es bastante similar al script infrastructure/messenger-deploy.sh en su versión final, que creó en el Paso 3 de Configurar Consul. La principal diferencia es que CONSUL_SERVICE_NAME es messenger-migrator en lugar de messenger, y PGUSER corresponde al superusuario «migrator» que creó en el Paso 1 anterior. Es importante que CONSUL_SERVICE_NAME sea messenger-migrator. Si se estableciera en messenger, NGINX incluiría automáticamente este servicio en la rotación para recibir llamadas a la API, pero no está diseñado para gestionar tráfico. El script despliega una instancia de corta duración que desempeña la función de migrador. Esto evita que cualquier problema con la migración afecte al tráfico gestionado por las instancias principales del servicio messenger.
  2. Vuelve a desplegar la base de datos PostgreSQL. Como en este tutorial utilizas scripts de bash, debes detener y reiniciar el servicio de base de datos. En una aplicación de producción, normalmente basta con ejecutar un script de infraestructura como código para añadir únicamente los elementos que hayan cambiado.
docker stop messenger-db-primaryCONSUL_HOST=consul-client CONSUL_PORT=8500 ./infrastructure/messenger-db-deploy.sh
  1. Despliega el servicio de migración de la base de datos PostgreSQL:
CONSUL_HOST=consul-client CONSUL_PORT=8500 ./infrastructure/messenger-db-migrator-deploy.sh
  1. Compruebe que la instancia se esté ejecutando según lo previsto:
docker ps --format "{{.Names}}"
...
messenger-migrator
  1. También puedes comprobar en la interfaz de usuario de Consul que el servicio de migración de la base de datos se ha registrado correctamente en Consul como messenger-migrator (de nuevo, no se registra con el nombre messenger porque no gestiona tráfico):
    Pestaña Services de la interfaz de Consul con una instancia de cada uno de los servicios consul y messenger-migrator, y cuatro instancias del servicio messenger
  2. Ahora, el último paso: ¡ejecuta los scripts de migración de la base de datos! Estos scripts no se parecen a scripts reales de migración de bases de datos, pero sí utilizan el servicio messenger-migrator para ejecutar scripts específicos de la base de datos. Una vez migrada la base de datos, detén el servicio messenger-migrator:
docker exec -i -e PGDATABASE=postgres -e CREATE_DB_NAME=messenger messenger-migrator node scripts/create-db.mjsdocker exec -i messenger-migrator node scripts/create-schema.mjs docker exec -i messenger-migrator node scripts/create-seed-data.mjs docker stop messenger-migrator

Prueba el servicio messenger en acción

Ahora que ha migrado la base de datos de messenger a su formato definitivo, el servicio messenger ya está listo para que pueda verlo en acción. Para ello, ejecute algunas consultas básicas con curl en el servicio NGINX (configuró NGINX como punto de entrada del sistema en Configurar NGINX).

Algunos de los siguientes comandos utilizan la biblioteca jq library para dar formato a la salida JSON. Puede instalarla si es necesario u omitirla de la línea de comandos si lo desea.

  1. Crea una conversación:
curl -d '{"participant_ids": [1, 2]}' -H "Content-Type: application/json" -X POST 'http://localhost:8085/conversations'{
  "conversation": { "id": "1", "inserted_at": "YYYY-MM-DDT06:41:59.000Z" }
}
  1. Envía un mensaje a la conversación desde un usuario con ID 1:
curl -d '{"content": "This is the first message"}' -H "User-Id: 1" -H "Content-Type: application/json" -X POST 'http://localhost:8085/conversations/1/messages' | jq{
  "message": {
    "id": "1",
    "content": "This is the first message",
    "index": 1,
    "user_id": 1,
    "username": "James Blanderphone",
    "conversation_id": 1,
    "inserted_at": "YYYY-MM-DDT06:42:15.000Z"
  }
}
  1. Responde con un mensaje de otro usuario (con ID 2):
curl -d '{"content": "This is the second message"}' -H "User-Id: 2" -H "Content-Type: application/json" -X POST 'http://localhost:8085/conversations/1/messages' | jq{
  "message": {
    "id": "2",
    "content": "This is the second message",
    "index": 2,
    "user_id": 2,
    "username": "Normalavian Ropetoter",
    "conversation_id": 1,
    "inserted_at": "YYYY-MM-DDT06:42:25.000Z"
  }
}
  1. Obtén los mensajes:
curl -X GET 'http://localhost:8085/conversations/1/messages' | jq{
  "messages": [
    {
      "id": "1",
      "content": "This is the first message",
      "user_id": "1",
      "channel_id": "1",
      "index": "1",
      "inserted_at": "YYYY-MM-DDT06:42:15.000Z",
      "username": "James Blanderphone"
    },
    {
      "id": "2",
      "content": "This is the second message",
      "user_id": "2",
      "channel_id": "1",
      "index": "2",
      "inserted_at": "YYYY-MM-DDT06:42:25.000Z",
      "username": "Normalavian Ropetoter"
    }
  ]
}

Limpieza

¡A lo largo de este tutorial, has creado una cantidad considerable de contenedores e imágenes! Utiliza los siguientes comandos para eliminar los contenedores y las imágenes de Docker que no quieras conservar.

  • Para eliminar cualquier contenedor de Docker que esté en ejecución:
docker rm $(docker stop $(docker ps -a -q --filter ancestor=messenger --format="{{.ID}}"))docker rm $(docker stop messenger-db-primary) docker rm $(docker stop messenger-lb)
  • Para eliminar los servicios de plataforma:
# From the platform repositorydocker compose down
  • Para eliminar todas las imágenes de Docker utilizadas a lo largo del tutorial:
docker rmi messengerdocker rmi messenger-lb docker rmi postgres:15.1 docker rmi hashicorp/consul:1.14.4 docker rmi rabbitmq:3.11.4-management-alpine

Próximos pasos

Puede que estés pensando: «Parece mucho trabajo para configurar algo sencillo», y ¡tendrías razón! Adoptar una arquitectura centrada en microservicios exige ser meticuloso a la hora de estructurar y configurar los servicios. A pesar de toda la complejidad, has avanzado considerablemente:

  • Has configurado un entorno orientado a microservicios que otros equipos pueden entender fácilmente.
  • Configuras el sistema de microservicios para que sea relativamente flexible tanto en términos de escalabilidad como de uso de los distintos servicios implicados.

Para seguir aprendiendo sobre microservicios, consulta Microservices March 2023. Unidad 2: Introducción a la gestión de secretos en microservicios ofrece una visión detallada y accesible de la gestión de secretos en entornos de microservicios.


Compartir

Artículos relacionados del blog

Únete a F5 NGINX en AppWorld 2024
NGINX | 04/01/2024

Únete a F5 NGINX en AppWorld 2024

Descubre las numerosas razones para asistir al evento insignia de F5, que incluirá un emocionante anuncio sobre la cartera de productos NGINX.

Rotación de certificados SSL/TLS sin reinicios en NGINX Open Source
NGINX | 26/09/2023

Rotación de certificados SSL/TLS sin reinicios en NGINX Open Source

La función de diccionario compartido permite configurar NGINX sin necesidad de reiniciarlo al renovar los certificados SSL/TLS.

Exposición de APIs en Kubernetes
NGINX | 10/03/2022

Exposición de APIs en Kubernetes

La unidad 2 de Microservices March 2022 se centra en cómo exponer tus API en Kubernetes. Aquí tienes enlaces a los recursos que necesitas: la retransmisión en directo del 14 de marzo, artículos de blog y otros recursos de preparación, y el laboratorio práctico «Protege las API de Kubernetes mediante la limitación de solicitudes».

NGINX Sprint 2.0: visión clara, código renovado y nuevos compromisos con el código abierto
NGINX | 23/08/2021

NGINX Sprint 2.0: visión clara, código renovado y nuevos compromisos con el código abierto

Hoy, en NGINX Sprint 2.0, anunciamos cómo respaldaremos nuestra visión del futuro de las aplicaciones modernas con una mayor presencia en la comunidad de código abierto, incluida la participación en el proyecto comunitario Kubernetes, nuevos proyectos de software de código abierto y la arquitectura de referencia para aplicaciones modernas.

Presentamos NGINX Plus R22
NGINX | 09/06/2020

Presentamos NGINX Plus R22

NGINX Plus R22 incorpora nuevas funcionalidades, como la compatibilidad con OCSP para verificar la revocación de certificados en TLS mutuo, el uso de varios proveedores de identidad OIDC en una configuración, el seguimiento en tiempo real de la limitación de solicitudes y conexiones en el panel de NGINX Plus y mejoras en NGINX JavaScript.

Presentamos NGINX Plus R17
NGINX | 11/12/2018

Presentamos NGINX Plus R17

NGINX Plus R17 incorpora compatibilidad con la limitación de velocidad en dos fases y con TLS 1.3, la versión más reciente del protocolo Transport Layer Security. La configuración de OpenID Connect es más sencilla y NGINX ModSecurity WAF es el doble de rápido que antes. También se ha actualizado el módulo JavaScript de NGINX.