Создание слоя: поднимаем базу при помощи контейнеров
Котик, какие планы на сегодня?
Углубляться в сильные дебри работы контейнеров и Docker в целом не планирую. Я даю лишь поверхностные знания, дабы сложилась полная картинка работы ГИС систем.
Более глубоким изучением тематики вы можете заняться самостоятельно.
Решение, описанное в данной статье, не претендует название production ready.
Контейнеризация и Docker
Мы ж вроде только про данные поговорили, причем тут какие-то контейнеры? Еда у котиков вроде в миске обычно... Что вообще происходит???
Нет, это не те контейнеры, о которых мы могли подумать.
Контейнеризация
Контейнеризация — это подход к разработке программного обеспечения, при котором приложение или служба, их зависимости и конфигурация упаковываются вместе в образ контейнера.
Вспомните грузовые контейнеры. В них помещают какие-либо вещи, которые потом перевозят в другое место.
Так же и у нас, только у нас в контейнер помещается приложение или сервис, который затем можно запускать на любой ОС, без необходимости устанавливать кучу разного ПО перед этим.
Такой подход к разработке очень облегчает настройку окружения для разработки, а также позволяет создать одинаковые условия для работы приложения во всех окружениях, будь это тест/прод или среда разработки.
Docker
Docker — это платформа контейнеризации с открытым исходным кодом, с помощью которой можно автоматизировать создание приложений, их доставку и управление. Платформа позволяет быстрее тестировать и выкладывать приложения, запускать на одной машине требуемое количество контейнеров.
Благодаря контейнеризации и использованию Docker разработчики больше не задумываются о том, в какой среде будет функционировать их приложение и будут ли в этой в среде необходимые для тестирования опции и зависимости. Достаточно упаковать приложение со всеми зависимостями и процессами в контейнер, чтобы запускать в любых системах: Linux, Windows и macOS. Платформа Docker позволила отделить приложения от инфраструктуры. Контейнеры не зависят от базовой инфраструктуры, их можно легко перемещать между облачной и локальной инфраструктурами.
Особенности контейнеров
- Важнейшая особенность контейнеров — их сравнительно короткий жизненный цикл. Любой контейнер можно остановить, перезапустить или уничтожить, если это необходимо. Данные, которые содержатся в контейнере, при этом тоже пропадут. Так выработалось правило проектирования приложений: не хранить важные данные в контейнере.
- Объем контейнеров измеряется в мегабайтах, поскольку в них упаковывают лишь те процессы и зависимости ОС, которые необходимы для выполнения кода. Легковесные контейнеры быстро запускаются и экономят место на диске.
- Один контейнер соответствует одному запущенному процессу. Отключение отдельного контейнера для отладки или обновления никак не помешает нормальной работе всего приложения.
- Контейнеризация обеспечивает надежную изоляцию процессов и повышает уровень безопасности систем. Приложения, которые работают внутри контейнера, не имеют доступа к основной ОС и не могут на неё влиять.
- Благодаря контейнерам можно автоматизировать развертывание приложений на разных хостах.
- Использование контейнеров позволяет перейти с монолита на микросервисную архитектуру. За счет этого ускоряется разработка новой функциональности, поскольку нет опасений, что изменения в одной компоненте затронут всю остальную систему.
- С точки зрения эффективности контейнеры котируются выше виртуальных машин. На одинаковом оборудовании можно запустить большое количество контейнеров, тогда как ВМ будет в разы меньше. Это важно при использовании облачной инфраструктуры — потребуется меньше ресурсов.
Ряд терминов из мира Docker
Образ контейнера: пакет со всеми зависимостями и сведениями, необходимыми для создания контейнера. Образ включает в себя все зависимости, а также конфигурацию развертывания и выполнения для среды выполнения контейнера. Как правило, образ создается на основе нескольких базовых образов, наслоенных друг на друга в файловой системе контейнера. После создания образ остается неизменным.
Dockerfile: текстовый файл, содержащий инструкции по сборке образа Docker. Он похож на пакетный сценарий, где первая строка указывает базовый образ, с которого начинается работа, а следующие инструкции устанавливают необходимые программы, копируют файлы и т. п. для создания необходимой рабочей среды.
Сборка: действие по созданию образа контейнера на основе сведений и контекста, предоставленных файлом Dockerfile, а также дополнительных файлов в папке, где создается образ. Сборка образов выполняется с помощью следующей команды Docker:
docker build
Контейнер: экземпляр образа Docker. Контейнер отвечает за выполнение одного приложения, процесса или службы. Он состоит из содержимого образа Docker, среды выполнения и стандартного набора инструкций. При масштабировании службы вы создаете несколько экземпляров контейнера из одного образа.
Тома: файловая систему с возможностью записи, которую может использовать этот контейнер. Поскольку образы доступны только для чтения, а большинству программ требуется возможность записи в файловую систему, тома добавляют слой с поддержкой записи поверх образа контейнера, который программы смогут использовать как файловую систему с возможностью записи.
Тег: метка для образа, которая позволяет различать разные образы или версии одного образа (в зависимости от номера версии или целевой среды).
Многоэтапная сборка: позволяет сократить итоговый размер образов. Например, большой базовый образ, содержащий пакет SDK, можно использовать для компиляции и публикации, а затем можно использовать небольшой базовый образ среды выполнения для размещения приложения.
Репозиторий (registry): место хранения готовых образов Docker. Репозиторий может быть публичный, как Docker Hub, так и приватный - развернутый в рамках корпоративной сети или локально, например, Nexus.
Если имя образа похоже на такое - registry.company.name.ru/namespace/name:1.1.0, то скорее всего у вас используется некий приватный репозиторий.
Если имя образа содержит только название образа - namespace/name:1.1.0, то, по-умолчанию, образы загружаются с Docker Hub.
При загрузке образов docker смотрит наличие скаченных или собранных локально. Если имя и тег совпадут, тогда скачивать ничего не будет.
Docker compose
Docker compose — это инструментальное средство, входящее в состав Docker. Оно предназначено для решения задач, связанных с развёртыванием проектов.
Зачем он нужен, если Docker сам по себе самодостаточный?
Программисты, а может и DevOPS'ы, по своей натуре, довольно ленивые создания. Кому, как ни котикам, это знать!😂
Docker compose призван облегчить жизнь разработчикам.
Представим ситуацию... Да чего представлять, она такой и будет в рамках разработки нашего ГИС.
Нам необходимо запустить несколько приложений:
В случае использования Docker в чистом виде, на каждое приложением нам бы пришлось написать свою команду для запуска. Такие команды обычно очень длинные, например:
А если нам потребуется перед запуском контейнера оставить его, то добавится к этому еще как минимум 2 команды:
docker container stop container_name && docker container rm container_name
Согласитесь, выглядит страшновато и не супер удобно. Необходимо запоминать описание флагов (или часто подглядывать в docker help).
Docker compose призван решить эту проблему. Он предоставляет возможность описания всех необходимых параметров запуска контейнера в yaml файле. Также он продвигает идеологию запуска контейнеров в виде отдельных сервисов.
В одном yaml файле можно прописать N разных сервисов и запустить/остановить их все одной командой. Помимо запуска сервисов, сразу можно настроить общие тома и создать, а затем объединить, контейнеры в единую сеть.
Сейчас очень важно понять - все, что запущено внутри контейнеров, запущено в рамках своей сети. Если попытаться в приложении использовать в качестве хоста для подключения ключевое слово localhost или ip 127.0.0.1, это приведет к тому, что приложение попытается обратиться к сети, в рамках которой работает контейнер, наружу оно не пойдет.
Исключением в этом случае будет ручное указание работы сети docker в режиме моста (bridge).
Запуск PostgreSQL в контейнере
Сегодня мы с вами рассмотрим самый простой вариант запуска контейнера с помощью Docker и Docker Compose.
YAML файл для запуска контейнера выглядит следующим образом
В самом верху файла всегда указывается version. Версия эта относится конкретно к yaml. За счет нее docker compose понимает, какой набор параметров он может обработать.
Версии обновляются, посмотреть различия можно тут.
Переходим к services. Данная секция представляет собой именованный массив сервисов/контейнеров. postgres - название сервиса.
Под названием сервиса указывается уже ряд параметров, которые при использовании Docker мы бы прописывали в команде в виде отдельных ключей. Кратко пробегусь по каждому из них.
- platform - указывает контейнеру, какую платформу использовать в качестве основы. Платформу обычно никто не указывает, но мне она нужна, потому что на Apple M1 контейнер без этого просто не стартует
- container_name - имя контейнера
- image - базовый образ контейнера. Будет скачиваться с Docker Hub
- restart - политика перезапуска контейнера. Наш контейнер будет пытаться запуститься заново, во всех случаях, кроме того, когда мы его останавливает вручную
- ports - поскольку posgres у нас запущен как сервис внутри контейнера на определенном порту, нам необходимо иметь возможно достучаться до этого порта. Для этого используется маппинг портов - внешний_порт:внутренний_порт
- volumes - настройка файловой системы, томов. Поскольку это база данных, нам важно сохранять информацию на основной ОС, чтоб в случае перезапуска контейнера сохранить всю информацию. Указание мапппинга аналогично портам - внешная_папка:внутренняя_папка.
Конструкция ${PWD} означает указание пути до текущей директории, откуда выполняем команду. Например, мы находимся в директории /users/cat/project/gis, выполняем команду в терминале из этой директории. Полный путь до папки в этом случае будет выглядеть как /users/cat/project/gis/data/db.
- environment - переменные окружения внутри контейнера.
У меня присутствует интересная переменная $PG_PASS. Она нужна для того, чтобы не светить пароль в git. Вы при локальной разработке можете забить строку с паролем прямо в файл и не думать об этом.
Описание переменных окружения, необходимых для нормальной работы Postgres в рамках Docker вы можете найти на странице образа в Docker hub.
Обратите внимание, никакой сети я не указывал. Docker создаст ее автоматически.
Теперь нам осталось выполнить только команду
PG_PASS=YOUR_PASSWORD docker-compose -f postgres/docker-compose.yml up -d
docker ps -a
убеждаемся в наличии запущенного контейнера, работающего на определенном порту
Если у вашего контейнера STATUS похож на "Exited...", то скорее всего вы что-то сконфигурировали неправильно, или образ содержит ошибку.
Узнать, что произошло, можно, выполнив следующую команду
docker logs container_name
Как мы видим, у нас все прошло успешно. Можно двигаться дальше.
GUI для работы с БД
Не все котики захотят работать в командной строке. Вводить команды, запоминать их... Не наш подход.
Кстати о командах. В контейнере можно выполнять команды.
docker exec -it container_name your_command
Иногда хочется зайти прямо внутрь контейнера и пошалить там. Для этого можно вызвать bash или sh, но всегда это доступно. С этим надо быть максимально аккуратным. Если ваш контейнер внутри работает из под пользователя root, то стоит задуматься, а не сменить ли образ или пересобрать под другим пользователем. С таким пользователем можно огрести множество проблем с безопасностью, например, подключив том к контейнеру, можно перейти в него и выполнить пару команд на удаление каких-нибудь папок.
Ладно, это было лирическое отступление. Возвращаемся к GUI.
На сегодняшний момент есть два варианта, как можно удобно работать с базой - в IDE или воспользоваться pgAdmin.
Я предпочитаю именно второй вариант. Там, кстати, можно посмотреть объекты прямо на карте.
Подключаемся к БД
У вас после установки первый раз будет только одна группа серверов. Кликаем по ней ПКМ и регистрируем новый сервер.
На вкладке General нам необходимо указать название нашего сервера. У меня это будет Development. Остальные настройки не меняем и переходим на вкладку Connection.
На вкладке Connection нам необходимо заполнить следующие поля:
- Host name/address - ip адрес нашего сервера
- Port - порт
- Maintenance database - база данных, которая будет открываться по-умолчанию после коннекта к серверу
- Username - пользователь из под которого коннектимся к серверу
- Password - пароль для пользователя
- Save password? - включаем, если хотим не вводить постоянно пароль.
- Host name - 127.0.0.1 или localhost. Поскольку мы подключаемся с ОС к контейнеру, у которого наружу проброшен порт, мы делаем это в рамках своей сети, соответственно, можем использовать localhost
- Port - внешний порт, у нас он равен внутреннему, а именно, 5432
- Maintenance database - не меняю, так как баз у нас пока еще нет других
- Username - пока пользователей не заводили, поэтому оставляю суперпользователя postgres
- Password - помните я говорил про переменную $PG_PASS? Так вот она и содержит пароль для суперпользователя. Указываем его в поле
- Save password? - естественно включаю
Остальные вкладки мы не трогаем. Нажимаем save и проверяем, что сервер успешно добавлен, присутствует бд Postgres.
Так же можем убедиться в наличии расширения PostGIS.
Чтобы останавливать контейнер, используем команду
docker-compose -f postgres/docker-compose.yml down -v
Репозиторий с исходным кодом проекта
Я создал репозиторий с исходным кодом проекта. Разрабатывать будем там, всем примеры кода можно будет посмотреть там.
Долго не радуйтесь, поскольку проект будет довольно большой, в один прекрасный момент я создам для себя приватный репозиторий, в рамках которого продолжу разработку. Я не хочу, чтобы мой проект нагло своровали и использовали для своих нужд, а также я очень надеюсь, что вы будете кодить параллельно со мной и вкладывать частичку себя в вашу систему.
Надеюсь, мы меня поймете и поддержите 🐱