Контейнерное приложение Node.js для разработки с Docker Compose

Вступление

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

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

  • Среды изолированы, что облегчает поиск и устранение неполадок, а также помогает новым членам команды.

  • Среды являются переносимыми, что позволяет упаковывать и делиться своим кодом с другими.

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

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

  • Убедитесь, что изменения в коде приложения работают без перезапуска.

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

  • Сохраните эти данные.

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

изображение: https: //assets.digitalocean.com/articles/node_docker_dev/persisted_data.png [Полная коллекция акул]

Предпосылки

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

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

Первым шагом в создании этой установки будет клонирование кода проекта и изменение его файла https://docs.npmjs.com/files/package.json [+ package.json +], который включает в себя зависимости проекта. Мы добавим https://www.npmjs.com/package/nodemon [+ nodemon +] к https://docs.npmjs.com/files/package.json#devdependencies [+ devDependencies +] проекта, указав что мы будем использовать его во время разработки. Запуск приложения с помощью + nodemon + гарантирует, что оно будет автоматически перезапущено всякий раз, когда вы вносите изменения в свой код.

Сначала клонируйте https://github.com/do-community/nodejs-mongo-mongoose [+ nodejs-mongo-mongoose + репозиторий] из DigitalOcean Community аккаунта GitHub. . Этот репозиторий содержит код из установки, описанной в [How для интеграции MongoDB с приложением вашего узла, в котором объясняется, как интегрировать базу данных MongoDB с существующим приложением Node, используя Mongoose.

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

git clone https://github.com/do-community/nodejs-mongo-mongoose.git

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

cd

Откройте файл проекта + package.json, используя` + nano` или ваш любимый редактор:

nano package.json

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

~ / Node_project / package.json

...
"dependencies": {
   "ejs": "^2.6.1",
   "express": "^4.16.4",
   "mongoose": "^5.4.10"
 }



}

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

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

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

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

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

nano app.js

Внутри вы увидите определение + port + constant, а также https: / /expressjs.com/en/4x/api.html#app.listen [+ listen + function], которая использует эту константу для указания порта, который будет прослушивать приложение:

~ / Дома / node_project / app.js

...
const port = 8080;
...
app.listen(port, function () {
 console.log('Example app listening on port 8080!');
});

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

~ / Дома / node_project / app.js

...

...
app.listen(port, function () {
 console.log();
});

Наше новое определение констант динамически назначает + port +, используя значение, переданное во время выполнения, или + 8080 +. Точно так же мы переписали функцию + listen + для использования https://www.digitalocean.com/community/tutorials/how-to-work-with-strings-in-javascript#string-literals-and-string -values ​​[шаблонный литерал], который будет интерполировать значение порта при прослушивании соединений. Поскольку мы будем отображать наши порты в другом месте, эти изменения не позволят нам постоянно пересматривать этот файл при изменении среды.

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

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

nano db.js

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

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

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

  • Соединяется с базой данных, используя https://mongoosejs.com/docs/api.html#connection_Connection [+ mongoose.connect + метод].

Для получения дополнительной информации о файле, пожалуйста, смотрите https://www.digitalocean.com/community/tutorials/how-to-integrate-mongodb-with-your-node-application#step-3-%E2%80%94- создание схем и моделей мангуста [Шаг 3] из https://www.digitalocean.com/community/tutorials/how-to-integrate-mongodb-with-your-node-app[[How для интеграции MongoDB с вашим Приложение узла.

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

~ / Node_project / db.js

...
const MONGO_USERNAME = '';
const MONGO_PASSWORD = '';
const MONGO_HOSTNAME = '127.0.0.1';
const MONGO_PORT = '27017';
const MONGO_DB = '';
...

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

~ / Node_project / db.js

...
const {
 MONGO_USERNAME,
 MONGO_PASSWORD,
 MONGO_HOSTNAME,
 MONGO_PORT,
 MONGO_DB
} = process.env;
...

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

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

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

nano .env

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

~ / Node_project / .env

MONGO_USERNAME=
MONGO_PASSWORD=
MONGO_PORT=27017
MONGO_DB=

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

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

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

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

nano .dockerignore

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

~ / Node_project / .dockerignore

...
.gitignore

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

Файл + .gitignore + в этом репозитории уже содержит + .env +, но не стесняйтесь проверить, что он там есть:

nano .gitignore

~~ / node_project / .gitignore

...
.env
...

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

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

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

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

nano db.js

