База данных в контейнере Docker — как ее запустить, и зачем она нужна

от автора

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

image


Давайте без промедления запустим базу данных PostgreSQL в контейнере Docker!

Чтобы у вас получились все шаги, описанные ниже, у вас на ПК должен быть установлен Docker. О том, как это делается в самых популярных ОС, даны здесь: Ubuntu, Windows, Mac.

Для быстрого старта откройте командную строку и выполните команду:

$ docker run --name postgres-docker -e POSTGRES_PASSWORD=postgres -p 5432:5432 -d postgres

Вот в чём её суть:

  • Она подтягивает Docker-образ postgres из Docker Hub,
  • устанавливает переменную окружения POSTGRES_PASSWORD в значение postgres,
  • называет (—name) контейнер Docker вот так: postgres-docker ,
  • отображает внутренний порт 5432 этого контейнера на внешний порт 5432, так, что появляется возможность зайти в этот контейнер извне,
  • после чего позволяет нам выполнять контейнер Docker в фоновом режиме (-d).

Итак, если теперь вы хотите зайти в базу данных через какое-нибудь приложение с графическим пользовательским интерфейсом (GUI) – например, pgAdmin, Adminer, т.д.), у вас это должно получиться.

Я предпочитаю пользоваться DBeaver, но вы можете работать с тем инструментом, который нравится вам больше всего. Итак, чтобы подключиться к базе данных из приложения, нужно указать параметры соединения (большинство из них задаются по умолчанию в образе Docker). Полное резюме по ним приводится на следующем скриншоте из DBeaver:

image

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

image

Ладно, а что, если вы настолько нерд ?, что просто не хотите пользоваться таким чудесным приложением с GUI, а предпочитаете выполнять всю работу с базой данных прямо в командной строке? Как вам в таком случае войти в контейнер?

Нет ничего проще, всего лишь введите в командной строке следующий код:

$ docker exec -it postgres-docker bash

При помощи exec мы заходим в образ postgres-docker в открепленном режиме -it и приступаем к работе с его bash-приложением (bash).

Так мы попадаем в командную строку контейнера Docker – и поэтому можем войти в базу данных как пользователь postgres.

root@377ef2b9b13e:/# psql -U postgres  psql (11.4 (Debian 11.4-1.pgdg90+1)) Type "help" for help. postgres=#

Здесь мы можем делать всё, что нам захочется: создавать новые базы данных, новые таблицы, наполнять их данными и т.д. Например, можно создать простую таблицу:

postgres=# CREATE TABLE public.persons (id int PRIMARY KEY, lastName varchar(255), firstName varchar(255), address varchar(255));

Но теперь логичен вопрос: а можно ли автоматизировать создание базы данных? В особенности, если речь идёт о сложной базе данных, содержащей множество таблиц? Ответ: да, конечно!

Создаём наш собственный образ PostgreSQL Docker на основе Dockerfile

Чтобы решить эту задачу, нам потребуется создать наш собственный образ postgres Docker. Это модно сделать на основе Dockerfile – текстового документа, используемого в Docker для сборки пользовательского образа.

FROM postgres  ENV POSTGRES_PASSWORD postgres  ENV POSTGRES_DB testdb  COPY init.sql /docker-entrypoint-initdb.d/

Вышеприведённая инструкция состоит из четырёх шагов, а именно:

  • Первым делом мы приказываем Docker подтянуть образ postgres (этот шаг уже был рассмотрен выше),
  • Затем устанавливаем значения для двух переменных окружения (ENV): POSTGRES_PASSWORD и POSTGRES_DB – соответственно postgres и testdb (список всех переменных, доступных в данном образе, можно найти в Docker Hub),
  • и, наконец, копируем (COPY) файл init.sql, расположенный в том же каталоге, что и Dockerfile, в каталог /docker-entrypoint-initdb.d/, находящийся в используемом нами postgres-образе Docker. По умолчанию все скрипты, находящиеся в этом каталоге, будут автоматически выполняться в ходе пуска контейнера.

Последнее, что нам остаётся сделать – создать вышеупомянутый файл init.sql и положить туда все SQL-скрипты. В моём случае речь о единственном скрипте для создания таблицы:

CREATE TABLE public.persons (     id int PRIMARY KEY,     firstName varchar(255),     lastName varchar(255),     address varchar(255) );

Теперь у нас подготовлены два файла (Dockerfile и init.sql), на основе которых мы можем собрать наш собственный образ Docker. Чтобы это сделать, откроем окно терминала в том каталоге, где находятся эти файлы, и выполним команду:

