Docker в учебных целях (на примере PostgreSQL)

Зачем нужен Docker

Логотип Docker

Docker — это платформа, которая позволяет «создавать, поставлять и запускать любое приложение повсюду». Революционность данного решения состоит в его кроссплатформенности и относительной простоте установки и использования в практически любой компьютерной среде: так, например, на компьютере с Microsoft Windows вы можете работать с полноценным Linux-терминалом внутри полноценного дистрибутива GNU/Linux, не устанавливая себе вторую операционную систему в режиме двойной загрузки. Более того, вы сможете установить себе контейнеры с любым количеством разных дистрибутивов Linux (Ubuntu, Debian, Arch Linux, AlmaLinux, Oracle Linux, Amazon Linux, Alpine и многие другие).

Это похоже на использование виртуальных машин, только проще — намного проще и намного удобнее. А еще Docker гораздо быстрее любой виртуальной машины!

Но преимущества этой технологии не ограничиваются развертыванием операционных систем: образы Docker позволяют упаковать прикладное программное обеспечение и развернуть его на любом современном компьютере: это может быть СУБД; компиляторы, интерпретаторы и движки для языков программирования, например, Go, Python или Node.js; командные оболочки вроде Bash и др. Вот почему Docker отлично подойдет для учебных целей — вы можете получить и использовать новые технологии без лишних затрат времени и ресурсов.

И, самое главное, вам не нужно создавать эти образы самому (если, конечно, вы не захотите научиться делать это сами): на «складе» т. е. в публичном репозитории DockerHub, имеются образы с ПО практически для любых ваших потребностей!

Но есть один нюанс: развертываемые с помощью Docker приложения ориентированы на работу в режиме командной строки. Вы же не боитесь командной строки? К тому же для Docker-контейнеров можно использовать программы с графическим интерфейсом — в данной заметке мы также увидим, как это работает.

С документацией по Docker можно ознакомиться на официальном сайте. Также рекомендую ознакомится с прекрасным руководством The Docker Handbook. Существует множество видеокурсов по использованию Docker, в частности, один из таких курсов — Docker для начинающих: полный курс (2021).

Развертывание образов Docker на локальной машине

Для начала надо установить Docker на вашей локально машине. Как это сделать — зависит от ОС на вашем компьютере; поэтому мы не будем останавливаться на данном шаге, переадресовав вас к официальному руководству.

Для MS Windows и macOS существуют приложения с графическим интерфейсом — Docker Desktop. Для Linux тоже есть графическая DockStation, но далее в этой заметке мы будем устанавливать и запускать Docker-контейнеры из командной строки, поскольку такой подход позволит нам разобраться с важными настройками.

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

Образ — это упакованное разработчиком исходное программное обеспечение. Он похож на диск с программой, который вы вставляете в компьютер и устанавливаете в свою операционную систему; при этом сам образ никак не изменяется. Технически образ состоит из различных слоев файловой системы и метаданных, но сейчас тонкости строения образов нам не нужны.

Контейнер — это запущенный (другими словами: активированный) экземпляр Docker-образа. У вас может быть несколько контейнеров, запущенных с одного образа — это как несколько экземпляров одной книги, напечатанных с одного и того же типографского макета. Но, будучи запущенными, контейнеры начинают жить своей собственной жизнью: вы можете модифицировать содержимое контейнера, и он будет теперь отличаться от прочих своих братьев, которые породил один и тот же образ (как физический экземпляр вашей книги с вашими пометками отличается от экземпляров той же самой книги у других читателей или в библиотеке). Как бы вы не модифицировали дочерний контейнер, это никак не отразится ни на исходном образе, ни на других «сестринских» контейнерах — все они изолированы друг от друга. (Мы не будем затрагивать эту тему, но модифицированный контейнер очень просто превратить в новый исходный образ — так возможно не только сохранять ПО с нужными настройками, но и передавать настроенное ПО другим пользователям в виде образа для установки.)

