Node.js en contenedores usando Docker

La tecnología de contenedores es una de las mejores opciones para el desarrollo y despliegue de software. Le permite compartir algunos de los recursos del sistema operativo mientras encapsula el código y otras preocupaciones. Puede pensar en los contenedores como máquinas virtuales pero con menos espacio.

Los contenedores son ideales para microservicios donde reemplaza monolitos con muchos servicios. Cada uno de ellos funciona de forma aislada y se comunica con otros servicios a través de una interfaz bien definida (normalmente REST).

Docker es una de las implementaciones más populares de contenedores. ¿Qué es Docker? La página tiene una clara comparación de contenedores con máquinas virtuales. En pocas palabras, las máquinas virtuales usan hipervisor y cada máquina virtual tiene su propio sistema operativo, mientras que los contenedores comparten el sistema operativo y solo bibliotecas, contenedores, ejecutables, etc. separados.

Este es un diagrama simplificado sobre cómo funcionan las máquinas virtuales (fuente:docker.com).


Mientras que con los contenedores se comparten más cosas. Por lo tanto, obtiene un inicio, ejecución, aceleración, etc. más rápidos.


Estos son algunos de los beneficios de los contenedores Docker:

  • Permitir un rápido desarrollo/implementación de aplicaciones.
  • Son extensibles.
  • Son ligeros.
  • Son portátiles entre máquinas y entornos.
  • Son rápidos y ocupan poco espacio.
  • Son fáciles de usar.
  • Permita un comportamiento coherente desde el desarrollo hasta la producción.
  • Se puede versionar y los componentes se pueden reutilizar.
  • Permitir comunidad/colaboración.
  • Son de fácil mantenimiento.

Antes de que podamos comenzar a trabajar con Docker, debemos definir los términos de uso común, es decir, la terminología que se usa con frecuencia en el ecosistema de Docker:

Imágenes – Los planos de nuestra aplicación que forman la base de los contenedores. Usaremos el docker pull comando para descargar la imagen especificada.

Contenedores – Creado a partir de imágenes de Docker y ejecuta la aplicación real. Creamos un contenedor usando docker run . Se puede ver una lista de contenedores en ejecución usando el docker ps comando.

Demonio Docker – El servicio en segundo plano que se ejecuta en el host que gestiona la creación, ejecución y distribución de contenedores Docker. El daemon es el proceso que se ejecuta en el sistema operativo con el que se comunican los clientes. Es lo que hace que Docker Engine funcione.

Cliente Docker – La herramienta de línea de comandos que permite al usuario interactuar con el daemon. Puede haber otras formas de clientes, como Kitematic, que proporciona una GUI.

Puerto acoplable – Un registro de imágenes de Docker. Puede pensar en el registro como un directorio de todas las imágenes de Docker disponibles. Si es necesario, se pueden alojar sus propios registros de Docker y se pueden utilizar para extraer imágenes.

Dockerfile – Una receta a partir de la cual puedes crear una imagen. Dockerfile tiene la imagen base, instrucciones para agregar o copiar archivos, comandos para ejecutar, puertos para exponer y otra información. Dockerfile distingue entre mayúsculas y minúsculas.

Redacción de Docker – Un mecanismo para orquestar múltiples contenedores necesarios para un servicio(s) desde un único archivo de configuración docker-compose.yml .

[Nota al margen]

Leer publicaciones de blog es bueno, pero ver cursos en video es aún mejor porque son más atractivos.

Muchos desarrolladores se quejaron de la falta de material de video de calidad asequible en Node. Es una distracción ver videos de YouTube y una locura pagar $ 500 por un curso de video de Node.

Visite Node University, que tiene cursos de video GRATUITOS en Node:node.university.

[Fin de la nota al margen]

Anfitrión – Su computadora que aloja el demonio de la ventana acoplable o una máquina remota que aloja el demonio/motor de la ventana acoplable.

Node.js es una de las plataformas de más rápido crecimiento. Es excelente para aplicaciones web y API, especialmente para microservicios. Veamos cómo puede comenzar con Node y Docker siguiendo estos pasos:

  • Instalando Docker
  • Conceptos básicos de Docker
  • Creación de imágenes de nodos
  • Trabajar con múltiples contenedores:Node y MongoDB

Instalación de Docker

En primer lugar, necesitaría obtener el demonio Docker. Si es un usuario de macOS como yo, entonces la forma más fácil es ir al sitio web oficial de Docker https://docs.docker.com/docker-for-mac.

Si no es usuario de macOS, puede seleccionar una de las opciones de esta página:https://docs.docker.com/engine/installation.

Una vez que se complete la instalación, pruebe su instalación de Docker ejecutando:

$ docker run hello-world

Si ve un mensaje como este, lo más probable es que no haya iniciado Docker:

Cannot connect to the Docker daemon. Is the docker daemon running on this host? 

Inicie Docker. Si usó macOS, puede utilizar la aplicación GUI. De lo contrario, CLI.

Así es como se ve ejecutar Docker deamon en mi macOS:


Incluso puedo configurar la cantidad de memoria que necesita, ya sea que se actualice automáticamente o se inicie solo al iniciar sesión.

Por el contrario, si ve un mensaje como el siguiente, ¡deamon se está ejecutando y está listo para trabajar con Docker!

Unable to find image 'hello-world:latest' locally
latest: Pulling from library/hello-world
 
c04b14da8d14: Pull complete
Digest: sha256:0256e8a36e2070f7bf2d0b0763dbabdd67798512411de4cdcf9431a1feb60fd9
Status: Downloaded newer image for hello-world:latest
 
Hello from Docker!
This message shows that your installation appears to be working correctly.
 
...

A continuación, descargaremos una versión ligera de Linux como imagen. Se llama Alpino. Obtendremos esta imagen alpina de Docker Hub.

$ docker pull alpine

Espera a que se descargue la imagen. Espero que tengas una velocidad de Internet rápida. Lo bueno es que necesitas descargar la imagen solo una vez. Se almacenará en su computadora para uso futuro. De hecho, avancemos y verifiquemos que la imagen esté allí ejecutando:

$ docker images

Te mostrará Alpine, hello world y probablemente nada más. Está bien, porque acabas de empezar a trabajar con Docker. Primero aprendamos los conceptos básicos de Docker.

Fundamentos de Docker

Primero, para instalar una imagen (extraer) de Docker Hub, hay un docker pull {name} dominio. Ya lo usaste para Alpine:

$ docker pull alpine

Algunas otras imágenes por nombres de Docker Hub son:

  • boron :Node.js v6 basado en Debian Jessie
  • argon :Node.js v4 basado en Debian Jessie
  • ubuntu :Ubuntu
  • redis :Redis basado en Debian Jessie
  • mongodb :MongoDB basado en Debian Wheezy

Una vez que descarga una imagen, puede ejecutarla con docker run {name} , por ejemplo,

$ docker run alpine

Pero entonces nada ¡sucedió! Eso es porque cuando llamas $ docker run {name} , el cliente de Docker (CLI):

  • Encuentra la imagen (alpine en este caso)
  • Carga el contenedor
  • Ejecuta comandos (si los hay) en el contenedor

Cuando ejecutamos $ docker run alpine , no proporcionamos ningún comando, por lo que el contenedor se inició, ejecutó un comando vacío y luego salió.

Probemos un mejor comando que imprimirá hello world from alpine :

$ docker run alpine echo "hello from alpine"

Como resultado, Docker ejecuta el comando echo en nuestro contenedor alpino y luego sale.

¡Imagínese cuánto tiempo podría haber tomado arrancar una máquina virtual, ejecutar un comando en ella y luego matarla! Mucho más tiempo que un contenedor. Ese es un beneficio de los contenedores.

Si fuera el caso de que los contenedores solo pueden ejecutar un comando de eco y salir, serían muy inútiles. Afortunadamente, los contenedores pueden realizar procesos de ejecución prolongada, es decir, se ejecutan sin salir. Para ver todos los contenedores que se están ejecutando actualmente, use este comando:

$ docker ps

El ps le mostrará una lista de todos los contenedores que hemos ejecutado en esta computadora (llamada host):

$ docker ps -a

Para detener un contenedor desconectado, ejecute $ docker stop {ID} proporcionando el ID del contenedor.

Algunas opciones útiles para el doker run comando son:

  • -d separará nuestra terminal (bg/daemon).
  • -rm eliminará el contenedor después de ejecutarse.
  • -it adjunta un tty interactivo en el contenedor.
  • -p publicará/expondrá puertos para nuestro contenedor.
  • --name un nombre para nuestro contenedor.
  • -v monta un volumen para compartir entre el host y el contenedor.
  • -e proporciona variables de entorno al contenedor.
  • docker run --help para todas las banderas

Creación de imágenes acoplables

Si recuerda la definición, existe algo llamado Dockerfile. Así es como podemos crear nuevas imágenes. De hecho, cada imagen en Docker Hub tiene Dockerfile. Un Dockerfile es solo un archivo de texto que contiene una lista de comandos que el cliente de Docker llama mientras crea una imagen.