$ docker build -t my-postgres-image .  Sending build context to Docker daemon  62.46kB Step 1/4 : FROM postgres  ---> 79db2bf18b4a Step 2/4 : ENV POSTGRES_PASSWORD postgres  ---> Running in 0e9f8331845e Removing intermediate container 0e9f8331845e  ---> 01fb59dfd17f Step 3/4 : ENV POSTGRES_DB testdb  ---> Running in 2d424d207e71 Removing intermediate container 2d424d207e71  ---> 2139195ef615 Step 4/4 : COPY init.sql /docker-entrypoint-initdb.d/  ---> d627b332ac02 Successfully built d627b332ac02 Successfully tagged my-postgres-image:latest

В принципе, вышеприведённая команда приказывает Docker собрать из Dockerfile образ под названием my-postgres. Для проверки можете ввести:

$ docker images -aREPOSITORY          TAG     IMAGE ID      CREATED                my-postgres-image   latest  d627b332ac02  About a minute ago  

Отлично! Теперь давайте запустим всё это как контейнер:

$ docker run -d --name my-postgres-container -p 5555:5432 my-postgres-image

Подключившись к базе данных следующим способом (пароль — postgres):

image

Вы должны получить новую базу данных, в которой уже определена таблица persons:

image

Отлично, а теперь давайте убедимся, в самом ли деле сохранятся после останова контейнера те данные, что мы вставили в таблицу. Первым делом эти данные нужно вставить, поэтому выполните в вашем любимом инструменте следующую команду:

INSERT INTO public.persons      (id, firstname, lastname, address) VALUES     (1, 'Luke', 'Skywalker', 'Tatooine'),     (2, 'Leia', 'Organa', 'Alderaan'),     (3, 'Han', 'Solo', 'Corellia');

Затем вернитесь в строку терминала и остановите контейнер при помощи:

$ docker stop my-postgres-container my-postgres-container

Далее перезапустите контейнер при помощи следующей команды:

$ docker container start my-postgres-container

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

image

Окей… но что было бы, если бы для перезапуска контейнера мы воспользовались командой docker run (как в первый раз), а не docker container start?

Команда docker run создаёт новый контейнер из образа my-postgres-image, так что все изменения, внесённые в my-postgres-container, в новом контейнере не сохраняются.

Следует ли пробовать в продакшене базу данных, положенную в Docker?

Да, но вы уверены, что ваши данные долговременно сохранены? Можем ли мы с сегодняшнего дня переместить в контейнеры Docker все базы данных, расположенные на продакшен-серверах/в продакшен-облаках?

Чтобы ответить на этот вопрос, необходимо сначала сделать шаг назад и понять, как именно данные хранятся в контейнерах Docker.

Инструменты оркестровки, и Docker в том числе, создавались исходя из того, что контейнеры должны работать без сохранения состояния, то есть, не должны в процессе эксплуатации сохранять каких-либо данных. Такие контейнеры следует расценивать как функции, так как функции всегда работают одинаково, вне зависимости от их содержимого (внутренних переменных). Дело в том, что другие инструменты (например, Kubernetes), могут увеличивать количество контейнерных инстансов в зависимости от трафика запросов. А, если бы контейнеры могли работать с сохранением состояния, то в таком случае несколько однотипных контейнеров могли бы проявлять себя по-разному, даже в случае, если все они собраны на основе одного и того же исходного образа.

Для контейнеров с базами данных такой вариант неприемлем. Не может быть нескольких контейнеров, таких, в которых база данных была бы одна и та же, а данные – отличались. Единственный вариант решения этой задачи – иметь в каждом оркестровочном инструменте единственный инстанс контейнера, но в таком случае терялась бы одна из самых мощных возможностей оркестратора – приумножение количества контейнерных инстансов в зависимости от трафика запросов. В таком случае могло бы получиться узкое место, из-за которого замедлялась бы работа всего приложения.

Хорошо, так значит ли это, что контейнеризовать базы данных бессмысленно? Ответить на этот вопрос не так просто. В принципе, если вы выводите ваше решение в продакшен, то не кладите базу данных в Docker. Лучше было бы воспользоваться базой данных как услугой – такой сервис предоставляют многие облачные провайдеры (AWS, GCP, т.д.). Но, если данные не критичны, то есть, если они используются только для разработки или тестирования, то можно обойтись и контейнерами.

Остался один вопрос: как в Docker обеспечивается персистентность данных? Есть три механизма для обеспечения персистентного хранения даннхы в Docker, но я хочу рассказать о наиболее предпочтительном — томах. Если вы хотите подробнее познакомиться с монтированием при помощи bind и tmpfs, то почитайте официальную документацию.

