Contêiner profissional de aplicativos Node.js. usando o Docker

O autor do material, cuja tradução publicamos hoje, é um engenheiro de DevOps. Ele diz que precisa usar o Docker . Em particular, essa plataforma de gerenciamento de contêiner é usada em vários estágios do ciclo de vida dos aplicativos Node.js. O uso do Docker, uma tecnologia que, recentemente, tem sido extremamente popular, permite otimizar o processo de desenvolvimento e saída dos projetos Node.js. em produção.

imagem

Agora estamos publicando uma série de artigos sobre o Docker, projetados para quem deseja aprender esta plataforma para uso em diversas situações. O mesmo material se concentra principalmente no uso profissional do Docker no desenvolvimento do Node.js.

O que é uma janela de encaixe?


O Docker é um programa desenvolvido para organizar a virtualização no nível do sistema operacional (contêiner). No coração dos contêineres estão imagens em camadas. Simplificando, o Docker é uma ferramenta que permite criar, implantar e executar aplicativos usando contêineres independentes do sistema operacional em que são executados. O contêiner inclui uma imagem do sistema operacional base necessário para o aplicativo funcionar, a biblioteca da qual esse aplicativo depende e o próprio aplicativo. Se vários contêineres estiverem em execução no mesmo computador, eles usarão os recursos deste computador juntos. Os contêineres do Docker podem compactar projetos criados usando uma variedade de tecnologias. Estamos interessados ​​em projetos baseados em Node.js.

Criando um projeto Node.js


