Введение

Если вы активно разрабатываете приложение, использование Docker может упростить рабочий процесс и процесс развертывания вашего приложения в рабочей среде. Работа с контейнерами в разработке дает следующие преимущества:

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

Из этого туториала вы узнаете, как настроить среду разработки для приложения Node.js с помощью Docker. Вы создадите два контейнера — один для приложения Node, а другой для базы данных MongoDB — с помощью Docker Compose. Поскольку это приложение работает с Node и MongoDB, ваша установка будет выполнять следующие действия:

  • Синхронизируйте код приложения на хосте с кодом в контейнере, чтобы облегчить внесение изменений во время разработки.
  • Убедитесь, что изменения в коде приложения работают без перезагрузки.
  • Создайте базу данных приложения, защищенную пользователем и паролем.
  • Сохраните эти данные.

В конце этого руководства у вас будет работающее приложение с информацией об акулах, работающее в контейнерах Docker:

Полная целевая страница коллекции акул

Предварительные условия

Чтобы следовать этому руководству, вам понадобится:

  • Сервер разработки под управлением Ubuntu 18.04, а также пользователь без полномочий root с привилегиями sudo и активным брандмауэром. Инструкции по их настройке см. в этом руководстве по первоначальной настройке сервера.
  • Docker установлен на вашем сервере, следуя Steps 1 and 2 «Как установить и использовать Docker в Ubuntu 18.04».
  • Docker Compose установлен на вашем сервере, следуя Step 1 инструкции по установке Docker Compose в Ubuntu 18.04.

Шаг 1 — Клонирование проекта и изменение зависимостей

Первым шагом в создании этой установки будет клонирование кода проекта и изменение его файла package.json , который включает зависимости проекта. Вы добавите nodemon в devDependencies проекта, указав, что будете использовать его во время разработки. Запуск приложения с помощью nodemon гарантирует, что оно будет автоматически перезапускаться всякий раз, когда вы вносите изменения в свой код.

Сначала клонируйте репозиторий nodejs-mongo-mongoose из учетной записи GitHub сообщества DigitalOcean. Этот репозиторий включает в себя код из настройки, описанной в разделе «Как интегрировать MongoDB с вашим приложением Node», в котором объясняется, как интегрировать базу данных MongoDB с существующим приложением Node с помощью Mongoose.

Клонируйте репозиторий в каталог с именем node_project :

Перейдите в каталог node_project :

Откройте файл package.json проекта с помощью nano или вашего любимого редактора:

Под зависимостями проекта и над закрывающей фигурной скобкой создайте новый объект devDependencies , который включает nodemon :

~/node_project/package.json

Сохраните и закройте файл, когда закончите редактирование. Если вы используете nano , нажмите CTRL+X , затем Y , затем ENTER .

Подготовив код проекта и изменив его зависимости, вы можете перейти к рефакторингу кода для контейнерного рабочего процесса.

Шаг 2. Настройка приложения для работы с контейнерами

Модификация вашего приложения для контейнерного рабочего процесса означает сделать ваш код более модульным. Контейнеры обеспечивают переносимость между средами, и ваш код должен отражать это, оставаясь максимально отделенным от базовой операционной системы. Для достижения этой цели вы проведете рефакторинг своего кода, чтобы более эффективно использовать свойство Process.env узла Node. Это возвращает объект с информацией о вашей пользовательской среде во время выполнения. Вы можете использовать этот объект в своем коде для динамического назначения информации о конфигурации во время выполнения с помощью переменных среды.

Начните с app.js , вашей основной точки входа в приложение. Откройте файл:

Внутри вы увидите определение константы port , а также функцию listen , которая использует эту константу для указания порта, который приложение будет прослушивать:

~/home/node_project/app.js

Переопределите константу port , чтобы обеспечить динамическое назначение во время выполнения с помощью process.env . Внесите следующие изменения в определение константы и функцию listen :

~/home/node_project/app.js

Ваше новое определение константы назначает port динамически, используя значение, переданное во время выполнения, или 8080 . Аналогичным образом вы переписали функцию listen , чтобы использовать литерал шаблона, который будет интерполировать значение порта при прослушивании соединений. Поскольку вы будете сопоставлять свои порты где-то еще, эти изменения избавят вас от необходимости постоянно пересматривать этот файл по мере изменения вашей среды.