Puede incluir las siguientes instrucciones en el Dockerfile:

  • DE :(requerido como la primera instrucción en el archivo) Especifica la imagen base a partir de la cual se crea el contenedor de Docker y contra la cual se ejecutan las instrucciones posteriores de Dockerfile.
    La imagen se puede alojar en un repositorio público, un repositorio privado alojado por un registro de terceros o un repositorio que ejecuta en EC2.

  • EXPONER :enumera los puertos que se exponen en el contenedor.

  • AÑADIR :agrega archivos específicos a una ubicación en el contenedor

  • DIR.TRABAJO :establece el directorio de trabajo actual para ejecutar comandos en el contenedor.

  • VOLUMEN :marca un punto de montaje como disponible externamente para el host (u otros contenedores).

  • CMD :especifica un ejecutable y parámetros predeterminados, que se combinan en el comando que ejecuta el contenedor en el inicio. Utilice el siguiente formato:

    CMD ["executable","param1","param2"]
    

CMD también se puede utilizar para proporcionar parámetros predeterminados para un ENTRYPOINT comando omitiendo el argumento ejecutable. Se debe especificar un ejecutable en un CMD o un PUNTO DE ENTRADA , pero no ambos. Para escenarios básicos, use un CMD y omita el ENTRYPOINT .

PUNTO DE ENTRADA :utiliza el mismo formato JSON que CMD y, como CMD , especifica un comando para ejecutar cuando se inicia el contenedor. También permite ejecutar un contenedor como ejecutable con docker run.

Si define un ENTRYPOINT , también puede usar un CMD para especificar parámetros predeterminados que se pueden anular con la opción -d de docker run. El comando definido por un ENTRYPOINT (incluidos los parámetros) se combina con parámetros de **CMD **o ejecución de la ventana acoplable cuando se ejecuta el contenedor.

CORRE :especifica uno o más comandos que instalan paquetes y configuran su aplicación web dentro de la imagen.

ENV – establece la variable de entorno {key} al valor {value} usando {key}={value} . Ejemplo de sintaxis:

ENV myName="John Doe" myDog=Rex The Dog myCat=fluffy

Para obtener más información sobre las instrucciones que puede incluir en el Dockerfile , vaya a Referencia de Dockerfile:http://docs.docker.io/reference/builder. Para consejos y mejores prácticas de Dockerfile:
https://docs.docker.com/engine/userguide/eng-image/dockerfile_best-practices

Los volúmenes pueden compartir código entre el host (su computadora) y el contenedor. En otras palabras, un volumen Docker es un agujero de gusano entre un contenedor Docker efímero y el host. Es excelente para el desarrollo o los datos persistentes. El siguiente comando montará un volumen desde un directorio de trabajo actual (pwd) en el host. Los archivos estarán disponibles en /www/ en el contenedor La forma de recordar las opciones es de izquierda a derecha, es decir, host:contaner .

$ docker run -v $(pwd)/:/www/ -it ubuntu

Una vez que se ejecuta el comando, estará dentro del contenedor gracias al -it . Allí puede navegar hasta el /www con cd /www . ¿Qué ves? (usa ls )? ¡Tus archivos! Ahora aquí está la magia. Si modifica, elimina y agrega archivos a su carpeta de host, ¡esos cambios estarán automáticamente en el contenedor!

Además, incluso si el contenedor se detiene, los datos persistentes aún existen en el host de Docker y serán accesibles.

Creación de imágenes de nodos Docker

Ahora, cuando se trata de Node, tiene la opción de obtener una de las imágenes oficiales de Node de Docker Hub. Las versiones actuales son Boron y Argon, pero también hay versión 7 y lanzamientos nocturnos.

Otra opción es crear una imagen de nodo desde la base de Debian o Ubuntu. Ni siquiera tiene que componer el Dockerfile usted mismo. Puede tomar prestadas algunas líneas de las imágenes oficiales y agregarlas o quitarlas según sea necesario.

Procederemos con la primera opción ya que es la forma más sencilla. Entonces creamos Dockerfile en nuestra carpeta de proyecto Node.js justo donde tiene package.json y node_modules , es decir, la raíz del proyecto. Cada proyecto suele ser una carpeta o incluso un repositorio Git separado. Luego, escriba las instrucciones de Dockerfile:

FROM node:argon
 
# Create app directory
RUN mkdir -p /usr/src/app
WORKDIR /usr/src/app
 
# Install app dependencies
COPY package.json /usr/src/app/
RUN npm install
 
# Bundle app source
COPY . /usr/src/app
 
EXPOSE 3000
CMD [ "npm", "start" ]

Estamos comenzando desde la imagen de Argon, creando una carpeta para el código de su aplicación. Luego, copiamos los archivos de código fuente de la carpeta actual (raíz del proyecto). Finalmente, exponemos el puerto de la aplicación (por alguna extraña razón, casi siempre es 3000) y arrancamos el servidor con npm start asumiendo que tiene ese script npm definido en su package.json . Si no eres fanático de los scripts npm como npm start , luego simplemente use node app.js o node server.js dependiendo de su nombre de archivo.