Antes de empacotar um projeto Node.js em um contêiner do Docker, precisamos criar esse projeto. Vamos fazer isso. Aqui está o arquivo package.json deste projeto:

 { "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 as dependências do projeto, execute o comando npm install . No decorrer deste comando, entre outras coisas, o arquivo package-lock.json será criado. Agora crie o arquivo index.js , que conterá o código do projeto:

 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 você pode ver, aqui descrevemos um servidor simples que retorna algum texto em resposta a solicitações.

Criar arquivo de encaixe


Agora que o aplicativo está pronto, vamos falar sobre como empacotá-lo em um contêiner do Docker. Ou seja, será sobre a parte mais importante de qualquer projeto baseado no Docker, sobre o Dockerfile.

Um Dockerfile é um arquivo de texto que contém instruções para criar uma imagem do Docker para um aplicativo. As instruções neste arquivo, se não entrarmos em detalhes, descrevem a criação de camadas de um sistema de arquivos multinível, que possui tudo o que um aplicativo precisa para funcionar. A plataforma Docker pode armazenar em cache camadas de imagens que, ao reutilizar camadas que já estão no cache, aceleram o processo de criação de imagens.

Na programação orientada a objetos, existe uma classe. Classes são usadas para criar objetos. No Docker, as imagens podem ser comparadas com classes e os contêineres podem ser comparados com instâncias de imagens, ou seja, com objetos. Considere o processo de geração de um Dockerfile, que nos ajudará a descobrir isso.

Crie um Dockerfile vazio:

 touch Dockerfile 

Como vamos criar um contêiner para o aplicativo Node.js., a primeira coisa que precisamos colocar no contêiner será a imagem básica do Nó, que pode ser encontrada no Docker Hub . Usaremos a versão LTS do Node.js. Como resultado, a primeira declaração do nosso Dockerfile será a seguinte:

 FROM node:8 

Depois disso, crie um diretório para o nosso código. Ao mesmo tempo, graças à instrução ARG usada aqui, podemos, se necessário, especificar o nome do diretório do /app que não seja /app durante a montagem do contêiner. Detalhes sobre este manual podem ser encontrados aqui .

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

Como usamos a imagem do Node, as plataformas Node.js e npm já estarão instaladas nela. Usando o que já está na imagem, você pode organizar a instalação das dependências do projeto. Usando o sinalizador NODE_ENV (ou se a NODE_ENV ambiente NODE_ENV definida como production ), o npm não instalará os módulos listados na seção devDependencies do arquivo devDependencies .

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

Aqui estamos copiando o arquivo package*.json para a imagem, em vez de, por exemplo, copiar todos os arquivos do projeto. Fazemos exatamente isso porque as instruções Dockerfile RUN , COPY e ADD criam camadas de imagem adicionais, para que você possa usar os recursos de armazenamento em cache das camadas da plataforma Docker. Com essa abordagem, na próxima vez que coletarmos uma imagem semelhante, o Docker descobrirá se é possível reutilizar as camadas de imagem que já estão no cache e, nesse caso, aproveitará o que já existe, em vez de criar novas. camadas. Isso permite que você economize bastante tempo ao montar camadas no decorrer do trabalho em grandes projetos, que incluem muitos módulos npm.

Agora copie os arquivos do projeto para o diretório de trabalho atual. Aqui não usaremos a instrução ADD , mas a instrução COPY . De fato, na maioria dos casos, recomenda-se dar preferência à instrução COPY .

A instrução ADD , em comparação com COPY , possui alguns recursos que, no entanto, nem sempre são necessários. Por exemplo, estamos falando de opções para descompactar arquivos .tar e baixar arquivos por URL.

 #    COPY . . 

Os contêineres do Docker são ambientes isolados. Isso significa que, quando iniciarmos o aplicativo no contêiner, não poderemos interagir com ele diretamente sem abrir a porta em que esse aplicativo escuta. Para informar ao Docker que existe um aplicativo em um determinado contêiner que escuta em uma determinada porta, você pode usar a instrução EXPOSE .

 #   ,      EXPOSE 3000 

Até o momento, nós, usando o Dockerfile, descrevemos a imagem que o aplicativo conterá e tudo o que ele precisa para iniciar com êxito. Agora adicione a instrução ao arquivo que permite iniciar o aplicativo. Esta é uma instrução CMD . Permite especificar um determinado comando com parâmetros que serão executados quando o contêiner iniciar e, se necessário, poderão ser substituídos pelas ferramentas de linha de comando.

 #   CMD ["npm", "start"] 

Veja como será o Dockerfile finalizado:

 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"] 

Montagem da imagem


Preparamos um arquivo Dockerfile que contém instruções para criar a imagem, com base no qual um contêiner com um aplicativo em execução será criado. Monte a imagem executando um comando no seguinte formato:

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

No nosso caso, ficará assim:

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

O Dockerfile possui uma instrução ARG que descreve o argumento APP_DIR . Aqui nós definimos o seu significado. Se isso não for feito, o valor atribuído a ele será atribuído no arquivo, ou seja - app .

Depois de montar a imagem, verifique se o Docker a vê. Para fazer isso, execute o seguinte comando:

 docker images 

Em resposta a este comando, aproximadamente o seguinte deve ser produzido.


Imagens do Docker

Lançamento de imagem


Depois de montar a imagem do Docker, podemos executá-la, ou seja, criar uma instância dela, representada por um contêiner em funcionamento. Para fazer isso, use um comando deste tipo:

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

No nosso caso, ficará assim:

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

Pediremos ao sistema informações sobre contêineres em funcionamento usando este comando:

 docker ps 

Em resposta a isso, o sistema deve produzir aproximadamente o seguinte:


Docker Containers

Até agora, tudo está indo como esperado, embora ainda não tenhamos tentado acessar o aplicativo em execução no contêiner. Ou seja, nosso contêiner, chamado node-app , escuta na porta 8000 . Para tentar acessá-lo, você pode abrir um navegador e acessá-lo em localhost:8000 . Além disso, para verificar a integridade do contêiner, você pode usar o seguinte comando:

 curl -i localhost:8000 

Se o contêiner realmente funcionar, algo como o mostrado na figura a seguir será retornado em resposta a este comando.


Resultado da verificação de integridade do contêiner

Com base na mesma imagem, por exemplo, com base no recém-criado, é possível criar muitos contêineres. Além disso, você pode enviar nossa imagem para o registro do Docker Hub, que permitirá que outros desenvolvedores carreguem nossa imagem e iniciem os contêineres apropriados em casa. Essa abordagem simplifica o trabalho com projetos.

Recomendações


Aqui estão algumas sugestões que vale a pena considerar para aproveitar o poder do Docker e criar imagens o mais compactas possível.

▍1 Sempre crie um arquivo .dockerignore


Na pasta do projeto que você planeja colocar no contêiner, sempre é necessário criar um arquivo .dockerignore . Ele permite que você ignore arquivos e pastas que não são necessários ao criar a imagem. Com essa abordagem, podemos reduzir o chamado contexto de construção, o que nos permitirá montar rapidamente a imagem e reduzir seu tamanho. Este arquivo suporta modelos de nome de arquivo, pois é semelhante a um arquivo .gitignore . Recomenda-se adicionar um comando ao .dockerignore devido ao qual o Docker ignorará a pasta /.git , pois essa pasta geralmente contém materiais grandes (principalmente durante o desenvolvimento de um projeto) e a adição à imagem leva a um aumento no tamanho. Além disso, copiar esta pasta para uma imagem não faz muito sentido.

§ 2 Use o processo de montagem de imagens em vários estágios


Considere o exemplo quando coletamos um projeto para uma determinada organização. Este projeto usa muitos pacotes npm, e cada um desses pacotes pode instalar pacotes adicionais dos quais depende. A realização de todas essas operações leva a um tempo adicional gasto no processo de montagem da imagem (embora isso, graças aos recursos de cache do Docker, não seja tão importante). Pior, a imagem resultante que contém as dependências de um determinado projeto é bastante grande. Aqui, se estamos falando de projetos front-end, podemos lembrar que esses projetos geralmente são processados ​​usando empacotadores como o webpack, o que torna possível compactar convenientemente tudo o que um aplicativo precisa em um pacote de vendas. Como resultado, os arquivos do pacote npm para esse projeto são desnecessários. E isso significa que podemos nos livrar desses arquivos depois de criar o projeto usando o mesmo webpack.

Armado com essa idéia, tente fazer o seguinte:

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

Essa abordagem, no entanto, não nos convém. Como já dissemos, as instruções RUN , ADD e COPY criam camadas armazenadas em cache pelo Docker, portanto, precisamos encontrar uma maneira de lidar com a instalação de dependências, criar o projeto e excluir arquivos desnecessários com um único comando. Por exemplo, pode ser assim:

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

Neste exemplo, há apenas uma instrução RUN que instala as dependências, node_modules projeto e exclui a pasta node_modules . Isso leva ao fato de que o tamanho da imagem não será tão grande quanto o tamanho da imagem que inclui a pasta node_modules . Usamos os arquivos desta pasta apenas durante o processo de compilação do projeto e, em seguida, excluí-lo. É verdade que essa abordagem é ruim, pois leva muito tempo para instalar dependências do npm. Você pode eliminar essa desvantagem usando a tecnologia de montagem de imagens em vários estágios.

Imagine que estamos trabalhando em um projeto de front-end com muitas dependências e usamos o webpack para compilar esse projeto. Com essa abordagem, podemos, com o objetivo de reduzir o tamanho da imagem, tirar proveito dos recursos do Docker para montagem de imagens em vários estágios .

 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"] 

Com essa abordagem, a imagem resultante é muito menor que a imagem anterior e também usamos o node:alpine imagem node:alpine , que é muito pequena. E aqui está uma comparação de um par de imagens, durante as quais fica claro que a imagem do node:alpine é muito menor que a imagem do node:8 .


Comparando Imagens do Repositório Nó

▍3 Usar cache do Docker


Esforce-se para usar os recursos de cache do Docker para criar suas imagens. Já prestamos atenção a esse recurso ao trabalhar com um arquivo que foi acessado pelo nome package*.json . Isso reduz o tempo de criação da imagem. Mas essa oportunidade não deve ser usada precipitadamente.

Suponha que descrevamos no Dockerfile a instalação de pacotes em uma imagem criada a partir da imagem base do Ubuntu:16.04 :

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

Quando o sistema processa esse arquivo, se houver muitos pacotes instalados, as operações de atualização e instalação levarão muito tempo. Para melhorar a situação, decidimos aproveitar os recursos de cache de camada do Docker e reescrever o Dockerfile da seguinte maneira:

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

Agora, ao montar a imagem pela primeira vez, tudo corre como deveria, pois o cache ainda não foi formado. Imagine agora que precisamos instalar outro pacote, o package-2 . Para fazer isso, reescrevemos o arquivo:

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

Como resultado desse comando, o package-2 não será instalado ou atualizado. Porque O fato é que, ao executar a instrução RUN apt-get update , o Docker não vê nenhuma diferença entre essa instrução e a instrução executada anteriormente, como resultado, obtém dados do cache. E esses dados já estão desatualizados. Ao processar a instrução RUN apt-get install sistema a executa, pois não se parece com uma instrução semelhante no Dockerfile anterior, mas durante a instalação, podem ocorrer erros ou a versão antiga dos pacotes será instalada. Como resultado, verifica-se que os comandos de update e install devem ser executados na mesma instrução RUN , como é feito no primeiro exemplo. O armazenamento em cache é um ótimo recurso, mas o uso imprudente desse recurso pode levar a problemas.

▍4 Minimize o número de camadas de imagem


Recomenda-se, sempre que possível, minimizar o número de camadas da imagem, pois cada camada é o sistema de arquivos da imagem do Docker, o que significa que quanto menores as camadas da imagem, mais compacta será. Ao usar o processo de várias etapas de montagem da imagem, é obtida uma redução no número de camadas na imagem e uma redução no tamanho da imagem.

Sumário


Neste artigo, examinamos o processo de empacotamento de aplicativos Node.js em contêineres do Docker e de trabalho com esses contêineres. Além disso, fizemos algumas recomendações que, a propósito, podem ser usadas não apenas ao criar contêineres para projetos Node.js.

Caros leitores! Se você usa o Docker profissionalmente ao trabalhar com projetos Node.js., compartilhe recomendações sobre o uso efetivo deste sistema com iniciantes.

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


All Articles