О драйверах для хранения данных

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

Драйверы хранилища в сравнении с томами Docker

Docker использует драйверы хранения для хранения слоев образов и для хранения данных в записываемом слое контейнера. Записываемый слой контейнера не сохраняется после удаления контейнера, но подходит для хранения эфемерных данных, которые создаются во время выполнения. Драйверы хранения оптимизированы для экономии места, но (в зависимости от драйвера хранения) скорость записи ниже, чем производительность собственной файловой системы, особенно для драйверов хранения, использующих файловую систему «копирование при записи». Приложения с интенсивной записью, такие как хранилище баз данных, страдают от избыточной производительности, особенно если существующие данные находятся на уровне только для чтения.

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

Образы и слои

Образ Docker строится из серии слоёв. Каждый слой представляет собой инструкцию в Dockerfile образа. Каждый слой, кроме самого последнего, доступен только для чтения. Рассмотрим следующий Dockerfile:

# syntax=docker/dockerfile:1
FROM ubuntu:18.04
LABEL org.opencontainers.image.authors="[email protected]"
COPY . /app
RUN make /app
RUN rm -r $HOME/.cache
CMD python /app/app.py

Этот Dockerfile содержит четыре команды. Команды, изменяющие файловую систему, создают слой. Команда FROM начинает с создания слоя из образа ubuntu:18.04. Команда LABEL только изменяет метаданные образа и не создает новый слой. Команда COPY добавляет некоторые файлы из текущего каталога вашего клиента Docker. Первая команда RUN создаёт ваше приложение с помощью команды make и записывает результат в новый слой. Вторая команда RUN удаляет каталог кэша и записывает результат в новый слой. Наконец, команда CMD указывает, какую команду выполнить внутри контейнера, которая только изменяет метаданные образа, но не создает слой образа.

Каждый слой - это только набор отличий от предыдущего слоя. Обратите внимание, что как добавление, так и удаление файлов приведет к созданию нового слоя. В приведенном выше примере каталог $HOME/.cache удалён, но все ещё будет доступен в предыдущем слое и увеличит общий размер образа. Обратитесь к разделам Лучшие практики написания Docker-файлов и использование многоэтапную сборку, чтобы узнать, как оптимизировать ваши Docker-файлы для создания эффективных образов.

Слои укладываются друг на друга. Когда вы создаёте новый контейнер, вы добавляете новый записываемый слой поверх нижележащих слоев. Этот слой часто называют «слоем контейнера». Все изменения, вносимые в работающий контейнер, такие как запись новых файлов, изменение существующих файлов и удаление файлов, записываются в этот тонкий записываемый слой контейнера. На схеме ниже показан контейнер, основанный на образе ubuntu:15.04.

Слои контейнера на основе образа Ubuntu

Драйвер хранилища обрабатывает детали того, как эти уровни взаимодействуют друг с другом. Существуют различные драйверы хранения, которые имеют свои преимущества и недостатки в различных ситуациях.

Контейнер и слои

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

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

Containers sharing same image

Docker использует драйверы хранилищ для управления содержимым слоёв образов и записываемого слоя контейнера. Каждый драйвер хранилища по-своему справляется с реализацией, но все драйверы используют стекируемые слои образов и стратегию копирования при записи (CoW).

Примечание

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

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

Чтобы посмотреть приблизительный размер запущенного контейнера, можно использовать команду docker ps -s. Два разных столбца относятся к размеру.

  • size: объём данных (на диске), который используется для записываемого слоя каждого контейнера.

  • virtual size: объём данных, используемых для данных образа, доступных только для чтения, используемых контейнером, плюс доступный для записи слой контейнера size. Несколько контейнеров могут совместно использовать некоторые или все данные образа, доступные только для чтения. Два контейнера, запущенные из одного и того же образа, совместно используют 100% данных, доступных только для чтения, в то время как два контейнера с разными образами, которые имеют общие слои, совместно используют эти общие слои. Следовательно, вы не можете просто суммировать виртуальные размеры. Это завышает общее использование диска на потенциально нетривиальную величину.

Общее дисковое пространство, используемое всеми запущенными контейнерами на диске, представляет собой некоторую комбинацию значений size и virtual size каждого контейнера. Если несколько контейнеров запускаются с одного и того же образа, общий размер дискового пространства для этих контейнеров будет равен SUM (size контейнеров) плюс один размер образа (virtual size - size).

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

  • Дисковое пространство, используемое для файлов журналов, хранящихся в logging-driver. Это может быть нетривиальным, если ваш контейнер генерирует большое количество данных журнала, а ротация журнала не настроена.

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

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

  • Память, записанная на диск (если включён свопинг).

  • Контрольные точки, если вы используете экспериментальную функцию контрольных точек/восстановления.

