Вступление
Если вы активно разрабатываете приложение, использование Docker может упростить ваш рабочий процесс и процесс развертывания приложения в рабочей среде. Работа с контейнерами в разработке предлагает следующие преимущества:
-
Среды единообразны, это означает, что вы можете выбирать языки и зависимости, которые вы хотите для своего проекта, не беспокоясь о системных конфликтах.
-
Среды изолированы, что облегчает поиск и устранение неполадок, а также помогает новым членам команды.
-
Среды являются переносимыми, что позволяет упаковывать и делиться своим кодом с другими.
Из этого туториала вы узнаете, как настроить среду разработки для приложения Node.js с помощью Docker. Вы создадите два контейнера - один для приложения Node, а другой для базы данных MongoDB - с помощью Docker Compose. Поскольку это приложение работает с Node и MongoDB, наша установка сделает следующее:
-
Синхронизируйте код приложения на хосте с кодом в контейнере, чтобы облегчить изменения во время разработки.
-
Убедитесь, что изменения в коде приложения работают без перезапуска.
-
Создайте базу данных, защищенную пользователем и паролем, для данных приложения.
-
Сохраните эти данные.
В конце этого урока у вас будет работающее информационное приложение по акулам, работающее на контейнерах Docker:
изображение: https: //assets.digitalocean.com/articles/node_docker_dev/persisted_data.png [Полная коллекция акул]
Предпосылки
Чтобы следовать этому уроку, вам понадобится:
-
Сервер разработки под управлением Ubuntu 18.04, а также пользователь без полномочий root с привилегиями
+ sudo +
и активным брандмауэром. Инструкции по их настройке см. В этом Initial руководстве по установке сервера. -
Docker установлен на вашем сервере, следуя шагам 1 и 2 из Как установить и Используйте Docker в Ubuntu 18.04.
-
Docker Compose установлен на вашем сервере, следуя шагу 1 из Как установить Docker Compose в Ubuntu 18,04.
Шаг 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 +