Si eres nuevo en docker o en la dockerización de aplicaciones NodeJS, echa un vistazo a estos posts.

Al final de este post, tendremos una comprensión bastante buena de Docker Compose. También tendremos una aplicación Docker Compose NodeJS con integración de Redis.

Así que vamos a empezar con el plan anterior y a recorrerlo paso a paso. Es importante que tengas presente que este es un tutorial detallado por lo que requiere de una lectura un poco más extensa a la habitual.

via GIPHY

Necesidad de Docker Compose


Docker Compose entra en escena tan pronto como varios contenedores Docker están involucrados.

En otras palabras, si nuestra aplicación se vuelve lo suficientemente compleja como para justificar un enfoque multicontenedor, Docker Compose se vuelve importante para gestionar el mismo.

Pero, ¿por qué necesitamos múltiples contenedores?

Consideremos una situación en la que estamos construyendo una aplicación NodeJS que tiene que interactuar con Redis por alguna razón. En este caso podemos seguir el siguiente enfoque utilizando un único contenedor Docker.

Si ves arriba, nuestro único contenedor docker está ejecutando tanto la aplicación Node como Redis.

Hemos construido una imagen que tiene tanto NodeJS como Redis. Luego, hemos utilizado esa imagen para crear un contenedor docker.

Sin embargo, si nuestra humilde aplicación se hace popular con el tiempo, este único contenedor, veremos la necesidad de escalar nuestra aplicación. En otras palabras, nos gustaría aumentar el número de contenedores. La siguiente ilustración muestra esta situación.

Como puedes ver, ahora estamos ejecutando múltiples contenedores docker usando la imagen. Sin embargo, tan pronto como esto sucede, nos encontramos con problemas porque ahora nuestros datos en Redis ya no son consistentes a través de estas diferentes instancias. Cada contenedor es su propio mundo aislado.

Un mejor enfoque para el diseño de una aplicación de este tipo sería el siguiente:

Si ves arriba, tenemos un contenedor sólo para la aplicación NodeJS. Y el segundo contenedor docker es para ejecutar Redis. En este caso, también podemos aumentar el número de contenedores docker que ejecutan la aplicación NodeJS sin tener problemas de datos.

Y aquí es donde Docker Compose nos ayuda en la definición y ejecución de múltiples contenedores que pertenecen a la misma aplicación.

Creación de una aplicación NodeJS y Dockerización de la misma mediante Dockerfile


Para demostrar el uso de Docker Compose, vamos a empezar a crear una pequeña aplicación usando NodeJS y Redis.

El trabajo de esta pequeña aplicación es llevar un registro del número de visitas. Cada vez que alguien visita la aplicación, incrementa el recuento de visitas.

Vamos a utilizar el siguiente enfoque para hacer esta aplicación.

Un contenedor Docker alojará nuestra aplicación NodeJS. El otro contenedor tendrá una copia de Redis.

PASO 1 - Definir las dependencias


Para hacer nuestra aplicación NodeJS, necesitamos armar un archivo package.json. Este archivo lista todas las dependencias que nuestra aplicación necesita para funcionar correctamente.

Crearemos una carpeta de proyecto llamada docker-compose-nodejs-redis Y dentro de esa carpeta crearemos el archivo package.json como se indica a continuación.

{
    "dependencies": {
        "express": "*",
        "redis": "2.8.0" 
    },
    "scripts": {
        "start": "node index.js"
    }
}

Aquí especificamos que necesitamos express para nuestra aplicación. Cualquier versión estaría bien para nuestras necesidades. Además, especificamos redis como la segunda dependencia. Por último, también definimos un script de inicio para arrancar nuestra aplicación.

PASO 2 - Escribir la lógica de la aplicación

La lógica general de nuestra aplicación es muy simple. A continuación se muestra lo que tenemos que añadir en el archivo index.js.

const express = require('express')
const redis = require('redis')

const app = express()
const client = redis.createClient()

//Set initial visits
client.set('visits', 0);

//defining the root endpoint
app.get('/', (req, res) => {
    client.get('visits', (err, visits) => {
        res.send('Number of visits is: ' + visits + 1)
        client.set('visits', parseInt(visits) + 1)
    })
})

//specifying the listening port
app.listen(8081, ()=>{
    console.log('Listening on port 8081')
})

