Contenedor profesional de aplicaciones Node.js usando Docker

El autor del material, cuya traducción publicamos hoy, es un ingeniero de DevOps. Él dice que tiene que usar Docker . En particular, esta plataforma de gestión de contenedores se utiliza en varias etapas del ciclo de vida de las aplicaciones Node.js. El uso de Docker, una tecnología que recientemente ha sido extremadamente popular, le permite optimizar el proceso de desarrollo y salida de los proyectos Node.js en producción.

imagen

Ahora estamos publicando una serie de artículos sobre Docker diseñados para aquellos que desean aprender esta plataforma para su uso en una variedad de situaciones. El mismo material se centra principalmente en el uso profesional de Docker en el desarrollo de Node.js.

¿Qué es un acoplador?


Docker es un programa diseñado para organizar la virtualización a nivel del sistema operativo (contenedorización). En el corazón de los contenedores hay imágenes en capas. En pocas palabras, Docker es una herramienta que le permite crear, implementar y ejecutar aplicaciones utilizando contenedores independientes del sistema operativo en el que se ejecutan. El contenedor incluye una imagen del sistema operativo base necesario para que la aplicación funcione, la biblioteca de la que depende esta aplicación y esta aplicación en sí. Si varios contenedores se ejecutan en la misma computadora, entonces usan los recursos de esta computadora juntos. Los contenedores Docker pueden empaquetar proyectos creados usando una variedad de tecnologías. Estamos interesados ​​en proyectos basados ​​en Node.js.

Crear un proyecto Node.js


Antes de empaquetar un proyecto Node.js en un contenedor Docker, necesitamos crear este proyecto. Hagámoslo Aquí está el archivo package.json de este proyecto:

 { "name": "node-app", "version": "1.0.0", "description": "The best way to manage your Node app using Docker", "main": "index.js", "scripts": {   "start": "node index.js" }, "author": "Ankit Jain <ankitjain28may77@gmail.com>", "license": "ISC", "dependencies": {   "express": "^4.16.4" } } 

Para instalar las dependencias del proyecto, ejecute el npm install . En el curso de este comando, entre otras cosas, se package-lock.json archivo package-lock.json . Ahora cree el archivo index.js , que contendrá el código del proyecto:

 const express = require('express'); const app = express(); app.get('/', (req, res) => { res.send('The best way to manage your Node app using Docker\n'); }); app.listen(3000); console.log('Running on http://localhost:3000'); 

Como puede ver, aquí describimos un servidor simple que devuelve algo de texto en respuesta a las solicitudes.

Crear Dockerfile


Ahora que la aplicación está lista, hablemos sobre cómo empaquetarla en un contenedor Docker. A saber, se tratará de cuál es la parte más importante de cualquier proyecto basado en Docker, sobre el Dockerfile.

Un Dockerfile es un archivo de texto que contiene instrucciones para crear una imagen Docker para una aplicación. Las instrucciones en este archivo, si no entra en detalles, describen la creación de capas de un sistema de archivos multinivel, que tiene todo lo que una aplicación necesita para funcionar. La plataforma Docker puede almacenar en caché las capas de imagen, lo que, al reutilizar las capas que ya están en el caché, acelera el proceso de creación de imágenes.

En la programación orientada a objetos, existe una clase. Las clases se usan para crear objetos. En Docker, las imágenes se pueden comparar con clases y los contenedores se pueden comparar con instancias de imágenes, es decir, con objetos. Considere el proceso de generar un Dockerfile, que nos ayudará a resolver esto.

Cree un Dockerfile vacío:

 touch Dockerfile 

Dado que vamos a construir un contenedor para la aplicación Node.js, lo primero que necesitamos poner en el contenedor es la imagen de Nodo base, que se puede encontrar en Docker Hub . Utilizaremos la versión LTS de Node.js. Como resultado, la primera declaración de nuestro Dockerfile será la siguiente declaración:

 FROM node:8 

Después de eso, cree un directorio para nuestro código. Al mismo tiempo, gracias a la instrucción ARG utilizada aquí, podemos, si es necesario, especificar el nombre del directorio de la aplicación que no sea /app durante el ensamblaje del contenedor. Los detalles sobre este manual se pueden encontrar aquí .

 #   ARG APP_DIR=app RUN mkdir -p ${APP_DIR} WORKDIR ${APP_DIR} 