Вы увидите код, который мы добавили ранее, вместе с константой + url + для URI соединения Mongo и методом Mongoose + connect +:

~ / Node_project / db.js

...
const {
 MONGO_USERNAME,
 MONGO_PASSWORD,
 MONGO_HOSTNAME,
 MONGO_PORT,
 MONGO_DB
} = process.env;

const url = `mongodb://${MONGO_USERNAME}:${MONGO_PASSWORD}@${MONGO_HOSTNAME}:${MONGO_PORT}/${MONGO_DB}?authSource=admin`;

mongoose.connect(url, {useNewUrlParser: true});

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

~ / Node_project / db.js

...
const {
 MONGO_USERNAME,
 MONGO_PASSWORD,
 MONGO_HOSTNAME,
 MONGO_PORT,
 MONGO_DB
} = process.env;







...

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

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

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

~ / Node_project / db.js

...
mongoose.connect(url, {useNewUrlParser: true});

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

~ / Node_project / db.js

...

В случае успешного подключения наша функция записывает соответствующее сообщение; в противном случае он будет https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/catch [+ catch +] и зарегистрирует ошибку, что позволит нам устранить неполадки.

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

~ / Node_project / db.js

const mongoose = require('mongoose');

const {
 MONGO_USERNAME,
 MONGO_PASSWORD,
 MONGO_HOSTNAME,
 MONGO_PORT,
 MONGO_DB
} = process.env;

const options = {
 useNewUrlParser: true,
 reconnectTries: Number.MAX_VALUE,
 reconnectInterval: 500,
 connectTimeoutMS: 10000,
};

const url = `mongodb://${MONGO_USERNAME}:${MONGO_PASSWORD}@${MONGO_HOSTNAME}:${MONGO_PORT}/${MONGO_DB}?authSource=admin`;

mongoose.connect(url, options).then( function() {
 console.log('MongoDB is connected');
})
 .catch( function(err) {
 console.log(err);
});

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

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

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

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

Однако перед определением наших сервисов мы добавим в наш проект инструмент под названием https://github.com/Eficode/wait-for [+ wait-for +], чтобы наше приложение пыталось подключиться к нашей базе данных только один раз. задачи запуска базы данных завершены. Этот скрипт-обертка использует `+ netcat + ` для опроса, принимают ли конкретный хост и порт TCP-соединения. С его помощью вы можете контролировать попытки вашего приложения подключиться к вашей базе данных, проверяя, готова ли база данных принимать подключения.

Хотя Compose позволяет вам определять зависимости между службами, используя https://docs.docker.com/compose/compose-file/#depends_on [+ависимый_он + параметр], этот порядок основан на том, работает ли контейнер достаточно чем его готовность. Использование + disabled_on + не будет оптимальным для нашей установки, поскольку мы хотим, чтобы наше приложение подключалось только после завершения задач запуска базы данных, включая добавление пользователя и пароля в базу данных аутентификации + admin +. Для получения дополнительной информации об использовании + wait-for + и других инструментов для управления порядком запуска см. Соответствующие recommendations в документации Compose.

Откройте файл с именем + wait-for.sh +:

nano wait-for.sh

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

~ / Node_project / приложение / wait-for.sh

#!/bin/sh

# original script: https://github.com/eficode/wait-for/blob/master/wait-for

TIMEOUT=15
QUIET=0

echoerr() {
 if [ "$QUIET" -ne 1 ]; then printf "%s\n" "$*" 1>&2; fi
}

usage() {
 exitcode="$1"
 cat << USAGE >&2
Usage:
 $cmdname host:port [-t timeout] [-- command args]
 -q | --quiet                        Do not output any status messages
 -t TIMEOUT | --timeout=timeout      Timeout in seconds, zero for no timeout
 -- COMMAND ARGS                     Execute command with args after the test finishes
USAGE
 exit "$exitcode"
}

wait_for() {
 for i in `seq $TIMEOUT` ; do
   nc -z "$HOST" "$PORT" > /dev/null 2>&1

   result=$?
   if [ $result -eq 0 ] ; then
     if [ $# -gt 0 ] ; then
       exec "$@"
     fi
     exit 0
   fi
   sleep 1
 done
 echo "Operation timed out" >&2
 exit 1
}

while [ $# -gt 0 ]
do
 case "$1" in
   *:* )
   HOST=$(printf "%s\n" "$1"| cut -d : -f 1)
   PORT=$(printf "%s\n" "$1"| cut -d : -f 2)
   shift 1
   ;;
   -q | --quiet)
   QUIET=1
   shift 1
   ;;
   -t)
   TIMEOUT="$2"
   if [ "$TIMEOUT" = "" ]; then break; fi
   shift 2
   ;;
   --timeout=*)
   TIMEOUT="${1#*=}"
   shift 1
   ;;
   --)
   shift
   break
   ;;
   --help)
   usage 0
   ;;
   *)
   echoerr "Unknown argument: $1"
   usage 1
   ;;
 esac
