Представьте, что вам приходится многократно вводить длинные команды для таких задач, как создание и запуск контейнеров Docker. Это было бы утомительно и отнимало бы много времени.
Однако, определяя сценарии npm, мы можем легко запускать эти задачи с помощью коротких и четких команд. Это оптимизирует наш рабочий процесс и снижает усилия, необходимые для выполнения общих задач разработчиков, делая процесс разработки более эффективным и приятным.
Скрипты Npm обеспечивают удобный и эффективный подход к автоматизации повторяющихся задач, повышая производительность и позволяя нам сосредоточиться на нашем основном бизнесе.
В этом пошаговом руководстве мы создадим простой контейнер Docker и поэкспериментируем с различными сценариями npm, чтобы увидеть, как оптимизировать процесс сборки.
Начиная
Настроим репозиторий. Затем мы поэкспериментируем с некоторыми скриптами npm, чтобы упростить разработку.
В новой папке запустите следующее:
npm init -y
Флаг -y
означает «да» или «да для всех». Когда вы используете его, он позволяет вам инициализировать новый файл package.json
со всеми значениями по умолчанию без каких-либо запросов пользователя. Это означает, что вы автоматически принимаете все настройки по умолчанию для вашей конфигурации package.json
.
Теперь давайте установим Express в качестве зависимости. Нам это понадобится в файле server.js, который мы создадим заранее.
npm i express
Теперь создайте новый файл —server.js
в корне этого репозитория. Чтобы сосредоточиться в этом пошаговом руководстве на сценариях npm, мы создадим базовое приложение Node.js, используя платформу Express, чтобы создать простой HTTP-сервер, который выполняет следующие действия:
- Импортирует модуль Express и создает приложение Express.
- Устанавливает порт сервера на порт
4005
. Мы также можем использовать переменную окруженияPORT
с помощьюprocess.env
или по умолчанию 4005. - Определяет сообщение, которое будет отправлено в качестве ответа клиентам, когда они обращаются к корневому пути ('/').
- Настраивает маршрут для обработки запросов HTTP GET к корневому пути. При доступе он записывает сообщение в консоль сервера и отправляет определенное сообщение в качестве ответа.
- Запускает сервер на указанном порту, и как только сервер запускается, он записывает определенное сообщение в консоль.
server.js
будет выглядеть так:
const express = require('express'); const app = express(); const port = process.env.PORT || 4005; let runningMessage = 'Server is running on port ' + port; app.get('/', (req, res) => { console.log('API was requested'); res.send(runningMessage); } ); const server = app.listen(port, () => { console.log(runningMessage); });
Теперь давайте создадим Dockerfile, который устанавливает образ Docker для нашего приложения. Он устанавливает зависимости приложения, копирует код приложения и указывает команду по умолчанию для запуска приложения Node.js с использованием node server.js
при открытии порта 4005 внутри контейнера. Чтобы запустить приложение, нам нужно собрать образ из этого Dockerfile, а затем создать и запустить контейнер на основе этого образа. В корневом каталоге создайте Dockerfile:
FROM node:18.10.0 # Create app directory WORKDIR /usr/src/app # Install app dependencies COPY package*.json ./ RUN npm install # Bundle app source COPY . . # Expose port EXPOSE 4005 # Run app CMD [ "node", "server.js" ]
Создайте .dockerignore
в корневой папке:
node_modules npm-debug.log
Файл .dockerignore
необходим для управления содержимым образа Docker, уменьшения его размера, повышения безопасности и предотвращения непреднамеренного раскрытия конфиденциальной информации.
Теперь, когда все настроено, давайте добавим два основных сценария в файл package.json
. файл. Под объектом scripts
добавьте следующие сценарии npm:
"docker:build": "docker build -t npm_docker .", "docker:run": "docker run -p 4005:4005 -d --name npm_d npm_docker",
Флаг -t
в команде docker:build
используется для пометки изображения именем (в данном случае npm_docker
). Это имя будет использоваться для ссылки на образ при запуске контейнеров на его основе.
В команде docker:run
-p 4005:4005
сопоставляет порт 4005 с хоста с портом 4005 в контейнере. Это позволяет отправлять трафик с хост-компьютера в приложение, работающее внутри контейнера. -d
указывает, что контейнер работает в автономном режиме, то есть он будет работать в фоновом режиме как демон. Флаг --name
дает контейнеру имя (npm_d
), которое можно использовать для управления и взаимодействия с контейнером позже.
Соглашение об именовании
Здесь мы используем соглашение об именах, такое как category:name
, поскольку оно обеспечивает ясность, организацию и согласованность в файле package.json
.
В контексте docker:build
и docker:run
это означает намерение и отношение скриптов к операциям Docker. Префикс docker:
явно указывает на то, что эти скрипты относятся к задачам Docker.
Это соглашение помогает избежать конфликтов имен, повышает удобочитаемость и позволяет легко группировать и выполнять связанные сценарии. Следуя этому соглашению, разработчики могут легко понимать и управлять сборкой Docker и выполнением задач в рамках проекта.
Чтобы узнать больше о соглашениях об именах, прочитайте это: package-json-conventions.
Задачи разработки Docker
Проблема возникает с Docker, когда нам нужно обновить код приложения в работающем контейнере. В отличие от традиционных настроек разработки, мы не можем просто перестроить образ Docker и повторно запустить его, поскольку существующий контейнер уже запущен.
С Docker мы сначала должны остановить запуск контейнера, затем удалить его, а затем перестроить и повторно запустить новый контейнер, используя новый образ, всякий раз, когда мы вносим изменения в исходный код нашего приложения.
Для этого добавим еще два скрипта. мы назовем его docker:stop
и docker:remove
, чтобы он соответствовал нашему соглашению об именах.
Теперь обновитеscripts
в package.json
следующим образом:
... "scripts": { "docker:stop": "docker container stop npm_d", "docker:remove": "docker container rm npm_d", "docker:build": "docker build -t npm_docker .", "docker:run": "docker run -p 4005:4005 -d --name npm_d npm_docker", "test": "echo \"Error: no test specified\" && exit 1" }, ...
Теперь, когда вы вносите какие-либо изменения в свой server.js
, вы можете собрать только что обновленный контейнер, но вы не можете запустить его, так как контейнер с таким именем уже запущен. Следовательно, вам обязательно нужно запустить docker:stop
и docker:remove
перед запуском команды docker:run
.
Автоматизация задач разработки Docker
Процесс остановки работающего контейнера, его удаления, перестроения образа Docker и создания нового контейнера всякий раз, когда мы вносим изменения в код, — не самый эффективный рабочий процесс. Однако у нас есть преимущество, поскольку Docker предоставляет аргумент, который позволяет нам активно отслеживать изменения кода и автоматически обновлять контейнер, не выполняя ручные действия.
Таким образом, хотя скрипты NPM не являются ограничивающим фактором в этом сценарии, Способность Docker активно прослушивать изменения кода и отображать их в контейнере предоставляет нам более эффективный и удобный способ разработки и тестирования наших приложений в среде Dockerized.
Именно тогда на помощь приходит другой аргумент, доступный через инструмент Docker CLI, который называется «монтирование тома».
Давайте быстро обновим наш scripts
в package.json
и посмотрим, что делает монтирование тома.
... "scripts": { "docker:stop": "docker container stop npm_d", "docker:remove": "docker container rm npm_d", "docker:build": "docker build -t npm_docker .", "docker:run": "docker run -p 4005:4005 -d --name npm_d npm_docker", "docker:run:dev": "docker run -p 4005:4005 -d -v %cd%:/usr/src/app --name npm_d npm_docker", "test": "echo \"Error: no test specified\" && exit 1" }, ...
Сценарий в docker:run:dev
почти аналогичен сценарию в docker:run
, за исключением аргумента -v. Эта опция монтирует текущий рабочий каталог (%cd%
) с хоста в каталог /usr/src/app
внутри контейнера.
ПРИМЕЧАНИЕ. Путь слева от двоеточия (:
) — это путь к расположению вашего исходного кода. На компьютере с Windows мы можем использовать %cd%
, а на компьютере с Linux мы можем использовать $(pwd)
для получения рабочего каталога.
Путь справа от двоеточия (:
) — это путь к каталогу внутри контейнера Docker. куда вы хотите смонтировать или отобразить исходный код. Этот процесс накладывает ваш локальный исходный код с хост-компьютера на контейнер, обеспечивая динамические обновления всякий раз, когда вы вносите изменения в свой код.
Когда вы используете -v
для монтирования вашего локального исходного кода в контейнер, это позволяет отражать изменения внутри контейнера без его пересборки. Однако это делает ваш код доступным только внутри контейнера; он не решает проблему обнаружения и автоматического перезапуска сервера Node.js всякий раз, когда в вашем коде происходят изменения. Чтобы решить эту проблему, мы будем использовать nodemon
.
Nodemon — это утилита, которая отслеживает ваше приложение Node.js на наличие изменений в файлах. Он автоматически перезапускает сервер Node.js при обнаружении любых изменений в вашей кодовой базе. Эта функция имеет решающее значение для процесса разработки, поскольку избавляет вас от необходимости вручную останавливать и перезапускать сервер каждый раз, когда вы вносите изменения.
Теперь в Dockerfile
нам нужно внести два изменения:
- Чтобы обеспечить доступность Nodemon в контейнере, мы устанавливаем его глобально. В отличие от разработки, где Nodemon обычно устанавливается как dev-зависимость, здесь он нужен для производственных систем внутри контейнера. При глобальной установке Nodemon становится инструментом командной строки, доступным во всем контейнере. Таким образом, мы можем использовать Nodemon для запуска и мониторинга нашего приложения на предмет изменений в рабочей среде.
- Чтобы запустить наше приложение с помощью Nodemon внутри контейнера, мы заменим команду
node
наnodemon
для запускаserver.js
. Кроме того, мы добавим аргумент-L
, чтобы указать устаревший режим наблюдения, поскольку мы работаем внутри контейнера.
Обновленный Dockerfile
должен выглядеть так:
FROM node:18.10.0 # Create app directory WORKDIR /usr/src/app # Install app dependencies COPY package*.json ./ RUN npm install RUN npm install -g nodemon # Bundle app source COPY . . # Expose port EXPOSE 4005 # Run app CMD [ "nodemon", "-L", "server.js" ]
Благодаря этому изменению наш Dockerfile теперь правильно настроен для использования Nodemon для запуска приложения и отслеживания изменений кода.
Подведение итогов
Перед запуском docker:run:dev
нам нужно убедиться, что нет запущенного контейнера с таким же именем. Мы можем запустить сценарии docker:stop
и docker:remove
, которые мы определили выше, а затем docker:build
перед запуском docker:run:dev
.
Здесь мы можем использовать операторы &&
и &
. &&
работает как логическое И. Выполняется первая команда, и если она завершается успешно (возвращает код выхода 0), только тогда запускается вторая команда. Такое поведение гарантирует, что последующие команды будут выполняться условно в зависимости от успеха предыдущих. Он позволяет создать последовательность команд, в которой каждый шаг зависит от успешного завершения предыдущего шага.
Использование &
в сценариях npm инициирует параллельное выполнение, позволяя второй команде выполняться независимо в фоновом режиме, даже если первая команда все еще выполняется или обнаруживает ошибки.
Теперь, зная это, попробуем изменить scripts
в package.json
:
... "scripts": { "docker:stop": "docker container stop npm_d", "docker:remove": "docker container rm npm_d", "docker:build": "docker build -t npm_docker .", "docker:run:dev": "docker run -p 4005:4005 -d -v %cd%:/usr/src/app --name npm_d npm_docker", "docker:run": "npm run docker:stop & npm run docker:remove & npm run docker:build && npm run docker:run:dev" }, ...
Здесь мы использовали &
между npm run docker:stop
, npm run docker:remove
и npm run docker:build
. Это связано с тем, что даже при любом новом прогоне контейнера с заданным именем (в нашем случае npm_d
) не существует, то цепочка команд не должна останавливаться, а процесс сборки должен продолжаться.
Однако между npm run docker:build
и npm:docker:run
используется &&
. Это связано с тем, что если по какой-либо причине процесс сборки завершится неудачно, то команда запуска не должна выполняться, и цепочка команд завершается.
Теперь давайте попробуем упростить это еще больше. Мы будем использовать пакет npm-run-all
для последовательного запуска npm-скриптов. Установите npm-run all
с помощью следующей команды:
npm i npm-run-all
Теперь обратите внимание на изменение scripts
из package.json
:
... "scripts": { "docker:stop": "docker container stop npm_d || true", "docker:remove": "docker container rm npm_d || true", "docker:build": "docker build -t npm_docker .", "docker:run": "docker run -p 4005:4005 -d -v %cd%:/usr/src/app --name npm_d npm_docker", "docker:run:dev": "npm-run-all docker:*" }, ...
Обратите внимание, как мы использовали docker:*
в docker:run:dev
. npm-run-all
предоставляет универсальный и мощный способ управления и контроля выполнения скриптов npm, снижая сложность и повышая эффективность процесса сборки.
Приведенная выше команда означает, что все сценарии, начинающиеся с префикса docker:
, будут выполняться последовательно. Здесь нам пригодилось наше соглашение об именах.
Здесь || true
гарантирует, что даже если docker:stop
и docker:remove
выдают ошибку, цепочка команд не должна останавливаться, а выполнение переходит к команде сборки.
Вот и все, просто запустив docker:run:dev
, вы будете готовы к работе. Используя сценарии npm, вы можете легко управлять своими контейнерами Docker и другими повторяющимися задачами, что позволяет вам больше сосредоточиться на своем основном бизнесе и меньше на ручных настройках.
Дополнительные материалы на PlainEnglish.io.
Подпишитесь на нашу бесплатную еженедельную рассылку новостей. Подпишитесь на нас в Twitter, LinkedIn, YouTube и Discord .