Использование драйвера хранилища ZFS

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

Он был создан компанией Sun Microsystems (сейчас Oracle Corporation) и является открытым источником по лицензии CDDL. Из-за несовместимости лицензий CDDL и GPL, ZFS не может поставляться как часть основного ядра Linux. Однако проект ZFS On Linux (ZoL) предоставляет модуль ядра вне дерева и инструменты пользовательского пространства, которые могут быть установлены отдельно.

Порт ZFS on Linux (ZoL) жив и развивается. Однако на данный момент не рекомендуется использовать драйвер хранения zfs Docker для производственного использования, если у вас нет значительного опыта работы с ZFS в Linux.

Примечание

Существует также FUSE-реализация ZFS на платформе Linux. Это не рекомендуется. Родной драйвер ZFS (ZoL) более протестирован, более производителен и более широко используется. В остальной части этого документа речь идет о родном порте ZoL.

Пререквизиты

  • ZFS требует одного или нескольких выделенных блочных устройств, предпочтительно твердотельных накопителей (SSD).

  • Каталог /var/lib/docker/ должен быть смонтирован в файловой системе формата ZFS.

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

    Примечание

    Нет необходимости использовать MountFlags=slave, поскольку dockerd и containerd находятся в разных пространствах имен монтирования.

Настройка Docker с драйвером хранилища zfs

  1. Останов Docker.

  2. Скопируйте содержимое /var/lib/docker/ в /var/lib/docker.bk и удалите содержимое /var/lib/docker/.

    $ sudo cp -au /var/lib/docker /var/lib/docker.bk
    
    $ sudo rm -rf /var/lib/docker/*
    
  3. Создайте новый zpool на выделенном блочном устройстве или устройствах и вмонтируйте его в /var/lib/docker/. Убедитесь, что вы указали правильные устройства, поскольку это разрушительная операция. Этот пример добавляет два устройства в пул.

    $ sudo zpool create -f zpool-docker -m /var/lib/docker /dev/xvdf /dev/xvdg
    

    Команда создаёт zpool и называет его zpool-docker. Имя предназначено только для отображения, и вы можете использовать другое имя. Проверьте, что пул был создан и смонтирован правильно с помощью zfs list.

    $ sudo zfs list
    
    NAME           USED  AVAIL  REFER  MOUNTPOINT
    zpool-docker    55K  96.4G    19K  /var/lib/docker
    
  4. Настройка Docker на использование zfs. Отредактируйте /etc/docker/daemon.json и установите storage-driver на zfs. Если до этого файл был пустым, теперь он должен выглядеть следующим образом:

    {
      "storage-driver": "zfs"
    }
    

    Сохраните и закройте файл.

  5. Запуск Docker. Используйте docker info, чтобы проверить, что драйвер хранилища - zfs.

    $ sudo docker info
      Containers: 0
       Running: 0
       Paused: 0
       Stopped: 0
      Images: 0
      Server Version: 17.03.1-ce
      Storage Driver: zfs
       Zpool: zpool-docker
       Zpool Health: ONLINE
       Parent Dataset: zpool-docker
       Space Used By Parent: 249856
       Space Available: 103498395648
       Parent Quota: no
       Compression: off
    <...>
    

Управление zfs

Увеличение ёмкости на работающем устройстве

Чтобы увеличить размер zpool, необходимо добавить выделенное блочное устройство на хост Docker, а затем добавить его к zpool с помощью команды zpool add:

$ sudo zpool add zpool-docker /dev/xvdh

Ограничение квоты хранения контейнера на запись

Если вы хотите реализовать квоту на основе каждого образа/набора данных, вы можете установить параметр хранения size, чтобы ограничить объем пространства, которое может использовать один контейнер для своего записываемого слоя.

Отредактируйте /etc/docker/daemon.json и добавьте следующее:

{
  "storage-driver": "zfs",
  "storage-opts": ["size=256M"]
}

Просмотреть все варианты хранения для каждого драйвера хранения в справочной документации по демону

Сохраните и закройте файл и перезапустите Docker.

Как работает драйвер хранилища zfs

ZFS использует следующие объекты:

  • файловые системы: тонкое обеспечение, пространство выделяется из zpool по требованию.

  • snapshots: копии файловых систем с эффективным использованием пространства только для чтения.

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

Процесс создания клона:

снимки и клоны zfs
  1. Из файловой системы создается снимок, доступный только для чтения.

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

Файловые системы, моментальные снимки и клоны выделяют пространство из базового диска zpool.

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

Объединенная файловая система каждого запущенного контейнера монтируется на точку монтирования в /var/lib/docker/zfs/graph/. Продолжайте читать, чтобы узнать, как устроена объединенная файловая система.

Наложение образа и совместное использование

Базовый слой образа - это файловая система ZFS. Каждый дочерний слой является клоном ZFS на основе снимка ZFS нижележащего слоя. Контейнер - это клон ZFS, основанный на моментальном снимке ZFS верхнего слоя образа, из которого он создан.

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

пул zfs для контейнера Docker

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

  1. Базовый слой образа существует на хосте Docker в виде файловой системы ZFS.

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

    На схеме «Слой 1» добавляется путем создания ZFS-снимка базового уровня и последующего создания клона из этого снимка. Клон доступен для записи и потребляет пространство по требованию из zpool. Снимок доступен только для чтения, сохраняя базовый слой как неизменяемый объект.

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

    На схеме слой чтения-записи контейнера создается путем создания моментального снимка верхнего слоя образа (Слой 1) и создания клона из этого моментального снимка.

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

Как чтение и запись контейнеров работает с zfs?

Чтение файлов

Каждый записываемый слой контейнера представляет собой клон ZFS, который делит все свои данные с набором данных, на основе которого он был создан (снимки его родительских слоев). Операции чтения выполняются быстро, даже если считываемые данные находятся в глубоком слое. На этой диаграмме показано, как работает совместное использование блоков:

совместное использование блоков zfs

Запись файлов

Запись нового файла: пространство выделяется по требованию из базового zpool, и блоки записываются непосредственно в записываемый слой контейнера.

Модификация существующего файла: место выделяется только для измененных блоков, и эти блоки записываются в записываемый слой контейнера с использованием стратегии копирования при записи (CoW). Это минимизирует размер слоя и повышает производительность записи.

Удаление файла или каталога:

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

  • Если вы создадите, а затем удалите файл или каталог в пределах слоя контейнера, доступного для записи, блоки будут восстановлены zpool.

Производительность ZFS и Docker

Существует несколько факторов, влияющих на производительность Docker, использующего драйвер хранилища zfs.

  • Память: Память оказывает большое влияние на производительность ZFS. ZFS изначально была разработана для больших серверов корпоративного класса с большим объемом памяти.

  • ** Особенности ZFS**: ZFS включает функцию дедупликации. Использование этой функции может сэкономить дисковое пространство, но использует большое количество памяти. Рекомендуется отключить эту функцию для zpool, который вы используете с Docker, если вы не используете SAN, NAS или другие технологии аппаратного RAID.

  • Кэширование ZFS: ZFS кэширует дисковые блоки в структуре памяти, называемой адаптивным кэшем замены (ARC). Функция Single Copy ARC в ZFS позволяет использовать одну кэшированную копию блока совместно с несколькими клонами. Благодаря этой функции несколько запущенных контейнеров могут использовать одну копию кэшированного блока. Эта функция делает ZFS хорошим вариантом для PaaS и других случаев использования с высокой плотностью.

  • Фрагментация: Фрагментация является естественным побочным продуктом файловых систем типа ZFS. ZFS смягчает это за счёт использования небольшого размера блока в 128k. Журнал намерений ZFS (ZIL) и объединение записей (отложенная запись) также помогают уменьшить фрагментацию. Вы можете отслеживать фрагментацию с помощью zpool status. Однако не существует способа дефрагментации ZFS без переформатирования и восстановления файловой системы.

  • Используйте собственный драйвер ZFS для Linux: Реализация ZFS FUSE не рекомендуется из-за низкой производительности.

Лучшие практики в области производительности

  • Используйте быстрое хранение данных: Твердотельные накопители (SSD) обеспечивают более быстрое чтение и запись, чем вращающиеся диски.

  • Используйте тома для рабочих нагрузок с высокой интенсивностью записи: Тома обеспечивают наилучшую и наиболее предсказуемую производительность при работе с большими объемами записи. Это происходит потому, что они обходят драйвер хранилища и не несут никаких потенциальных накладных расходов, возникающих при тонком выделении ресурсов и копировании при записи. У томов есть и другие преимущества, например, возможность совместного использования данных контейнерами и сохранение данных даже тогда, когда ни один из запущенных контейнеров их не использует.