Мнение о рабочем процессе разработки Docker для проектов Node.js — часть 2

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

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

Структура каталогов и файлы

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

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

└── Main_Project_Directory/
    ├── server-code/
    │   ├── server.js
    │   ├── package.json
    │   └── ... (All your other source code files)
    ├── .gitignore
    ├── .dockerignore
    ├── Dockerfile
    ├── docker-compose.yml
    └── README.md
Вход в полноэкранный режим Выход из полноэкранного режима

Dockerfile

Вот полный Dockerfile

# Base node images can be found here: https://hub.docker.com/_/node?tab=description&amp%3Bpage=1&amp%3Bname=alpine
ARG NODE_IMAGE=node:16.17-alpine

#####################################################################
# Base Image
#
# All these commands are common to both development and production builds
#
#####################################################################
FROM $NODE_IMAGE AS base
ARG NPM_VERSION=npm@8.18.0

# While root is the default user to run as, why not be explicit?
USER root

# Run tini as the init process and it will clean zombie processes as needed
# Generally you can achieve this same effect by adding `--init` in your `docker RUN` command
# And Nodejs servers tend not to spawn processes, so this is belt and suspenders
# More info: https://github.com/krallin/tini
RUN apk add --no-cache tini
# Tini is now available at /sbin/tini
ENTRYPOINT ["/sbin/tini", "--"]

# Upgrade some global packages
RUN npm install -g $NPM_VERSION

# Specific to your framework
#
# Some frameworks force a global install tool such as aws-amplify or firebase.  Run those commands here
# RUN npm install -g firebase

# Create space for our code to live
RUN mkdir -p /home/node/app && chown -R node:node /home/node/app
WORKDIR /home/node/app

# Switch to the `node` user instead of running as `root` for improved security
USER node

# Expose the port to listen on here.  Express uses 8080 by default so we'll set that here.
ENV PORT=8080
EXPOSE $PORT

#####################################################################
# Development build
#
# These commands are unique to the development builds
#
#####################################################################
FROM base AS development

# Copy the package.json file over and run `npm install`
COPY server-code/package*.json ./
RUN npm install

# Now copy rest of the code.  We separate these copies so that Docker can cache the node_modules directory
# So only when you add/remove/update package.json file will Docker rebuild the node_modules dir.
COPY server-code ./

# Finally, if the container is run in headless, non-interactive mode, start up node
# This can be overridden by the user running the Docker CLI by specifying a different endpoint
CMD ["npx", "nodemon","server.js"]

#####################################################################
# Production build
#
# These commands are unique to the production builds
#
#####################################################################
FROM base AS production

# Indicate to all processes in the container that this is a production build
ARG NODE_ENV=production
ENV NODE_ENV=${NODE_ENV}

# Now copy all source code
COPY --chown=node:node server-code ./
RUN npm install && npm cache clean --force

# Finally, if the container is run in headless, non-interactive mode, start up node
# This can be overridden by the user running the Docker CLI by specifying a different endpoint
CMD ["node","server.js"]
Вход в полноэкранный режим Выход из полноэкранного режима

Dockerfile: Управление версиями и многоступенчатость

Давайте начнем с верхней части файла:

# Base node images can be found here: https://hub.docker.com/_/node?tab=description&amp%3Bpage=1&amp%3Bname=alpine
ARG NODE_IMAGE=node:16.17-alpine

#####################################################################
# Base Image
#
# All these commands are common to both development and production builds
#
#####################################################################
FROM $NODE_IMAGE AS base
ARG NPM_VERSION=npm@8.18.0
Войти в полноэкранный режим Выход из полноэкранного режима

Во-первых, мы знаем, что версии Node.js и npm меняются со временем. Это критические зависимости, поэтому мы поместили их в начало файла, чтобы разработчики могли обновить их при запуске нового проекта.