Como usamos la imagen Node, las plataformas Node.js y npm ya estarán instaladas en ella. Usando lo que ya está en la imagen, puede organizar la instalación de las dependencias del proyecto. Usando el indicador NODE_ENV (o si la NODE_ENV entorno NODE_ENV establecida en production ) npm no instalará los módulos listados en la sección devDependencies del archivo devDependencies .

 #   COPY package*.json ./ RUN npm install #     # RUN npm install --production 

Aquí estamos copiando el package*.json a la imagen, en lugar de, por ejemplo, copiar todos los archivos del proyecto. Hacemos eso precisamente porque las instrucciones Dockerfile RUN , COPY y ADD crean capas de imágenes adicionales, por lo que puede usar las funciones de almacenamiento en caché de las capas de la plataforma Docker. Con este enfoque, la próxima vez que recopilemos una imagen similar, Docker descubrirá si es posible reutilizar las capas de imágenes que ya están en el caché y, de ser así, aprovechará lo que ya está allí, en lugar de crear otras nuevas. capas Esto le permite ahorrar mucho tiempo al ensamblar capas en el curso del trabajo en proyectos grandes, que incluyen muchos módulos npm.

Ahora copie los archivos del proyecto al directorio de trabajo actual. Aquí no usaremos la instrucción ADD , sino la instrucción COPY . De hecho, en la mayoría de los casos se recomienda dar preferencia a la instrucción COPY .

La instrucción ADD , en comparación con COPY , tiene algunas características que, sin embargo, no siempre son necesarias. Por ejemplo, estamos hablando de opciones para desempaquetar archivos .tar y descargar archivos por URL.

 #    COPY . . 

Los contenedores Docker son entornos aislados. Esto significa que cuando lancemos la aplicación en el contenedor, no podremos interactuar con ella directamente sin abrir el puerto en el que escucha esta aplicación. Para informar a Docker que hay una aplicación en un determinado contenedor que escucha en un determinado puerto, puede usar la instrucción EXPOSE .

 #   ,      EXPOSE 3000 

Hasta la fecha, nosotros, utilizando el Dockerfile, hemos descrito la imagen que contendrá la aplicación y todo lo que necesita para iniciarse con éxito. Ahora agregue las instrucciones al archivo que le permite iniciar la aplicación. Esta es una instrucción CMD . Le permite especificar un determinado comando con parámetros que se ejecutarán cuando se inicie el contenedor y, si es necesario, pueden ser anulados por las herramientas de línea de comandos.

 #   CMD ["npm", "start"] 

Así se verá el Dockerfile terminado:

 FROM node:8 #   ARG APP_DIR=app RUN mkdir -p ${APP_DIR} WORKDIR ${APP_DIR} #   COPY package*.json ./ RUN npm install #     # RUN npm install --production #    COPY . . #   ,      EXPOSE 3000 #   CMD ["npm", "start"] 

Asamblea de imagen


Hemos preparado un archivo Dockerfile que contiene instrucciones para construir la imagen, sobre la base de la cual se creará un contenedor con una aplicación en ejecución. Ensamble la imagen ejecutando un comando de la siguiente forma:

 docker build --build-arg <build arguments> -t <user-name>/<image-name>:<tag-name> /path/to/Dockerfile 

En nuestro caso, se verá así:

 docker build --build-arg APP_DIR=var/app -t ankitjain28may/node-app:V1 . 

Dockerfile tiene una declaración ARG que describe el argumento APP_DIR . Aquí establecemos su significado. Si esto no se hace, tomará el valor que se le asigna en el archivo, es decir, la app .

Después de ensamblar la imagen, verifique si Docker la ve. Para hacer esto, ejecute el siguiente comando:

 docker images 

En respuesta a este comando, se debe generar aproximadamente lo siguiente.


Imágenes de Docker

Lanzamiento de imagen


Después de haber ensamblado la imagen de Docker, podemos ejecutarla, es decir, crear una instancia de ella, representada por un contenedor de trabajo. Para hacer esto, use un comando de este tipo:

 docker run -p <External-port:exposed-port> -d --name <name of the container> <user-name>/<image-name>:<tag-name> 

