База
May 22, 2023

Создание слоя: поднимаем базу при помощи контейнеров


Котик, какие планы на сегодня?

  1. Поговорим про контейнеризацию и Docker
  2. Запустим Postgres в контейнере

Ремарка

Углубляться в сильные дебри работы контейнеров и 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 призван облегчить жизнь разработчикам.

Представим ситуацию... Да чего представлять, она такой и будет в рамках разработки нашего ГИС.
Нам необходимо запустить несколько приложений:

  • PostgreSQL
  • Geoserver
  • Frontend
  • Backend

В случае использования 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 файл для запуска контейнера выглядит следующим образом

Пример 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.

Вкладка General

На вкладке 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? - естественно включаю
Ввод всех полей на вкладке Connection

Остальные вкладки мы не трогаем. Нажимаем save и проверяем, что сервер успешно добавлен, присутствует бд Postgres.

Созданный сервер

Так же можем убедиться в наличии расширения PostGIS.

Смотрим наличие расширения PostGIS.

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

docker-compose -f postgres/docker-compose.yml down -v

Репозиторий с исходным кодом проекта

Я создал репозиторий с исходным кодом проекта. Разрабатывать будем там, всем примеры кода можно будет посмотреть там.
Долго не радуйтесь, поскольку проект будет довольно большой, в один прекрасный момент я создам для себя приватный репозиторий, в рамках которого продолжу разработку. Я не хочу, чтобы мой проект нагло своровали и использовали для своих нужд, а также я очень надеюсь, что вы будете кодить параллельно со мной и вкладывать частичку себя в вашу систему.

Надеюсь, мы меня поймете и поддержите 🐱


Репозиторий