Docker - отличный инструмент, который помогает разработчикам создавать, развертывать и запускать приложения более эффективно стандартизованным способом. Мы можем разрабатывать в той же среде, что и приложение, запущенное в производстве. Вы можете ускорить отладку или даже предотвратить предстоящие ошибки, установив ту же настройку локально. В предыдущем посте я писал об упрощенном способе использования Docker для разработки внешнего интерфейса, а теперь покажу то же самое для проектов на Node.js.

Приложение

В качестве примера я собрал базовое приложение и постарался сделать его настолько простым, насколько это возможно. Если вам нравится экспериментировать самостоятельно, вы можете клонировать репозиторий и начать вносить изменения и посмотреть, как это будет.

// src/index.js
'use strict';
const express = require('express');
const port = process.env.PORT || 3000;
const app = express();

app.get('/', (req, res) => res.send('Hello World!'));

app.listen(port, () => console.log(`App listening on port ${port}!`));

Приложение состоит из одного файла, который запускает веб-сервер и отвечает на запросы. Я использовал хорошо известную Express web framework для ответа на запросы и сделал порт настраиваемым с помощью переменной окружения. Нам нужно, чтобы он был настраиваемым, потому что этот порт может отличаться от порта, используемого в разработке.

Разработка

Для развития мы хотели бы иметь

  • та же среда, что и на производстве
  • легко настроить среду
  • видеть изменения файлов автоматически в браузере
  • использовать автозавершение кода в редакторе

Чтобы выполнить все требования, мы будем использовать Docker с Docker Compose для создания идентичного контейнера как для разработки, так и для производства, а также пакет Nodemon для перезапуска приложения при изменении файла.

Мы можем перезапустить при изменении файла, изменив сценарий запуска с node src/index.js на nodemon --watch src src/index.js. Он делает то же самое, что и раньше, с добавлением перезапуска при каждом изменении файла в папке src.

Давайте перейдем к более захватывающей части, где мы раскручиваем контейнер локально.

# docker-compose.yml
version: "3"

services:
  server:
    image: node:12
    working_dir: /app
    volumes:
      - ./:/app
    ports:
      - 3000:3000
    environment:
      - PORT=3000
    command: sh -c "npm install && npm run dev"

Первое, что вы можете заметить, это то, что файл конфигурации Docker Compose не содержит настраиваемого образа Docker. В большинстве случаев он нам не нужен, но при необходимости мы всегда можем добавить его с помощью свойства build. В нашей настройке мы будем использовать базовый образ узла.

Вместо копирования файлов в Dockerfile я выбрал двустороннюю синхронизацию файлов с volumes. Это более требовательно к ресурсам, чем копирование файлов, но тот факт, что установленные пакеты NPM появляются на главном компьютере, что делает доступным завершение кода, делает его несложным.

Мы не должны воспринимать вещи как должное: мы устанавливаем настраиваемые переменные среды. В нашем случае настраивается порт, где сервер прослушивает входящие звонки. Установка его в конфигурации делает его более читабельным, так как он находится рядом с определением ports: местом, где мы объявляем, какие внутренние порты контейнера мы хотели бы видеть открытыми на хост-машине.

Последний шаг - запустить приложение со свойством command. Мы всегда запускаем команду npm install, которая может немного повлиять на производительность запуска, но также гарантирует, что зависимости остаются актуальными при запуске контейнера. Вы можете удалить его из command, но в этом случае вам придется вручную запускать его перед запуском контейнера или при изменении содержимого файла package.json.

Производство

Мы можем с удовольствием разработать приложение с предыдущей настройкой, но нам также необходимо создать развертываемый контейнер для производства. На данный момент невозможно отложить создание пользовательского образа докера. Посмотрим, как он может быть оптимальным.

# Dockerfile
FROM node:12 AS base
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production

FROM node:12-alpine
WORKDIR /app
COPY --from=base /app .
COPY . .

EXPOSE 3000

CMD npm start

Файл начинается с объявления начального изображения, которое называется базовым. Называть его необязательно, но многое проясняет при использовании Dockers multi-stage build.

Нам нужно скопировать только файлы пакета, так как они необходимы для установки тех же версий, которые использовались для разработки. Команда npm install заменена на npm ci --only=production. У него есть два основных отличия. npm ci устанавливает те же версии, что и в файле блокировки, и не пытается обновить их, как это делает npm install. Второй - флаг --only=production, который пропускает установку devDependencies, которая нам не нужна в производственной среде.

Мы сэкономили много драгоценного места на изображении, пропустив devDependencies, но изображение по-прежнему остается значительным (около 500 МБ). У Node есть гораздо меньший образ под названием alpine, который содержит только необходимые пакеты: и меньшее количество пакетов означает меньше дискового пространства, памяти, лучшую скорость и безопасность. Для установки пакетов иногда требуется стандартный образ, но с многоэтапными сборками Docker мы можем переключиться на меньший образ после установки пакета и скопировать пакеты с предыдущего шага. Таким образом, мы получаем лучшее из обоих миров: небольшой размер образа и возможность установить что угодно.

Если мы посмотрим на размер изображения с docker images, мы увидим, что оно уменьшилось до 100 МБ. Образ готов; мы можем развернуть его на производстве.

Резюме

Сначала я не понимал, зачем мне усложнять повседневную жизнь другой технологией, необходимой для развития. Другим пришлось показать мне, что с синхронизированными папками с volumes я не смогу отличить разработку на моей локальной машине. После этого и того факта, что я могу протестировать ту же инфраструктуру на моем локальном компьютере, они убедили меня использовать Docker ежедневно. Я надеюсь, что описанный выше рабочий процесс поможет другим полюбить Docker за его преимущества.

Первоначально опубликовано на https://dev.to 28 января 2020 г.