En nuestro caso, se verá así:

 docker run -p 8000:3000 -d --name node-app ankitjain28may/node-app:V1 

Le pediremos al sistema información sobre contenedores que funcionan con este comando:

 docker ps 

En respuesta a esto, el sistema debería generar algo como lo siguiente:


Contenedores Docker

Hasta ahora, todo va como se esperaba, aunque aún no hemos intentado acceder a la aplicación que se ejecuta en el contenedor. A saber, nuestro contenedor, denominado node-app , escucha en el puerto 8000 . Para intentar acceder a él, puede abrir un navegador y acceder a él en localhost:8000 . Además, para verificar el estado del contenedor, puede usar el siguiente comando:

 curl -i localhost:8000 

Si el contenedor realmente funciona, se devolverá algo como el que se muestra en la siguiente figura en respuesta a este comando.


Resultado del control de salud del contenedor

Sobre la base de la misma imagen, por ejemplo, sobre la base de recién creado, es posible crear muchos contenedores. Además, puede enviar nuestra imagen al registro de Docker Hub, lo que permitirá a otros desarrolladores cargar nuestra imagen y lanzar los contenedores apropiados en casa. Este enfoque simplifica el trabajo con proyectos.

Recomendaciones


Aquí hay algunas sugerencias que vale la pena considerar para aprovechar el poder de Docker y crear imágenes tan compactas como sea posible.

▍1. Siempre cree un archivo .dockerignore


En la carpeta del proyecto que planea colocar en el contenedor, siempre necesita crear un archivo .dockerignore . Le permite ignorar archivos y carpetas que no son necesarios al construir la imagen. Con este enfoque, podemos reducir el llamado contexto de construcción, que nos permitirá ensamblar rápidamente la imagen y reducir su tamaño. Este archivo admite plantillas de nombre de archivo, en este caso es similar a un archivo .gitignore . Se recomienda agregar un comando a .dockerignore debido a que Docker ignorará la carpeta /.git , ya que esta carpeta generalmente contiene materiales grandes (especialmente durante el desarrollo de un proyecto) y agregarla a la imagen conduce a un aumento en su tamaño. Además, copiar esta carpeta en una imagen no tiene mucho sentido.

▍2. Utilice el proceso de ensamblaje de imágenes en varias etapas


Considere el ejemplo cuando recopilamos un proyecto para una determinada organización. Este proyecto utiliza muchos paquetes npm, y cada uno de estos paquetes puede instalar paquetes adicionales de los que depende. La realización de todas estas operaciones lleva a un tiempo adicional dedicado al proceso de ensamblar la imagen (aunque esto, gracias a las capacidades de almacenamiento en caché de Docker, no es tan importante). Peor aún, la imagen resultante que contiene las dependencias de un determinado proyecto es bastante grande. Aquí, si hablamos de proyectos front-end, podemos recordar que dichos proyectos generalmente se procesan utilizando paquetes como webpack, que permiten empaquetar convenientemente todo lo que una aplicación necesita en un paquete de ventas. Como resultado, los archivos de paquete npm para dicho proyecto son innecesarios. Y esto significa que podemos deshacernos de dichos archivos después de construir el proyecto usando el mismo paquete web.

Armado con esta idea, intente hacer esto:

 #   COPY package*.json ./ RUN npm install --production # - COPY . . RUN npm run build:production #    npm- RUN rm -rf node_modules 

Tal enfoque, sin embargo, no nos conviene. Como ya dijimos, las instrucciones RUN , ADD y COPY crean capas almacenadas en caché por Docker, por lo que debemos encontrar una manera de manejar la instalación de dependencias, construir el proyecto y luego eliminar archivos innecesarios con un solo comando. Por ejemplo, podría verse así:

 #      COPY . . #  ,      RUN npm install --production && npm run build:production && rm -rf node_module 

En este ejemplo, solo hay una instrucción RUN que instala las dependencias, node_modules proyecto y elimina la carpeta node_modules . Esto lleva al hecho de que el tamaño de la imagen no será tan grande como el tamaño de la imagen que incluye la carpeta node_modules . Usamos los archivos de esta carpeta solo durante el proceso de compilación del proyecto y luego lo eliminamos. Es cierto que este enfoque es malo, ya que lleva mucho tiempo instalar dependencias npm. Puede eliminar este inconveniente utilizando la tecnología de ensamblaje de imágenes en varias etapas.