Проще говоря, образ — это исходный (родительский) и неизменяемый вариант ПО, а контейнер — это дочерний и изменяемый вариант ПО, работающий на вашей машине. После того как пользователь первый раз запустил контейнер, он может затем исполнять (стартовать) данный контейнер нужное ему количество раз; после исполнения Docker-контейнер завершает работу с сохраненными изменениями — это как прогресс в компьютерной игре, который сохраняется по окончании игрового сеанса.

Основная функция Docker — создавать, передавать и запускать упакованное ПО на любом компьютере, где установлен сам Docker. Для обычного пользователя такое ПО выглядит как программа с командной строкой, которой можно манипулировать: при старте контейнер запускает на вашем компьютере какой-то процесс (программу), который реагирует на ваши команды и выполняет определенную работу; когда этот процесс завершается, контейнер останавливается. Все необходимые изменения (например, новые файлы, созданные при использовании контейнера) будут сохранены либо в данном контейнере, либо — если установить нужные настройки — в специальном томе, т. е. во внешнем (по отношению к контейнеру) месте вашей ОС — в частности, в каком-либо специальном каталоге на жестком диске (это как облачное хранилище, которое одновременно подключено и к контейнеру, и к файловой системе вашей ОС, хотя обычно это физически раздел именно вашей файловой системы). В этом отношении контейнер можно назвать песочницей — специально выделенной (изолированной) средой для исполнения компьютерных программ.

Чтобы усвоить данную концепцию, давайте установим и запустим контейнер с версией языка программирования Python. Мы загрузим с DockerHub свежий официальный образ в Linux-контейнере, основанном на Debian.

Шаг 1 — скачиваем образ Python

Чтобы выбрать нужный нам образ из различных вариантов, существуют специальные теги (в данном случае необходимо будет указать тег latest). Сама загрузка происходит командой docker pull:

docker pull python:latest

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

Проверим наличие образов в нашей системе при помощи команды:

docker images

Как мы видим, нужный образ установлен и получил свой уникальный ID (IMAGE ID).

Загрузка образа Python

Шаг 2 — создаем новый контейнер из образа

Это, пожалуй, самый сложный шаг, поскольку именно на данном этапе нам надо определиться с настройками для контейнера, в частности:

  1. определить запускаемый образ, который породит дочерний контейнер;
  2. дать ему удобное имя (ключ --name NAME, где NAME — имя будущего контейнера);
  3. определить, будет ли данный контейнер стартовать в интерактивном режиме (ключ --it) и какой процесс будет тогда запущен;
  4. если мы хотим использовать том для обмена данными и файлами между операционной системой и контейнером — указать путь к данному тому (ключ --v 'PATH:VOLUME', где PATH — полный путь к папке вашей ОС, которая станет такой точкой обмена, а VOLUME — каталог в контейнере, в который монтируется папка из вашей операционной системы) и др.

Первоначальный запуск контейнера происходит командой docker run с нужными ключами; в частности, приведенная ниже команда создает из скачанного образа контейнер с именем python3 и подключает к нему том (папка операционной системы /home/nobus/.docker-mnt монтируется в контейнере в стандартный для Linux каталог для внешних устройств — /mnt), причем контейнер запускается в интерактивном режиме, при его старте будет исполнена командная оболочка bash:

docker run --name python3 -v '/home/nobus/.docker-mnt:/mnt' -it python:latest bash

Всё прошло удачно, контейнер запустился и мы оказались в командной оболочке, которой было передано управление. Из этой командной оболочки мы можем не только запускать интерпретатор языка Python, но и выполнять другие программы. Пока же давайте выйдем из bash, что остановит оболочку и завершит выполнение контейнера:

exit

А теперь проверим наличие вновь созданного контейнера командой docker ps (поскольку контейнер остановлен, нам потребуется ключ -a):

docker ps -a

Отлично, мы создали контейнер из готового образа (и он также получил свой уникальный ID).

Первоначальный запуск контейнера c Python

Шаг 3 — стартуем контейнер и останавливаем его