Когда вы закончите редактирование, сохраните и закройте файл.

Далее вы измените информацию о подключении к базе данных, удалив все учетные данные конфигурации. Откройте файл db.js , который содержит следующую информацию:

На данный момент файл выполняет следующие действия:

  • Импортирует Mongoose, Object Document Mapper (ODM), который вы используете для создания схем и моделей для данных вашего приложения.
  • Устанавливает учетные данные базы данных как константы, включая имя пользователя и пароль.
  • Подключается к базе данных с помощью метода mongoose.connect .

Для получения дополнительной информации о файле см. Шаг 3 статьи «Как интегрировать MongoDB с вашим приложением Node».

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

~/node_project/db.js

Вместо жесткого кодирования этой информации вы можете использовать process.env для сбора значений этих констант во время выполнения. Измените блок, чтобы он выглядел так:

~/node_project/db.js

Сохраните и закройте файл, когда закончите редактирование.

На этом этапе вы модифицировали db.js для работы с переменными среды вашего приложения, но вам все еще нужен способ передать эти переменные в ваше приложение. Создайте файл .env со значениями, которые вы можете передать приложению во время выполнения.

Откройте файл:

Этот файл будет содержать информацию, которую вы удалили из db.js : имя пользователя и пароль для базы данных вашего приложения, а также настройку порта и имя базы данных. Не забудьте обновить имя пользователя, пароль и имя базы данных, указанные здесь, своей собственной информацией:

~/node_project/.env

Обратите внимание, что вы removed настройку хоста, которая изначально присутствовала в db.js Теперь вы определите свой хост на уровне файла Docker Compose вместе с другой информацией о ваших сервисах и контейнерах.

Сохраните и закройте этот файл, когда закончите редактирование.

Поскольку ваш файл .env содержит конфиденциальную информацию, вам необходимо убедиться, что он включен в файлы .dockerignore и .gitignore вашего проекта, чтобы он не копировался в ваш контроль версий или контейнеры.

Откройте файл .dockerignore :

Добавьте следующую строку в конец файла:

~/node_project/.dockerignore

Сохраните и закройте файл, когда закончите редактирование.

Файл .gitignore в этом репозитории уже включает .env , но вы можете проверить его наличие:

~~/node_project/.gitignore

На этом этапе вы успешно извлекли конфиденциальную информацию из кода вашего проекта и приняли меры для контроля того, как и где копируется эта информация. Теперь вы можете добавить в свой код подключения к базе данных, чтобы оптимизировать его для контейнерного рабочего процесса.

Шаг 3 — Изменение настроек подключения к базе данных

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

Откройте db.js для редактирования:

Обратите внимание на код, добавленный ранее, а также константу url для URI подключения Mongo и метод connect Mongoose:

~/node_project/db.js

В настоящее время ваш метод connect принимает опцию, которая сообщает Mongoose использовать новый анализатор URL-адресов Mongo. Вы можете добавить параметры к этому методу, чтобы определить параметры попыток повторного подключения. Для этого создайте константу options , включающую соответствующую информацию в дополнение к новой опции анализатора URL-адресов. Под константами Mongo добавьте следующее определение константы options :

~/node_project/db.js

Опция reconnectTries сообщает Mongoose продолжать попытки подключения бесконечно, а reconnectInterval определяет период между попытками подключения в миллисекундах. connectTimeoutMS определяет 10 секунд как период ожидания драйвера Mongo перед неудачной попыткой подключения.

Теперь вы можете использовать новую константу options в методе connect Mongoose для точной настройки параметров подключения Mongoose. Вы также добавите обещание обрабатывать потенциальные ошибки соединения.

В настоящее время метод connect Mongoose выглядит следующим образом:

~/node_project/db.js

Удалите существующий метод connect и замените его следующим кодом, который включает константу options и обещание:

~/node_project/db.js

В случае успешного соединения ваша функция регистрирует соответствующее сообщение; в противном случае он catch и зарегистрирует ошибку, что позволит вам устранить неполадку.