Imagine que estamos trabajando en un proyecto frontend que tiene muchas dependencias, y usamos webpack para construir este proyecto. Con este enfoque, podemos, en aras de reducir el tamaño de la imagen, aprovechar las capacidades de Docker para el ensamblaje de imágenes en varias etapas .

 FROM node:8 As build #  RUN mkdir /app && mkdir /src WORKDIR /src #   COPY package*.json ./ RUN npm install #     # RUN npm install --production #       COPY . . RUN npm run build:production #    ,     FROM node:alpine #      build   app COPY --from=build ./src/build/* /app/ ENTRYPOINT ["/app"] CMD ["--help"] 

Con este enfoque, la imagen resultante es mucho más pequeña que la imagen anterior, y también utilizamos el node:alpine imagen node:alpine , que es muy pequeña. Y aquí hay una comparación de un par de imágenes, durante las cuales se puede ver que la imagen del node:alpine mucho más pequeña que la imagen del node:8 .


Comparación de imágenes del repositorio de nodos

▍3. Usar caché de Docker


Esfuércese por utilizar las capacidades de almacenamiento en caché de Docker para crear sus imágenes. Ya prestamos atención a esta característica cuando trabajamos con un archivo al que se accedió mediante el package*.json nombres package*.json . Esto reduce el tiempo de construcción de la imagen. Pero esta oportunidad no se debe usar precipitadamente.

Supongamos que describimos en Dockerfile la instalación de paquetes en una imagen creada a partir de la imagen base de Ubuntu:16.04 :

 FROM ubuntu:16.04 RUN apt-get update && apt-get install -y \   curl \   package-1 \   .   . 

Cuando el sistema procesará este archivo, si hay muchos paquetes instalados, las operaciones de actualización e instalación tomarán mucho tiempo. Para mejorar la situación, decidimos aprovechar las capacidades de almacenamiento en caché de capas de Docker y reescribimos el Dockerfile de la siguiente manera:

 FROM ubuntu:16.04 RUN apt-get update RUN apt-get install -y \   curl \   package-1 \   .   . 

Ahora, al ensamblar la imagen por primera vez, todo sale como debería, ya que el caché aún no se ha formado. Imagine ahora que necesitamos instalar otro paquete, package-2 . Para hacer esto, reescribimos el archivo:

 FROM ubuntu:16.04 RUN apt-get update RUN apt-get install -y \   curl \   package-1 \   package-2 \   .   . 

Como resultado de dicho comando, package-2 no se instalará ni actualizará. Por qué El hecho es que al ejecutar la instrucción RUN apt-get update , Docker no ve ninguna diferencia entre esta instrucción y la instrucción ejecutada anteriormente, como resultado, toma datos del caché. Y estos datos ya están desactualizados. Al procesar la instrucción RUN apt-get install sistema la ejecuta, ya que no parece una instrucción similar en el Dockerfile anterior, pero durante la instalación, pueden producirse errores o se instalará la versión anterior de los paquetes. Como resultado, resulta que los comandos de update e install deben ejecutarse dentro de la misma instrucción RUN , como se hace en el primer ejemplo. El almacenamiento en caché es una gran característica, pero el uso imprudente de esta característica puede generar problemas.

▍4. Minimiza el número de capas de imagen


Se recomienda, siempre que sea posible, esforzarse por minimizar el número de capas de imagen, ya que cada capa es el sistema de archivos de la imagen Docker, lo que significa que cuanto más pequeñas sean las capas en la imagen, más compacta será. Cuando se usa el proceso de múltiples etapas de ensamblar imágenes, se logra una reducción en el número de capas en la imagen y una disminución en el tamaño de la imagen.

Resumen


En este artículo, analizamos el proceso de empaquetar aplicaciones Node.js en contenedores Docker y trabajar con dichos contenedores. Además, hicimos algunas recomendaciones que, por cierto, pueden usarse no solo al crear contenedores para proyectos de Node.js.

Estimados lectores! Si utiliza Docker profesionalmente cuando trabaja con proyectos Node.js, comparta recomendaciones sobre el uso efectivo de este sistema con principiantes.

Source: https://habr.com/ru/post/440656/


All Articles