Легенда
Когда приходит новый разработчик, то перед ним встает задача запустить окружение для разработки. И до недавнего времени - это часто становилось похожим на танцы с бубном. Поставь 10 разных пакетов определенных версий, а еще окажется что твой собственный pet-проект требует другие версии или это даже может быть другой проект в рамках той же работы. Каждый раз из этой ситуации специалист выходил по своему, но основной проблемой помимо временных затрат на эту конфигурацию - оставалось, то что работоспособность от разработчика к разработчику или runner не гарантировалась.
К счастью - эта проблема решена в современном мире разработки, если не полностью, то в большей мере. Нам на выручку пришел Docker.
Данная статья сделана для FrontEnd разработчиков, которые не знакомы с Docker. Мы разберем некоторые вопросы связанные с оптимизацией трафика для запуска и затронем немного вопросы безопасности.
По итогу, мы:
-
Научимся работать с Docker для локальной разработки
-
Создадим артефакт для production, который в будущем сможет использовать DevOps.
-
В конце немного поговорим о безопасности
Установка Docker
Установка докер довольна простая и лучше всего описана в официальной документации.
Также нам для работы понадобится docker-compose, например для MacOS при установке Docker Desktop он также автоматом установится, в linux системах же его придется ставить отдельно.
Справка по CLI Docker
docker и docker-compose поддерживают флаг --help для корня и для команд
docker --helpdocker ps --helpdocker-compose --helpdocker-compose up --help
Особенности проекта в статье
Пусть наш FE проект имеет 2 скрипта в package.json: "dev" для разработки и "build" для создания production кода.
В статье мы рассмотрим версию где результатом сборки являются статические файлы. Для node сервера все будет немного сложнее, но не сильно.
GitHub: тут
TL;DR; Сразу код
docker-compose.dev.yml
version: "3.9"services: web: image: node:15.8-alpine3.11 ports: - "3000:3000" volumes: - ".:/app" environment: NODE_ENV: development HOST: 0.0.0.0 working_dir: /app command: sh -c "cd /app; yarn install; yarn run dev --host 0.0.0.0"
version: "3.9"services: web: build: context: . dockerfile: Dockerfile ports: - "80:80" environment: NODE_ENV: production
FROM node:15.8-alpine3.11 AS buildWORKDIR /appCOPY package.json package.jsonRUN yarn installCOPY . .RUN yarn buildFROM nginx:1.19-alpineCOPY --from=build /app/dist /opt/siteCOPY nginx.conf /etc/nginx/nginx.conf
worker_processes auto;events { worker_connections 8000; multi_accept on;}http { include /etc/nginx/mime.types; default_type application/octet-stream; server { listen 80; listen [::]:80 default ipv6only=on; root /opt/site; location / { try_files $uri $uri/ /index.html; } }}
Давайте разбираться, что произошло:
Этап разработки:
docker-compose.dev.yml - Этого фала достаточно для разработки, остальные файлы используются для создания production артефакта
1 шаг.Объявляем сервис web. Выбираем образ который
будет делать сборку: node:15.8-alpine3.11
Лучше всего
детализировать используемые версии, стоит их держать точно такими
же, как и у сборщика production build.
2 шаг.Выбираем порты, которые будут в нашей хост системе отражать порты из запущенного контейнера.
3 шаг.Монтируем все из текущей директории в контейнер. Это нужно, чтобы локальные изменения сразу вызывали rebuild, однако это порождает некоторые проблемы с безопасностью разберем их в конце статьи.
4 шаг.environment позволяет задать переменные окружения, которые интересны в твоем конкретном случае.
5 шаг.working_dir определяет рабочую директорию внутри контейнера, относительно которой будут исполнены последующие команды.
6 шаг.Устанавливаем зависимости и стартуем разработку
(webpack-dev-server с явным заданием хоста): command: sh -c
"yarn install; yarn run dev --host 0.0.0.0
Запускаем разработку с помощью команды:
docker-compose -f docker-compose.dev.yml up
Этап production:
Хочу особо отметить, что приведенная конфигурация работает для отдачи простой статики. Если используется SSR, нужно будет внести изменения: поднимать сервер на node и т.д.
Что отличается в конфигурации docker-compose.yml
от
develop версии?
-
Указана сборка из
Dockerfile
, вместо использования образаimage
-
Поменялась переменная окружения NODE_ENV:
development
->production
-
Нет секции
command
потому что статика будет раздаваться с помощьюnginx
Конфигурация nginx максимально простая и не обременяет процесс раздачи файлов и fallback на /index.html в случае, если пытаются получить какой-то файл, которого нет.
Самое интересное кроется в Dockerfile
: multi-stage
building, который используется для уменьшения результирующего
артефакта.
Dockerfile
Первая стадия это сборка
1 строка. Для этого указываем тот же исходный образ, который использовали для develop FROM node:15.8-alpine3.11 AS build
Важно! Даем стадии имя build, чтобы в следующих стадиях именовать ее по имени, а не по индексу, который может измениться, если включим дополнительные стадии.
2 строка. Указываем рабочую директорию
/app
3-5 строки. Здесь останавливаемся подробнее:
Вопрос: почему сначала копируем только package.json и производим
установку?
Ответ (не заставляет себя долго ждать): при первом запуске разница
не будет заметна, но уже со следующей попытки сборки разница будет
очевидна.
Если не было изменений в package.json, то слои, по которым собирается docker не изменятся, и данные шаги будут просто взяты из кэша. Это многократно ускорит процесс и снизит сетевую нагрузку в разы. Нам как раз это и надо.
6 строка. Копируем оставшиеся файлы и запускаем build.
Вторая стадия формирование артефакта
По своей сути артефакт в нашем случае это контейнер nginx со статикой.
8 строка. Указываем образ nginx который возьмем за основу.
9 строка. Копируем файлы из первой стадии в папку из которой будем раздавать статику.
10 строка. Копируем в артефакт файл конфигурации nginx
Запускать артефакт можно например так: docker-compose
up
Немного о проблеме безопасности для deveopment
Не стоит использовать volumes с директориями на компьютере, которые не хотим отдать в доступ всем пользователям docker.
Как это проявляется? Давайте рассмотрим на примере:
Создаем обычную папку ~/project
. Добавляем в нее
файл secret.txt
, который содержит текст: secret
text
Под пользователем создаем docker-compose.yml
и
подключаем директорию, к которой не имеет доступ другой
пользователь.
version: "3.9"services: web: image: alpine:latest volumes: - "./project:/app" command: sh -c "sleep 3600
Строка 7. Контейнер будет жить 1 час.
Запускаем: docker-compose up -d
Заходим в систему под другим пользователем, который имеет доступ к docker.
Смотрим имя контейнера: docker ps
Заходим в контейнер: docker-compose exec -it
{имя_контейнера} /bin/sh
Теперь можно спокойно зайти в папку: cd /app
В которой лежит секретный файл sectret.txt
Файл можно просматривать и редактировать его содержимое.
Итоги
Несомненно, тема контейнеризация очень обширная и мы рассмотрели только крошечную часть, однако мы рассмотрели необходимую базу для старта. Я буду рад, если эта статья станет отправной точкой для FE-разработки в Docker.
GitHub: тут