Готовый файл будет выглядеть так:

~/node_project/db.js

Сохраните и закройте файл после завершения редактирования.

Теперь вы добавили устойчивость в код вашего приложения для обработки случаев, когда ваше приложение может не подключиться к вашей базе данных. Имея этот код, вы можете перейти к определению своих сервисов с помощью Compose.

Шаг 4 — Определение сервисов с помощью Docker Compose

После рефакторинга кода вы готовы написать файл docker-compose.yml с определениями ваших сервисов. Служба в Compose — это работающий контейнер, а определения службы, которые вы включите в файл docker-compose.yml , содержат информацию о том, как будет запускаться каждый образ контейнера. Инструмент Compose позволяет вам определить несколько сервисов для создания многоконтейнерных приложений.

Прежде чем определять свои службы, вы добавите в свой проект инструмент под названием wait-for , чтобы гарантировать, что ваше приложение попытается подключиться к вашей базе данных только после завершения задач запуска базы данных. Этот сценарий-оболочка использует netcat для опроса, принимает ли конкретный хост и порт TCP-соединения. Его использование позволяет вам контролировать попытки вашего приложения подключиться к вашей базе данных, проверяя, готова ли база данных принимать соединения.

Хотя Compose позволяет вам указывать зависимости между службами с помощью параметра depends_on , этот порядок основан на том, запущен ли контейнер, а не на его готовности. Использование depends_on не будет оптимальным для вашей настройки, поскольку вы хотите, чтобы ваше приложение подключалось только после завершения задач запуска базы данных, включая добавление пользователя и пароля в базу данных аутентификации admin . Дополнительную информацию об использовании wait-for и других инструментов для управления порядком запуска см. в соответствующих рекомендациях в документации Compose.

Откройте файл wait-for.sh :

Введите следующий код в файл, чтобы создать функцию опроса:

~/node_project/app/wait-for.sh

Сохраните и закройте файл, когда закончите добавлять код.

Сделайте скрипт исполняемым:

Затем откройте файл docker-compose.yml :

Сначала определите службу приложения nodejs , добавив в файл следующий код:

~/node_project/docker-compose.yml

Определение службы nodejs включает следующие параметры:

  • build : определяет параметры конфигурации, включая context и dockerfile , которые будут применяться, когда Compose создает образ приложения. Если вы хотите использовать существующий образ из реестра, такого как Docker Hub, вместо этого вы можете использовать инструкцию image с информацией о вашем имени пользователя, репозитории и теге изображения.
  • context : определяет контекст сборки образа — в данном случае текущий каталог проекта.
  • dockerfile : указывает файл Dockerfile в текущем каталоге проекта, который Compose будет использовать для создания образа приложения. Дополнительные сведения об этом файле см. в разделе «Как создать приложение Node.js с помощью Docker».
  • image , container_name : они присваивают имена изображению и контейнеру.
  • restart : определяет политику перезапуска. По умолчанию установлено значение no , но вы настроили перезапуск контейнера, если он не остановлен.
  • env_file : сообщает Compose, что вы хотите добавить переменные среды из файла с именем .env , расположенного в вашем контексте сборки.
  • environment : использование этой опции позволяет вам добавить настройки соединения Mongo, определенные вами в файле .env . Обратите внимание, что вы не устанавливаете для NODE_ENV значение development , поскольку это поведение Express по умолчанию, если NODE_ENV не установлен. При переходе на производство вы можете установить это значение на production , чтобы включить кэширование представлений и менее подробные сообщения об ошибках. Также обратите внимание, что вы указали контейнер базы данных db в качестве хоста, как описано в шаге 2.
  • ports : это сопоставляет порт 80 на хосте с портом 8080 в контейнере.
  • volumes : здесь вы включаете два типа монтирования:
    • Первый — это привязка, которая монтирует код вашего приложения на хосте в каталог /home/node/app контейнера. Это облегчит быструю разработку, поскольку любые изменения, внесенные вами в код хоста, будут немедленно внесены в контейнер.
    • Второй — именованный том node_modules . Когда Docker запускает инструкцию npm install указанную в Dockerfile приложения, npm создаст в контейнере новый каталог node_modules , который включает пакеты, необходимые для запуска приложения. Однако только что созданное привязывание скроет этот вновь созданный каталог node_modules . Поскольку node_modules на хосте пуст, привязка сопоставит пустой каталог с контейнером, переопределяя новый каталог node_modules и предотвращая запуск вашего приложения. Именованный том node_modules решает эту проблему, сохраняя содержимое каталога /home/node/app/node_modules и монтируя его в контейнер, скрывая привязку.

