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 г.