Поиск по сайту:

Как обмениваться данными между контейнерами Docker


Введение

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

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

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

Предпосылки

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

  • Пользователь без полномочий root с привилегиями sudo. В руководстве Initial Server Setup with Ubuntu 20.04 объясняется, как это настроить.
  • Docker установлен в соответствии с инструкциями из шагов 1 и 2 раздела «Как установить и использовать Docker в Ubuntu 20.04».

Примечание. Несмотря на то, что в предварительных требованиях содержатся инструкции по установке Docker в Ubuntu 20.04, команды docker для томов данных Docker в этой статье должны работать в других операционных системах, если установлен Docker и пользователь sudo был добавлен в группу docker.

Шаг 1 — Создание независимого тома

Представленная в выпуске Docker 1.9 команда docker volume create позволяет создать том, не связывая его с каким-либо конкретным контейнером. Мы будем использовать эту команду для добавления тома с именем DataVolume1:

  1. docker volume create --name DataVolume1

Отображается имя, указывающее, что команда выполнена успешно:

Output
DataVolume1

Чтобы использовать том, мы создадим новый контейнер из образа Ubuntu, используя флаг --rm, чтобы автоматически удалить его при выходе. Мы также будем использовать -v для монтирования нового тома. -v требует имя тома, двоеточие, а затем абсолютный путь к тому месту, где том должен появиться внутри контейнера. Если каталоги в пути не существуют как часть образа, они будут созданы при выполнении команды. Если они действительно существуют, смонтированный том скроет существующее содержимое:

  1. docker run -ti --rm -v DataVolume1:/datavolume1 ubuntu

Находясь в контейнере, давайте запишем некоторые данные в том:

  1. echo "Example1" > /datavolume1/Example1.txt

Поскольку мы использовали флаг --rm, наш контейнер будет автоматически удален при выходе. Однако наш том по-прежнему будет доступен.

  1. exit

Мы можем убедиться, что том присутствует в нашей системе с помощью docker volume inspect:

  1. docker volume inspect DataVolume1
Output
[ { "CreatedAt": "2018-07-11T16:57:54Z", "Driver": "local", "Labels": {}, "Mountpoint": "/var/lib/docker/volumes/DataVolume1/_data", "Name": "DataVolume1", "Options": {}, "Scope": "local" } ]

Примечание. Мы можем даже просмотреть данные на хосте по пути, указанному как Mountpoint. Однако нам следует избегать его изменения, поскольку это может привести к повреждению данных, если приложения или контейнеры не знают об изменениях.

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

  1. docker run --rm -ti -v DataVolume1:/datavolume1 ubuntu

Проверьте содержимое:

  1. cat /datavolume1/Example1.txt
Output
Example1

Выход из контейнера:

  1. exit

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

Шаг 2 — Создание тома, который сохраняется при удалении контейнера

В нашем следующем примере мы создадим том одновременно с контейнером, удалим контейнер, а затем присоединим том к новому контейнеру.

Мы будем использовать команду docker run для создания нового контейнера с использованием базового образа Ubuntu. -t даст нам терминал, а -i позволит нам взаимодействовать с ним. Для ясности мы будем использовать --name для идентификации контейнера.

Флаг -v позволит нам создать новый том, который мы назовем DataVolume2. Мы будем использовать двоеточие, чтобы отделить это имя от пути, по которому том должен быть смонтирован в контейнере. Наконец, мы укажем базовый образ Ubuntu и полагаемся на команду по умолчанию в файле Docker базового образа Ubuntu, bash, чтобы поместить нас в оболочку:

  1. docker run -ti --name=Container2 -v DataVolume2:/datavolume2 ubuntu

Примечание. Флаг -v очень гибкий. Он может связать или назвать том с небольшой корректировкой синтаксиса. Если первый аргумент начинается с / или ~/, вы создаете привязку. Удалите это, и вы назовете том. Например:

  • -v /path:/path/in/container монтирует каталог хоста, /path в /path/in/container
  • -v path:/path/in/container создает том с именем path, не связанный с хостом.

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

Находясь в контейнере, мы запишем некоторые данные в том:

  1. echo "Example2" > /datavolume2/Example2.txt
  2. cat /datavolume2/Example2.txt
Output
Example2

Выходим из контейнера:

  1. exit

Когда мы перезапустим контейнер, том смонтируется автоматически:

  1. docker start -ai Container2

Давайте проверим, что том действительно смонтирован и наши данные все еще на месте:

  1. cat /datavolume2/Example2.txt
Output
Example2

Наконец, давайте выйдем и очистим:

  1. exit

Docker не позволит нам удалить том, если на него ссылается контейнер. Давайте посмотрим, что произойдет, когда мы попытаемся:

  1. docker volume rm DataVolume2

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

Output
Error response from daemon: unable to remove volume: remove DataVolume2: volume is in use - [d0d2233b668eddad4986313c7a4a1bc0d2edaf0c7e1c02a6a6256de27db17a63]

Мы можем использовать этот идентификатор для удаления контейнера:

  1. docker rm d0d2233b668eddad4986313c7a4a1bc0d2edaf0c7e1c02a6a6256de27db17a63
