Site icon 8HOST.COM

Контейнеризация приложения Node.js для разработки: определение сервисов с помощью Docker Compose

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

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

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

Примечание: Прежде чем приступить к работе, выполните первую часть этой серии – мануал Контейнеризация приложения Node.js для разработки: базовая настройка.

1: Определение сервисов с помощью Docker Compose

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

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

Читайте также: Использование Netcat для создания и тестирования соединений TCP и UDP на VPS

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

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

nano 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, добавив в файл следующий код:

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 включает в себя следующие параметры:

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

...
db:
image: mongo:4.1.8-xenial
container_name: db
restart: unless-stopped
env_file: .env
environment:
- MONGO_INITDB_ROOT_USERNAME=$MONGO_USERNAME
- MONGO_INITDB_ROOT_PASSWORD=$MONGO_PASSWORD
volumes:
- dbdata:/data/db
networks:
- app-network

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

Мы также добавили сервис db в сеть app-network с опцией network.

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

...
networks:
app-network:
driver: bridge
volumes:
dbdata:
node_modules:

Пользовательская мостовая сеть 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 будет выглядеть так:

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
db:
image: mongo:4.1.8-xenial
container_name: db
restart: unless-stopped
env_file: .env
environment:
- MONGO_INITDB_ROOT_USERNAME=$MONGO_USERNAME
- MONGO_INITDB_ROOT_PASSWORD=$MONGO_PASSWORD
volumes:
- dbdata:/data/db
networks:
- app-network
networks:
app-network:
driver: bridge
volumes:
dbdata:
node_modules:

Сохраните и закройте файл.

Теперь все готово к запуску приложения.

2: Тестирование приложения

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

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

docker-compose up -d

Вы увидите вывод, подтверждающий, что ваши услуги были созданы:

...
Creating db ... done
Creating nodejs ... done

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

docker-compose logs

Вы увидите что-то вроде такого вывода, если все работает правильно:

...
nodejs    | [nodemon] starting `node app.js`
nodejs    | Example app listening on 8080!
nodejs    | MongoDB is connected
...
db        | 2019-02-22T17:26:27.329+0000 I ACCESS   [conn2] Successfully authenticated as principal 8host on admin

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

docker-compose ps

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

Name               Command               State          Ports
----------------------------------------------------------------------
db        docker-entrypoint.sh mongod      Up      27017/tcp
nodejs   ./wait-for.sh db:27017 --  ...   Up      0.0.0.0:80->8080/tcp

Когда сервисы запустятся, вы можете открыть в браузере http://your_server_ip. Вы увидите целевую страницу, которая выглядит следующим образом:

Want to Learn About Sharks?
Are you ready to learn about sharks?
Get Shark Info

Нажмите на кнопку Get Shark Info. Вы увидите страницу Shark Info с маленькой формой ввода Enter your shark в правой части экрана. Форма состоит из полей Shark Name и Shark Character.

Чтобы убедиться, что все работает правильно, добавьте в это поле информацию о любой акуле. Например, можно попробовать ввести в первое поле Megalodon Shark, а во второе – Ancient

Нажмите на кнопку Submit. Вы увидите, что добавленная вами информация появилась на странице.

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

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

docker-compose down

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

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

Stopping nodejs ... done
Stopping db     ... done
Removing nodejs ... done
Removing db     ... done
Removing network node_project_app-network

Восстановите контейнеры:

docker-compose up -d

Теперь вернитесь к форме ввода пользовательских данных. Введите новые данные о другой акуле, например, Whale Shark и Large.

Как только вы нажмете Submit, вы увидите, что новая акула была добавлена в коллекцию в базе данных, но предыдущие данные при этом не были потеряны.

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

Заключение

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

Читайте также:

В дальнейшей работе над приложениями вам могут понадобиться знания о рабочих процессах Cloud Native.