Vamos a recorrer lo que estamos haciendo en esta aplicación.

via GIPHY

Creamos una aplicación express. Además, creamos un cliente para conectarse a Redis.

  1. Aquí, hemos dejado intencionalmente los argumentos de la función createClient()en blanco. Vamos a actualizar los mismos más adelante en este post.
  2. Después de inicializar el cliente, utilizamos para establecer el contador de visitas iniciales a 0.
  3. A continuación, creamos un manejador de rutas exprés. Cada vez que alguien accede a nuestra aplicación, obtenemos el contador de visitas de Redis usando el cliente. Enviamos la respuesta a la persona que llama y también actualizamos el contador de visitas en 1.
  4. Por último, configuramos la aplicación para que empiece a escuchar en el puerto 8081.

Básicamente, eso es todo lo que necesitamos en nuestra aplicación.

PASO 3 - Creación del Dockerfile para la aplicación NodeJs


Ahora podemos crear un Dockerfile para nuestra aplicación NodeJS. En la carpeta del proyecto, crea un archivo llamado Dockerfile y añade el siguiente contenido al mismo.

#Specify a base image
FROM node:alpine

#Specify a working directory
WORKDIR /usr/app

#Copy the dependencies file
COPY ./package.json ./

#Install dependencies
RUN npm install 

#Copy remaining files
COPY ./ ./

#Default command
CMD ["npm","start"]

En pocas palabras, estamos utilizando una imagen base y luego copiar nuestros artefactos del proyecto a esa imagen. Por último, adjuntamos un comando de ejecución que se utilizará para iniciar el contenedor.

A continuación podemos construir la imagen Docker utilizando el comando docker build.

docker build .

Una vez creada la imagen, podemos crear un contenedor en funcionamiento con el id de la imagen.

docker run <image-id>

En este punto, recibiremos el siguiente error.

Nuestra aplicación Docker NodeJS no es capaz de encontrar la instancia de Redis. Por defecto, nuestra aplicación intenta acceder a Redis en 127.0.0.1:6379

Sin embargo, no tenemos Redis actualmente en ejecución. Sin embargo, tenemos una aplicación Node dockerizada con éxito.

Vamos a arreglar el problema con Redis en las próximas secciones.

Crear un archivo Docker Compose YAML


Antes de crear un archivo Docker Compose YAML real vamos a ver un caso sencillo. Ejecutaremos Redis en un contenedor separado en nuestra máquina usando el siguiente comando.

docker run redis

Aparecerá un servidor Redis en nuestra máquina. Podremos ver algo como lo siguiente en los registros.

El mensaje 'Ready to accept connections' significa que nuestro servidor Redis está listo para ser conectado.

Ahora, podemos volver a iniciar nuestro contenedor NodeJS Docker usando el comando docker run. Sin embargo, incluso ahora, obtenemos el mismo error diciendo 'Error: Redis Connection to 127.0.0.1:6379 failed'.

Pero, ¿por qué ha ocurrido esto?

¿No debería nuestro contenedor Docker NodeJS lograr conectarse con éxito al contenedor Redis?

La respuesta es que no. Por defecto, cuando tienes dos contenedores corriendo en tu máquina local, no tienen ninguna conexión directa entre ellos. En esencia, ambos contenedores son una especie de procesos aislados que se ejecutan en su mundo.

Y aquí es donde Docker Compose viene en nuestra ayuda. En otras palabras, Docker Compose nos facilita la conexión de múltiples contenedores Docker sin obligarnos a escribir complicados comandos usando la CLI de Docker.

Con esto, vamos a armar nuestro archivo YAML de Docker Compose. Para ello, necesitamos crear un archivo llamado docker-compose.yml en la raíz de nuestro proyecto. Una vez creado el archivo, podemos añadir la siguiente configuración al mismo.

version: '3'
services:
  redis-server: 
    image: 'redis'
  node-app:
    build: .
    ports:
      - "4001:8081"

Entendiendo el archivo YAML de Docker Compose