Также обратите внимание, что это [многоступенчатый Dockerfile (https://docs.docker.com/develop/develop-images/multistage-build/)]. Это сделано для того, чтобы разместить один файл как для разработки, так и для производственных сборок. Итак, мы имеем:

  1. Общая базовая стадия
  2. стадия development
  3. этап production.

Dockerfile: Базовая стадия

Вот команды в Dockerfile для стадии base.

Мы явно устанавливаем пользователя root для следующих нескольких команд

# While root is the default user to run as, why not be explicit?
USER root
Вход в полноэкранный режим Выйти из полноэкранного режима

Мы добавляем обработчик для перехвата сигналов завершения от системы, чтобы изящно завершить работу. Хотя это не очень важно для контейнеров Node.js, это хорошая практика на случай, если мы захотим использовать этот Dockerfile для других языков, таких как Python. Более подробную информацию можно найти на сайте https://github.com/krallin/tini.

# Run tini as the init process and it will clean zombie processes as needed
# Generally you can achieve this same effect by adding `--init` in your `docker RUN` command
# And Nodejs servers tend not to spawn processes, so this is belt and suspenders
# More info: https://github.com/krallin/tini
RUN apk add --no-cache tini
# Tini is now available at /sbin/tini
ENTRYPOINT ["/sbin/tini", "--"]
Вход в полноэкранный режим Выход из полноэкранного режима

Здесь мы устанавливаем указанную версию npm. У нас также есть возможность установить другие пакеты, которые должны быть глобальными, например aws-amplify или firebase.

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

# Upgrade some global packages
RUN npm install -g $NPM_VERSION

# Specific to your framework
#
# Some frameworks force a global install tool such as aws-amplify or firebase.  Run those commands here
# RUN npm install -g firebase
Вход в полноэкранный режим Выход из полноэкранного режима

Создайте произвольное место в контейнере для хранения вашего кода. Вы можете называть это как угодно, но мы будем придерживаться традиционного подхода Linux для единообразия. Обратите внимание, что мы также должны изменить право собственности на эту директорию для соответствующего пользователя (называемого node), чтобы пользователь мог правильно создавать/читать/писать файлы в этой директории.

Таким образом, ваше приложение будет жить в каталоге /home/node/app. Наконец, переключите последующие инструкции сборки на выполнение node. Это соответствует рекомендуемым лучшим практикам

# Create space for our code to live
RUN mkdir -p /home/node/app && chown -R node:node /home/node/app
WORKDIR /home/node/app

# Switch to the `node` user instead of running as `root` for improved security
USER node
Войдите в полноэкранный режим Выйти из полноэкранного режима

Укажите порт, который будет прослушивать приложение. Это настраивается, но должно быть изменено в нескольких файлах.

# Expose the port to listen on here.  Express uses 8080 by default so we'll set that here.
ENV PORT=8080
EXPOSE $PORT
Войти в полноэкранный режим Выйти из полноэкранного режима

Dockerfile: Стадия разработки

Это команды в Dockerfile для стадии development. При сборке вы должны указать стадию с помощью флага: --target=development.

Сначала мы копируем файл package.json из каталога server-code в рабочий каталог /home/node/app (заданный выше с помощью WORKDIR). Мы также включаем любые файлы package-lock.json.

#####################################################################
# Development build
#
# These commands are unique to the development builds
#
#####################################################################
FROM base AS development

# Copy the package.json file over and run `npm install`
COPY server-code/package*.json ./
Вход в полноэкранный режим Выход из полноэкранного режима

Далее мы запускаем npm install, чтобы установить все зависимости приложения, включая зависимости разработки.

RUN npm install
Вход в полноэкранный режим Выход из полноэкранного режима

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

И, наконец, мы запускаем npx nodemon server.js для запуска приложения, используя nodemon для перезагрузки кода при его изменении.

# Now copy rest of the code.  We separate these copies so that Docker can cache the node_modules directory
# So only when you add/remove/update package.json file will Docker rebuild the node_modules dir.
COPY server-code ./

# Finally, if the container is run in headless, non-interactive mode, start up node
# This can be overridden by the user running the Docker CLI by specifying a different endpoint
CMD ["npx", "nodemon","server.js"]
Вход в полноэкранный режим Выход из полноэкранного режима

Dockerfile: Стадия производства

Это команды в Dockerfile для стадии development. При сборке вы должны указать стадию с помощью флага: --target=development.

#####################################################################
# Production build
#
# These commands are unique to the production builds
#
#####################################################################
FROM base AS production
Вход в полноэкранный режим Выход из полноэкранного режима

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

# Indicate to all processes in the container that this is a production build
ARG NODE_ENV=production
ENV NODE_ENV=${NODE_ENV}
Вход в полноэкранный режим Выйдите из полноэкранного режима

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

# Now copy all source code
COPY --chown=node:node server-code ./
RUN npm install && npm cache clean --force
Вход в полноэкранный режим Выход из полноэкранного режима

Здесь мы запускаем приложение, указывая node запустить приложение server.js.

# Finally, if the container is run in headless, non-interactive mode, start up node
# This can be overridden by the user running the Docker CLI by specifying a different endpoint
CMD ["node","server.js"]
Вход в полноэкранный режим Выход из полноэкранного режима

Режим разработки без Docker Compose

Мы покажем, как использовать этот Dockerfile, используя только интерфейс командной строки (CLI). В следующем разделе мы упростим этот процесс с помощью Docker Compose.

  1. Если вы запускаете контейнер впервые, или если вы изменили какие-либо зависимости пакетов, то выполните команду run:

    docker build . -t mynodeapp:DEV --target=development
    

    Это соберет образ, используя инструкции стадии разработки из Dockerfile. Обратите внимание, что вы должны его как-то назвать, поэтому мы используем mynodeapp с версией DEV. Использование DEV помогает избежать развертывания на производстве, так как в нем не используется семантическое версионирование.

  2. Чтобы запустить контейнер, введите следующую команду:

    docker run -ti --rm -p 8080:8080 -v "$(pwd)/server-code:/home/node/app" -v /home/node/app/node_modules mynodeapp:DEV
    

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

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

Примечания

  • Если вы вносите какие-либо изменения в зависимые пакеты, то вам придется выполнить команду docker build, как показано выше. Выполняйте сборку каждый раз, когда вы добавляете, обновляете или удаляете пакет.
  • Мы предполагаем, что node будет работать на порту 8080. Если для вашего проекта это не так, смело меняйте его, но не забудьте изменить его везде.

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

  • docker run: Это основная команда Docker для получения образа контейнера и его запуска.
  • -ti: Это дает команду Docker запустить контейнер в интерактивном режиме, чтобы вы могли видеть консоль вывода.
  • --rm: После выхода из контейнера нажатием Ctrl-c этот флаг указывает Docker на очистку контейнера.
  • -p 8080:8080: Убедитесь, что порт 8080 на контейнере сопоставлен с портом 8080 на вашей локальной машине, чтобы вы могли использовать http://localhost:8080.
  • -v "$(pwd)/server-code:/home/node/app": Это отображает каталог server-code (вместе с вашим исходным кодом) в каталог контейнера /home/node/app. Таким образом, ваш исходный код и все, что находится в каталоге server-code, будет доступно в контейнере.
  • -v /home/node/app/node_modules: Это специальная команда, которая исключает каталог node_modules на вашей локальной машине и вместо него сохраняет каталог node_modules контейнера, который был создан на этапе сборки. Это важно, потому что node_modules на вашей локальной машине может быть полон пакетов, специфичных для операционной системы локальной машины. А поскольку нам нужны пакеты для контейнера, этот флаг делает этот каталог приоритетным.
  • mynodeapp:DEV: Это то, как вы хотите назвать образ вашего контейнера. Мы помечаем этот образ тегом DEV, чтобы убедиться, что вы случайно не развернете эту версию.

Режим разработки упрощен с помощью Docker Compose

Команды для включения этого рабочего процесса очень длинные, сложные и трудно запоминающиеся. Чтобы упростить этот рабочий процесс, у нас есть несколько вариантов. Одним из распространенных вариантов является создание сценариев оболочки для вышеуказанных команд. Это работает, но зависит от операционной системы (например, для Windows потребуется иное решение, чем для большинства оболочек).

Мы можем использовать Docker Compose, чтобы упростить наш рабочий процесс. Docker Compose — это инструмент, позволяющий собрать все команды в файл, который сделает всю работу за вас. Он также позволяет одновременно запускать несколько контейнеров. Это полезно, если вам нужно запустить одновременно веб-сервер и сервер базы данных для локальной разработки. Этот сценарий выходит за рамки данной статьи.

Обратите внимание, что Docker Compose, который мы собираемся показать здесь, предназначен только для разработки.

Сначала создайте файл docker-compose.yml:

services:
  app:
    build:
      context: .
      target: development
      args:
        - NODE_ENV=development
    environment:
        - NODE_ENV=development
    ports:
      - "8080:8080"
    volumes:
      - ./server-code:/home/node/app
      - /home/node/app/node_modules
Войти в полноэкранный режим Выход из полноэкранного режима

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

docker compose build
Войти в полноэкранный режим Выйти из полноэкранного режима

И очень легко запустить:

docker compose up
Войти в полноэкранный режим Выйти из полноэкранного режима

И когда вы закончите

docker compose down
Войти в полноэкранный режим Выйти из полноэкранного режима

Заключение

И это все. Не забудьте про .dockerignore и опционально .gitignore, но вы можете настроить их по своему усмотрению.

Оцените статью
devanswers.ru
Добавить комментарий