Keep the following points in mind when using this approach :

  • Ваша привязка смонтирует содержимое каталога node_modules в контейнере на хост, и этот каталог будет принадлежать пользователю root , поскольку именованный том был создан Docker.

  • Если у вас уже есть каталог node_modules на хосте, он переопределит каталог node_modules , созданный в контейнере. Настройка, которую вы создаете в этом руководстве, предполагает, что у вас not уже существующего каталога node_modules и вы не будете работать с npm на своем хосте. Это соответствует двенадцатифакторному подходу к разработке приложений, который сводит к минимуму зависимости между средами выполнения.

  • networks : указывает, что ваша служба приложений присоединится к сети app-network , которую вы определите в нижней части файла.
  • command : этот параметр позволяет указать команду, которая должна выполняться при запуске образа Compose. Обратите внимание, что это переопределит инструкцию CMD , которую вы установили в нашем приложении Dockerfile . Здесь вы запускаете приложение, используя сценарий wait-for , который опрашивает службу db на порту 27017 , чтобы проверить, готова ли служба базы данных. После успешного завершения теста готовности скрипт выполнит заданную вами команду /home/node/app/node_modules/.bin/nodemon app.js , чтобы запустить приложение с помощью nodemon . Это гарантирует, что любые будущие изменения, которые вы внесете в свой код, будут перезагружены без необходимости перезапускать приложение.

Затем создайте службу db , добавив следующий код под определением службы приложения:

~/node_project/docker-compose.yml

Некоторые настройки, определенные для службы nodejs , остались прежними, но вы также внесли следующие изменения в определения image , environment и volumes :

  • image : Чтобы создать этот сервис, Compose извлечет образ Mongo 4.1.8-xenial из Docker Hub. Вы закрепляете определенную версию, чтобы избежать возможных конфликтов в будущем при изменении образа Mongo. Дополнительные сведения о закреплении версий см. в документации Docker, посвященной передовым практикам Dockerfile.
  • MONGO_INITDB_ROOT_USERNAME , MONGO_INITDB_ROOT_PASSWORD : образ mongo делает эти переменные среды доступными, чтобы вы могли изменить инициализацию экземпляра базы данных. MONGO_INITDB_ROOT_USERNAME и MONGO_INITDB_ROOT_PASSWORD вместе создают пользователя root в базе данных аутентификации admin и обеспечивают включение аутентификации при запуске контейнера. Вы установили MONGO_INITDB_ROOT_USERNAME и MONGO_INITDB_ROOT_PASSWORD , используя значения из вашего файла .env , который вы передаете службе db с помощью опции env_file . Это означает, что ваш пользователь приложения sammy будет пользователем root в экземпляре базы данных с доступом ко всем административным и операционным привилегиям этой роли. При работе в рабочей среде вам потребуется создать выделенного пользователя приложения с соответствующими привилегиями.

Note: Имейте в виду, что эти переменные не вступят в силу, если вы запустите контейнер с существующим каталогом данных.

  • dbdata:/data/db : именованный том dbdata сохранит данные, хранящиеся в каталоге данных Mongo по умолчанию, /data/db . Это гарантирует, что вы не потеряете данные в случае остановки или удаления контейнеров.

Служба db также была добавлена в сеть сети app-network с опцией networks .

В качестве последнего шага добавьте определения тома и сети в конец файла:

~/node_project/docker-compose.yml

Пользовательская app-network мостовой сети обеспечивает связь между вашими контейнерами, поскольку они находятся на одном хосте демона Docker. Это оптимизирует трафик и связь внутри приложения, поскольку открывает все порты между контейнерами в одной мостовой сети, не открывая при этом порты внешнему миру. Таким образом, ваши контейнеры db и nodejs могут взаимодействовать друг с другом, и вам нужно только открыть порт 80 для внешнего доступа к приложению.