Output
d0d2233b668eddad4986313c7a4a1bc0d2edaf0c7e1c02a6a6256de27db17a63

Удаление контейнера не повлияет на объем. Мы можем увидеть, что он все еще присутствует в системе, перечислив тома с помощью docker volume ls:

  1. docker volume ls
Output
DRIVER VOLUME NAME local DataVolume2

И мы можем использовать docker volume rm, чтобы удалить его:

  1. docker volume rm DataVolume2

В этом примере мы создали пустой том данных одновременно с созданием контейнера. В нашем следующем примере мы рассмотрим, что происходит, когда мы создаем том с каталогом контейнера, который уже содержит данные.

Шаг 3 — Создание тома из существующего каталога с данными

Как правило, независимое создание тома с помощью docker volume create и создание его при создании контейнера эквивалентны, за одним исключением. Если мы создаем том одновременно с созданием контейнера и указываем путь к каталогу, содержащему данные в базовом образе, эти данные будут скопированы в том.

В качестве примера мы создадим контейнер и добавим том данных в /var, каталог, который содержит данные в базовом образе:

  1. docker run -ti --rm -v DataVolume3:/var ubuntu

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

Выйти из текущего контейнера:

  1. exit

На этот раз вместо того, чтобы полагаться на команду bash по умолчанию из базового образа, мы выполним собственную команду ls, которая покажет содержимое тома без входа в оболочку:

  1. docker run --rm -v DataVolume3:/datavolume3 ubuntu ls datavolume3

Каталог datavolume3 теперь содержит копию содержимого каталога /var базового образа:

Output
backups cache lib local lock log mail opt run spool tmp

Маловероятно, что мы захотим монтировать /var/ таким образом, но это может быть полезно, если мы создали собственный образ и нам нужен простой способ сохранения данных. В нашем следующем примере мы продемонстрируем, как том может быть разделен между несколькими контейнерами.

Шаг 4 — Совместное использование данных между несколькими контейнерами Docker

До сих пор мы присоединяли том к одному контейнеру за раз. Часто нам нужно, чтобы несколько контейнеров были подключены к одному и тому же объему данных. Это относительно просто сделать, но есть одно важное предостережение: в настоящее время Docker не обрабатывает блокировку файлов. Если вам нужно, чтобы несколько контейнеров выполняли запись на том, приложения, работающие в этих контейнерах, должны быть спроектированы для записи в общие хранилища данных, чтобы предотвратить повреждение данных.

Создайте Container4 и DataVolume4

Используйте docker run, чтобы создать новый контейнер с именем Container4 с присоединенным томом данных:

  1. docker run -ti --name=Container4 -v DataVolume4:/datavolume4 ubuntu

Далее мы создадим файл и добавим текст:

  1. echo "This file is shared between containers" > /datavolume4/Example4.txt

Затем мы выйдем из контейнера:

  1. exit

Это возвращает нас к командной строке хоста, где мы создадим новый контейнер, который монтирует том данных из Container4.

Создайте Container5 и подключите тома из Container4

Мы создадим Container5 и смонтируем тома из Container4:

  1. docker run -ti --name=Container5 --volumes-from Container4 ubuntu

Проверим сохраняемость данных:

  1. cat /datavolume4/Example4.txt
Output
This file is shared between containers

Теперь давайте добавим текст из Container5:

  1. echo "Both containers can write to DataVolume4" >> /datavolume4/Example4.txt

Наконец, мы выйдем из контейнера:

  1. exit

Далее мы проверим, что наши данные все еще присутствуют в Container4.

Просмотр изменений, сделанных в Container5

Давайте проверим изменения, которые были записаны в том данных с помощью Container5, перезапустив Container4:

  1. docker start -ai Container4

Проверьте изменения:

  1. cat /datavolume4/Example4.txt
Output
This file is shared between containers Both containers can write to DataVolume4

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

  1. exit

Опять же, Docker не обрабатывает блокировку файлов, поэтому приложения должны учитывать блокировку файлов сами. Можно смонтировать том Docker как доступный только для чтения, чтобы гарантировать, что повреждение данных не произойдет случайно, когда контейнеру требуется доступ только для чтения, добавив :ro. Давайте посмотрим, как это работает.

Запустите контейнер 6 и смонтируйте том только для чтения

После того, как том был смонтирован в контейнере, вместо того, чтобы размонтировать его, как в случае с типичной файловой системой Linux, мы можем вместо этого создать новый контейнер, смонтированный так, как мы хотим, и, при необходимости, удалить предыдущий контейнер. Чтобы сделать том доступным только для чтения, мы добавляем :ro в конец имени контейнера:

  1. docker run -ti --name=Container6 --volumes-from Container4:ro ubuntu

Мы проверим статус «только для чтения», попытавшись удалить файл нашего примера:

  1. rm /datavolume4/Example4.txt
Output
rm: cannot remove '/datavolume4/Example4.txt': Read-only file system

Наконец, мы выйдем из контейнера и очистим наши тестовые контейнеры и тома:

  1. exit

Теперь, когда мы закончили, давайте очистим наши контейнеры и том:

  1. docker rm Container4 Container5 Container6
  2. docker volume rm DataVolume4

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

Заключение

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