Docker, первые шаги.
Введение
В этой статье мы начнем делать первые шаги в изучении докера, особенно эта статья поможет junior/middle фронтенд разработчикам, которые хотят понять как работает докер и как за несколько простых шагов развернуть его на своей машине.
Какую проблему решает?
Для запуска проектов мы используем большое количество сервисов:
- pm2 или ts-node для запуска наших проектов на JS/TS
- Базы данных PostgreSQL, MySQL, MongoDB и т.д
- Веб-сервер nginx/apache
- Мониторинговые системы
- Redis или Memcache для кэширования данных
- и другие полезные вещи
Разворачивание такого большого количества сервисов на локальной машине всегда оборачивается болью, засорением памяти, конфликтом версий и танцами с бубном перед каждой сборкой проекта.
В идеале хочется чтобы все зависимости разворачивались сами и были изолированы от операционной системы, чтобы на другой машине не приходилось ставить и поднимать все вручную. Данные проблемы решает контейнеризация.
Контейнеризация
Контейнеризация - это методология разработки и развертывания приложений, которая позволяет упаковать приложение и все его зависимости в единую единицу, называемую контейнером. Контейнер является изолированным окружением, которое содержит все необходимые компоненты для запуска приложения, включая библиотеки, фреймворки и настройки. Это облегчает процесс развертывания приложений, поскольку контейнер может быть запущен на любой машине, поддерживающей контейнеризацию, без необходимости установки или настройки зависимостей. Контейнеризация также обеспечивает высокую степень изоляции между приложениями, что уменьшает вероятность конфликтов и повышает безопасность.
Проще говоря - внутри вашей операционной системы запускается еще одна полностью изолированная операционная система, которую вы можете отключить отключить в любой момент времени.
Docker
Docker - это одна из платформ для контейнеризации приложений, которая позволяет разработчикам упаковывать приложения и их зависимости в контейнеры. Docker предоставляет инструменты для создания, управления и развертывания контейнеров, а так же для автоматизации процессов сборки и развертывания приложений.
Инструкция по установке докера:
- Описать настройки контейнера - операционную систему, программы, команды для запуска.
- Собрать билд (build) контейнера из раннее описанных настроек.
- Запустить контейнер из созданного билда.
Поднимем наш первый контейнер
Перейдем в папку и инициализируем проект.
Будем использовать веб-сервер. Добавим express в проект.
Создадим файл index.js и напишем простой код для веб-сервера.
Напишем настройки для докер контейнера, для этого создадим файл в корне проекта Dockerfile.
FROM
Любой конечный Dockerfile называется образом. Для нашего контейнера нужна операционная система, в этом нам поможет команда FROM. Команда собирает контейнер из указанного образа операционной системы. Докер поддерживает большинство доступных ОС, самые популярные из них это - Ubuntu, Debian. Синтаксис выглядит так:
FROM <BASE>:<VERSION>, где BASE - название образа, а VERSION - версия.
Добавим в наш файл следующую команду, скажем докеру, что мы хотим использовать образ ubuntu:18.04 в нашем контейнере.
Уже сейчас мы можем собрать билд контейнера. Перейдем в папку и введем команду docker build -t server-app .
Флаг -t позволяет нам указать имя нашего контейнера, а . говорит докеру где нужно искать Dockerfile. Докер найдет необходимый образ, скачает его и соберет наш билд. В консоли вы увидите что-то наподобие этого.
RUN
Мы добавили образ системы, но для запуска сервера на express нам необходимо установить nodejs и npm в нашу систему, для этого мы воспользуемся командой RUN
Добавим следующую строку в нашел образ.
Синтаксис выглядит так - RUN <command>, где command означает любую валидную операцию в командой строке.
COPY / WORKDIR
Наш контейнер ничего не знает о сервере написанном в файле index.js, чтобы мы смогли запустить сервер, нам нужно скопировать файлы в наш контейнер. Для этого воспользуемся командой WORKDIR, чтобы указать рабочий каталог на который будут указывать все остальные команды по типу RUN, CMD, ENTRYPOINT, COPY и ADD.
Синтаксис команды выглядит так: WORKDIR <PATH>, где PATH - это путь до каталога.
Теперь воспользуемся командой COPY и скопируем файлы проекта в контейнер. Синтаксис: COPY <HOME_PATH>:<WORK_PATH>, где HOME_PATH - это путь до файла/папки, а WORK_PATH - это путь до места, куда мы хотим скопировать файлы.
Добавим в наш образ следующие строки:
Во-первых мы указали что путь /var/www является нашим корневым каталогом в контейнере.
Во-вторых указали, что хотим скопировать файл index.js в файл с названием index.js, т.к мы уже указали рабочий каталог, то он будет скопирован в папку /var/www
CMD
Попробуем запустить контейнер и посмотреть что мы успешно скопировали файл. Для этого воспользуемся командой CMD. Она будет выполняться после запуска контейнера и может быть только одна для всего докерфайла.
Синтаксис следующий: CMD [<COMMAND>, <PARAM_1>, <PARAM_N>], где COMMAND - название команды, PARAM_N - передаваемые параметры.
Соберем контейнер docker build -t server-app . и запустим контейнер docker run server-app
В результате вы должны увидеть название нашего файла:
Файл с сервером успешно скопирован в контейнер, осталось его запустить, но перед этим нам нужно установить зависимости из package.json, для этого немного изменим путь, скопировав всю папку с проектом целиком, а не только один index.js, потом добавим команду RUN для установки зависимостей и запустим сервер с помощью CMD:
Поздравляю, наш сервер запущен в контейнере!
Запуск Docker'а в фоновом процессе, остановка и мониторинг.
Для запуска в фоновом режиме воспользуемся флагом -d. Запустим наш контейнер.
В ответе мы видим название контейнера работающего в фоне. Введите docker ps для того чтобы увидеть все запущенные контейнеры.
Для остановки контейнера напишите docker stop -t 0 <CONTAINER_ID>, где флаг -t 0 - говорит докеру немедленно остановить контейнер, аCONTAINER_ID - первые несколько символов названия контейнера.
Проброс портов
Теперь попробуем зайти на localhost:3003 и получаем ошибку, почему? Потому что все что запускается внутри контейнера - остается внутри контейнера и наша хост машина ничего не знает о нем.
Для того чтобы что смогли получить доступ - нам нужно пробросить порт снаружи в контейнер. Команда EXPOSE добавляет информацию о порте для пользователя Dockerfile и несет только информационный характер. Для настоящего проброса при запуске контейнера нужно напрямую передать ему порт хоста и порт контейнера.
Синтаксис: -p <HOST_PORT>:<CONTAINER_PORT>
Добавим EXPOSE <PORT> в Dockerfile
Соберем контейнер и запустим с помощью команды docker run -d -p 4000:3003 server-app
Попробуйте перейти по адресу localhost:4000, поздравляю, сервер нам ответил.
Проброс файлов
Все, что запускается внутри контейнера - останется внутри контейнера при его остановки, поэтому держать файлы вроде базы данных нецелесообразно, потому что каждый раз после остановки - файлы будут стираться. Для таких случаев можно воспользоваться пробросом файлов в контейнер с хост машины.
Давайте создадим папку shared на уровень ниже относительно Dockerfile, чтобы при билде докер не подхватил его тоже, и внутрь папки поместим файл text.json.
Изменим наш index.js добавив отправку файла из папки shared.
Теперь соберем контейнер docker build -t server-app . и запустим его добавив новый флаг -v <HOST_ABSOLUTE_PATH>:<CONTAINER_ABSOLUTE_PATH>, где указываем абсолютный путь до папки и конечный путь внутри контейнера, команда запуска будет такой:
docker run -d -p 4000:3003 -v /Users/username/shared:/var/www/shared server-app
Попробуем перейти по localhost:4000 и видим что сервер прислал содержимое файла text.json, который мы пробросили в контейнер. Попробуйте изменить text.json и обновите страницу, вы увидите что контент так же изменился без сборки и перезапуска контейнера.
Проброс файловой системы применяют тогда, когда данные контейнера надо сохранить. Для этого выясняют, где в контейнере приложение хранит свои данные, и пробрасывают туда локальную папку из проекта. Содержимое пробрасываемых папок обязательно добавляют в .gitignore, оставляя в них только файл .gitkeep, чтобы не возникло приключений с отсутствующими папками (попробуйте прокинуть в контейнер папку, которой не существует на хосте, будет ошибка).
Заключение
В данной статье мы изучили базовые команды, научились собирать, запускать и останавливать контейнер, поняли как пробрасывать порты и файлы. Данная статья погрузила в мир докера, но постоянно делать одни и те же действия в виде сборки и запуска из командой строки не очень удобно. Скоро мы расскажем про Docker Compose, который значительно облегчит вашу жизнь, а пока подписывайтесь на наш телеграмм канал.