Тома Docker

Тома Docker – это каталоги, расположенные вне контейнера Docker на хост-машине. У контейнеров есть только ссылка на этот путь, и именно в нём они сохраняют всю информацию.

image

Source: docs.docker.com/storage/volumes

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

$ docker container inspect my-postgres-container"Mounts": [{       "Type": "volume",      "Name": "453e993be5d9f6f863313c3e111e5f53dc65eeb34bff42e5b",      "Source": "/var/lib/docker/volumes/453e993be5d9f6f863313c3e111e5f53dc65eeb34bff42e5b/_data",      "Destination": "/var/lib/postgresql/data",      "Driver": "local",      "Mode": "",      "RW": true,      "Propagation": ""  }],

Предыдущий листинг – это всего лишь часть файла JSON, выводимого в консоль. В Mounts содержится информация об отображённых каталогах. Параметр Source сообщает, где именно на локальной машине организовано долговременное хранение данных, а Destination указывает местоположение внутри контейнера Docker.

Еще есть параметр Name (имя тома), который по умолчанию присваивает Docker. На самом деле, он плохо поддаётся чтению, но его можно откорректировать.

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

$ docker run -d --name my-postgres-volume -p 7777:5432 -v postgres-volume:/var/lib/postgresql/data my-postgres-image2109993939fdc9fe290c3536bdee09dd4cfba2ff369cf15a17bc841afe0c056f

Единственное, что здесь было добавлено (не считая того, что мы изменили имя контейнера и откорректировали отображение портов) – это новый флаг -v : (или —volume, если предпочитаете), отвечающий за присваивание тома контейнеру Docker.

Теперь, попытавшись проверить (inspect) контейнер, вы должны получить следующую информацию:

$ docker container inspect my-postgres-volume"Mounts": [{      "Type": "volume",      "Name": "postgres-volume",      "Source": "/var/lib/docker/volumes/postgres-volume/_data",      "Destination": "/var/lib/postgresql/data",      "Driver": "local",      "Mode": "z",      "RW": true,      "Propagation": "" }],

Гораздо лучше! Если потребуется узнать информацию о томах, выполните команду:

$ docker volume lsDRIVER         VOLUME NAME local          453e993be5d9f6f863313c3e111e5f53dc65eeb34bff42e5b local          postgres-volume

Ещё один способ создать собственный том – воспользоваться этой командой:

$ docker volume create --name my-postgres-volumemy-postgres-volume

Тома, созданные таким образом, можно прикреплять к контейнеру таким же образом, как и при использовании флага -v в ходе первого запуска контейнера.

$ docker run -d --name my-postgres-volume-2 -p 2222:5432 -v my-postgres-volume:/var/lib/postgresql/data my-postgres-image

Ещё одна крутая черта тома такова: его можно одновременно прикреплять к разным контейнерам. Именно так сделано ниже, где мы прикрепляем к новому контейнеру том, уже используемый в настоящий момент.

$ docker run -d --name my-postgres-volume-3 -p 3333:5432 -v my-postgres-volume:/var/lib/postgresql/data my-postgres-image

Теперь, если inspect оба контейнера, то должен получиться точно такой же результат в части Mount файла JSON:

"Mounts": [{     "Type": "volume",     "Name": "my-postgres-volume",     "Source": "/var/lib/docker/volumes/my-postgres-volume/_data",     "Destination": "/var/lib/postgresql/data",     "Driver": "local",     "Mode": "z",     "RW": true,     "Propagation": "" }],

Но учитывайте, что у этого решения есть ограничение! Прямо сейчас у нас запущены и работают два контейнера. И, если вы внесёте в любой из них новые данные, например, в my-postgres-volume-2, это ещё не означает, что они будут добавлены и во второй контейнер ( my-postgres-volume-3 )! Обновление второго контейнера (с внесением новых данных) произойдёт только после перезапуска контейнера и возврата в него при помощи этих команд:

$ docker stop container my-postgres-volume-3$ docker start my-postgres-volume-3

Избавляемся от неиспользуемых томов

Последнее, о чём хотелось бы упомянуть – как избавиться от ненужных томов. Их можно удалить один за другим при помощи следующей команды:

$ docker volume rm {volume_name}

Или все сразу:

$ docker volume prune

Вот и всё! Если хотите, можете взять Dockerfile и скрипт SQL у меня в репозитории на GitHub:
wkrzywiec/docker-postgres


ссылка на оригинал статьи https://habr.com/ru/companies/piter/articles/736332/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *