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

Вступление

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

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

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

Предпосылки

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

[.note] #Note: Несмотря на то, что в предварительных требованиях есть инструкции по установке Docker в Ubuntu 18.04, командыdocker для томов данных Docker в этой статье должны работать в других операционных системах, если Docker установлен и пользователь sudo добавлен в группуdocker.
#

[[step-1 -—- created-an-independent-volume]] == Шаг 1. Создание независимого тома

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

docker volume create --name DataVolume1

Имя отображается, указывая, что команда была успешной:

OutputDataVolume1

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

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

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

echo "Example1" > /datavolume1/Example1.txt

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

exit

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

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"
    }
]

[.note] #Note: Мы даже можем посмотреть данные на хосте по пути, указанному какMountpoint. Однако нам следует избегать его изменения, поскольку это может вызвать повреждение данных, если приложения или контейнеры не знают об изменениях.
#

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

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

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

cat /datavolume1/Example1.txt
OutputExample1

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

exit

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

[[step-2 -—- create-a-volume-that-persists-when-the-container-is-remove]] == Шаг 2 - Создание тома, который сохраняется при удалении контейнера

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

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

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

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

[.Примечание]##

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

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

  • -v path:/path/in/container создает том с именемpath, не имеющий отношения к хосту.

Подробнее о подключении каталога с хоста см.How To Share Data between a Docker Container and the Host

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

echo "Example2" > /datavolume2/Example2.txt
cat /datavolume2/Example2.txt
OutputExample2

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

exit

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

docker start -ai Container2

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

cat /datavolume2/Example2.txt
OutputExample2

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

exit

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

docker volume rm DataVolume2

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

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

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

docker rm d0d2233b668eddad4986313c7a4a1bc0d2edaf0c7e1c02a6a6256de27db17a63
Outputd0d2233b668eddad4986313c7a4a1bc0d2edaf0c7e1c02a6a6256de27db17a63

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

docker volume ls
OutputDRIVER              VOLUME NAME
local               DataVolume2

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

docker volume rm DataVolume2

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

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

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

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

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

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

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

exit

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

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

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

Outputbackups
cache
lib
local
lock
log
mail
opt
run
spool
tmp

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

[[step-4 -—- sharing-data-between-multiple-docker-container]] == Шаг 4. Обмен данными между несколькими контейнерами Docker

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

Создать Контейнер4 и DataVolume4

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

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

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

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

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

exit

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

Создание контейнера 5 и монтирование томов из контейнера 4

Мы собираемся создатьContainer5 и смонтировать тома изContainer4:

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

Давайте проверим сохранность данных:

cat /datavolume4/Example4.txt
OutputThis file is shared between containers

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

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

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

exit

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

Посмотреть изменения, сделанные в контейнере5

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

docker start -ai Container4

Проверьте наличие изменений:

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

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

exit

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

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

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

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

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

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

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

exit

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

docker rm Container4 Container5 Container6
docker volume rm DataVolume4

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

Заключение

В этом руководстве мы создали том данных, который позволял сохранять данные в процессе удаления контейнера. Мы разделили объемы данных между контейнерами с оговоркой, что приложения должны быть разработаны для обработки блокировки файлов, чтобы предотвратить повреждение данных. Наконец, мы показали, как подключить общий том в режиме только для чтения. Если вы хотите узнать об обмене данными между контейнерами и хост-системой, см.How To Share Data between the Docker Container and the Host.

Related