Vamos a entender lo que estamos haciendo en este archivo.

  • La etiqueta versión define la versión de Docker Compose que queremos utilizar.
  • La sección de servicios se puede utilizar para definir los diversos servicios de Docker Compose que queremos que formen parte de esta construcción de Docker Compose. Puedes pensar en el servicio como un contenedor Docker que queremos crear.
  • En este caso, el primer servicio que declaramos es el redis-server. Además, especificamos que para crear un contenedor para este servicio, se debe utilizar la imagen de redis de Docker Hub.
  • El segundo servicio es el node-app. Para esto, especificamos la instrucción de construcción diciendo que necesitamos construir usando el Dockerfile en el directorio raíz. Además, especificamos un mapeo de puertos usando la etiqueta ports. Aquí, 4001 es el puerto de nuestra máquina local. Y 8081 es el puerto del contenedor Docker.

Y con esto, nuestro archivo Docker Compose YAML está hecho.

PASO 4 - Configurar la red de contenedores con Docker Compose

La mayor necesidad de Docker Compose es que los múltiples contenedores se conecten entre sí sin problemas. Sin embargo, en nuestro archivo YAML de Docker Compose, no hemos especificado ningún parámetro de red para conectar la aplicación Node a Redis.

Cuando usamos Docker Compose para crear múltiples contenedores, nuestros contenedores se crean en la misma red. En otras palabras, sólo con el uso de Docker Compose, nos aseguramos de que nuestros contenedores pueden hablar entre sí sin problemas.

El único cambio que necesitamos realizar es en nuestro archivo index.js donde creamos un cliente Redis.

const app = express()
const client = redis.createClient({
    host: 'redis-server',
    port: 6379
})

Cuando creamos el cliente, necesitamos pasar parámetros como el host y el puerto de nuestro servidor Redis. Normalmente, el host sería una URL de algún tipo. Sin embargo, cuando usamos Docker Compose para crear nuestros contenedores, podemos especificar el nombre del host por su nombre de servicio Docker Compose.

En este caso, nombramos nuestro servicio Redis como redis-server en el archivo docker-compose.yml Utilizamos el mismo nombre, es decir, redis-servidor para crear un cliente en el index.js.

PASO 5 - Iniciar contenedores con el comando Docker Compose Up

Ahora estamos listos para iniciar nuestros contenedores utilizando los comandos de Docker Compose.

Para iniciar los contenedores, podemos abrir el terminal (o símbolo del sistema) en la carpeta raíz de nuestro proyecto y emitir el siguiente comando.

docker-compose up

Podemos ver los registros de arranque como se muestra en la siguiente captura de pantalla.

Nuestra aplicación NodeJS Dockerizada se inició con éxito. También fue capaz de conectarse a nuestro contenedor Docker Redis.

Ahora podemos acceder a la aplicación en http://localhost:4001

Como puedes ver, se muestra el número de visitas. Si pulsamos refrescar en la página, podremos ver que el contador de visitas se actualiza con cada golpe.

Otro comando útil para docker compose es cuando quieres reconstruir una nueva imagen para tu contenedor. Esto es para un caso en el que has hecho algunos cambios en el código de tu aplicación y quieres reconstruir las imágenes.

docker-compose up --build

El build flag asegura que se construyan nuevas imágenes para sus contenedores.

PASO 6 - Detener los Contenedores Docker Compose en segundo plano


Un par de comandos útiles pueden ayudarnos a iniciar y detener nuestros contenedores Docker Compose en segundo plano. Para iniciar Docker Compose en segundo plano podemos utilizar el siguiente comando.

docker-compose up -d

Los contenedores Docker se pondrán en marcha en segundo plano y el control volverá a la terminal.

Para detener estos contenedores en funcionamiento, podemos ejecutar el siguiente comando.

docker-compose down

Como puedes ver en la captura de pantalla anterior, primero detenemos el contenedor Docker NodeJS seguido del contenedor Redis. Luego, eliminamos los contenedores y finalmente, eliminamos la red.

via GIPHY

Conclusión


Con esto, hemos completado nuestro objetivo de crear una aplicación Docker Compose NodeJS con integración de Redis.

Hemos creado una aplicación de seguimiento de visitantes utilizando NodeJS y Redis. Para ejecutar estas aplicaciones, las hemos dockerizado y luego, hemos utilizado Docker Compose para hacer girar múltiples contenedores. Para definir los servicios o contenedores Docker Compose, hemos creado un archivo Docker Compose YAML.

Utilizando la función de red disponible en Docker Compose, también hemos podido conectar nuestra aplicación NodeJS al servidor Redis.

El código de este post está disponible en Github para su referencia.

Plataforma de cursos gratis sobre programación