Para crear la imagen de la aplicación Node.js, ejecute $ docker build . Puede llevar más tiempo la primera vez que lo ejecute si aún no tiene agron. La próxima vez, será más rápido. Una vez que finaliza la compilación, puede ejecutar el contenedor de su aplicación como cualquier otra imagen:

$ docker run {name}

Aquí hay una trampa. Es posible que haya notado que su nueva imagen no tiene nombre si solo usó docker build . Y lo más probable es que ya tenga o tendrá varias imágenes. Por lo tanto, es mejor nombrar y etiquetar las imágenes cuando las construyas. Usa -t bandera y nam:tag formato. Por ejemplo,

$ docker build -t {your-name}/{your-app-name}:{tag} .

Los contenedores son rápidos, pero no es muy bueno crear nuevas imágenes cada vez que realiza un cambio en su código fuente. Entonces, para el desarrollo, podemos montar el código fuente como un volumen y usar algo como forever o nodemon o node-dev para escuchar los cambios de archivos y reiniciar el servidor en cualquier momento que hagamos presione guardar. En el caso de un volumen, no es necesario copiar el código fuente ya que se montará desde el volumen.

FROM node:argon
 
WORKDIR /usr/src/app 
RUN npm install

EXPOSE 3000
CMD [ "nodemon", "app.js" ]

El comando para ejecutar esta imagen será un poco más avanzado ya que ahora necesitamos montar el volumen:

$ docker run -v ./:/usr/src/app -it {name}

Ahora, los cambios que realice se pasarán al contenedor, el servidor se reiniciará y podrá desarrollar en su entorno de host mientras ejecuta el código en el contenedor. ¡Lo mejor de ambos mundos! (Es fantástico porque el entorno del contenedor será exactamente el mismo en producción que el que tiene ahora). Pero las aplicaciones no funcionan por sí solas. Necesitas algo de persistencia y otros servicios.

Trabajar con Múltiples Contenedores:Nodo y MongoDB

version: '2'
services:
 
  mongo:
    image: mongo
    command: mongod --smallfiles
    networks:
      - all
 
  web:
    image: node:argon
    volumes:
      - ./:/usr/src/app
    working_dir: /usr/src/app
    command: sh -c 'npm install; npm run seed; npm start'
    ports:
      - "3000:8080"
    depends_on:
      - mongo
    networks:
      - all
    environment:
      MONGODB_URI: "mongodb://mongo:27017/accounts"
 
networks:
  all:

Inspeccionemos este archivo ymp línea por línea. Comenzamos con una lista de servicios. Nombre de un servicio, es decir, mongodb estará disponible en otros contenedores para que podamos conectarnos a MongoDB con mongodb://mongo:27017/accounts . No tiene que pasar esta cadena de conexión en una variable ambiental. Solo lo hice para demostrar que puedes hacerlo.

La imagen, los volúmenes, los puertos y otros campos imitan las instrucciones de Dockerfile. La distinción clave es que usamos depends_on . Esto le dirá al web servicio para usar el mongo Servicio.

Para ejecutar la redacción de Docker, simplemente ejecute este comando de terminal (suponiendo que deamon se esté ejecutando):

$ docker-compose up

Puede ver el ejemplo de trabajo completo de una aplicación MERN (MongoDB, Express, React y Node) en https://github.com/azat-co/mern/blob/master/code. Docker compose es una manera brillante y fácil de iniciar un entorno de varios contenedores.

Resumen

Los contenedores son excelentes para obtener su código de forma segura en múltiples entornos con muy poca sobrecarga. Esto le permite minimizar cualquier discrepancia. La idea básica es que al desarrollar en un entorno idéntico al de producción, eliminará cualquier problema potencial relacionado con las diferencias entre desarrollo y producción. Además, al obtener una encapsulación más barata que con las máquinas virtuales, podemos dividir nuestras aplicaciones en servicios más granulares. Se pueden dividir no solo en aplicación, base de datos, caché, servidor web, sino incluso más. Podemos dividir las aplicaciones web en contenedores por recursos, por ejemplo, puntos finales para /accounts en un contenedor, puntos finales para /users en otro, etc…. pero este es un tema para otra publicación.

Lecturas adicionales y recursos de Docker

¡El aprendizaje nunca se detiene! Aquí hay algunas lecturas sobre Docker junto con recursos.

  • Impresionante ventana acoplable: https://github.com/veggiemonk/awesome-docker
  • Hola Taller Docker: http://docker.atbaker.me
  • Por qué Docker: https://blog.codeship.com/por-que-docker
  • Docker Weekly y archivos: https://blog.docker.com/docker-weekly-archives
  • Blog de código: https://blog.codeship.com

PD:La parte de AWS mencionada en la imagen destacada se cubrirá en una nueva publicación.