done

if [ "$HOST" = "" -o "$PORT" = "" ]; then
 echoerr "Error: you need to provide a host and port to test."
 usage 2
fi

wait_for "$@"

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

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

chmod +x wait-for.sh

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

nano docker-compose.yml

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

~ / Node_project / Докер-compose.yml

version: '3'

services:
 nodejs:
   build:
     context: .
     dockerfile: Dockerfile
   image: nodejs
   container_name: nodejs
   restart: unless-stopped
   env_file: .env
   environment:
     - MONGO_USERNAME=$MONGO_USERNAME
     - MONGO_PASSWORD=$MONGO_PASSWORD
     - MONGO_HOSTNAME=db
     - MONGO_PORT=$MONGO_PORT
     - MONGO_DB=$MONGO_DB
   ports:
     - "80:8080"
   volumes:
     - .:/home/node/app
     - node_modules:/home/node/app/node_modules
   networks:
     - app-network
   command: ./wait-for.sh db:27017 -- /home/node/app/node_modules/.bin/nodemon app.js

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

  • + build +: определяет параметры конфигурации, включая + context + и + dockerfile +, которые будут применены, когда Compose создаст образ приложения. Если вы хотите использовать существующий образ из реестра, например Docker Hub, вы можете использовать https://docs.docker.com/compose/compose-file/#image [ Вместо этого + image + инструкция] с информацией о вашем имени пользователя, хранилище и теге изображения.

  • + context +: определяет контекст сборки для сборки образа - в данном случае это текущий каталог проекта.

  • + dockerfile +: Указывает + Dockerfile + в каталоге вашего текущего проекта, поскольку файл Compose будет использовать для создания образа приложения. Для получения дополнительной информации об этом файле см. Https://www.digitalocean.com/community/tutorials/how-to-build-a-node-js-application-with-docker[Как создать приложение Node.js с Docker].

  • + image +, + container_name +: они применяют имена к изображению и контейнеру.

  • + restart +: определяет политику перезапуска. По умолчанию это + no +, но мы установили перезапуск контейнера, если он не остановлен.

  • + env_file +: это говорит Compose, что мы хотели бы добавить переменные окружения из файла с именем + .env +, расположенного в нашем контексте сборки.

  • + environment +: использование этой опции позволяет вам добавить настройки соединения Mongo, которые вы определили в файле + .env +. Обратите внимание, что мы не устанавливаем + NODE_ENV + в + development +, поскольку это Express’s https://github.com/expressjs/express/blob/dc538f6e810bd462c98ee7e6aae24c64d4b1da93/lib/application. js # L71 [по умолчанию] поведение, если + NODE_ENV + не установлено. При переходе в производство вы можете установить для этого параметра значение «+ production » на https://expressjs.com/en/advanced/best-practice-performance.html#set-node_env-to-production[enibility view caching и меньше verbose error Сообщения]. Также обратите внимание, что мы указали контейнер базы данных ` db +` в качестве хоста, как обсуждалось в https://www.digitalocean.com/community/tutorials/containerizing-a-node-js-application-for-development-with- docker-compose # step-2-% E2% 80% 94-настройка вашего приложения для работы с контейнерами [Шаг 2].

  • + ports +: это сопоставляет порт + 80 + на хосте с портом + 8080 + на контейнере.

  • + volume +: мы включаем два типа крепления:

  • Первым является bind mount, который монтирует код нашего приложения на хосте в каталог + / home / node / app + контейнера. Это облегчит быструю разработку, так как любые изменения, которые вы вносите в код вашего хоста, будут немедленно помещены в контейнер.

  • Второй - это volume, + node_modules +. Когда Docker запускает инструкцию + npm install +, указанную в приложении + Dockerfile +, + npm + создаст новый +node_modules+ Каталог ` в контейнере, содержащий пакеты, необходимые для запуска приложения. Однако только что созданная привязка будет скрывать этот недавно созданный каталог `+ node_modules +

Related