Теперь запустим наш контейнер заново. Можно это сделать при помощи команды docker start с указанием уникального ID контейнера (CONTAINER ID) или же с указанием имени контейнера — вот, кстати, для чего мы определяли это имя в шаге 2!

Итак, стартуем контейнер по его ID:

docker start <CONTAINER ID>

Теперь проверим, стартовал ли наш контейнер:

docker ps

Да, он стартовал и находится в запущенном режиме. Давайте остановим его командой docker stop (нам также потребуется указать CONTAINER ID, ведь у нас в системе может быть несколько запущенных контейнеров):

docker stop <CONTAINER ID>

Проверим, запущен ли контейнер:

docker ps

На данный момент запущенных контейнеров нет.

А теперь выполним те же действия с использованием имени контейнера. Запустим его:

docker start python3

И затем остановим:

docker stop python3
Старт и остановка контейнера c Python

Шаг 4 — работаем с контейнером в интерактивном режиме

Но пока толку от запущенного контейнера нам было немного — он стартовал как «черный ящик» — и мы не могли с ним взаимодействовать в интерактивном режиме. Можно организовать такое взаимодействие, подключившись к запущенному контейнеру командой docker attach (нам вновь потребуется указать ID или имя контейнера):

docker attach python3

Теперь для завершения работы контейнера мы просто должны выйти из интерактивного режима (команда exit; не надо специально останавливать контейнер!).

Есть и более простой путь: мы можем сразу стартовать контейнер в интерактивном режиме с ключом -i:

docker start -i python3

А теперь давайте снова запустим наш контейнер в интерактивном режиме и перейдем в интерпретатор Python, где можно будет выполнить команды ЯП.

Кстати, если создать файл на Python и сохранить его в нашем томе (папке ОС /home/nobus/.docker-mnt, который монтируется в каталог контейнера /mnt), то мы сможем запустить его в работающем контейнере!

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

Запуск контейнера в интерактивном режиме

Шаг 5 — Удаляем контейнер и образ из системы

Чтобы удалить контейнер, мы можем воспользоваться командой docker rm с указанием уникального CONTAINER ID:

docker rm <CONTAINER ID>

Удалить образ чуть сложнее — для этого требуется, чтобы предварительно были удалены те контейнеры, которые были созданы на его основе (предыдущей командой). После этого можно удалить и сам образ, что приведет к удалению всех его слоев, загруженных на ваш компьютер. Команда удаления образа — docker rmi с указанием уникального IMAGE ID:

docker rmi <IMAGE ID>
Удаление контейнера и образа из системы

Таким образом, использование контейнеров — достаточно простой процесс. Кстати, посмотреть все доступные команды можно при помощи команды docker -h.

Установка образа СУБД PostgreSQL 14 из DockerHub

PostgreSQL – свободная объектно-реляционная СУБД, которую многие специалисты считают лучшей из СУБД, основанных на языке SQL (Structured Query Language). Обычно развертывание СУБД на рабочей станции пользователя является не самой простой задачей, поскольку предполагает установку и запуск сервера базы данных, а также решение ряда проблем, связанных с правами доступа, поддержкой русского языка в интерактивном терминале, обеспечением безопасности данных и т. д. Далее мы увидим, что при использовании Docker установить данную СУБД на собственный компьютер и использовать ее как для обучения, так и в практических целях столь же просто, как и любой другой образ из репозитория DockerHub. (Кстати, и обновлять версии СУБД тоже просто — достаточно удалить старые контейнер и образ и установить новые!)

Хотя всегда доступен официальный образ Postgres, мы воспользуемся модифицированным образом — в нем сразу установлена поддержка русской локали.

Итак, давайте загрузим пересобранный образ PostgeSQL последней версии (14-й на момент редактирования заметки в 2022 году) на свой компьютер:

docker pull nobus1967/postgres:latest-ru

Проверим наличие образа и посмотрим его IMAGE ID: все в порядке.