Стратегия копирования при записи (CoW)

Копирование на запись - это стратегия совместного использования и копирования файлов для достижения максимальной эффективности. Если файл или каталог существует в нижнем слое образа, а другому слою (включая слой с возможностью записи) требуется доступ к нему на чтение, он просто использует существующий файл. В первый раз, когда другому слою необходимо изменить файл (при сборке образа или запуске контейнера), файл копируется в этот слой и изменяется. Это минимизирует ввод-вывод и размер каждого из последующих слоев. Более подробно эти преимущества описаны ниже.

Совместное использование способствует уменьшению размеров образов

Когда вы используете docker pull для извлечения образа из хранилища или когда вы создаете контейнер из образа, который еще не существует локально, каждый слой извлекается отдельно и хранится в локальной области хранения Docker, которая обычно находится по адресу /var/lib/docker/ на хостах Linux. В этом примере вы можете видеть, как происходит извлечение этих слоев:

$ docker pull ubuntu:18.04
18.04: Pulling from library/ubuntu
f476d66f5408: Pull complete
8882c27f669e: Pull complete
d9af21273955: Pull complete
f5029279ec12: Pull complete
Digest: sha256:ab6cb8de3ad7bb33e2534677f865008535427390b117d7939193f8d1a6613e34
Status: Downloaded newer image for ubuntu:18.04

Каждый из этих слоев хранится в собственном каталоге в локальной области хранения хоста Docker. Чтобы просмотреть слои в файловой системе, перечислите содержимое каталога /var/lib/docker/<storage-driver>. В данном примере используется драйвер хранилища overlay2:

$ ls /var/lib/docker/overlay2
16802227a96c24dcbeab5b37821e2b67a9f921749cd9a2e386d5a6d5bc6fc6d3
377d73dbb466e0bc7c9ee23166771b35ebdbe02ef17753d79fd3571d4ce659d7
3f02d96212b03e3383160d31d7c6aeca750d2d8a1879965b89fe8146594c453d
ec1ec45792908e90484f7e629330666e7eee599f08729c93890a7205a6ba35f5
l

Имена каталогов не соответствуют идентификаторам слоёв.

Теперь представьте, что у вас есть два разных Dockerfiles. Вы используете первый из них для создания образа под названием acme/my-base-image:1.0.

# syntax=docker/dockerfile:1
FROM alpine
RUN apk add --no-cache bash

Второй основан на acme/my-base-image:1.0, но имеет несколько дополнительных слоёв:

# syntax=docker/dockerfile:1
FROM acme/my-base-image:1.0
COPY . /app
RUN chmod +x /app/hello.sh
CMD /app/hello.sh

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