Ваш ключ volumes верхнего уровня определяет тома dbdata и node_modules . Когда Docker создает тома, содержимое тома сохраняется в части файловой системы хоста /var/lib/docker/volumes/ , которой управляет Docker. Содержимое каждого тома хранится в каталоге /var/lib/docker/volumes/ и монтируется к любому контейнеру, использующему этот том. Таким образом, данные с информацией об акулах, которые создадут ваши пользователи, сохранятся в томе dbdata , даже если вы удалите и заново создадите контейнер db .

Готовый файл docker-compose.yml будет выглядеть так:

~/node_project/docker-compose.yml

Сохраните и закройте файл, когда закончите редактирование.

Имея определения служб, вы готовы запустить приложение.

Шаг 5 — Тестирование приложения

Имея файл docker-compose.yml , вы можете создавать свои сервисы с помощью команды docker-compose up . Вы также можете проверить, сохранятся ли ваши данные, остановив и удалив контейнеры с помощью docker-compose down .

Сначала создайте образы контейнеров и создайте службы, запустив docker-compose up с флагом -d , который затем запустит контейнеры nodejs и db в фоновом режиме:

Вывод подтверждает, что ваши сервисы созданы:

Более подробную информацию о процессах запуска вы также можете получить, отобразив вывод журнала сервисов:

Если все началось правильно, результат будет следующий:

Вы также можете проверить состояние ваших контейнеров с помощью docker-compose ps :

Вывод показывает, что ваши контейнеры запущены:

Когда ваши службы запущены, вы можете посетить http:// your_server_ip в браузере:

Целевая страница приложения Everything Sharks

Нажмите кнопку Get Shark Info , чтобы перейти на страницу с формой ввода, где вы можете ввести имя акулы и описание общего характера этой акулы:

Форма информации об акулах, в которой вы можете ввести имя акулы и характеристики этой акулы.

В форму добавьте акулу по вашему выбору. Для этой демонстрации добавьте Megalodon Shark в поле Shark Name и Ancient в поле Shark Character :

Заполненная форма акулы

Нажмите кнопку Submit , и вам откроется страница с этой информацией об акулах:

Shark Вывод из отправленной вами формы

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

Вернувшись к своему терминалу, введите следующую команду, чтобы остановить и удалить контейнеры и сеть:

Обратите внимание, что вы не включаете опцию --volumes ; следовательно, ваш том dbdata не удаляется.

Следующий вывод подтверждает, что ваши контейнеры и сеть были удалены:

Воссоздайте контейнеры:

Теперь вернитесь к форме информации об акулах:

Форма информации об акулах

Введите новую акулу по вашему выбору. В этом примере будут использоваться Whale Shark и Large :

Введите новую акулу

Нажав Submit , вы заметите, что новая акула была добавлена в коллекцию акул в вашей базе данных без потери уже введенных вами данных:

Полная коллекция акул

Теперь ваше приложение работает в контейнерах Docker с включенным сохранением данных и синхронизацией кода.

Заключение

Следуя этому руководству, вы создали настройку разработки для своего приложения Node с использованием контейнеров Docker. Вы сделали свой проект более модульным и переносимым, извлекая конфиденциальную информацию и отделяя состояние вашего приложения от кода приложения. Вы также настроили шаблонный файл docker-compose.yml , который можно редактировать по мере изменения потребностей и требований разработки.

По мере разработки вам может быть интересно узнать больше о разработке приложений для контейнерных и облачных рабочих процессов. Дополнительные сведения по этим темам см. в разделах «Архитектура приложений для Kubernetes» и «Модернизация приложений для Kubernetes».

Чтобы узнать больше о коде, используемом в этом руководстве, см. разделы «Как создать приложение Node.js с помощью Docker» и «Как интегрировать MongoDB с вашим приложением Node». Информацию о развертывании приложения Node с обратным прокси-сервером Nginx с использованием контейнеров см. в разделе «Как защитить контейнерное приложение Node.js с помощью Nginx, Let's Encrypt и Docker Compose».