Далее создадим контейнер, с которым мы и будем работать. В принципе, активация Postgres-контейнера принципиально не отличается от той процедуры, что мы проделали при запуске Python-контейнера, за исключением двух моментов:

  1. Поскольку Postgres является сетевой СУБД, подключение к ней и обмен информацией происходят через сетевые порты ОС, которые идентифицируют назначение сетевых потоков данных в пределах одного компьютера. Стандартным для подключения к Postgres является порт 5432, но мы можем переназначить это число. В частности, на моем компьютере на порт 5432 уже назначена локально установленная версия Postgres, поэтому я переназначу порт контейнера на новый порт моей машины-хоста с номером 5433 при помощью ключа -p 5433:5432.
  2. Активируя контейнер, мы сразу зададим пароль для стандартного пользователя с именем postgres при помощи переменных окружения — для этого воспользуемся ключом -e POSTGRES_PASSWORD=PASS, где PASS — устанавливаемый пользователем пароль доступа; в текущей конфигурации зададим его равным 8-ми случайным символам, например, 4FKU8zkW.
Установка контейнера c Postgres

Также мы выполним команду первоначальной активации контейнера docker run с ключом -d, что позволит запустить его в фоновом режиме. Итак, поехали:

docker run -d -p 5433:5432 --name postgres -e POSTGRES_PASSWORD=4FKU8zkW nobus1967/postgres:latest-ru

Проверим наличие запущенного контейнера и увидим, что он подключен к нужному порту: порту 5432 в контейнере и порту 5433 в ОС. Теперь можно остановить наш новый контейнер.

Первоначальный запуск контейнера с Postgres

UPDATE (19.09.2022):

Внесены исправления для использования последней версии PostgreSQL из репозитория (последняя версия обозначения тегом latest-ru; на скриншотах осталась первоначальная версия).

Запуск и использование контейнера с СУБД

Далее мы подключимся к контейнеру с Postgres и попробуем выполнить несколько команд в стандартном интерактивном терминале СУБД.

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

docker start -i postgres
Запуск контейнера с Postgres в интерактивном режиме

СУБД запустилась и мы увидели сообщения на русском языке, но доступа к интерактивному терминалу так и не получили. Давайте остановим контейнер (воспользуемся в терминале комбинацией клавиш CTRL+C) и заново стартуем контейнер в стандартном режиме, а потом подключимся к нему командой docker exec c ключом -it postgres bash (т. е. в интерактивном режиме с запущенной командной оболочкой bash):

docker exec -it postgres bash
Запуск контейнера с Postgres в интерактивном режиме

Отлично, мы увидели приглашение командной оболочки #, введем в нее команду su postgres, чтобы стать пользователем с установленным по умолчанию именем, а затем запустим внутренний интерактивный терминал СУБД командой psql. Далее протестируем работу интерактивного терминала командой \conninfo.

Запуск контейнера с Postgres в интерактивном режиме

Теперь можно работать с СУБД. Кстати, русскоязычная версия документации доступна на сайте компании Postgres Pro. На том же ресурсе размещена бесплатная литература по PostgreSQL.

В завершение нашего сеанса выйдем из интерактивного терминала Postgres командой \q, а затем выйдем из-под пользователя postgres и из bash двумя командами exit (вторая команда не остановит наш контейнер, сделаем это вручную командой docker stop).

Работа со сторонними приложениями (на примере Beekeeper Studio)

Интерактивный терминал Postgres позволяет полноценно работать с СУБД, но для удобства можно использовать приложения с графическим интерфейсом пользователя, такие как бесплатное кроссплатформенное приложение Beekeeper Studio.

Окно Beekeeper Studio с настройками для запуска
                     PostgreSQL

После установки Beekeeper Studio можно легко подключиться к контейнеру через заданный порт (настройки видны на скриншоте; пароль мы используем тот же, что и при активации контейнера). Только не забывайте перед подключением стартовать контейнер!

Окно Beekeeper Studio с запущенной СУБД PostgreSQL

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

Удачной вам докеризации!