Если вы собираете образы из двух Docker-файлов, вы можете использовать команды docker image ls и docker image history, чтобы проверить, что криптографические идентификаторы общих слоев одинаковы.

  1. Создайте новый каталог cow-test/ и перейдите в него.

  2. Внутри cow-test/ создайте новый файл под названием hello.sh со следующим содержимым:

    #!/usr/bin/env bash
    echo "Hello world"
    
  3. Скопируйте содержимое первого Dockerfile выше в новый файл под названием Dockerfile.base.

  4. Скопируйте содержимое второго Dockerfile выше в новый файл под названием Dockerfile.

  5. В каталоге cow-test/ создайте первый образ. Не забудьте включить в команду завершающий .. Это устанавливает PATH, который указывает Docker, где искать любые файлы, которые нужно добавить в образ.

    $ docker build -t acme/my-base-image:1.0 -f Dockerfile.base .
    [+] Building 6.0s (11/11) FINISHED
    => [internal] load build definition from Dockerfile.base                                      0.4s
    => => transferring dockerfile: 116B                                                           0.0s
    => [internal] load .dockerignore                                                              0.3s
    => => transferring context: 2B                                                                0.0s
    => resolve image config for docker.io/docker/dockerfile:1                                     1.5s
    => [auth] docker/dockerfile:pull token for registry-1.docker.io                               0.0s
    => CACHED docker-image://docker.io/docker/dockerfile:1@sha256:9e2c9eca7367393aecc68795c671... 0.0s
    => [internal] load .dockerignore                                                              0.0s
    => [internal] load build definition from Dockerfile.base                                      0.0s
    => [internal] load metadata for docker.io/library/alpine:latest                               0.0s
    => CACHED [1/2] FROM docker.io/library/alpine                                                 0.0s
    => [2/2] RUN apk add --no-cache bash                                                          3.1s
    => exporting to image                                                                         0.2s
    => => exporting layers                                                                        0.2s
    => => writing image sha256:da3cf8df55ee9777ddcd5afc40fffc3ead816bda99430bad2257de4459625eaa   0.0s
    => => naming to docker.io/acme/my-base-image:1.0                                              0.0s
    
  6. Построение второго образа.

    $ docker build -t acme/my-final-image:1.0 -f Dockerfile .
    
    [+] Building 3.6s (12/12) FINISHED
    => [internal] load build definition from Dockerfile                                            0.1s
    => => transferring dockerfile: 156B                                                            0.0s
    => [internal] load .dockerignore                                                               0.1s
    => => transferring context: 2B                                                                 0.0s
    => resolve image config for docker.io/docker/dockerfile:1                                      0.5s
    => CACHED docker-image://docker.io/docker/dockerfile:1@sha256:9e2c9eca7367393aecc68795c671...  0.0s
    => [internal] load .dockerignore                                                               0.0s
    => [internal] load build definition from Dockerfile                                            0.0s
    => [internal] load metadata for docker.io/acme/my-base-image:1.0                               0.0s
    => [internal] load build context                                                               0.2s
    => => transferring context: 340B                                                               0.0s
    => [1/3] FROM docker.io/acme/my-base-image:1.0                                                 0.2s
    => [2/3] COPY . /app                                                                           0.1s
    => [3/3] RUN chmod +x /app/hello.sh                                                            0.4s
    => exporting to image                                                                          0.1s
    => => exporting layers                                                                         0.1s
    => => writing image sha256:8bd85c42fa7ff6b33902ada7dcefaaae112bf5673873a089d73583b0074313dd    0.0s
    => => naming to docker.io/acme/my-final-image:1.0                                              0.0s
    
  7. Проверка размеров образов:

    $ docker image ls
    
    REPOSITORY             TAG     IMAGE ID         CREATED               SIZE
    acme/my-final-image    1.0     8bd85c42fa7f     About a minute ago    7.75MB
    acme/my-base-image     1.0     da3cf8df55ee     2 minutes ago         7.75MB
    
  8. Ознакомление с историей каждого образа:

    $ docker image history acme/my-base-image:1.0
    
    IMAGE          CREATED         CREATED BY                                      SIZE      COMMENT
    da3cf8df55ee   5 minutes ago   RUN /bin/sh -c apk add --no-cache bash # bui…   2.15MB    buildkit.dockerfile.v0
    <missing>      7 weeks ago     /bin/sh -c #(nop)  CMD ["/bin/sh"]              0B
    <missing>      7 weeks ago     /bin/sh -c #(nop) ADD file:f278386b0cef68136…   5.6MB
    

    Некоторые шаги не имеют размера (0B) и являются изменениями только метаданных, которые не создают слой образа и не занимают никакого размера, кроме самих метаданных. Приведенный выше результат показывает, что это образ состоит из 2 слоёв образа.

    $ docker image history  acme/my-final-image:1.0
    
    IMAGE          CREATED         CREATED BY                                      SIZE      COMMENT
    8bd85c42fa7f   3 minutes ago   CMD ["/bin/sh" "-c" "/app/hello.sh"]            0B        buildkit.dockerfile.v0
    <missing>      3 minutes ago   RUN /bin/sh -c chmod +x /app/hello.sh # buil…   39B       buildkit.dockerfile.v0
    <missing>      3 minutes ago   COPY . /app # buildkit                          222B      buildkit.dockerfile.v0
    <missing>      4 minutes ago   RUN /bin/sh -c apk add --no-cache bash # bui…   2.15MB    buildkit.dockerfile.v0
    <missing>      7 weeks ago     /bin/sh -c #(nop)  CMD ["/bin/sh"]              0B
    <missing>      7 weeks ago     /bin/sh -c #(nop) ADD file:f278386b0cef68136…   5.6MB
    

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

    Каковы шаги <missing>?

    Строки <missing> в выводе docker history указывают на то, что эти шаги были либо собраны на другой системе и являются частью образа alpine, который был извлечён из Docker Hub, либо были собраны с помощью BuildKit в качестве сборщика. До BuildKit «классический» сборщик создавал новый «промежуточный» образ для каждого шага в целях кэширования, и в столбце IMAGE отображался идентификатор этого образа. BuildKit использует свой механизм кэширования и больше не требует промежуточных образов для кэширования. Обратитесь к разделу BuildKit, чтобы узнать о других улучшениях, внесенных в BuildKit.

  9. Просмотр слоёв для каждого образа

    Используйте команду docker image inspect для просмотра криптографических идентификаторов слоёв в каждом образе:

    $ docker image inspect --format "{{json .RootFS.Layers}}" acme/my-base-image:1.0
    [
      "sha256:72e830a4dff5f0d5225cdc0a320e85ab1ce06ea5673acfe8d83a7645cbd0e9cf",
      "sha256:07b4a9068b6af337e8b8f1f1dae3dd14185b2c0003a9a1f0a6fd2587495b204a"
    ]
    
    $ docker image inspect --format "{{json .RootFS.Layers}}" acme/my-final-image:1.0
    [
      "sha256:72e830a4dff5f0d5225cdc0a320e85ab1ce06ea5673acfe8d83a7645cbd0e9cf",
      "sha256:07b4a9068b6af337e8b8f1f1dae3dd14185b2c0003a9a1f0a6fd2587495b204a",
      "sha256:cc644054967e516db4689b5282ee98e4bc4b11ea2255c9630309f559ab96562e",
      "sha256:e84fb818852626e89a09f5143dbc31fe7f0e0a6a24cd8d2eb68062b904337af4"
    ]
    

    Обратите внимание, что первые два слоя одинаковы на обоих образах. На втором образе добавлены два дополнительных слоя. Общие слои образов хранятся только один раз в /var/lib/docker/, а также используются совместно при проталкивании и вытаскивании образов в реестр образов. Таким образом, общие слои образов позволяют сократить пропускную способность сети и объем памяти.

    Примечание

    Совет: форматируйте вывод команд Docker с помощью опции --format

    В приведённых выше примерах для удобочитаемости используется Команда docker image inspect с опцией --format для просмотра идентификаторов слоёв в виде массива JSON. Опция --format в командах Docker может быть мощной функцией, которая позволяет извлекать и форматировать специфическую информацию из вывода, не требуя дополнительных инструментов, таких как awk или `sed. Чтобы узнать больше о форматировании вывода команд docker с помощью флага --format, обратитесь к статье раздел команды форматирования и вывода журнала. Мы также распечатали вывод JSON с помощью утилиты jq.

Копирование делает контейнеры эффективными

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

Когда существующий файл в контейнере изменяется, драйвер хранилища выполняет операцию копирования при записи. Конкретные шаги зависят от конкретного драйвера хранилища. Для драйверов overlay2, overlay и aufs операция копирования при записи выполняется в следующей последовательности:

  • Найдите в слоях образа файл для обновления. Процесс начинается с самого нового слоя и по одному слою спускается к базовому слою. Когда результаты найдены, они добавляются в кэш для ускорения последующих операций.

  • Выполните операцию copy_up над первой найденной копией файла, чтобы скопировать файл на записываемый слой контейнера.

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

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

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

Примечание

Совет: Используйте тома для приложений с высокой интенсивностью записи

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

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

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

Чтобы проверить, как работает копирование на запись, в следующей процедуре запускается 5 контейнеров на основе образа acme/my-final-image:1.0, который мы создали ранее, и проверяется, сколько места они занимают.

  1. В терминале на хосте Docker выполните следующие команды docker run. Строки в конце - это идентификаторы каждого контейнера.

    $ docker run -dit --name my_container_1 acme/my-final-image:1.0 bash \
      && docker run -dit --name my_container_2 acme/my-final-image:1.0 bash \
      && docker run -dit --name my_container_3 acme/my-final-image:1.0 bash \
      && docker run -dit --name my_container_4 acme/my-final-image:1.0 bash \
      && docker run -dit --name my_container_5 acme/my-final-image:1.0 bash
    
    40ebdd7634162eb42bdb1ba76a395095527e9c0aa40348e6c325bd0aa289423c
    a5ff32e2b551168b9498870faf16c9cd0af820edf8a5c157f7b80da59d01a107
    3ed3c1a10430e09f253704116965b01ca920202d52f3bf381fbb833b8ae356bc
    939b3bf9e7ece24bcffec57d974c939da2bdcc6a5077b5459c897c1e2fa37a39
    cddae31c314fbab3f7eabeb9b26733838187abc9a2ed53f97bd5b04cd7984a5a
    
  2. Выполните команду docker ps с опцией --size, чтобы убедиться, что 5 контейнеров запущены, и посмотреть размер каждого контейнера.

    $ docker ps --size --format "table {{.ID}}\t{{.Image}}\t{{.Names}}\t{{.Size}}"
    
    CONTAINER ID   IMAGE                     NAMES            SIZE
    cddae31c314f   acme/my-final-image:1.0   my_container_5   0B (virtual 7.75MB)
    939b3bf9e7ec   acme/my-final-image:1.0   my_container_4   0B (virtual 7.75MB)
    3ed3c1a10430   acme/my-final-image:1.0   my_container_3   0B (virtual 7.75MB)
    a5ff32e2b551   acme/my-final-image:1.0   my_container_2   0B (virtual 7.75MB)
    40ebdd763416   acme/my-final-image:1.0   my_container_1   0B (virtual 7.75MB)
    

    Вывод выше показывает, что все контейнеры совместно используют слои образа только для чтения (7,75 МБ), но в файловую систему контейнера не было записано никаких данных, поэтому дополнительное хранилище для контейнеров не используется.

    Примечание

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

    Этот шаг требует Linux-машины и не работает на Docker Desktop для Mac или Docker Desktop для Windows, так как требует доступа к файловому хранилищу Docker Daemon.

    Хотя вывод docker ps предоставляет вам информацию о дисковом пространстве, потребляемом записываемым слоем контейнера, он не включает информацию о метаданных и лог-файлах, хранящихся для каждого контейнера.

    Более подробную информацию можно получить, изучив место хранения Docker Daemon (по умолчанию /var/lib/docker).

    $ sudo du -sh /var/lib/docker/containers/*
    
    36K  /var/lib/docker/containers/3ed3c1a10430e09f253704116965b01ca920202d52f3bf381fbb833b8ae356bc
    36K  /var/lib/docker/containers/40ebdd7634162eb42bdb1ba76a395095527e9c0aa40348e6c325bd0aa289423c
    36K  /var/lib/docker/containers/939b3bf9e7ece24bcffec57d974c939da2bdcc6a5077b5459c897c1e2fa37a39
    36K  /var/lib/docker/containers/a5ff32e2b551168b9498870faf16c9cd0af820edf8a5c157f7b80da59d01a107
    36K  /var/lib/docker/containers/cddae31c314fbab3f7eabeb9b26733838187abc9a2ed53f97bd5b04cd7984a5a
    

    Каждый из этих контейнеров занимает всего 36 тыс. места в файловой системе.

  3. Контейнерное хранение

    Чтобы продемонстрировать это, выполните следующую команду для записи слова „hello“ в файл на записываемом слое контейнера в контейнерах my_container_1, my_container_2 и my_container_3:

    $ for i in {1..3}; do docker exec my_container_$i sh -c 'printf hello > /out.txt'; done
    

    После этого повторное выполнение команды docker ps показывает, что эти контейнеры теперь потребляют по 5 байт. Эти данные уникальны для каждого контейнера и не являются общими. Слои контейнеров, доступные только для чтения, не затрагиваются и по-прежнему используются совместно всеми контейнерами.

    $ docker ps --size --format "table {{.ID}}"
    
    
    CONTAINER ID   IMAGE                     NAMES            SIZE
    cddae31c314f   acme/my-final-image:1.0   my_container_5   0B (virtual 7.75MB)
    939b3bf9e7ec   acme/my-final-image:1.0   my_container_4   0B (virtual 7.75MB)
    3ed3c1a10430   acme/my-final-image:1.0   my_container_3   5B (virtual 7.75MB)
    a5ff32e2b551   acme/my-final-image:1.0   my_container_2   5B (virtual 7.75MB)
    40ebdd763416   acme/my-final-image:1.0   my_container_1   5B (virtual 7.75MB)
    

Приведенные выше примеры иллюстрируют, как файловые системы copy-on-write помогают сделать контейнеры эффективными. Копирование на запись не только экономит место, но и сокращает время запуска контейнера. Когда вы создаете контейнер (или несколько контейнеров из одного образа), Docker необходимо создать только тонкий слой контейнера с возможностью записи.

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